<commit_before>////////////////////////////////////////////////////////////////////////////////
/// @brief test the compaction
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var jsunity = require("jsunity");
var internal = require("internal");

// -----------------------------------------------------------------------------
// --SECTION--                                                collection methods
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @brief test suite: collection
////////////////////////////////////////////////////////////////////////////////

function CompactionSuite () {
  var ERRORS = require("internal").errors;

  return {

////////////////////////////////////////////////////////////////////////////////
/// @brief test journals
////////////////////////////////////////////////////////////////////////////////

    testJournals : function () {
      var cn = "example";
      internal.db._drop(cn);
      var c1 = internal.db._create(cn, { "journalSize" : 1048576 } );

      // empty collection
      var fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertEqual(0, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.save({ "foo": "bar" });

      fig = c1.figures();
      assertEqual(1, c1.count());
      assertEqual(1, fig["alive"]["count"]);
      assertTrue(0 < fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(1, fig["journals"]["count"]);
      assertEqual(0, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.rotate();

      fig = c1.figures();
      assertEqual(1, c1.count());
      assertEqual(1, fig["alive"]["count"]);
      assertTrue(0 < fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertEqual(1, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.save({ "bar": "baz" });

      fig = c1.figures();
      assertEqual(2, c1.count());
      assertEqual(2, fig["alive"]["count"]);
      assertTrue(0 < fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(1, fig["journals"]["count"]);
      assertEqual(1, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.rotate();

      fig = c1.figures();
      assertEqual(2, c1.count());
      assertEqual(2, fig["alive"]["count"]);
      assertTrue(0 < fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertEqual(2, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.truncate();
      c1.rotate();

      internal.wait(10);

      fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertEqual(0, fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertEqual(1, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.drop();
    },

////////////////////////////////////////////////////////////////////////////////
/// @brief test figures after truncate and rotate
////////////////////////////////////////////////////////////////////////////////

    testFiguresTruncate : function () {
      var maxWait;
      var waited;
      var cn = "example";
      var n = 400;
      var payload = "the quick brown fox jumped over the lazy dog. a quick dog jumped over the lazy fox. boom bang.";

      for (var i = 0; i < 5; ++i) {
        payload += payload;
      }

      internal.db._drop(cn);
      var c1 = internal.db._create(cn, { "journalSize" : 1048576 } );

      for (var i = 0; i < n; ++i) {
        c1.save({ _key: "test" + i, value : i, payload : payload });
      }

      c1.unload();
      internal.wait(7);

      var fig = c1.figures();
      assertEqual(n, c1.count());
      assertEqual(n, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(1, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      c1.truncate();
      c1.rotate();

      var fig = c1.figures();

      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertTrue(0 < fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["size"]);
      assertTrue(0 < fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      // wait for compactor to run
      require("console").log("waiting for compactor to run");

      // set max wait time
      if (internal.valgrind) {
        maxWait = 750;
      }
      else {
        maxWait = 90;
      }

      waited = 0;

      while (waited < maxWait) {
        internal.wait(2);
        waited += 2;

        fig = c1.figures();
        if (fig["dead"]["deletion"] == 0 && fig["dead"]["count"] == 0) {
          break;
        }
      }

      fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertEqual(0, fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);

      internal.db._drop(cn);
    },

////////////////////////////////////////////////////////////////////////////////
/// @brief test figures after truncate and rotate, with compaction disabled
////////////////////////////////////////////////////////////////////////////////

    testFiguresNoCompact : function () {
      var maxWait;
      var cn = "example";
      var n = 400;
      var payload = "the quick brown fox jumped over the lazy dog. a quick dog jumped over the lazy fox. boom bang.";

      for (var i = 0; i < 5; ++i) {
        payload += payload;
      }

      internal.db._drop(cn);
      var c1 = internal.db._create(cn, { "journalSize" : 1048576, "doCompact" : false } );

      for (var i = 0; i < n; ++i) {
        c1.save({ _key: "test" + i, value : i, payload : payload });
      }

      c1.unload();
      internal.wait(5);

      var fig = c1.figures();
      assertEqual(n, c1.count());
      assertEqual(n, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(1, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      c1.truncate();
      c1.rotate();

      var fig = c1.figures();

      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertTrue(0 < fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["size"]);
      assertTrue(0 < fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      // wait for compactor to run
      require("console").log("waiting for compactor to run");

      // set max wait time
      if (internal.valgrind) {
        maxWait = 120;
      }
      else {
        maxWait = 15;
      }

      internal.wait(maxWait);

      fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertTrue(0 < fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["size"]);
      assertTrue(0 < fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      c1.save({ "some data": true });
      fig = c1.figures();
      assertEqual(1, fig["journals"]["count"]);

      internal.db._drop(cn);
    },

////////////////////////////////////////////////////////////////////////////////
/// @brief test document presence after compaction
////////////////////////////////////////////////////////////////////////////////

    testDocumentPresence : function () {
      var maxWait;
      var waited;
      var cn = "example";
      var n = 400;
      var payload = "the quick brown fox jumped over the lazy dog. a quick dog jumped over the lazy fox";

      for (var i = 0; i < 5; ++i) {
        payload += payload;
      }

      internal.db._drop(cn);
      var c1 = internal.db._create(cn, { "journalSize" : 1048576 } );

      for (var i = 0; i < n; ++i) {
        c1.save({ _key: "test" + i, value : i, payload : payload });
      }

      for (var i = 0; i < n; i += 2) {
        c1.remove("test" + i);
      }

      // this will create a barrier that will block compaction
      var doc = c1.document("test1");

      c1.rotate();

      var fig = c1.figures();
      assertEqual(n / 2, c1.count());
      assertEqual(n / 2, fig["alive"]["count"]);
      assertEqual(n / 2, fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["size"]);
      assertTrue(0 < fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      // trigger GC
      doc = null;
      internal.wait(0);

      // wait for compactor to run
      require("console").log("waiting for compactor to run");

      // set max wait time
      if (internal.valgrind) {
        maxWait = 750;
      }
      else {
        maxWait = 90;
      }

      waited = 0;
      var lastValue = fig["dead"]["deletion"];

      while (waited < maxWait) {
        internal.wait(5);
        waited += 5;

        fig = c1.figures();
        if (fig["dead"]["deletion"] == lastValue) {
          break;
        }
        lastValue = fig["dead"]["deletion"];
      }

      var doc;
      for (var i = 0; i < n; i++) {

        // will throw if document does not exist
        if (i % 2 == 0) {
          try {
            doc = c1.document("test" + i);
            fail();
          }
          catch (err) {
          }
        }
        else {
          doc = c1.document("test" + i);
        }
      }

      // trigger GC
      doc = null;
      internal.wait(0);

      c1.truncate();
      c1.rotate();

      waited = 0;

      while (waited < maxWait) {
        internal.wait(5);
        waited += 5;

        fig = c1.figures();
        if (fig["dead"]["deletion"] == 0) {
          break;
        }
      }

      var fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      internal.db._drop(cn);
    },

////////////////////////////////////////////////////////////////////////////////
/// @brief creates documents, rotates the journal and truncates all documents
///
/// this will fully compact the 1st datafile (with the data), but will leave
/// the journal with the delete markers untouched
////////////////////////////////////////////////////////////////////////////////

    testCompactAfterTruncate : function () {
      var maxWait;
      var waited;
      var cn = "example";
      var n = 400;
      var payload = "the quick brown fox jumped over the lazy dog. a quick dog jumped over the lazy fox";

      for (var i = 0; i < 5; ++i) {
        payload += payload;
      }

      internal.db._drop(cn);
      internal.wait(5);
      var c1 = internal.db._create(cn, { "journalSize" : 1048576 } );
      internal.wait(2);

      for (var i = 0; i < n; ++i) {
        c1.save({ value : i, payload : payload });
      }

      var fig = c1.figures();
      assertEqual(n, c1.count());
      assertEqual(n, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(1, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      // truncation will go fully into the journal...
      c1.rotate();

      c1.truncate();
      c1.unload();

      // trigger GC
      internal.wait(2);

      // wait for compactor to run
      require("console").log("waiting for compactor to run");

      // set max wait time
      if (internal.valgrind) {
        maxWait = 750;
      }
      else {
        maxWait = 90;
      }

      waited = 0;

      while (waited < maxWait) {
        internal.wait(5);
        waited += 5;

        fig = c1.figures();
        if (fig["dead"]["count"] == 0) {
          break;
        }
      }

      fig = c1.figures();
      assertEqual(0, c1.count());
      // all alive & dead markers should be gone
      assertEqual(0, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      // we should still have all the deletion markers
      assertEqual(n, fig["dead"]["deletion"]);

      internal.db._drop(cn);
    }

  };
}

// -----------------------------------------------------------------------------
// --SECTION--                                                              main
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suites
////////////////////////////////////////////////////////////////////////////////

jsunity.run(CompactionSuite);

return jsunity.done();

// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// End:

<commit_msg>fixed test<commit_after>////////////////////////////////////////////////////////////////////////////////
/// @brief test the compaction
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var jsunity = require("jsunity");
var internal = require("internal");

// -----------------------------------------------------------------------------
// --SECTION--                                                collection methods
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @brief test suite: collection
////////////////////////////////////////////////////////////////////////////////

function CompactionSuite () {
  var ERRORS = require("internal").errors;

  return {

////////////////////////////////////////////////////////////////////////////////
/// @brief test journals
////////////////////////////////////////////////////////////////////////////////

    testJournals : function () {
      var cn = "example";
      internal.db._drop(cn);
      var c1 = internal.db._create(cn, { "journalSize" : 1048576 } );

      // empty collection
      var fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertEqual(0, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.save({ "foo": "bar" });

      fig = c1.figures();
      assertEqual(1, c1.count());
      assertEqual(1, fig["alive"]["count"]);
      assertTrue(0 < fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(1, fig["journals"]["count"]);
      assertEqual(0, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.rotate();
      internal.wait(5);

      fig = c1.figures();
      assertEqual(1, c1.count());
      assertEqual(1, fig["alive"]["count"]);
      assertTrue(0 < fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertEqual(1, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.save({ "bar": "baz" });

      fig = c1.figures();
      assertEqual(2, c1.count());
      assertEqual(2, fig["alive"]["count"]);
      assertTrue(0 < fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(1, fig["journals"]["count"]);
      assertEqual(1, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.rotate();
      internal.wait(5);

      fig = c1.figures();
      assertEqual(2, c1.count());
      assertEqual(2, fig["alive"]["count"]);
      assertTrue(0 < fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(1 <= fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.truncate();
      c1.rotate();

      internal.wait(10);

      fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertEqual(0, fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertEqual(1, fig["datafiles"]["count"]);
      assertEqual(0, fig["compactors"]["count"]);

      c1.drop();
    },

////////////////////////////////////////////////////////////////////////////////
/// @brief test figures after truncate and rotate
////////////////////////////////////////////////////////////////////////////////

    testFiguresTruncate : function () {
      var maxWait;
      var waited;
      var cn = "example";
      var n = 400;
      var payload = "the quick brown fox jumped over the lazy dog. a quick dog jumped over the lazy fox. boom bang.";

      for (var i = 0; i < 5; ++i) {
        payload += payload;
      }

      internal.db._drop(cn);
      var c1 = internal.db._create(cn, { "journalSize" : 1048576 } );

      for (var i = 0; i < n; ++i) {
        c1.save({ _key: "test" + i, value : i, payload : payload });
      }

      c1.unload();
      internal.wait(7);

      var fig = c1.figures();
      assertEqual(n, c1.count());
      assertEqual(n, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(1, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      c1.truncate();
      c1.rotate();

      var fig = c1.figures();

      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertTrue(0 < fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["size"]);
      assertTrue(0 < fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      // wait for compactor to run
      require("console").log("waiting for compactor to run");

      // set max wait time
      if (internal.valgrind) {
        maxWait = 750;
      }
      else {
        maxWait = 90;
      }

      waited = 0;

      while (waited < maxWait) {
        internal.wait(2);
        waited += 2;

        fig = c1.figures();
        if (fig["dead"]["deletion"] == 0 && fig["dead"]["count"] == 0) {
          break;
        }
      }

      fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertEqual(0, fig["alive"]["size"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);

      internal.db._drop(cn);
    },

////////////////////////////////////////////////////////////////////////////////
/// @brief test figures after truncate and rotate, with compaction disabled
////////////////////////////////////////////////////////////////////////////////

    testFiguresNoCompact : function () {
      var maxWait;
      var cn = "example";
      var n = 400;
      var payload = "the quick brown fox jumped over the lazy dog. a quick dog jumped over the lazy fox. boom bang.";

      for (var i = 0; i < 5; ++i) {
        payload += payload;
      }

      internal.db._drop(cn);
      var c1 = internal.db._create(cn, { "journalSize" : 1048576, "doCompact" : false } );

      for (var i = 0; i < n; ++i) {
        c1.save({ _key: "test" + i, value : i, payload : payload });
      }

      c1.unload();
      internal.wait(5);

      var fig = c1.figures();
      assertEqual(n, c1.count());
      assertEqual(n, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(1, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      c1.truncate();
      c1.rotate();
      internal.wait(5);

      fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertTrue(0 < fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["size"]);
      assertTrue(0 < fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      // wait for compactor to run
      require("console").log("waiting for compactor to run");

      // set max wait time
      if (internal.valgrind) {
        maxWait = 120;
      }
      else {
        maxWait = 15;
      }

      internal.wait(maxWait);

      fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertTrue(0 < fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["size"]);
      assertTrue(0 < fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      c1.save({ "some data": true });
      fig = c1.figures();
      assertEqual(1, fig["journals"]["count"]);

      internal.db._drop(cn);
    },

////////////////////////////////////////////////////////////////////////////////
/// @brief test document presence after compaction
////////////////////////////////////////////////////////////////////////////////

    testDocumentPresence : function () {
      var maxWait;
      var waited;
      var cn = "example";
      var n = 400;
      var payload = "the quick brown fox jumped over the lazy dog. a quick dog jumped over the lazy fox";

      for (var i = 0; i < 5; ++i) {
        payload += payload;
      }

      internal.db._drop(cn);
      var c1 = internal.db._create(cn, { "journalSize" : 1048576 } );

      for (var i = 0; i < n; ++i) {
        c1.save({ _key: "test" + i, value : i, payload : payload });
      }

      for (var i = 0; i < n; i += 2) {
        c1.remove("test" + i);
      }

      // this will create a barrier that will block compaction
      var doc = c1.document("test1");

      c1.rotate();

      var fig = c1.figures();
      assertEqual(n / 2, c1.count());
      assertEqual(n / 2, fig["alive"]["count"]);
      assertEqual(n / 2, fig["dead"]["count"]);
      assertTrue(0 < fig["dead"]["size"]);
      assertTrue(0 < fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      // trigger GC
      doc = null;
      internal.wait(0);

      // wait for compactor to run
      require("console").log("waiting for compactor to run");

      // set max wait time
      if (internal.valgrind) {
        maxWait = 750;
      }
      else {
        maxWait = 90;
      }

      waited = 0;
      var lastValue = fig["dead"]["deletion"];

      while (waited < maxWait) {
        internal.wait(5);
        waited += 5;

        fig = c1.figures();
        if (fig["dead"]["deletion"] == lastValue) {
          break;
        }
        lastValue = fig["dead"]["deletion"];
      }

      var doc;
      for (var i = 0; i < n; i++) {

        // will throw if document does not exist
        if (i % 2 == 0) {
          try {
            doc = c1.document("test" + i);
            fail();
          }
          catch (err) {
          }
        }
        else {
          doc = c1.document("test" + i);
        }
      }

      // trigger GC
      doc = null;
      internal.wait(0);

      c1.truncate();
      c1.rotate();

      waited = 0;

      while (waited < maxWait) {
        internal.wait(5);
        waited += 5;

        fig = c1.figures();
        if (fig["dead"]["deletion"] == 0) {
          break;
        }
      }

      var fig = c1.figures();
      assertEqual(0, c1.count());
      assertEqual(0, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["size"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(0, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      internal.db._drop(cn);
    },

////////////////////////////////////////////////////////////////////////////////
/// @brief creates documents, rotates the journal and truncates all documents
///
/// this will fully compact the 1st datafile (with the data), but will leave
/// the journal with the delete markers untouched
////////////////////////////////////////////////////////////////////////////////

    testCompactAfterTruncate : function () {
      var maxWait;
      var waited;
      var cn = "example";
      var n = 400;
      var payload = "the quick brown fox jumped over the lazy dog. a quick dog jumped over the lazy fox";

      for (var i = 0; i < 5; ++i) {
        payload += payload;
      }

      internal.db._drop(cn);
      internal.wait(5);
      var c1 = internal.db._create(cn, { "journalSize" : 1048576 } );
      internal.wait(2);

      for (var i = 0; i < n; ++i) {
        c1.save({ value : i, payload : payload });
      }

      var fig = c1.figures();
      assertEqual(n, c1.count());
      assertEqual(n, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      assertEqual(0, fig["dead"]["deletion"]);
      assertEqual(1, fig["journals"]["count"]);
      assertTrue(0 < fig["datafiles"]["count"]);

      // truncation will go fully into the journal...
      c1.rotate();

      c1.truncate();
      c1.unload();

      // trigger GC
      internal.wait(2);

      // wait for compactor to run
      require("console").log("waiting for compactor to run");

      // set max wait time
      if (internal.valgrind) {
        maxWait = 750;
      }
      else {
        maxWait = 90;
      }

      waited = 0;

      while (waited < maxWait) {
        internal.wait(5);
        waited += 5;

        fig = c1.figures();
        if (fig["dead"]["count"] == 0) {
          break;
        }
      }

      fig = c1.figures();
      assertEqual(0, c1.count());
      // all alive & dead markers should be gone
      assertEqual(0, fig["alive"]["count"]);
      assertEqual(0, fig["dead"]["count"]);
      // we should still have all the deletion markers
      assertEqual(n, fig["dead"]["deletion"]);

      internal.db._drop(cn);
    }

  };
}

// -----------------------------------------------------------------------------
// --SECTION--                                                              main
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suites
////////////////////////////////////////////////////////////////////////////////

jsunity.run(CompactionSuite);

return jsunity.done();

// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// End:

<|endoftext|>


<commit_before>import Immutable from 'immutable'
import log from './logger'
import bw from './natives/bw'
import forge from './natives/forge'
import { setSettings as setSnpSettings, setMappings } from './natives/snp'

forge.on('startWndProc', () => log.verbose('forge\'s wndproc pump started'))
  .on('endWndProc', () => log.verbose('forge\'s wndproc pump finished'))

function buildMappings(lobby, setup) {
  // Build an object mapping Fake-IP => { address, port }, ordered consistently between all
  // players (host first, then all other players ordered by slot). Computers are not included, as
  // we [obviously] won't be sending network traffic to them. This mapping will be used by our
  // SNP to shuttle packets back and forth from Storm while:
  // - Keeping consistent IPs/ports between all players of the game (even though they might differ
  //   due to NAT, LAN, etc.)
  // - Supporting protocols Storm doesn't properly support (i.e. IPv6)

  const players = Immutable.fromJS(lobby.players).valueSeq().filterNot(p => p.get('isComputer'))
  const hostId = lobby.hostId
  const ordered = players.filter(p => p.get('id') === hostId).concat(
      players.filterNot(p => p.get('id') === hostId).sortBy(p => p.get('slot')))
  const netInfos = ordered.map(p => {
    const netInfo = setup.networkInfo[p.get('id')]
    // TODO(tec27): The address really would have been whittled down to one by this point (and not
    // be an array)
    return { address: netInfo.addresses[0], port: netInfo.port }
  })
  return netInfos.toKeyedSeq().mapKeys(i => `10.27.27.${i}`).toJS()
}

export default async function initGame({ lobby, settings, setup, localUser }) {
  setSnpSettings(settings)
  bw.setSettings(settings)

  const mappings = buildMappings(lobby, setup)
  log.verbose('generated network mappings: ' + JSON.stringify(mappings))
  setMappings(mappings)

  if (!forge.inject()) {
    throw new Error('forge injection failed')
  }
  log.verbose('forge injected')

  await bw.initProcess()
  log.verbose('process initialized')
  forge.runWndProc()
}
<commit_msg>Fix settings being passed into C++ so they match what we expect.<commit_after>import Immutable from 'immutable'
import log from './logger'
import bw from './natives/bw'
import forge from './natives/forge'
import { setSettings as setSnpSettings, setMappings } from './natives/snp'

forge.on('startWndProc', () => log.verbose('forge\'s wndproc pump started'))
  .on('endWndProc', () => log.verbose('forge\'s wndproc pump finished'))

function buildMappings(lobby, setup) {
  // Build an object mapping Fake-IP => { address, port }, ordered consistently between all
  // players (host first, then all other players ordered by slot). Computers are not included, as
  // we [obviously] won't be sending network traffic to them. This mapping will be used by our
  // SNP to shuttle packets back and forth from Storm while:
  // - Keeping consistent IPs/ports between all players of the game (even though they might differ
  //   due to NAT, LAN, etc.)
  // - Supporting protocols Storm doesn't properly support (i.e. IPv6)

  const players = Immutable.fromJS(lobby.players).valueSeq().filterNot(p => p.get('isComputer'))
  const hostId = lobby.hostId
  const ordered = players.filter(p => p.get('id') === hostId).concat(
      players.filterNot(p => p.get('id') === hostId).sortBy(p => p.get('slot')))
  const netInfos = ordered.map(p => {
    const netInfo = setup.networkInfo[p.get('id')]
    // TODO(tec27): The address really would have been whittled down to one by this point (and not
    // be an array)
    return { address: netInfo.addresses[0], port: netInfo.port }
  })
  return netInfos.toKeyedSeq().mapKeys(i => `10.27.27.${i}`).toJS()
}

export default async function initGame({ lobby, settings, setup, localUser }) {
  // TODO(tec27): handle global settings?
  setSnpSettings(settings.local)
  bw.setSettings(settings.local)

  const mappings = buildMappings(lobby, setup)
  log.verbose('generated network mappings: ' + JSON.stringify(mappings))
  setMappings(mappings)

  if (!forge.inject()) {
    throw new Error('forge injection failed')
  }
  log.verbose('forge injected')

  await bw.initProcess()
  log.verbose('process initialized')
  forge.runWndProc()
}
<|endoftext|>


<commit_before>var eb = require("../eventBus");
var View = require('../view');
var template = require('../tmpl/conversation-view');
var CommentView = require('../views/vote-view');
var CommentFormView = require("../views/comment-form")
var ChangeVotesView = require("../views/change-votes");
var display = require("../util/display");
var MetadataQuestionsFilterView = require("../views/metadataQuestionsFilterView");
var ResultsView = require("../views/results-view");
var VoteModel = require("../models/vote");
var ParticipantModel = require("../models/participant");
var ConversationModel = require("../models/conversation");
var CommentModel = require("../models/comment");
var UserModel = require("../models/user");
var CommentsCollection = require("../collections/comments");
var VotesCollection = require("../collections/votes");
var MetadataQuestionsCollection = require("../collections/metadataQuestions");
var ResultsCollection = require("../collections/results");
var PolisStorage = require("../util/polisStorage");
var popoverEach = require("../util/popoverEach");
var Utils = require("../util/utils");
var VisView = require("../lib/VisView");
var TutorialController = require("../controllers/tutorialController");
var ServerClient = require("../lib/polis");

var ANALYZE_TAB = "analyzeTab";
var WRITE_TAB = "commentFormTab";
var VOTE_TAB = "commentViewTab";

var isIE8 = navigator.userAgent.match(/MSIE 8/)

function shouldShowVisUnderTabs() {
  return display.xs();
}
function shouldHideVisWhenWriteTabShowing() {
  return shouldShowVisUnderTabs();
}

module.exports =  View.extend({
  name: "conversation-view",
  template: template,
  events: {
  },
  destroyPopovers: function() {
    popoverEach("destroy");
  },
  onClusterTapped : function() {
    this.destroyPopovers();
    var that = this;
      // if (window.isMobile()) {
      //    window.scrollTo(0, $("#visualization_div").offset().top);
      // }
  },
  onAnalyzeTabPopulated: function() {
    $('li.query_result_item').first().trigger('click');
  },
  updateVotesByMeCollection: function() {
    console.log("votesByMe.fetch");
    if (this.pid < 0) {
      // DEMO_MODE
      return;
    }
    this.votesByMe.fetch({
      data: $.param({
        zid: this.zid,
        pid: this.pid
      }),
      reset: false
    });
  },
  hideVis: function() {
    $("#visualization_div").hide();
    $("#visualization_div_under_tabs").hide();
  },
  showVis: function() {
    $("#visualization_div").show();
    $("#visualization_div_under_tabs").show();
  },

  initialize: function() {
    var that = this;
    var vis;
    var zid = this.zid = this.model.get("zid");
    var pid = this.pid;
    var zinvite = this.zinvite = this.model.get("zinvite");
    var is_public = this.model.get("is_public");
    var registerForPersonUpdates = _.once(function() {
      that.serverClient.addPersonUpdateListener(onPersonUpdate);
    });

    this.tutorialController = new TutorialController();
    var metadataCollection = new MetadataQuestionsCollection([], {
        zid: zid
    });

    var resultsCollection = new ResultsCollection();

    window.m = metadataCollection;

    // HTTP PATCH - model.save({patch: true})

    function onClusterTapped() {
        that.onClusterTapped();
    }

    function onPersonUpdate() {
      if (vis) {
        vis.upsertNode.apply(vis, arguments);
      }
    }

    function initPcaVis() {
      if (shouldShowVisUnderTabs()) {
        elSelector = "#visualization_div_under_tabs";
        $("#visualization_div").html("").height(0);
        // $("#visualization_div").parent().css("display", "none");
      } else {
        elSelector = "#visualization_div";
        // $("#visualization_div").parent().css("display", "");
        $("#visualization_div_under_tabs").html("").height(0);
      }
      that.elSelector = elSelector;

      var w = $(elSelector).width();
      var h = w/2;
      $(elSelector).height(h);
      vis = new VisView({
          getPid: function() {
            if (!_.isId(pid)) {
              //alert("bad pid: " + pid);
            }
            return pid;
          },
          isIE8: isIE8,
          getCommentsForProjection: serverClient.getCommentsForProjection,
          getCommentsForGroup: serverClient.getCommentsForGroup,
          getReactionsToComment: serverClient.getReactionsToComment,
          getUserInfoByPid: serverClient.getUserInfoByPidSync,
          getTotalVotesByPidSync: serverClient.getTotalVotesByPidSync,
          getPidToBidMapping: serverClient.getPidToBidMapping,
          w: w,
          h: h,
          computeXySpans: Utils.computeXySpans,
          el_queryResultSelector: ".query_results_div",
          el_carouselSelector: "#carousel",
          el: elSelector,
          el_raphaelSelector: elSelector, //"#raphael_div",
      });

      registerForPersonUpdates();

      that.tutorialController.setHandler("blueDot", function(){
        that.$blueDotPopover = that.$(elSelector).popover({
          title: "DOTS ARE PEOPLE",
          content: "Each dot represent one or more people. The blue circle represents you. By reacting to a comment, you have caused your dot to move. As you and other participants react, you will move closer to people who reacted similarly to you, and further from people who reacted differently. <button type='button' id='blueDotPopoverButton' class='btn btn-lg btn-primary' style='display: block; margin-top:20px'> Ok, got it </button>",
          html: true,
          trigger: "manual",
          placement: "bottom"
        }).popover("show");
        $('#blueDotPopoverButton').click(function(){
          that.$blueDotPopover.popover("destroy");
        });
      });
      that.tutorialController.setHandler("shadedGroup", function(){
        that.$shadedGroupPopover = that.$(elSelector).popover({
          title: "CLICK ON GROUPS",
          content: "Shaded areas represent groups. Click on a shaded area to show comments that most represent this group's opinion, and separate this group from the other groups.<button type='button' id='shadedGroupPopoverButton' class='btn btn-lg btn-primary' style='display: block; margin-top:20px'> Ok, got it </button>",
          html: true,
          trigger: "manual",
          placement: "bottom"
        }).popover("show");
        $('#shadedGroupPopoverButton').click(function(){
          that.$shadedGroupPopover.popover("destroy");
        });
      });
      that.tutorialController.setHandler("analyzePopover", function(){
        setTimeout(function(){
          that.$analyzeViewPopover = that.$('.query_results > li').first().popover({
            title: "COMMENTS FOR THIS GROUP",
            content: "Clicking on a shaded area brings up the comments that brought this group together: comments that were agreed upon, and comments that were disagreed upon. Click on a comment to see which participants agreed (green/up) and which participants disagreed (red/down) across the whole conversation. Participants who haven't reacted to the selected comment disappear. <button type='button' id='analyzeViewPopoverButton' class='btn btn-lg btn-primary' style='display: block; margin-top:20px'> Ok, got it </button>",
            html: true,
            trigger: "manual",
            placement: "bottom"
          });
          that.$('.query_results > li').first().trigger('click');
          that.$analyzeViewPopover.popover("show");
          that.$('#analyzeViewPopoverButton').click(function(){
            that.$analyzeViewPopover.popover("destroy");
          })
        },1500)
      })
    }

    // just a quick hack for now.
    // we may need to look into something more general
    // http://stackoverflow.com/questions/11216392/how-to-handle-scroll-position-on-hashchange-in-backbone-js-application
    var scrollTopOnFirstShow = _.once(function() {
      // scroll to top
      window.scroll(0,0);
    });

    this.votesByMe = new VotesCollection();

    var serverClient = that.serverClient = new ServerClient({
      zid: zid,
      zinvite: zinvite,
      tokenStore: PolisStorage.token,
      pid: pid,
      votesByMe: this.votesByMe,
      //commentsStore: PolisStorage.comments,
      //reactionsByMeStore: PolisStorage.reactionsByMe,
      utils: window.utils,
      protocol: /localhost/.test(document.domain) ? "http" : "https",
      domain: /localhost/.test(document.domain) ? "localhost:5000" : "www.polis.io",
      basePath: "",
      logger: console
    });

      this.serverClient.addPollingScheduledCallback(function() {
        that.updateVotesByMeCollection();
      });
      this.serverClient.startPolling();
      /* child views */

      this.metadataQuestionsView = new MetadataQuestionsFilterView({
        serverClient: serverClient,
        zid: zid,
        collection: metadataCollection
      });

      this.listenTo(this.metadataQuestionsView, "answersSelected", function(enabledAnswers) {
        console.log(enabledAnswers);
        serverClient.queryParticipantsByMetadata(enabledAnswers).then(
          vis.emphasizeParticipants,
          function(err) {
            console.error(err);
          });
      });

      this.changeVotes = new ChangeVotesView({
        serverClient: serverClient,
        zid: zid
      });

      this.commentView = new CommentView({
        serverClient: serverClient,
        votesByMe: this.votesByMe,
        is_public: is_public,
        pid: pid,
        zid: zid
      });
      // this.commentView.on("vote", this.tutorialController.onVote);

      this.commentsByMe = new CommentsCollection({
        zid: zid,
        pid: pid
      });

      this.commentForm = new CommentFormView({
        pid: pid,
        collection: this.commentsByMe,
        zid: zid
      });

      this.resultsView = new ResultsView({
        serverClient: serverClient,
        zid: zid,
        collection: resultsCollection
      });

      // this.votesByMe.on("all", function(x) {
      //   console.log("votesByMe.all", x);
      // });
      // this.votesByMe.on("change", function() {
      //   console.log("votesByMe.change");
      //   serverClient.updateMyProjection(that.votesByMe);
      // });
      var updateMyProjectionAfterAddingVote = _.throttle(function() {
        console.log("votesByMe.add");
        setTimeout(function() {
          serverClient.updateMyProjection(that.votesByMe);
        }, 300); // wait a bit to let the dot blink before moving it.
      }, 200);
      this.votesByMe.on("add", updateMyProjectionAfterAddingVote);

      this.commentForm.on("commentSubmitted", function() {
        // $("#"+VOTE_TAB).tab("show");
      });

      /* tooltips */
      console.log("here are the views children:");
      console.dir(this.children);

      metadataCollection.fetch({
          data: $.param({
              zid: zid
          }),
          processData: true
      });
      this.commentForm.updateCollection();

    // Clicking on the background dismisses the popovers.
    this.$el.on("click", function() {
      that.destroyPopovers();
    });

    eb.on("clusterClicked", onClusterTapped);
    eb.on("queryResultsRendered", _.bind(this.onAnalyzeTabPopulated, this));

    this.listenTo(this, "rendered", function(){
      setTimeout(function() {

      scrollTopOnFirstShow();

      // $("#visualization_div").affix({
      //   offset: {
      //     top: 150 //will be set dynamically
      //   }
      // });
      function deselectHulls() {
        if (vis) {
          vis.deselect();
        }
      }

      // Before shown
      $('a[data-toggle="tab"]').on('show.bs.tab', function (e) {
        if (e.target && e.target.id === WRITE_TAB && shouldHideVisWhenWriteTabShowing()) {
          // When we're switching to the write tab, hide the vis.
          that.hideVis();
        }
         // previous tab
        if (e.relatedTarget && e.relatedTarget.id === WRITE_TAB) {
          // When we're leaving the write tab, show the vis again.
          that.showVis();
        }
        if(e.relatedTarget && e.relatedTarget.id === ANALYZE_TAB) {
          that.$("#carousel").hide();
        }
        if(e.target && e.target.id === ANALYZE_TAB) {
          that.$("#carousel").show();
        }
      });

      // After shown
      $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
        console.log(e.target);
        // e.relatedTarget // previous tab
        if(e.target && e.target.id === ANALYZE_TAB) {
          console.log(123);
          that.$('li.query_result_item').first().trigger('click');
        }
      });

      that.commentView.on("showComment", _.once(function() {
        that.$("#"+VOTE_TAB).tooltip({
          title: "Start here - read and react to comments submitted by others.",
          placement: "top",
          delay: { show: 300, hide: 200 },
          container: "body"

        })
        .on("click", deselectHulls);
      }));

      that.$("#" + WRITE_TAB).tooltip({
        title: "If your ideas aren't already represented, submit your own comments. Other participants will be able to react.",
        placement: "top",
        delay: { show: 300, hide: 200 },
        container: "body"
      })
      .on("click", deselectHulls);

      that.$("#"+ANALYZE_TAB).tooltip({
        title: "Filters! Click on the \"analyze\" tab to sort participants using metadata. For instance, maybe you only want to see female respondants under 40, or only managers in the NYC office, etc.",
        placement: "top",
        delay: { show: 300, hide: 200 },
        container: "body"

      // Wait until the first comment is shown before showing the tooltip
      });
      that.commentView.on("showComment", _.once(function() {

        that.$commentViewPopover = that.$("#commentView").popover({
          title: "START HERE",
          content: "Read comments submitted by other participants and react using these buttons. <button type='button' id='commentViewPopoverButton' class='btn btn-lg btn-primary' style='display: block; margin-top:20px'> Ok, got it </button>",
          html: true, //XSS risk, not important for now
          trigger: "manual",
          placement: "bottom"
        });

        setTimeout(function(){
          that.$commentViewPopover.popover("show");
          $("#commentViewPopoverButton").click(function(){
            that.$commentViewPopover.popover("destroy");
          });
        },1000);
      }));

      if (isIE8) {
        // Can't listen to the "resize" event since IE8 fires a resize event whenever a DOM element changes size.
        // http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer
       setTimeout(initPcaVis, 10); // give other UI elements a chance to load
        document.body.onresize = _.debounce(initPcaVis, 1000)
      } else {
        setTimeout(initPcaVis, 10); // give other UI elements a chance to load
        $(window).resize(_.debounce(initPcaVis, 100));
      }

  }, 0); // end listenTo "rendered"
  });

  } // end initialize
});<commit_msg>remove resize listener for ie8<commit_after>var eb = require("../eventBus");
var View = require('../view');
var template = require('../tmpl/conversation-view');
var CommentView = require('../views/vote-view');
var CommentFormView = require("../views/comment-form")
var ChangeVotesView = require("../views/change-votes");
var display = require("../util/display");
var MetadataQuestionsFilterView = require("../views/metadataQuestionsFilterView");
var ResultsView = require("../views/results-view");
var VoteModel = require("../models/vote");
var ParticipantModel = require("../models/participant");
var ConversationModel = require("../models/conversation");
var CommentModel = require("../models/comment");
var UserModel = require("../models/user");
var CommentsCollection = require("../collections/comments");
var VotesCollection = require("../collections/votes");
var MetadataQuestionsCollection = require("../collections/metadataQuestions");
var ResultsCollection = require("../collections/results");
var PolisStorage = require("../util/polisStorage");
var popoverEach = require("../util/popoverEach");
var Utils = require("../util/utils");
var VisView = require("../lib/VisView");
var TutorialController = require("../controllers/tutorialController");
var ServerClient = require("../lib/polis");

var ANALYZE_TAB = "analyzeTab";
var WRITE_TAB = "commentFormTab";
var VOTE_TAB = "commentViewTab";

var isIE8 = navigator.userAgent.match(/MSIE 8/)

function shouldShowVisUnderTabs() {
  return display.xs();
}
function shouldHideVisWhenWriteTabShowing() {
  return shouldShowVisUnderTabs();
}

module.exports =  View.extend({
  name: "conversation-view",
  template: template,
  events: {
  },
  destroyPopovers: function() {
    popoverEach("destroy");
  },
  onClusterTapped : function() {
    this.destroyPopovers();
    var that = this;
      // if (window.isMobile()) {
      //    window.scrollTo(0, $("#visualization_div").offset().top);
      // }
  },
  onAnalyzeTabPopulated: function() {
    $('li.query_result_item').first().trigger('click');
  },
  updateVotesByMeCollection: function() {
    console.log("votesByMe.fetch");
    if (this.pid < 0) {
      // DEMO_MODE
      return;
    }
    this.votesByMe.fetch({
      data: $.param({
        zid: this.zid,
        pid: this.pid
      }),
      reset: false
    });
  },
  hideVis: function() {
    $("#visualization_div").hide();
    $("#visualization_div_under_tabs").hide();
  },
  showVis: function() {
    $("#visualization_div").show();
    $("#visualization_div_under_tabs").show();
  },

  initialize: function() {
    var that = this;
    var vis;
    var zid = this.zid = this.model.get("zid");
    var pid = this.pid;
    var zinvite = this.zinvite = this.model.get("zinvite");
    var is_public = this.model.get("is_public");
    var registerForPersonUpdates = _.once(function() {
      that.serverClient.addPersonUpdateListener(onPersonUpdate);
    });

    this.tutorialController = new TutorialController();
    var metadataCollection = new MetadataQuestionsCollection([], {
        zid: zid
    });

    var resultsCollection = new ResultsCollection();

    window.m = metadataCollection;

    // HTTP PATCH - model.save({patch: true})

    function onClusterTapped() {
        that.onClusterTapped();
    }

    function onPersonUpdate() {
      if (vis) {
        vis.upsertNode.apply(vis, arguments);
      }
    }

    function initPcaVis() {
      if (shouldShowVisUnderTabs()) {
        elSelector = "#visualization_div_under_tabs";
        $("#visualization_div").html("").height(0);
        // $("#visualization_div").parent().css("display", "none");
      } else {
        elSelector = "#visualization_div";
        // $("#visualization_div").parent().css("display", "");
        $("#visualization_div_under_tabs").html("").height(0);
      }
      that.elSelector = elSelector;

      var w = $(elSelector).width();
      var h = w/2;
      $(elSelector).height(h);
      vis = new VisView({
          getPid: function() {
            if (!_.isId(pid)) {
              //alert("bad pid: " + pid);
            }
            return pid;
          },
          isIE8: isIE8,
          getCommentsForProjection: serverClient.getCommentsForProjection,
          getCommentsForGroup: serverClient.getCommentsForGroup,
          getReactionsToComment: serverClient.getReactionsToComment,
          getUserInfoByPid: serverClient.getUserInfoByPidSync,
          getTotalVotesByPidSync: serverClient.getTotalVotesByPidSync,
          getPidToBidMapping: serverClient.getPidToBidMapping,
          w: w,
          h: h,
          computeXySpans: Utils.computeXySpans,
          el_queryResultSelector: ".query_results_div",
          el_carouselSelector: "#carousel",
          el: elSelector,
          el_raphaelSelector: elSelector, //"#raphael_div",
      });

      registerForPersonUpdates();

      that.tutorialController.setHandler("blueDot", function(){
        that.$blueDotPopover = that.$(elSelector).popover({
          title: "DOTS ARE PEOPLE",
          content: "Each dot represent one or more people. The blue circle represents you. By reacting to a comment, you have caused your dot to move. As you and other participants react, you will move closer to people who reacted similarly to you, and further from people who reacted differently. <button type='button' id='blueDotPopoverButton' class='btn btn-lg btn-primary' style='display: block; margin-top:20px'> Ok, got it </button>",
          html: true,
          trigger: "manual",
          placement: "bottom"
        }).popover("show");
        $('#blueDotPopoverButton').click(function(){
          that.$blueDotPopover.popover("destroy");
        });
      });
      that.tutorialController.setHandler("shadedGroup", function(){
        that.$shadedGroupPopover = that.$(elSelector).popover({
          title: "CLICK ON GROUPS",
          content: "Shaded areas represent groups. Click on a shaded area to show comments that most represent this group's opinion, and separate this group from the other groups.<button type='button' id='shadedGroupPopoverButton' class='btn btn-lg btn-primary' style='display: block; margin-top:20px'> Ok, got it </button>",
          html: true,
          trigger: "manual",
          placement: "bottom"
        }).popover("show");
        $('#shadedGroupPopoverButton').click(function(){
          that.$shadedGroupPopover.popover("destroy");
        });
      });
      that.tutorialController.setHandler("analyzePopover", function(){
        setTimeout(function(){
          that.$analyzeViewPopover = that.$('.query_results > li').first().popover({
            title: "COMMENTS FOR THIS GROUP",
            content: "Clicking on a shaded area brings up the comments that brought this group together: comments that were agreed upon, and comments that were disagreed upon. Click on a comment to see which participants agreed (green/up) and which participants disagreed (red/down) across the whole conversation. Participants who haven't reacted to the selected comment disappear. <button type='button' id='analyzeViewPopoverButton' class='btn btn-lg btn-primary' style='display: block; margin-top:20px'> Ok, got it </button>",
            html: true,
            trigger: "manual",
            placement: "bottom"
          });
          that.$('.query_results > li').first().trigger('click');
          that.$analyzeViewPopover.popover("show");
          that.$('#analyzeViewPopoverButton').click(function(){
            that.$analyzeViewPopover.popover("destroy");
          })
        },1500)
      })
    }

    // just a quick hack for now.
    // we may need to look into something more general
    // http://stackoverflow.com/questions/11216392/how-to-handle-scroll-position-on-hashchange-in-backbone-js-application
    var scrollTopOnFirstShow = _.once(function() {
      // scroll to top
      window.scroll(0,0);
    });

    this.votesByMe = new VotesCollection();

    var serverClient = that.serverClient = new ServerClient({
      zid: zid,
      zinvite: zinvite,
      tokenStore: PolisStorage.token,
      pid: pid,
      votesByMe: this.votesByMe,
      //commentsStore: PolisStorage.comments,
      //reactionsByMeStore: PolisStorage.reactionsByMe,
      utils: window.utils,
      protocol: /localhost/.test(document.domain) ? "http" : "https",
      domain: /localhost/.test(document.domain) ? "localhost:5000" : "www.polis.io",
      basePath: "",
      logger: console
    });

      this.serverClient.addPollingScheduledCallback(function() {
        that.updateVotesByMeCollection();
      });
      this.serverClient.startPolling();
      /* child views */

      this.metadataQuestionsView = new MetadataQuestionsFilterView({
        serverClient: serverClient,
        zid: zid,
        collection: metadataCollection
      });

      this.listenTo(this.metadataQuestionsView, "answersSelected", function(enabledAnswers) {
        console.log(enabledAnswers);
        serverClient.queryParticipantsByMetadata(enabledAnswers).then(
          vis.emphasizeParticipants,
          function(err) {
            console.error(err);
          });
      });

      this.changeVotes = new ChangeVotesView({
        serverClient: serverClient,
        zid: zid
      });

      this.commentView = new CommentView({
        serverClient: serverClient,
        votesByMe: this.votesByMe,
        is_public: is_public,
        pid: pid,
        zid: zid
      });
      // this.commentView.on("vote", this.tutorialController.onVote);

      this.commentsByMe = new CommentsCollection({
        zid: zid,
        pid: pid
      });

      this.commentForm = new CommentFormView({
        pid: pid,
        collection: this.commentsByMe,
        zid: zid
      });

      this.resultsView = new ResultsView({
        serverClient: serverClient,
        zid: zid,
        collection: resultsCollection
      });

      // this.votesByMe.on("all", function(x) {
      //   console.log("votesByMe.all", x);
      // });
      // this.votesByMe.on("change", function() {
      //   console.log("votesByMe.change");
      //   serverClient.updateMyProjection(that.votesByMe);
      // });
      var updateMyProjectionAfterAddingVote = _.throttle(function() {
        console.log("votesByMe.add");
        setTimeout(function() {
          serverClient.updateMyProjection(that.votesByMe);
        }, 300); // wait a bit to let the dot blink before moving it.
      }, 200);
      this.votesByMe.on("add", updateMyProjectionAfterAddingVote);

      this.commentForm.on("commentSubmitted", function() {
        // $("#"+VOTE_TAB).tab("show");
      });

      /* tooltips */
      console.log("here are the views children:");
      console.dir(this.children);

      metadataCollection.fetch({
          data: $.param({
              zid: zid
          }),
          processData: true
      });
      this.commentForm.updateCollection();

    // Clicking on the background dismisses the popovers.
    this.$el.on("click", function() {
      that.destroyPopovers();
    });

    eb.on("clusterClicked", onClusterTapped);
    eb.on("queryResultsRendered", _.bind(this.onAnalyzeTabPopulated, this));

    this.listenTo(this, "rendered", function(){
      setTimeout(function() {

      scrollTopOnFirstShow();

      // $("#visualization_div").affix({
      //   offset: {
      //     top: 150 //will be set dynamically
      //   }
      // });
      function deselectHulls() {
        if (vis) {
          vis.deselect();
        }
      }

      // Before shown
      $('a[data-toggle="tab"]').on('show.bs.tab', function (e) {
        if (e.target && e.target.id === WRITE_TAB && shouldHideVisWhenWriteTabShowing()) {
          // When we're switching to the write tab, hide the vis.
          that.hideVis();
        }
         // previous tab
        if (e.relatedTarget && e.relatedTarget.id === WRITE_TAB) {
          // When we're leaving the write tab, show the vis again.
          that.showVis();
        }
        if(e.relatedTarget && e.relatedTarget.id === ANALYZE_TAB) {
          that.$("#carousel").hide();
        }
        if(e.target && e.target.id === ANALYZE_TAB) {
          that.$("#carousel").show();
        }
      });

      // After shown
      $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
        console.log(e.target);
        // e.relatedTarget // previous tab
        if(e.target && e.target.id === ANALYZE_TAB) {
          console.log(123);
          that.$('li.query_result_item').first().trigger('click');
        }
      });

      that.commentView.on("showComment", _.once(function() {
        that.$("#"+VOTE_TAB).tooltip({
          title: "Start here - read and react to comments submitted by others.",
          placement: "top",
          delay: { show: 300, hide: 200 },
          container: "body"

        })
        .on("click", deselectHulls);
      }));

      that.$("#" + WRITE_TAB).tooltip({
        title: "If your ideas aren't already represented, submit your own comments. Other participants will be able to react.",
        placement: "top",
        delay: { show: 300, hide: 200 },
        container: "body"
      })
      .on("click", deselectHulls);

      that.$("#"+ANALYZE_TAB).tooltip({
        title: "Filters! Click on the \"analyze\" tab to sort participants using metadata. For instance, maybe you only want to see female respondants under 40, or only managers in the NYC office, etc.",
        placement: "top",
        delay: { show: 300, hide: 200 },
        container: "body"

      // Wait until the first comment is shown before showing the tooltip
      });
      that.commentView.on("showComment", _.once(function() {

        that.$commentViewPopover = that.$("#commentView").popover({
          title: "START HERE",
          content: "Read comments submitted by other participants and react using these buttons. <button type='button' id='commentViewPopoverButton' class='btn btn-lg btn-primary' style='display: block; margin-top:20px'> Ok, got it </button>",
          html: true, //XSS risk, not important for now
          trigger: "manual",
          placement: "bottom"
        });

        setTimeout(function(){
          that.$commentViewPopover.popover("show");
          $("#commentViewPopoverButton").click(function(){
            that.$commentViewPopover.popover("destroy");
          });
        },1000);
      }));

      if (isIE8) {
        // Can't listen to the "resize" event since IE8 fires a resize event whenever a DOM element changes size.
        // http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer
       setTimeout(initPcaVis, 10); // give other UI elements a chance to load
        // document.body.onresize = _.debounce(initPcaVis, 1000)
      } else {
        setTimeout(initPcaVis, 10); // give other UI elements a chance to load
        $(window).resize(_.debounce(initPcaVis, 100));
      }

  }, 0); // end listenTo "rendered"
  });

  } // end initialize
});<|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 || {};
    emoji.init_colons();

    Whisper.ConversationView = Whisper.View.extend({
        className: function() {
            return [ 'conversation', this.model.get('type') ].join(' ');
        },
        id: function() {
            return 'conversation-' + this.model.cid;
        },
        template: $('#conversation').html(),
        render_attributes: function() {
            return {
                group: this.model.get('type') === 'group',
                title: this.model.getTitle()
            };
        },
        initialize: function(options) {
            this.listenTo(this.model, 'destroy', this.stopListening);
            this.listenTo(this.model, 'change:name', this.updateTitle);

            this.render();

            this.appWindow = options.appWindow;
            this.fileInput = new Whisper.FileInputView({
                el: this.$('.attachments'),
                window: this.appWindow.contentWindow
            });

            this.view = new Whisper.MessageListView({
                collection: this.model.messageCollection,
                window: this.appWindow.contentWindow
            });
            this.$('.discussion-container').append(this.view.el);
            this.view.render();

            this.$messageField = this.$('.send-message');

            var onResize = this.forceUpdateMessageFieldSize.bind(this);
            this.appWindow.contentWindow.addEventListener('resize', onResize);

            var onFocus = function() {
                if (this.$el.css('display') !== 'none') {
                    this.markRead();
                }
            }.bind(this);
            this.appWindow.contentWindow.addEventListener('focus', onFocus);

            this.appWindow.onClosed.addListener(function () {
                this.appWindow.contentWindow.removeEventListener('resize', onResize);
                this.appWindow.contentWindow.removeEventListener('focus', onFocus);
                window.autosize.destroy(this.$messageField);
                this.remove();
            }.bind(this));

            setTimeout(function() {
                this.view.scrollToBottom();
            }.bind(this), 10);
        },

        events: {
            'submit .send': 'sendMessage',
            'input .send-message': 'updateMessageFieldSize',
            'keydown .send-message': 'updateMessageFieldSize',
            'click .destroy': 'destroyMessages',
            'click .end-session': 'endSession',
            'click .leave-group': 'leaveGroup',
            'click .new-group-update': 'newGroupUpdate',
            'click .verify-identity': 'verifyIdentity',
            'click .view-members': 'viewMembers',
            'click .drop-down': 'toggleMenu',
            'click .openInbox' : 'openInbox',
            'click' : 'onClick',
            'select .entry': 'messageDetail',
            'force-resize': 'forceUpdateMessageFieldSize'
        },

        viewMembers: function() {
            var view = new Whisper.GroupMemberList({ model: this.model });
            this.$el.hide();
            view.$el.insertAfter(this.el);
            this.listenTo(view, 'back', function() {
                view.remove();
                this.$el.show();
            }.bind(this));
        },

        openInbox: function() {
            openInbox();
        },

        onClick: function(e) {
            this.closeMenu(e);
            this.markRead(e);
        },

        markRead: function(e) {
            this.model.markRead();
        },

        verifyIdentity: function() {
            if (this.model.isPrivate()) {
                var their_number = this.model.id;
                var our_number = textsecure.storage.user.getNumber();
                textsecure.storage.axolotl.getIdentityKey(their_number).then(function(their_key) {
                    textsecure.storage.axolotl.getIdentityKey(our_number).then(function(our_key) {
                        var view = new Whisper.KeyVerificationView({
                            model: { their_key: their_key, your_key: our_key }
                        });
                        this.$el.hide();
                        view.render().$el.insertAfter(this.el);
                        this.listenTo(view, 'back', function() {
                            view.remove();
                            this.$el.show();
                        }.bind(this));
                    }.bind(this));
                }.bind(this));
            }
        },

        messageDetail: function(e, data) {
            var view = new Whisper.MessageDetailView({
                model: data.message,
                conversation: this.model
            });
            view.$el.insertAfter(this.$el);
            this.$el.hide();
            view.render();
            this.listenBack(view);
        },

        listenBack: function(view) {
            this.listenTo(view, 'back', function() {
                view.remove();
                this.$el.show();
            });
        },

        closeMenu: function(e) {
            if (e && !$(e.target).hasClass('drop-down')) {
                this.$('.menu-list').hide();
            }
        },

        endSession: function() {
            this.model.endSession();
            this.$('.menu-list').hide();
        },

        leaveGroup: function() {
            this.model.leaveGroup();
            this.$('.menu-list').hide();
        },

        toggleMenu: function() {
            this.$('.menu-list').toggle();
        },

        newGroupUpdate: function() {
            this.newGroupUpdateView = new Whisper.NewGroupUpdateView({
                model: this.model,
                window: this.appWindow.contentWindow
            });
            this.newGroupUpdateView.$el.insertAfter(this.el);
            this.$el.hide();
            this.listenBack(this.newGroupUpdateView);
        },

        destroyMessages: function(e) {
            this.confirm("Permanently delete this conversation?").then(function() {
                this.model.destroyMessages();
            }.bind(this));
            this.$('.menu-list').hide();
        },

        sendMessage: function(e) {
            e.preventDefault();
            var input = this.$messageField;
            var message = this.replace_colons(input.val()).trim();
            var convo = this.model;

            if (message.length > 0 || this.fileInput.hasFiles()) {
                this.fileInput.getFiles().then(function(attachments) {
                    convo.sendMessage(message, attachments);
                });
                input.val("");
                this.forceUpdateMessageFieldSize(e);
                this.fileInput.deleteFiles();
            }
        },

        replace_colons: function(str) {
            return str.replace(emoji.rx_colons, function(m){
                var idx = m.substr(1, m.length-2);
                var val = emoji.map.colons[idx];
                if (val) {
                    return emoji.data[val][0][0];
                } else {
                    return m;
                }
            });
        },

        updateTitle: function() {
            this.$('.conversation-title').text(this.model.getTitle());
        },

        updateMessageFieldSize: function (event) {
            var keyCode = event.which || event.keyCode;

            if (keyCode === 13 && !event.altKey && !event.shiftKey && !event.ctrlKey) {
                // enter pressed - submit the form now
                event.preventDefault();
                return this.$('.bottom-bar form').submit();
            }

            this.view.measureScrollPosition();
            window.autosize(this.$messageField);

            var $discussionContainer = this.$('.discussion-container'),
                $bottomBar = this.$('.bottom-bar');

            $bottomBar.outerHeight(this.$messageField.outerHeight() + 1);
            var $bottomBarNewHeight = $bottomBar.outerHeight();
            $discussionContainer.outerHeight(this.$el.outerHeight() - $bottomBarNewHeight - this.$('.conversation-header').outerHeight());

            this.view.scrollToBottomIfNeeded();
        },

        forceUpdateMessageFieldSize: function (event) {
            if (this.$el.css('display') === 'none') {
                return;
            }
            this.view.scrollToBottomIfNeeded();
            window.autosize.update(this.$messageField);
            this.updateMessageFieldSize(event);
        }
    });
})();
<commit_msg>After destroying messages, remove the conversation view<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 || {};
    emoji.init_colons();

    Whisper.ConversationView = Whisper.View.extend({
        className: function() {
            return [ 'conversation', this.model.get('type') ].join(' ');
        },
        id: function() {
            return 'conversation-' + this.model.cid;
        },
        template: $('#conversation').html(),
        render_attributes: function() {
            return {
                group: this.model.get('type') === 'group',
                title: this.model.getTitle()
            };
        },
        initialize: function(options) {
            this.listenTo(this.model, 'destroy', this.stopListening);
            this.listenTo(this.model, 'change:name', this.updateTitle);

            this.render();

            this.appWindow = options.appWindow;
            this.fileInput = new Whisper.FileInputView({
                el: this.$('.attachments'),
                window: this.appWindow.contentWindow
            });

            this.view = new Whisper.MessageListView({
                collection: this.model.messageCollection,
                window: this.appWindow.contentWindow
            });
            this.$('.discussion-container').append(this.view.el);
            this.view.render();

            this.$messageField = this.$('.send-message');

            var onResize = this.forceUpdateMessageFieldSize.bind(this);
            this.appWindow.contentWindow.addEventListener('resize', onResize);

            var onFocus = function() {
                if (this.$el.css('display') !== 'none') {
                    this.markRead();
                }
            }.bind(this);
            this.appWindow.contentWindow.addEventListener('focus', onFocus);

            this.appWindow.onClosed.addListener(function () {
                this.appWindow.contentWindow.removeEventListener('resize', onResize);
                this.appWindow.contentWindow.removeEventListener('focus', onFocus);
                window.autosize.destroy(this.$messageField);
                this.remove();
            }.bind(this));

            setTimeout(function() {
                this.view.scrollToBottom();
            }.bind(this), 10);
        },

        events: {
            'submit .send': 'sendMessage',
            'input .send-message': 'updateMessageFieldSize',
            'keydown .send-message': 'updateMessageFieldSize',
            'click .destroy': 'destroyMessages',
            'click .end-session': 'endSession',
            'click .leave-group': 'leaveGroup',
            'click .new-group-update': 'newGroupUpdate',
            'click .verify-identity': 'verifyIdentity',
            'click .view-members': 'viewMembers',
            'click .drop-down': 'toggleMenu',
            'click .openInbox' : 'openInbox',
            'click' : 'onClick',
            'select .entry': 'messageDetail',
            'force-resize': 'forceUpdateMessageFieldSize'
        },

        viewMembers: function() {
            var view = new Whisper.GroupMemberList({ model: this.model });
            this.$el.hide();
            view.$el.insertAfter(this.el);
            this.listenTo(view, 'back', function() {
                view.remove();
                this.$el.show();
            }.bind(this));
        },

        openInbox: function() {
            openInbox();
        },

        onClick: function(e) {
            this.closeMenu(e);
            this.markRead(e);
        },

        markRead: function(e) {
            this.model.markRead();
        },

        verifyIdentity: function() {
            if (this.model.isPrivate()) {
                var their_number = this.model.id;
                var our_number = textsecure.storage.user.getNumber();
                textsecure.storage.axolotl.getIdentityKey(their_number).then(function(their_key) {
                    textsecure.storage.axolotl.getIdentityKey(our_number).then(function(our_key) {
                        var view = new Whisper.KeyVerificationView({
                            model: { their_key: their_key, your_key: our_key }
                        });
                        this.$el.hide();
                        view.render().$el.insertAfter(this.el);
                        this.listenTo(view, 'back', function() {
                            view.remove();
                            this.$el.show();
                        }.bind(this));
                    }.bind(this));
                }.bind(this));
            }
        },

        messageDetail: function(e, data) {
            var view = new Whisper.MessageDetailView({
                model: data.message,
                conversation: this.model
            });
            view.$el.insertAfter(this.$el);
            this.$el.hide();
            view.render();
            this.listenBack(view);
        },

        listenBack: function(view) {
            this.listenTo(view, 'back', function() {
                view.remove();
                this.$el.show();
            });
        },

        closeMenu: function(e) {
            if (e && !$(e.target).hasClass('drop-down')) {
                this.$('.menu-list').hide();
            }
        },

        endSession: function() {
            this.model.endSession();
            this.$('.menu-list').hide();
        },

        leaveGroup: function() {
            this.model.leaveGroup();
            this.$('.menu-list').hide();
        },

        toggleMenu: function() {
            this.$('.menu-list').toggle();
        },

        newGroupUpdate: function() {
            this.newGroupUpdateView = new Whisper.NewGroupUpdateView({
                model: this.model,
                window: this.appWindow.contentWindow
            });
            this.newGroupUpdateView.$el.insertAfter(this.el);
            this.$el.hide();
            this.listenBack(this.newGroupUpdateView);
        },

        destroyMessages: function(e) {
            this.confirm("Permanently delete this conversation?").then(function() {
                this.model.destroyMessages();
                this.remove();
            }.bind(this));
            this.$('.menu-list').hide();
        },

        sendMessage: function(e) {
            e.preventDefault();
            var input = this.$messageField;
            var message = this.replace_colons(input.val()).trim();
            var convo = this.model;

            if (message.length > 0 || this.fileInput.hasFiles()) {
                this.fileInput.getFiles().then(function(attachments) {
                    convo.sendMessage(message, attachments);
                });
                input.val("");
                this.forceUpdateMessageFieldSize(e);
                this.fileInput.deleteFiles();
            }
        },

        replace_colons: function(str) {
            return str.replace(emoji.rx_colons, function(m){
                var idx = m.substr(1, m.length-2);
                var val = emoji.map.colons[idx];
                if (val) {
                    return emoji.data[val][0][0];
                } else {
                    return m;
                }
            });
        },

        updateTitle: function() {
            this.$('.conversation-title').text(this.model.getTitle());
        },

        updateMessageFieldSize: function (event) {
            var keyCode = event.which || event.keyCode;

            if (keyCode === 13 && !event.altKey && !event.shiftKey && !event.ctrlKey) {
                // enter pressed - submit the form now
                event.preventDefault();
                return this.$('.bottom-bar form').submit();
            }

            this.view.measureScrollPosition();
            window.autosize(this.$messageField);

            var $discussionContainer = this.$('.discussion-container'),
                $bottomBar = this.$('.bottom-bar');

            $bottomBar.outerHeight(this.$messageField.outerHeight() + 1);
            var $bottomBarNewHeight = $bottomBar.outerHeight();
            $discussionContainer.outerHeight(this.$el.outerHeight() - $bottomBarNewHeight - this.$('.conversation-header').outerHeight());

            this.view.scrollToBottomIfNeeded();
        },

        forceUpdateMessageFieldSize: function (event) {
            if (this.$el.css('display') === 'none') {
                return;
            }
            this.view.scrollToBottomIfNeeded();
            window.autosize.update(this.$messageField);
            this.updateMessageFieldSize(event);
        }
    });
})();
<|endoftext|>


<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import { intersect } from './utils';

/**
 * Delete elements by the given list of ids.
 * If given list of ids is `null`, delete all currently selected elements.
 *
 * Elements will be deleted on the current page only.
 *
 * If an element id does not correspond do an element on the current page, id is ignored.
 *
 * If an empty list or a list of only unknown ids is given, state is unchanged.
 *
 * If any id to delete is the current background element, background element will be unset for the page,
 * and default background element will be restored. Default background element cannot be removed.
 *
 * If any id to delete is in current selection, deleted ids are removed from selection.
 * Otherwise selection is unchanged.
 *
 * Current page is unchanged.
 *
 * @param {Object} state Current state
 * @param {Object} payload Action payload
 * @param {Array.<string>} payload.elementIds List of ids of elements to delete.
 * @return {Object} New state
 */
function deleteElements(state, { elementIds }) {
  const idsToDelete = elementIds === null ? state.selection : elementIds;

  if (idsToDelete.length === 0) {
    return state;
  }

  const pageIndex = state.pages.findIndex(({ id }) => id === state.current);

  const oldPage = state.pages[pageIndex];
  const pageElementIds = oldPage.elements.map(({ id }) => id);
  const backgroundElement = oldPage.elements[0];

  const isDeletingBackground = idsToDelete.some(
    (id) => id === backgroundElement.id
  );
  const backgroundIsDefault = backgroundElement.isDefaultBackground;

  const validDeletionIds =
    isDeletingBackground && backgroundIsDefault
      ? idsToDelete.filter((id) => id !== backgroundElement.id)
      : idsToDelete;

  // Nothing to delete?
  const hasAnythingToDelete =
    intersect(pageElementIds, validDeletionIds).length > 0;
  if (!hasAnythingToDelete) {
    return state;
  }

  let newElements = oldPage.elements.filter(
    (element) => !validDeletionIds.includes(element.id)
  );

  // Restore default background if non-default bg has been deleted.
  if (isDeletingBackground && !backgroundIsDefault) {
    newElements = [oldPage.defaultBackgroundElement, ...newElements];
  }

  const newPage = {
    ...oldPage,
    elements: newElements,
  };

  const newPages = [
    ...state.pages.slice(0, pageIndex),
    newPage,
    ...state.pages.slice(pageIndex + 1),
  ];

  // This check is to make sure not to modify the selection array if no update is necessary.
  const wasAnySelected =
    intersect(state.selection, validDeletionIds).length > 0;
  const newSelection = wasAnySelected
    ? state.selection.filter((id) => !validDeletionIds.includes(id))
    : state.selection;

  return {
    ...state,
    pages: newPages,
    selection: newSelection,
  };
}

export default deleteElements;
<commit_msg>Make sure element animation gets deleted when the element is deleted<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import { intersect } from './utils';

/**
 * Delete elements by the given list of ids.
 * If given list of ids is `null`, delete all currently selected elements.
 *
 * Elements will be deleted on the current page only.
 *
 * If an element id does not correspond do an element on the current page, id is ignored.
 *
 * If an empty list or a list of only unknown ids is given, state is unchanged.
 *
 * If any id to delete is the current background element, background element will be unset for the page,
 * and default background element will be restored. Default background element cannot be removed.
 *
 * If any id to delete is in current selection, deleted ids are removed from selection.
 * Otherwise selection is unchanged.
 *
 * Current page is unchanged.
 *
 * @param {Object} state Current state
 * @param {Object} payload Action payload
 * @param {Array.<string>} payload.elementIds List of ids of elements to delete.
 * @return {Object} New state
 */
function deleteElements(state, { elementIds }) {
  const idsToDelete = elementIds === null ? state.selection : elementIds;

  if (idsToDelete.length === 0) {
    return state;
  }

  const pageIndex = state.pages.findIndex(({ id }) => id === state.current);

  const oldPage = state.pages[pageIndex];
  const pageElementIds = oldPage.elements.map(({ id }) => id);
  const backgroundElement = oldPage.elements[0];

  const isDeletingBackground = idsToDelete.some(
    (id) => id === backgroundElement.id
  );
  const backgroundIsDefault = backgroundElement.isDefaultBackground;

  const validDeletionIds =
    isDeletingBackground && backgroundIsDefault
      ? idsToDelete.filter((id) => id !== backgroundElement.id)
      : idsToDelete;

  // Nothing to delete?
  const hasAnythingToDelete =
    intersect(pageElementIds, validDeletionIds).length > 0;
  if (!hasAnythingToDelete) {
    return state;
  }

  let newElements = oldPage.elements.filter(
    (element) => !validDeletionIds.includes(element.id)
  );

  // Restore default background if non-default bg has been deleted.
  if (isDeletingBackground && !backgroundIsDefault) {
    newElements = [oldPage.defaultBackgroundElement, ...newElements];
  }

  // Remove animations associated with elements
  const oldAnimations = oldPage.animations || [];
  const newAnimations = oldAnimations.filter((anim) =>
    anim.targets.some((elementId) => !validDeletionIds.includes(elementId))
  );

  const newPage = {
    ...oldPage,
    elements: newElements,
    ...(oldAnimations.length > 0 ? { animations: newAnimations } : {}),
  };

  const newPages = [
    ...state.pages.slice(0, pageIndex),
    newPage,
    ...state.pages.slice(pageIndex + 1),
  ];

  // This check is to make sure not to modify the selection array if no update is necessary.
  const wasAnySelected =
    intersect(state.selection, validDeletionIds).length > 0;
  const newSelection = wasAnySelected
    ? state.selection.filter((id) => !validDeletionIds.includes(id))
    : state.selection;

  return {
    ...state,
    pages: newPages,
    selection: newSelection,
  };
}

export default deleteElements;
<|endoftext|>