<commit_before>define([
    'angular',
    'lodash',
    './utils'
  ],
  function (angular, _, Utils) {
    'use strict';

    var module = angular.module('grafana.controllers');
    var targetLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

    module.controller('ZabbixAPIQueryCtrl', function ($scope, $sce, templateSrv) {

      var zabbixCache = $scope.datasource.zabbixCache;

      $scope.init = function () {
        $scope.targetLetters = targetLetters;
        $scope.metric = {};

        if (!$scope.target.mode || $scope.target.mode !== 1) {
          $scope.downsampleFunctionList = [
            {name: "avg", value: "avg"},
            {name: "min", value: "min"},
            {name: "max", value: "max"}
          ];

          // Set avg by default
          if (!$scope.target.downsampleFunction) {
            $scope.target.downsampleFunction = $scope.downsampleFunctionList[0];
          }

          // Load metrics from cache
          if (zabbixCache._initialized) {
            $scope.getMetricsFromCache();
            $scope.initFilters();
            console.log("Cached", $scope.metric);
          } else {
            zabbixCache.refresh().then(function () {
              $scope.getMetricsFromCache();
              $scope.initFilters();
              console.log("From server", $scope.metric);
            });
          }

          setItemAlias();
        }
        else if ($scope.target.mode === 1) {
          $scope.slaPropertyList = [
            {name: "Status", property: "status"},
            {name: "SLA", property: "sla"},
            {name: "OK time", property: "okTime"},
            {name: "Problem time", property: "problemTime"},
            {name: "Down time", property: "downtimeTime"}
          ];
          $scope.itserviceList = [{name: "test"}];
          $scope.updateITServiceList();
        }
      };

      $scope.initFilters = function () {
        $scope.onGroupBlur();
        $scope.onHostBlur();
        $scope.onApplicationBlur();
      };

      $scope.getMetricsFromCache = function () {
        $scope.metric = {
          groupList: zabbixCache.getGroups(),
          hostList: zabbixCache.getHosts(),
          applicationList: zabbixCache.getApplications(),
          itemList: zabbixCache.getItems()
        };
      };

      // Get list of metric names for bs-typeahead directive
      function getMetricNames(scope, metricList) {
        return _.uniq(_.map(scope.metric[metricList], 'name'));
      }

      // Map functions for bs-typeahead
      $scope.getGroupNames = _.partial(getMetricNames, $scope, 'groupList');
      $scope.getHostNames = _.partial(getMetricNames, $scope, 'filteredHosts');
      $scope.getApplicationNames = _.partial(getMetricNames, $scope, 'filteredApplications');
      $scope.getItemNames = _.partial(getMetricNames, $scope, 'filteredItems');

      $scope.filterHosts = function () {
        var group = $scope.target.group;
        var groups = [];
        var hosts = [];

        // Filter groups by regex
        if (group.isRegex) {
          var filterPattern = Utils.buildRegex(group.filter);
          groups = _.filter($scope.metric.groupList, function (groupObj) {
            return filterPattern.test(groupObj.name);
          });
        }
        // Find hosts in selected group
        else {
          var finded = _.find($scope.metric.groupList, {'name': group.filter});
          if (finded) {
            groups.push(finded);
          } else {
            groups = undefined;
          }
        }

        if (groups) {
          var groupids = _.map(groups, 'groupid');
          hosts = _.filter($scope.metric.hostList, function (hostObj) {
            return _.intersection(groupids, hostObj.groups).length;
          });
        }
        return hosts;
      };

      $scope.filterApplications = function () {
        var host = $scope.target.host;
        var hosts = [];
        var apps = [];

        // Filter hosts by regex
        if (host.isRegex) {
          var filterPattern = Utils.buildRegex(host.filter);
          hosts = _.filter($scope.metric.hostList, function (hostObj) {
            return filterPattern.test(hostObj.name);
          });
        }
        // Find applications in selected host
        else {
          var finded = _.find($scope.metric.hostList, {'name': host.filter});
          if (finded) {
            hosts.push(finded);
          } else {
            hosts = undefined;
          }
        }

        if (hosts) {
          var hostsids = _.map(hosts, 'hostid');
          apps = _.filter($scope.metric.applicationList, function (appObj) {
            return _.intersection(hostsids, appObj.hosts).length;
          });
        }

        return apps;
      };

      $scope.filterItems = function () {
        var app = $scope.target.application;
        var apps = [];
        var items = [];

        // Filter applications by regex
        if (app.isRegex) {
          var filterPattern = Utils.buildRegex(app.filter);
          apps = _.filter($scope.metric.applicationList, function (appObj) {
            return filterPattern.test(appObj.name);
          });
        }
        // Find items in selected application
        else {
          var finded = _.find($scope.metric.applicationList, {'name': app.filter});
          if (finded) {
            apps.push(finded);
          } else {
            apps = undefined;
          }
        }

        if (apps) {
          var appids = _.flatten(_.map(apps, 'applicationids'));
          items = _.filter($scope.metric.itemList, function (itemObj) {
            return _.intersection(appids, itemObj.applications).length;
          });
        }

        return items;
      };

      $scope.onTargetPartChange = function (targetPart) {
        var regexStyle = {'color': '#CCA300'};
        targetPart.isRegex = Utils.isRegex(targetPart.filter);
        targetPart.style = targetPart.isRegex ? regexStyle : {};
      };

      // Handle group blur and filter hosts
      $scope.onGroupBlur = function() {
        $scope.metric.filteredHosts = $scope.filterHosts();
        $scope.parseTarget();
        $scope.get_data();
      };

      // Handle host blur and filter applications
      $scope.onHostBlur = function() {
        $scope.metric.filteredApplications = $scope.filterApplications();
        $scope.parseTarget();
        $scope.get_data();
      };

      // Handle application blur and filter items
      $scope.onApplicationBlur = function() {
        $scope.metric.filteredItems = $scope.filterItems();
        $scope.parseTarget();
        $scope.get_data();
      };

      $scope.onItemBlur = function () {
        $scope.parseTarget();
        $scope.get_data();
      };

      $scope.parseTarget = function() {
        // Parse target
      };

      // Validate target and set validation info
      $scope.validateTarget = function () {};

      /**
       * Switch query editor to specified mode.
       * Modes:
       *  0 - items
       *  1 - IT services
       *  2 - Text metrics
       */
      $scope.switchEditorMode = function (mode) {
        $scope.target.mode = mode;
        $scope.init();
      };

      /**
       * Take alias from item name by default
       */
      function setItemAlias() {
        if (!$scope.target.alias && $scope.target.item) {
          $scope.target.alias = $scope.target.item.name;
        }
      }

      $scope.targetBlur = function () {
        setItemAlias();
        if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) {
          $scope.oldTarget = angular.copy($scope.target);
          $scope.get_data();
        }
      };

      /**
       * Call when IT service is selected.
       */
      $scope.selectITService = function () {
        if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) {
          $scope.oldTarget = angular.copy($scope.target);
          $scope.get_data();
        }
      };

      $scope.duplicate = function () {
        var clone = angular.copy($scope.target);
        $scope.panel.targets.push(clone);
      };

      $scope.moveMetricQuery = function (fromIndex, toIndex) {
        _.move($scope.panel.targets, fromIndex, toIndex);
      };

      /**
       * Update list of IT services
       */
      $scope.updateITServiceList = function () {
        $scope.datasource.zabbixAPI.getITService().then(function (iteservices) {
          $scope.itserviceList = [];
          $scope.itserviceList = $scope.itserviceList.concat(iteservices);
        });
      };

      /**
       * Add templated variables to list of available metrics
       *
       * @param {Array} metricList List of metrics which variables add to
       */
      function addTemplatedVariables(metricList) {
        _.each(templateSrv.variables, function (variable) {
          metricList.push({
            name: '$' + variable.name,
            templated: true
          });
        });
      }

      $scope.init();

    });

  });
<commit_msg>Load default values for new target.<commit_after>define([
    'angular',
    'lodash',
    './utils'
  ],
  function (angular, _, Utils) {
    'use strict';

    var module = angular.module('grafana.controllers');
    var targetLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

    module.controller('ZabbixAPIQueryCtrl', function ($scope, $sce, templateSrv) {

      var zabbixCache = $scope.datasource.zabbixCache;

      $scope.init = function () {
        $scope.targetLetters = targetLetters;
        $scope.metric = {};

        // Load default values
        var targetDefaults = {
          mode: 0,
          group: { filter: "" },
          host: { filter: "" },
          application: { filter: "" },
          item: { filter: "" }
        };
        _.defaults($scope.target, targetDefaults);

        if ($scope.target.mode ===0 ||
            $scope.target.mode === 2) {

          $scope.downsampleFunctionList = [
            {name: "avg", value: "avg"},
            {name: "min", value: "min"},
            {name: "max", value: "max"}
          ];

          // Set avg by default
          if (!$scope.target.downsampleFunction) {
            $scope.target.downsampleFunction = $scope.downsampleFunctionList[0];
          }

          // Load metrics from cache
          if (zabbixCache._initialized) {
            $scope.getMetricsFromCache();
            $scope.initFilters();
            console.log("Cached", $scope.metric);
          } else {
            zabbixCache.refresh().then(function () {
              $scope.getMetricsFromCache();
              $scope.initFilters();
              console.log("From server", $scope.metric);
            });
          }

          setItemAlias();
        }
        else if ($scope.target.mode === 1) {
          $scope.slaPropertyList = [
            {name: "Status", property: "status"},
            {name: "SLA", property: "sla"},
            {name: "OK time", property: "okTime"},
            {name: "Problem time", property: "problemTime"},
            {name: "Down time", property: "downtimeTime"}
          ];
          $scope.itserviceList = [{name: "test"}];
          $scope.updateITServiceList();
        }
      };

      $scope.initFilters = function () {
        $scope.onGroupBlur();
        $scope.onHostBlur();
        $scope.onApplicationBlur();
      };

      $scope.getMetricsFromCache = function () {
        $scope.metric = {
          groupList: zabbixCache.getGroups(),
          hostList: zabbixCache.getHosts(),
          applicationList: zabbixCache.getApplications(),
          itemList: zabbixCache.getItems()
        };
      };

      // Get list of metric names for bs-typeahead directive
      function getMetricNames(scope, metricList) {
        return _.uniq(_.map(scope.metric[metricList], 'name'));
      }

      // Map functions for bs-typeahead
      $scope.getGroupNames = _.partial(getMetricNames, $scope, 'groupList');
      $scope.getHostNames = _.partial(getMetricNames, $scope, 'filteredHosts');
      $scope.getApplicationNames = _.partial(getMetricNames, $scope, 'filteredApplications');
      $scope.getItemNames = _.partial(getMetricNames, $scope, 'filteredItems');

      $scope.filterHosts = function () {
        var group = $scope.target.group;
        var groups = [];
        var hosts = [];

        // Filter groups by regex
        if (group.isRegex) {
          var filterPattern = Utils.buildRegex(group.filter);
          groups = _.filter($scope.metric.groupList, function (groupObj) {
            return filterPattern.test(groupObj.name);
          });
        }
        // Find hosts in selected group
        else {
          var finded = _.find($scope.metric.groupList, {'name': group.filter});
          if (finded) {
            groups.push(finded);
          } else {
            groups = undefined;
          }
        }

        if (groups) {
          var groupids = _.map(groups, 'groupid');
          hosts = _.filter($scope.metric.hostList, function (hostObj) {
            return _.intersection(groupids, hostObj.groups).length;
          });
        }
        return hosts;
      };

      $scope.filterApplications = function () {
        var host = $scope.target.host;
        var hosts = [];
        var apps = [];

        // Filter hosts by regex
        if (host.isRegex) {
          var filterPattern = Utils.buildRegex(host.filter);
          hosts = _.filter($scope.metric.hostList, function (hostObj) {
            return filterPattern.test(hostObj.name);
          });
        }
        // Find applications in selected host
        else {
          var finded = _.find($scope.metric.hostList, {'name': host.filter});
          if (finded) {
            hosts.push(finded);
          } else {
            hosts = undefined;
          }
        }

        if (hosts) {
          var hostsids = _.map(hosts, 'hostid');
          apps = _.filter($scope.metric.applicationList, function (appObj) {
            return _.intersection(hostsids, appObj.hosts).length;
          });
        }

        return apps;
      };

      $scope.filterItems = function () {
        var app = $scope.target.application;
        var apps = [];
        var items = [];

        // Filter applications by regex
        if (app.isRegex) {
          var filterPattern = Utils.buildRegex(app.filter);
          apps = _.filter($scope.metric.applicationList, function (appObj) {
            return filterPattern.test(appObj.name);
          });
        }
        // Find items in selected application
        else {
          var finded = _.find($scope.metric.applicationList, {'name': app.filter});
          if (finded) {
            apps.push(finded);
          } else {
            apps = undefined;
          }
        }

        if (apps) {
          var appids = _.flatten(_.map(apps, 'applicationids'));
          items = _.filter($scope.metric.itemList, function (itemObj) {
            return _.intersection(appids, itemObj.applications).length;
          });
        }

        return items;
      };

      $scope.onTargetPartChange = function (targetPart) {
        var regexStyle = {'color': '#CCA300'};
        targetPart.isRegex = Utils.isRegex(targetPart.filter);
        targetPart.style = targetPart.isRegex ? regexStyle : {};
      };

      // Handle group blur and filter hosts
      $scope.onGroupBlur = function() {
        $scope.metric.filteredHosts = $scope.filterHosts();
        $scope.parseTarget();
        $scope.get_data();
      };

      // Handle host blur and filter applications
      $scope.onHostBlur = function() {
        $scope.metric.filteredApplications = $scope.filterApplications();
        $scope.parseTarget();
        $scope.get_data();
      };

      // Handle application blur and filter items
      $scope.onApplicationBlur = function() {
        $scope.metric.filteredItems = $scope.filterItems();
        $scope.parseTarget();
        $scope.get_data();
      };

      $scope.onItemBlur = function () {
        $scope.parseTarget();
        $scope.get_data();
      };

      $scope.parseTarget = function() {
        // Parse target
      };

      // Validate target and set validation info
      $scope.validateTarget = function () {};

      /**
       * Switch query editor to specified mode.
       * Modes:
       *  0 - items
       *  1 - IT services
       *  2 - Text metrics
       */
      $scope.switchEditorMode = function (mode) {
        $scope.target.mode = mode;
        $scope.init();
      };

      /**
       * Take alias from item name by default
       */
      function setItemAlias() {
        if (!$scope.target.alias && $scope.target.item) {
          $scope.target.alias = $scope.target.item.name;
        }
      }

      $scope.targetBlur = function () {
        setItemAlias();
        if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) {
          $scope.oldTarget = angular.copy($scope.target);
          $scope.get_data();
        }
      };

      /**
       * Call when IT service is selected.
       */
      $scope.selectITService = function () {
        if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) {
          $scope.oldTarget = angular.copy($scope.target);
          $scope.get_data();
        }
      };

      $scope.duplicate = function () {
        var clone = angular.copy($scope.target);
        $scope.panel.targets.push(clone);
      };

      $scope.moveMetricQuery = function (fromIndex, toIndex) {
        _.move($scope.panel.targets, fromIndex, toIndex);
      };

      /**
       * Update list of IT services
       */
      $scope.updateITServiceList = function () {
        $scope.datasource.zabbixAPI.getITService().then(function (iteservices) {
          $scope.itserviceList = [];
          $scope.itserviceList = $scope.itserviceList.concat(iteservices);
        });
      };

      /**
       * Add templated variables to list of available metrics
       *
       * @param {Array} metricList List of metrics which variables add to
       */
      function addTemplatedVariables(metricList) {
        _.each(templateSrv.variables, function (variable) {
          metricList.push({
            name: '$' + variable.name,
            templated: true
          });
        });
      }

      $scope.init();

    });

  });
<|endoftext|>


<commit_before>"use strict";

var sample1 = require('../../fixtures/sample1');
var containerSample = require('../../fixtures/container_anno_sample');
var Document = require('../../../document');
var deleteSelection = Document.Transformations.deleteSelection;

QUnit.module('Transformations/deleteSelection');

QUnit.test("deleting a property selection", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'property',
    path: ['p2', 'content'],
    startOffset: 10,
    endOffset: 15
  });
  var args = {selection: sel};
  args = deleteSelection(doc, args);
  assert.equal(doc.get(['p2', 'content']), 'Paragraph annotation', 'Selected text should be deleted.');
  assert.equal(args.selection.start.offset, 10, 'Selection should be collapsed to the left');
});

QUnit.test("deleting a property selection before annotation", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'property',
    path: ['p2', 'content'],
    startOffset: 0,
    endOffset: 4
  });
  var anno = doc.get('em1');
  var oldStartOffset = anno.startOffset;
  var oldEndOffset = anno.endOffset;
  var args = {selection: sel};
  deleteSelection(doc, args);
  assert.equal(anno.startOffset, oldStartOffset-4, 'Annotation start should be shifted left.');
  assert.equal(anno.endOffset, oldEndOffset-4, 'Annotation end should be shifted left.');
});

QUnit.test("deleting a property selection overlapping annotation start", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'property',
    path: ['p2', 'content'],
    startOffset: 10,
    endOffset: 20
  });
  var anno = doc.get('em1');
  var args = {selection: sel};
  deleteSelection(doc, args);
  assert.equal(anno.startOffset, 10, 'Annotation start should be shifted left.');
  assert.equal(anno.endOffset, 15, 'Annotation end should be shifted left.');
});

QUnit.test("deleting a property selection overlapping annotation end", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'property',
    path: ['p2', 'content'],
    startOffset: 20,
    endOffset: 30
  });
  var anno = doc.get('em1');
  var args = {selection: sel};
  deleteSelection(doc, args);
  assert.equal(anno.startOffset, 15, 'Annotation start should not change.');
  assert.equal(anno.endOffset, 20, 'Annotation end should be shifted left.');
});

QUnit.test("deleting a container selection", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'container',
    containerId: 'main',
    startPath: ['h2', 'content'],
    startOffset: 8,
    endPath: ['p2', 'content'],
    endOffset: 10
  });
  var args = {selection: sel, containerId: 'main'};
  var out = deleteSelection(doc, args);
  var selection = out.selection;
  var anno = doc.get('em1');
  assert.equal(doc.get(['h2', 'content']), "Section with annotation", "Remaining content of p2 should get appended to remains of h2");
  assert.ok(selection.isCollapsed(), 'Selection should be collapsed afterwards.');
  assert.deepEqual(selection.path, ['h2', 'content'], 'Cursor should be in h2.');
  assert.equal(selection.startOffset, 8, 'Cursor should be at the end of h2s remains');
  assert.deepEqual(anno.path, ['h2', 'content'], 'Annotation should have been transferred to h2.');
  assert.deepEqual([anno.startOffset, anno.endOffset], [13, 23], 'Annotation should have been placed correctly.');
});

// QUnit.test("deleting the full document", function(assert) {
//   var doc = sample1();
//   var sel = doc.createSelection({
//     type: 'container',
//     containerId: 'main',
//     startPath: ['h1', 'content'],
//     startOffset: 0,
//     endPath: ['p3', 'content'],
//     endOffset: 11
//   });
//   var args = { selection: sel, containerId: 'main' };
//   var out = deleteSelection(doc, args);
// });

QUnit.test("Edge case: delete container selection spaning multiple nodes containing container annotations", function(assert) {
  // the annotation spans over three nodes
  // we start the selection within the anno in the first text node
  // and expand to the end of the third node
  var doc = containerSample();
  var sel = doc.createSelection({
    type: 'container',
    containerId: 'main',
    startPath: ['p1', 'content'],
    startOffset: 7,
    endPath: ['p3', 'content'],
    endOffset: 10
  });
  var args = { selection: sel, containerId: 'main' };
  var out = deleteSelection(doc, args);
  var selection = out.selection;
  var a1 = doc.get('a1');
  assert.equal(doc.get(['p1', 'content']), "0123456", "Remaining content of p1 should be truncated.");
  assert.ok(selection.isCollapsed(), 'Selection should be collapsed afterwards.');
  assert.deepEqual(a1.endPath, ['p1', 'content'], "Container annotation should be truncated");
  assert.equal(a1.endOffset, 7, "Container annotation should be truncated");
});

QUnit.test("Edge case: delete container selection with 2 fully selected paragraphs", function(assert) {
  // when all nodes under a container selection are covered
  // fully, we want to have a default text type get inserted
  // and the cursor at its first position
  var doc = containerSample();
  var sel = doc.createSelection({
    type: 'container',
    containerId: 'main',
    startPath: ['p2', 'content'],
    startOffset: 0,
    endPath: ['p3', 'content'],
    endOffset: 10
  });
  var args = { selection: sel, containerId: 'main' };
  var out = deleteSelection(doc, args);
  var selection = out.selection;
  assert.ok(selection.isCollapsed(), 'Selection should be collapsed afterwards.');
  assert.equal(selection.startOffset, 0, 'Cursor should be at first position');
  var p = doc.get(selection.path[0]);
  assert.equal(p.type, "paragraph", 'Cursor should be in an empty paragraph');
  assert.equal(p.content.length, 0, 'Paragraph should be empty.');
  assert.equal(doc.get('main').getPosition(p.id), 1);
});
<commit_msg>Added test case for deleting structured nodes partially.<commit_after>"use strict";

var sample1 = require('../../fixtures/sample1');
var containerSample = require('../../fixtures/container_anno_sample');
var Document = require('../../../document');
var deleteSelection = Document.Transformations.deleteSelection;

QUnit.module('Transformations/deleteSelection');

QUnit.test("Deleting a property selection", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'property',
    path: ['p2', 'content'],
    startOffset: 10,
    endOffset: 15
  });
  var args = {selection: sel};
  args = deleteSelection(doc, args);
  assert.equal(doc.get(['p2', 'content']), 'Paragraph annotation', 'Selected text should be deleted.');
  assert.equal(args.selection.start.offset, 10, 'Selection should be collapsed to the left');
});

QUnit.test("Deleting a property selection before annotation", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'property',
    path: ['p2', 'content'],
    startOffset: 0,
    endOffset: 4
  });
  var anno = doc.get('em1');
  var oldStartOffset = anno.startOffset;
  var oldEndOffset = anno.endOffset;
  var args = {selection: sel};
  deleteSelection(doc, args);
  assert.equal(anno.startOffset, oldStartOffset-4, 'Annotation start should be shifted left.');
  assert.equal(anno.endOffset, oldEndOffset-4, 'Annotation end should be shifted left.');
});

QUnit.test("Deleting a property selection overlapping annotation start", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'property',
    path: ['p2', 'content'],
    startOffset: 10,
    endOffset: 20
  });
  var anno = doc.get('em1');
  var args = {selection: sel};
  deleteSelection(doc, args);
  assert.equal(anno.startOffset, 10, 'Annotation start should be shifted left.');
  assert.equal(anno.endOffset, 15, 'Annotation end should be shifted left.');
});

QUnit.test("Deleting a property selection overlapping annotation end", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'property',
    path: ['p2', 'content'],
    startOffset: 20,
    endOffset: 30
  });
  var anno = doc.get('em1');
  var args = {selection: sel};
  deleteSelection(doc, args);
  assert.equal(anno.startOffset, 15, 'Annotation start should not change.');
  assert.equal(anno.endOffset, 20, 'Annotation end should be shifted left.');
});

QUnit.test("Deleting a container selection", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'container',
    containerId: 'main',
    startPath: ['h2', 'content'],
    startOffset: 8,
    endPath: ['p2', 'content'],
    endOffset: 10
  });
  var args = {selection: sel, containerId: 'main'};
  var out = deleteSelection(doc, args);
  var selection = out.selection;
  var anno = doc.get('em1');
  assert.equal(doc.get(['h2', 'content']), "Section with annotation", "Remaining content of p2 should get appended to remains of h2");
  assert.ok(selection.isCollapsed(), 'Selection should be collapsed afterwards.');
  assert.deepEqual(selection.path, ['h2', 'content'], 'Cursor should be in h2.');
  assert.equal(selection.startOffset, 8, 'Cursor should be at the end of h2s remains');
  assert.deepEqual(anno.path, ['h2', 'content'], 'Annotation should have been transferred to h2.');
  assert.deepEqual([anno.startOffset, anno.endOffset], [13, 23], 'Annotation should have been placed correctly.');
});

QUnit.test("Deleting all", function(assert) {
  var doc = sample1();
  var sel = doc.createSelection({
    type: 'container',
    containerId: 'main',
    startPath: ['h1', 'content'],
    startOffset: 0,
    endPath: ['p3', 'content'],
    endOffset: 11
  });
  var args = { selection: sel, containerId: 'main' };
  var out = deleteSelection(doc, args);
  // there should be an empty paragraph now
  var container = doc.get('main');
  assert.equal(container.nodes.length, 1, "There should be one empty paragraph");
  var first = container.getChildAt(0);
  var defaultTextType = doc.getSchema().getDefaultTextType();
  assert.equal(first.type, defaultTextType, "Node should be a default text node");
  var path = out.selection.getPath();
  var address = container.getAddress(path);
  assert.ok(out.selection.isCollapsed(), "Selection should be collapsed (Cursor).");
  assert.deepEqual(address, [0,0], "Cursor should be in empty text node.");
  assert.equal(out.selection.start.offset, 0, "Cursor should be at first position.");
});

QUnit.test("Deleting partially", function(assert) {
  var doc = sample1();
  var structuredNode;
  doc.transaction(function(tx) {
    structuredNode = tx.create({
      id: "sn1",
      type: "structured-node",
      title: "0123456789",
      body: "0123456789",
      caption: "0123456789"
    });
    tx.get('main').show(structuredNode.id, 1);
  });
  var sel = doc.createSelection({
    type: 'container',
    containerId: 'main',
    startPath: ['h1', 'content'],
    startOffset: 0,
    endPath: [structuredNode.id, 'body'],
    endOffset: 5
  });
  var args = { selection: sel, containerId: 'main' };
  var out = deleteSelection(doc, args);
  var container = doc.get('main');
  var first = container.getChildAt(0);
  assert.equal(first.id, structuredNode.id, "First node should have been deleted.");
  assert.equal(first.title, "", "Node's title should have been deleted.");
  assert.equal(first.body, "56789", "Node's body should have been truncated.");
  // Check selection
  var path = out.selection.getPath();
  var address = container.getAddress(path);
  assert.ok(out.selection.isCollapsed(), "Selection should be collapsed (Cursor).");
  assert.deepEqual(address, [0,0], "Cursor should be in empty text node.");
  assert.equal(out.selection.start.offset, 0, "Cursor should be at first position.");
});

QUnit.test("Edge case: delete container selection spaning multiple nodes containing container annotations", function(assert) {
  // the annotation spans over three nodes
  // we start the selection within the anno in the first text node
  // and expand to the end of the third node
  var doc = containerSample();
  var sel = doc.createSelection({
    type: 'container',
    containerId: 'main',
    startPath: ['p1', 'content'],
    startOffset: 7,
    endPath: ['p3', 'content'],
    endOffset: 10
  });
  var args = { selection: sel, containerId: 'main' };
  var out = deleteSelection(doc, args);
  var selection = out.selection;
  var a1 = doc.get('a1');
  assert.equal(doc.get(['p1', 'content']), "0123456", "Remaining content of p1 should be truncated.");
  assert.ok(selection.isCollapsed(), 'Selection should be collapsed afterwards.');
  assert.deepEqual(a1.endPath, ['p1', 'content'], "Container annotation should be truncated");
  assert.equal(a1.endOffset, 7, "Container annotation should be truncated");
});

QUnit.test("Edge case: delete container selection with 2 fully selected paragraphs", function(assert) {
  // when all nodes under a container selection are covered
  // fully, we want to have a default text type get inserted
  // and the cursor at its first position
  var doc = containerSample();
  var sel = doc.createSelection({
    type: 'container',
    containerId: 'main',
    startPath: ['p2', 'content'],
    startOffset: 0,
    endPath: ['p3', 'content'],
    endOffset: 10
  });
  var args = { selection: sel, containerId: 'main' };
  var out = deleteSelection(doc, args);
  var selection = out.selection;
  assert.ok(selection.isCollapsed(), 'Selection should be collapsed afterwards.');
  assert.equal(selection.startOffset, 0, 'Cursor should be at first position');
  var p = doc.get(selection.path[0]);
  assert.equal(p.type, "paragraph", 'Cursor should be in an empty paragraph');
  assert.equal(p.content.length, 0, 'Paragraph should be empty.');
  assert.equal(doc.get('main').getPosition(p.id), 1);
});
<|endoftext|>


<commit_before>
var ssbc = require('ssb-client')
var pull = require('pull-stream')
var explain = require('explain-error')

var links_rx = /(\!?\[[^\[\]]+\]\([^)]+\))/
var parts_rx = /\!?\[([^\[\]]+)\]\(([^)]+)\)/

function isString (s) {
  return 'string' === typeof s
}

function links (text) {
  return text.split(links_rx)
    .filter(function (_, i) {
      return i%2
    }).map(function (e) {
      var parts = parts_rx.exec(e)
      return {
        text: parts[1], target: parts[2],
        image: e[0]==='!'
      }
    })
}

function match (q, t) {
  var m = q.length
  q.forEach(function (e) {
    if(~t.indexOf(e)) m--
  })
  return !m
}

function isHttp (url) {
  return /^https?:\/\//.test(url)
}

var query = process.argv.slice(2)

function Search (sbot) {
  var table = [], waiting = [], ready = false
  pull(
    sbot.createLogStream({live: true}),
    pull.filter(function (data) {
      if(data.sync) {
        ready = true
        while(waiting.length)
          waiting.shift()()
        return false
      }
      return 'string' === typeof data.value.content.text
    }),
    pull.map(function (data) {
      var l = links(data.value.content.text)
      l.forEach(function (e) { e.key = data.key })
      return l
    }),
    pull.flatten(),
    pull.drain(function (data) {
      table.push(data)
    }, function (_) {
  //    if(err) throw explain(err, 'ssb-seach: log stream errored')
      sbot.close(true)
    })
  )

  return {
    //todo: implement limit
    query: function (opts) {
      if(isString(opts)) opts = {query: [opts]}
      if(!opts || !opts.query)
        throw new Error('search.query: opts.query is required')
      var query = opts.query
      return pull(
        pull.values(table),
        pull.filter(function (e) {
          return match(query, e.text)
        }),
        opts && opts.limit ? pull.take(opts.limit) : pull.through()
      )
    },
    ready: function (cb) {
      if(ready) cb()
      else waiting.push(cb)
    }
  }
}

module.exports = {
  name: 'search',
  version: '0.0.0',
  manifest: {
    query: 'source',
    ready: 'async'
  },
  init: Search
}

if(!module.parent)
  ssbc(function (err, sbot) {
    if(err) throw explain(err, 'could not connect to sbot')
    var search = Search(sbot)
    search.ready(function (){
      var n = 0
      pull(
        search.query({query: process.argv.slice(2)}),
        pull.drain(function (data) {
          n++
          console.log(data)
        }, function () {
          console.log(''+n, 'result(s)')
          sbot.close(true)
        })
      )
    })
  })

<commit_msg>only require ssb-client when developing<commit_after>
var pull = require('pull-stream')
var explain = require('explain-error')

var links_rx = /(\!?\[[^\[\]]+\]\([^)]+\))/
var parts_rx = /\!?\[([^\[\]]+)\]\(([^)]+)\)/

function isString (s) {
  return 'string' === typeof s
}

function links (text) {
  return text.split(links_rx)
    .filter(function (_, i) {
      return i%2
    }).map(function (e) {
      var parts = parts_rx.exec(e)
      return {
        text: parts[1], target: parts[2],
        image: e[0]==='!'
      }
    })
}

function match (q, t) {
  var m = q.length
  q.forEach(function (e) {
    if(~t.indexOf(e)) m--
  })
  return !m
}

function isHttp (url) {
  return /^https?:\/\//.test(url)
}

var query = process.argv.slice(2)

function Search (sbot) {
  var table = [], waiting = [], ready = false
  pull(
    sbot.createLogStream({live: true}),
    pull.filter(function (data) {
      if(data.sync) {
        ready = true
        while(waiting.length)
          waiting.shift()()
        return false
      }
      return 'string' === typeof data.value.content.text
    }),
    pull.map(function (data) {
      var l = links(data.value.content.text)
      l.forEach(function (e) { e.key = data.key })
      return l
    }),
    pull.flatten(),
    pull.drain(function (data) {
      table.push(data)
    }, function (_) {
  //    if(err) throw explain(err, 'ssb-seach: log stream errored')
      sbot.close(true)
    })
  )

  return {
    //todo: implement limit
    query: function (opts) {
      if(isString(opts)) opts = {query: [opts]}
      if(!opts || !opts.query)
        throw new Error('search.query: opts.query is required')
      var query = opts.query
      return pull(
        pull.values(table),
        pull.filter(function (e) {
          return match(query, e.text)
        }),
        opts && opts.limit ? pull.take(opts.limit) : pull.through()
      )
    },
    ready: function (cb) {
      if(ready) cb()
      else waiting.push(cb)
    }
  }
}

module.exports = {
  name: 'search',
  version: '0.0.0',
  manifest: {
    query: 'source',
    ready: 'async'
  },
  init: Search
}

if(!module.parent)
  require('ssb-client')(function (err, sbot) {
    if(err) throw explain(err, 'could not connect to sbot')
    var search = Search(sbot)
    search.ready(function (){
      var n = 0
      pull(
        search.query({query: process.argv.slice(2)}),
        pull.drain(function (data) {
          n++
          console.log(data)
        }, function () {
          console.log(''+n, 'result(s)')
          sbot.close(true)
        })
      )
    })
  })

<|endoftext|>


<commit_before>/*jshint evil:true*/

module('Utility - common', {
    setup: function () {
        this.util = Crocodoc.getUtility('common');
    }
});

QUnit.cases([
    { list: [1, 2, 5, 7, 10], x: 5, result: 2 },
    { list: [1, 2, 5, 7, 10], x: 0, result: 0 },
    { list: [1, 2, 5, 7, 10], x: 100, result: 5 },
    { list: [{ a: 1 },{ a: 5 },{ a: 13 },{ a: 100 },{ a: 101 }], x: 100, prop: 'a', result: 3 },
    { list: [{ a: 1 },{ a: 5 },{ a: 13 },{ a: 100 },{ a: 101 }], x: 1, prop: 'a', result: 0 }
]).test('bisectLeft() should return the correct index when called', function (params) {
    equal(this.util.bisectLeft(params.list, params.x, params.prop), params.result);
});

QUnit.cases([
    { list: [1, 2, 5, 7, 10], x: 5, result: 3 },
    { list: [1, 2, 5, 7, 10], x: 0, result: 0 },
    { list: [1, 2, 5, 7, 10], x: 100, result: 5 },
    { list: [{ b: 1 },{ b: 5 },{ b: 13 },{ b: 100 },{ b: 101 }], x: 100, prop: 'b', result: 4 },
    { list: [{ b: 1 },{ b: 5 },{ b: 13 },{ b: 100 },{ b: 101 }], x: 1, prop: 'b', result: 1 }
]).test('bisectRight() should return the correct index when called', function (params) {
    equal(this.util.bisectRight(params.list, params.x, params.prop), params.result);
});

QUnit.cases([
    { x: 5, a: 5, b: 10, result: 5 },
    { x: 1, a: 5, b: 10, result: 5 },
    { x: 8, a: 5, b: 10, result: 8 },
    { x: 15, a: 5, b: 10, result: 10 }
]).test('clamp() should return the correct value when called', function (params) {
    equal(this.util.clamp(params.x, params.a, params.b), params.result);
});

QUnit.cases([
    { val: 2, result: false },
    { val: 'function () {}', result: false },
    { val: function () {}, result: true },
    { val: new Function(), result: true },
    { val: ({}).toString, result: true }
]).test('isFn() should return the correct value when called', function (params) {
    equal(this.util.isFn(params.val), params.result);
});

QUnit.cases([
    { arr: [1, 3], val: 2, result: -1 },
    { arr: [2, 3], val: 2, result: 0 },
    { arr: [1, 2, 3], val: 2, result: 1 },
    { arr: [1, 2, 'c'], val: 'c', result: 2 },
    { arr: [1, 2, 'c'], val: '1', result: -1 }
]).test('inArray() should return the correct result when called', function (params) {
    equal(this.util.inArray(params.val, params.arr), params.result);
});

QUnit.cases([
    { low: 10, high: 20, max: 15, result: { min: 5, max: 15 } },
    { low: 10, high: 20, max: 20, result: { min: 10, max: 20 } },
    { low: 0, high: 50, max: 20, result: { min: 0, max: 20 } },
    { low: 10, high: 50, max: 20, result: { min: 0, max: 20 } },
    { low: 10, high: 50, max: 60, result: { min: 10, max: 50 } },
    { low: -10, high: 50, max: 60, result: { min: 0, max: 60 } },
]).test('constrainRange() should return the expected range object when called', function (params) {
    deepEqual(this.util.constrainRange(params.low, params.high, params.max), params.result);
});

test('makeAbsolute() should return an absolute href when called', function () {
    var path = '/hello/world',
        origin = location.protocol + '//' + location.host;

    equal(this.util.makeAbsolute(path), origin + path);
});

test('throttle() should return a function that calls the provided function only as often as requested when called', function () {
    var wait = 100,
        clock = sinon.useFakeTimers(),
        spy = sinon.spy(),
        throttledFn = this.util.throttle(wait, spy);

    throttledFn();
    throttledFn();
    clock.tick(wait);
    throttledFn();
    clock.tick(wait);

    ok(spy.calledTwice);
    clock.restore();
});

function hasStyle(el, prop, style) {
    var styles = getComputedStyle && getComputedStyle(el) || el.currentStyle;
    return styles[prop] === style;
}

test('insertCSS() should insert the given CSS string when called', function () {
    var str = 'body { background-color: rgb(255, 0, 0) !important; }';
        stylesheetEl = this.util.insertCSS(str);

    // test if the style has been applied
    ok(hasStyle(document.body, 'background-color', 'rgb(255, 0, 0)'));
    stylesheetEl.parentNode.removeChild(stylesheetEl);
});

// assumes insertCSS works properly
test('appendCSSRule() should append the given CSS rule when called', function () {
    var stylesheetEl = this.util.insertCSS(''),
        sheet = stylesheetEl.styleSheet || stylesheetEl.sheet,
        selector = 'body',
        rule = 'background-color: rgb(255, 0, 0) !important;';

    this.util.appendCSSRule(sheet, selector, rule);
    ok(hasStyle(document.body, 'background-color', 'rgb(255, 0, 0)'));
    stylesheetEl.parentNode.removeChild(stylesheetEl);
});

// assumes insertCSS works properly
test('deleteCSSRule() should delete the given CSS rule when called', function () {
    var stylesheetEl = this.util.insertCSS(''),
        sheet = stylesheetEl.styleSheet || stylesheetEl.sheet,
        selector = 'body',
        rule = 'background-color: rgb(255, 0, 0) !important;';

    var index = this.util.appendCSSRule(sheet, selector, rule);
    this.util.deleteCSSRule(sheet, index);

    ok(!hasStyle(document.body, 'background-color', 'rgb(255, 0, 0)'));

    stylesheetEl.parentNode.removeChild(stylesheetEl);
});
<commit_msg>Fix insertCSS/appendCSSRule/deleteCSSRule tests in Firefox<commit_after>/*jshint evil:true*/

module('Utility - common', {
    setup: function () {
        this.util = Crocodoc.getUtility('common');
    }
});

QUnit.cases([
    { list: [1, 2, 5, 7, 10], x: 5, result: 2 },
    { list: [1, 2, 5, 7, 10], x: 0, result: 0 },
    { list: [1, 2, 5, 7, 10], x: 100, result: 5 },
    { list: [{ a: 1 },{ a: 5 },{ a: 13 },{ a: 100 },{ a: 101 }], x: 100, prop: 'a', result: 3 },
    { list: [{ a: 1 },{ a: 5 },{ a: 13 },{ a: 100 },{ a: 101 }], x: 1, prop: 'a', result: 0 }
]).test('bisectLeft() should return the correct index when called', function (params) {
    equal(this.util.bisectLeft(params.list, params.x, params.prop), params.result);
});

QUnit.cases([
    { list: [1, 2, 5, 7, 10], x: 5, result: 3 },
    { list: [1, 2, 5, 7, 10], x: 0, result: 0 },
    { list: [1, 2, 5, 7, 10], x: 100, result: 5 },
    { list: [{ b: 1 },{ b: 5 },{ b: 13 },{ b: 100 },{ b: 101 }], x: 100, prop: 'b', result: 4 },
    { list: [{ b: 1 },{ b: 5 },{ b: 13 },{ b: 100 },{ b: 101 }], x: 1, prop: 'b', result: 1 }
]).test('bisectRight() should return the correct index when called', function (params) {
    equal(this.util.bisectRight(params.list, params.x, params.prop), params.result);
});

QUnit.cases([
    { x: 5, a: 5, b: 10, result: 5 },
    { x: 1, a: 5, b: 10, result: 5 },
    { x: 8, a: 5, b: 10, result: 8 },
    { x: 15, a: 5, b: 10, result: 10 }
]).test('clamp() should return the correct value when called', function (params) {
    equal(this.util.clamp(params.x, params.a, params.b), params.result);
});

QUnit.cases([
    { val: 2, result: false },
    { val: 'function () {}', result: false },
    { val: function () {}, result: true },
    { val: new Function(), result: true },
    { val: ({}).toString, result: true }
]).test('isFn() should return the correct value when called', function (params) {
    equal(this.util.isFn(params.val), params.result);
});

QUnit.cases([
    { arr: [1, 3], val: 2, result: -1 },
    { arr: [2, 3], val: 2, result: 0 },
    { arr: [1, 2, 3], val: 2, result: 1 },
    { arr: [1, 2, 'c'], val: 'c', result: 2 },
    { arr: [1, 2, 'c'], val: '1', result: -1 }
]).test('inArray() should return the correct result when called', function (params) {
    equal(this.util.inArray(params.val, params.arr), params.result);
});

QUnit.cases([
    { low: 10, high: 20, max: 15, result: { min: 5, max: 15 } },
    { low: 10, high: 20, max: 20, result: { min: 10, max: 20 } },
    { low: 0, high: 50, max: 20, result: { min: 0, max: 20 } },
    { low: 10, high: 50, max: 20, result: { min: 0, max: 20 } },
    { low: 10, high: 50, max: 60, result: { min: 10, max: 50 } },
    { low: -10, high: 50, max: 60, result: { min: 0, max: 60 } },
]).test('constrainRange() should return the expected range object when called', function (params) {
    deepEqual(this.util.constrainRange(params.low, params.high, params.max), params.result);
});

test('makeAbsolute() should return an absolute href when called', function () {
    var path = '/hello/world',
        origin = location.protocol + '//' + location.host;

    equal(this.util.makeAbsolute(path), origin + path);
});

test('throttle() should return a function that calls the provided function only as often as requested when called', function () {
    var wait = 100,
        clock = sinon.useFakeTimers(),
        spy = sinon.spy(),
        throttledFn = this.util.throttle(wait, spy);

    throttledFn();
    throttledFn();
    clock.tick(wait);
    throttledFn();
    clock.tick(wait);

    ok(spy.calledTwice);
    clock.restore();
});

function getStyle(el, prop) {
    var styles = getComputedStyle && getComputedStyle(el) || el.currentStyle;
    return styles[prop];
}

test('insertCSS() should insert the given CSS string when called', function () {
    var str = 'body { background-color: rgb(255, 0, 0) !important; }',
        stylesheetEl = this.util.insertCSS(str);

    // test if the style has been applied
    equal(getStyle(document.body, 'backgroundColor'), 'rgb(255, 0, 0)');
    stylesheetEl.parentNode.removeChild(stylesheetEl);
});

// assumes insertCSS works properly
test('appendCSSRule() should append the given CSS rule when called', function () {
    var stylesheetEl = this.util.insertCSS(''),
        sheet = stylesheetEl.styleSheet || stylesheetEl.sheet,
        selector = 'body',
        rule = 'background-color: rgb(255, 0, 0) !important;';

    this.util.appendCSSRule(sheet, selector, rule);
    equal(getStyle(document.body, 'backgroundColor'), 'rgb(255, 0, 0)');
    stylesheetEl.parentNode.removeChild(stylesheetEl);
});

// assumes insertCSS and appendCSSRule works properly
test('deleteCSSRule() should delete the given CSS rule when called', function () {
    var stylesheetEl = this.util.insertCSS(''),
        sheet = stylesheetEl.styleSheet || stylesheetEl.sheet,
        selector = 'body',
        rule = 'background-color: rgb(255, 0, 0) !important;';

    // make sure the background color is not already red...
    notEqual(getStyle(document.body, 'backgroundColor'), 'rgb(255, 0, 0)');

    var index = this.util.appendCSSRule(sheet, selector, rule);

    equal(getStyle(document.body, 'backgroundColor'), 'rgb(255, 0, 0)');

    this.util.deleteCSSRule(sheet, index);

    notEqual(getStyle(document.body, 'backgroundColor'), 'rgb(255, 0, 0)');

    stylesheetEl.parentNode.removeChild(stylesheetEl);
});
<|endoftext|>


<commit_before>/**
 * Brewing module
 * manage phases
 *
 * @module Brewer
 **/

var schedule = require('node-schedule');

var Logger = require('./../module/Logger');
var LOG = 'Brewer';

// Core dependencies
var temperature = require('../temperature');
var coreEvent = require('../event');

var _actualBrew;
var _prevPhase;
var _actualPhase;

// private fns
var resetActualBrew;
var brewChanged;
var endBrew;
var startNextPhase;
var activateActualPhase;

var BOILING_TOLERATED_TEMP = 99;

/**
 * Initialize
 *
 * @method init
 */
exports.init = function () {
  resetActualBrew();

  Logger.info('Brewer is successfully initialized', LOG);
};

/**
 * Get Brew progress
 *
 * @method getProgress
 * @return {Boolean}
 */
exports.getProgress = function () {
  return _actualBrew && _actualBrew.inProgress;
};

/**
 * Cancel brew
 *
 * @method cancelBrew
 */
exports.cancelBrew = function () {
  endBrew();
};

/**
 * Reset actual brew
 *
 * @method resetActualBrew
 */
resetActualBrew = function () {

  // Cancel jobs
  if (_actualBrew && _actualBrew.jobs) {
    _actualBrew.jobs.forEach(function (job) {
      job.cancel();
    });
  }

  // Reset actual brew
  _actualBrew = {
    name: null,
    startTime: null,
    jobs: [],
    phases: [],
    inProgress: false,
    paused: false
  };

  // Reset phase (actual, previous)
  _actualPhase = null;
  _prevPhase = null;

  // Emit brew ended
  coreEvent.brewer.ended();

  brewChanged();
};

/**
 * End actual brew
 *
 * @method endBrew
 */
endBrew = function () {

  // log
  Logger.info('Brew ended', LOG, {
    name: _actualBrew.name,
    date: new Date()
  });

  // Reset
  resetActualBrew();

  // Set point temp
  temperature.setPointToInactive();

  // Emit brew change
  brewChanged();
};

/**
 * Brew changed
 *
 * @method brewChanged
 */
brewChanged = function () {
  var pureActualBrew = {
    name: _actualBrew.name,
    startTime: _actualBrew.startTime,
    inProgress: _actualBrew.inProgress,
    paused: _actualBrew.paused,
    phases: _actualBrew.phases.map(function (phase) {
      return {
        min: phase.min,
        temp: phase.temp,
        jobEnd: phase.jobEnd || null,
        inProgress: phase.inProgress,
        tempReached: phase.tempReached
      };
    })
  };

  // Notify EventDispatcher module
  coreEvent.brewer.brewChanged(pureActualBrew);
};

/**
 * Set paused state
 * Pause the actual phase and save the state for restore
 * Restore paused state
 *
 * @method resetActualBrew
 * @return {Boolean} state of actual brew
 */
exports.setPaused = function () {
  var pauseDiff;

  _actualBrew.paused = !_actualBrew.paused;

  // event
  coreEvent.brewer.statusChanged(_actualBrew.paused);

  // Temp didn't be reached
  if (_actualPhase.inProgress === true && _actualPhase.tempReached === false) {
    if (_actualBrew.paused === true) {

      Logger.info('Paused', LOG, {
        name: _actualBrew.name,
        holdTemp: temperature.getActual()
      });

      temperature.setPoint(temperature.getActual());
    } else {

      Logger.info('Resumed', LOG, {
        name: _actualBrew.name
      });

      temperature.setPoint(_actualPhase.temp);
    }
  }

  // Temp was reached
  else if (_actualPhase.inProgress === true && _actualPhase.tempReached === true) {
    if (_actualBrew.paused) {
      _actualPhase.pausedAt = new Date();
      _actualPhase.job.cancel();

      // Hold actual temp
      temperature.setPoint(temperature.getActual());

      // log
      Logger.info('Paused', LOG, {
        name: _actualBrew.name
      });

    } else {
      pauseDiff = new Date().getTime() - _actualPhase.pausedAt.getTime();

      _actualPhase.jobEnd.setTime(_actualPhase.jobEnd.getTime() + pauseDiff);
      _actualPhase.job = schedule.scheduleJob(_actualPhase.jobEnd, startNextPhase);
      _actualBrew.jobs.push(_actualPhase.job);

      // Restore point temperature
      temperature.setPoint(_actualPhase.temp);

      // log
      Logger.info('Resumed', LOG, {
        name: _actualBrew.name,
        wait: pauseDiff
      });
    }
  }

  // Emit brew changed
  brewChanged();

  return _actualBrew.paused;
};

/**
 * Set new brew
 *
 * @method setBrew
 * @param {Object} params
 */
exports.setBrew = function (params) {

  // cancel earlier
  endBrew();

  // set actual
  _actualBrew.name = params.name;
  _actualBrew.phases = params.phases;
  _actualBrew.startTime = new Date(params.startTime);

  // Set default phases
  _actualBrew.phases = _actualBrew.phases.map(function (phase) {
    phase.inProgress = false;
    phase.tempReached = false;

    return phase;
  });

  Logger.info('set brew', LOG, {
    name: _actualBrew.name,
    phases: _actualBrew.phases,
    startTime: _actualBrew.startTime
  });

  if (_actualBrew.startTime <= new Date()) {
    startNextPhase();
  } else {
    _actualBrew.jobs.push(schedule.scheduleJob(_actualBrew.startTime, startNextPhase));
  }

  // Emit brew change
  brewChanged();
};

/**
 * Get actual brew
 *
 * @method getActualBrew
 */
exports.getActualBrew = function () {
  return _actualBrew;
};

/**
 * Start the next phase
 *
 * @method startNextPhase
 */
startNextPhase = function () {
  var actualIdx = _actualBrew.phases.indexOf(_actualPhase);
  var phasesLength = _actualBrew.phases.length;

  _actualBrew.inProgress = true;

  if (_actualPhase) {
    _prevPhase = _actualPhase;
    _prevPhase.inProgress = false;
    _prevPhase.job.cancel();
    _prevPhase.job = null;

    coreEvent.brewer.phaseChanged({ status: 'ended' });

    Logger.info('stop phase', LOG, {phase: _prevPhase});
  }

  // start new phase
  if (phasesLength - 1 > actualIdx) {
    _actualPhase = _actualBrew.phases[actualIdx + 1];
    _actualPhase.tempReached = false;
    _actualPhase.inProgress = true;

    // Set point temperature
    temperature.setPoint(_actualPhase.temp);

    // log
    Logger.info('start phase', LOG, _actualPhase);

    // Emit brew change
    brewChanged();
  }

  // no more phase left
  else {
    // End brew
    endBrew();
  }
};

/**
 * Called from external by the EventDispatcher module
 * Fires when temperature updated
 *
 * @method onTempUpdate
 */
exports.onTempUpdate = function (temp) {
  var isHeating;
  var isCooling;
  var isBoiling;

  // prev phase temp
  if (!_prevPhase) {
    _prevPhase = _prevPhase || {};
    _prevPhase.temp = temp;
  }

  // actual phase
  if (!_actualBrew.paused && _actualPhase && !_actualPhase.tempReached) {

    // Log wait
    Logger.wait(5000, 'temp waiting', LOG, {
      actualTemp: temp,
      waitForTemp: _actualPhase.temp
    });

    isHeating = _prevPhase.temp <= _actualPhase.temp;
    isCooling = _prevPhase.temp >= _actualPhase.temp;
    isBoiling = 100 === _actualPhase.temp;

    // Heating
    if (temp >= _actualPhase.temp && isHeating) {
      Logger.info('Temp reached for the phase', LOG, {mode: 'heating'});

      activateActualPhase();
    }

    // Heating: Boiling
    else if (isBoiling && temp >= BOILING_TOLERATED_TEMP && isHeating) {
      Logger.info('Temp reached for the phase', LOG, {mode: 'heating'});

      activateActualPhase();
    } else if (temp < _actualPhase.temp && isHeating) {
      Logger.silly('Heating necessary to reach the phase temp', LOG, {mode: 'heating'});
    }

    // Wait for cool
    else if (temp <= _actualPhase.temp && isCooling) {
      Logger.info('Temp reached for the phase', LOG, {mode: 'cooling'});

      activateActualPhase();
    } else if (temp > _actualPhase.temp && isCooling) {
      Logger.silly('Cooling necessary to reach the phase temp', LOG, {mode: 'cooling'});
    }

  }

};

/**
 * Set the actual phase to the active phase
 *
 * @method activateActualPhase
 */
activateActualPhase = function () {

  // Change state of the actual phase
  _actualPhase.tempReached = true;

  // Log
  Logger.info('activate phase', LOG, _actualPhase);

  // schedule phase end trigger
  _actualPhase.jobEnd = new Date();
  _actualPhase.jobEnd.setMinutes(_actualPhase.jobEnd.getMinutes() + _actualPhase.min);
  _actualPhase.job = schedule.scheduleJob(_actualPhase.jobEnd, startNextPhase);
  _actualBrew.jobs.push(_actualPhase.job);

  brewChanged();
};
<commit_msg>Send bre changed for brew ended, only once<commit_after>/**
 * Brewing module
 * manage phases
 *
 * @module Brewer
 **/

var schedule = require('node-schedule');

var Logger = require('./../module/Logger');
var LOG = 'Brewer';

// Core dependencies
var temperature = require('../temperature');
var coreEvent = require('../event');

var _actualBrew;
var _prevPhase;
var _actualPhase;

// private fns
var resetActualBrew;
var brewChanged;
var endBrew;
var startNextPhase;
var activateActualPhase;

var BOILING_TOLERATED_TEMP = 99;

/**
 * Initialize
 *
 * @method init
 */
exports.init = function () {
  resetActualBrew();

  Logger.info('Brewer is successfully initialized', LOG);
};

/**
 * Get Brew progress
 *
 * @method getProgress
 * @return {Boolean}
 */
exports.getProgress = function () {
  return _actualBrew && _actualBrew.inProgress;
};

/**
 * Cancel brew
 *
 * @method cancelBrew
 */
exports.cancelBrew = function () {
  endBrew();
};

/**
 * Reset actual brew
 *
 * @method resetActualBrew
 */
resetActualBrew = function () {

  // Cancel jobs
  if (_actualBrew && _actualBrew.jobs) {
    _actualBrew.jobs.forEach(function (job) {
      job.cancel();
    });
  }

  // Reset actual brew
  _actualBrew = {
    name: null,
    startTime: null,
    jobs: [],
    phases: [],
    inProgress: false,
    paused: false
  };

  // Reset phase (actual, previous)
  _actualPhase = null;
  _prevPhase = null;

  // Emit brew ended
  coreEvent.brewer.ended();

  brewChanged();
};

/**
 * End actual brew
 *
 * @method endBrew
 */
endBrew = function () {

  // log
  Logger.info('Brew ended', LOG, {
    name: _actualBrew.name,
    date: new Date()
  });

  // Reset
  resetActualBrew();

  // Set point temp
  temperature.setPointToInactive();
};

/**
 * Brew changed
 *
 * @method brewChanged
 */
brewChanged = function () {
  var pureActualBrew = {
    name: _actualBrew.name,
    startTime: _actualBrew.startTime,
    inProgress: _actualBrew.inProgress,
    paused: _actualBrew.paused,
    phases: _actualBrew.phases.map(function (phase) {
      return {
        min: phase.min,
        temp: phase.temp,
        jobEnd: phase.jobEnd || null,
        inProgress: phase.inProgress,
        tempReached: phase.tempReached
      };
    })
  };

  // Notify EventDispatcher module
  coreEvent.brewer.brewChanged(pureActualBrew);
};

/**
 * Set paused state
 * Pause the actual phase and save the state for restore
 * Restore paused state
 *
 * @method setPaused
 * @return {Boolean} state of actual brew
 */
exports.setPaused = function () {
  var pauseDiff;

  _actualBrew.paused = !_actualBrew.paused;

  // event
  coreEvent.brewer.statusChanged(_actualBrew.paused);

  // Temp didn't be reached
  if (_actualPhase.inProgress === true && _actualPhase.tempReached === false) {
    if (_actualBrew.paused === true) {

      Logger.info('Paused', LOG, {
        name: _actualBrew.name,
        holdTemp: temperature.getActual()
      });

      temperature.setPoint(temperature.getActual());
    } else {

      Logger.info('Resumed', LOG, {
        name: _actualBrew.name
      });

      temperature.setPoint(_actualPhase.temp);
    }
  }

  // Temp was reached
  else if (_actualPhase.inProgress === true && _actualPhase.tempReached === true) {
    if (_actualBrew.paused) {
      _actualPhase.pausedAt = new Date();
      _actualPhase.job.cancel();

      // Hold actual temp
      temperature.setPoint(temperature.getActual());

      // log
      Logger.info('Paused', LOG, {
        name: _actualBrew.name
      });

    } else {
      pauseDiff = new Date().getTime() - _actualPhase.pausedAt.getTime();

      _actualPhase.jobEnd.setTime(_actualPhase.jobEnd.getTime() + pauseDiff);
      _actualPhase.job = schedule.scheduleJob(_actualPhase.jobEnd, startNextPhase);
      _actualBrew.jobs.push(_actualPhase.job);

      // Restore point temperature
      temperature.setPoint(_actualPhase.temp);

      // log
      Logger.info('Resumed', LOG, {
        name: _actualBrew.name,
        wait: pauseDiff
      });
    }
  }

  // Emit brew changed
  brewChanged();

  return _actualBrew.paused;
};

/**
 * Set new brew
 *
 * @method setBrew
 * @param {Object} params
 */
exports.setBrew = function (params) {

  // cancel earlier
  endBrew();

  // set actual
  _actualBrew.name = params.name;
  _actualBrew.phases = params.phases;
  _actualBrew.startTime = new Date(params.startTime);

  // Set default phases
  _actualBrew.phases = _actualBrew.phases.map(function (phase) {
    phase.inProgress = false;
    phase.tempReached = false;

    return phase;
  });

  Logger.info('set brew', LOG, {
    name: _actualBrew.name,
    phases: _actualBrew.phases,
    startTime: _actualBrew.startTime
  });

  if (_actualBrew.startTime <= new Date()) {
    startNextPhase();
  } else {
    _actualBrew.jobs.push(schedule.scheduleJob(_actualBrew.startTime, startNextPhase));
  }

  // Emit brew change
  brewChanged();
};

/**
 * Get actual brew
 *
 * @method getActualBrew
 */
exports.getActualBrew = function () {
  return _actualBrew;
};

/**
 * Start the next phase
 *
 * @method startNextPhase
 */
startNextPhase = function () {
  var actualIdx = _actualBrew.phases.indexOf(_actualPhase);
  var phasesLength = _actualBrew.phases.length;

  _actualBrew.inProgress = true;

  if (_actualPhase) {
    _prevPhase = _actualPhase;
    _prevPhase.inProgress = false;
    _prevPhase.job.cancel();
    _prevPhase.job = null;

    coreEvent.brewer.phaseChanged({ status: 'ended' });

    Logger.info('stop phase', LOG, {phase: _prevPhase});
  }

  // start new phase
  if (phasesLength - 1 > actualIdx) {
    _actualPhase = _actualBrew.phases[actualIdx + 1];
    _actualPhase.tempReached = false;
    _actualPhase.inProgress = true;

    // Set point temperature
    temperature.setPoint(_actualPhase.temp);

    // log
    Logger.info('start phase', LOG, _actualPhase);

    // Emit brew change
    brewChanged();
  }

  // no more phase left
  else {
    // End brew
    endBrew();
  }
};

/**
 * Called from external by the EventDispatcher module
 * Fires when temperature updated
 *
 * @method onTempUpdate
 */
exports.onTempUpdate = function (temp) {
  var isHeating;
  var isCooling;
  var isBoiling;

  // prev phase temp
  if (!_prevPhase) {
    _prevPhase = _prevPhase || {};
    _prevPhase.temp = temp;
  }

  // actual phase
  if (!_actualBrew.paused && _actualPhase && !_actualPhase.tempReached) {

    // Log wait
    Logger.wait(5000, 'temp waiting', LOG, {
      actualTemp: temp,
      waitForTemp: _actualPhase.temp
    });

    isHeating = _prevPhase.temp <= _actualPhase.temp;
    isCooling = _prevPhase.temp >= _actualPhase.temp;
    isBoiling = 100 === _actualPhase.temp;

    // Heating
    if (temp >= _actualPhase.temp && isHeating) {
      Logger.info('Temp reached for the phase', LOG, {mode: 'heating'});

      activateActualPhase();
    }

    // Heating: Boiling
    else if (isBoiling && temp >= BOILING_TOLERATED_TEMP && isHeating) {
      Logger.info('Temp reached for the phase', LOG, {mode: 'heating'});

      activateActualPhase();
    } else if (temp < _actualPhase.temp && isHeating) {
      Logger.silly('Heating necessary to reach the phase temp', LOG, {mode: 'heating'});
    }

    // Wait for cool
    else if (temp <= _actualPhase.temp && isCooling) {
      Logger.info('Temp reached for the phase', LOG, {mode: 'cooling'});

      activateActualPhase();
    } else if (temp > _actualPhase.temp && isCooling) {
      Logger.silly('Cooling necessary to reach the phase temp', LOG, {mode: 'cooling'});
    }

  }

};

/**
 * Set the actual phase to the active phase
 *
 * @method activateActualPhase
 */
activateActualPhase = function () {

  // Change state of the actual phase
  _actualPhase.tempReached = true;

  // Log
  Logger.info('activate phase', LOG, _actualPhase);

  // schedule phase end trigger
  _actualPhase.jobEnd = new Date();
  _actualPhase.jobEnd.setMinutes(_actualPhase.jobEnd.getMinutes() + _actualPhase.min);
  _actualPhase.job = schedule.scheduleJob(_actualPhase.jobEnd, startNextPhase);
  _actualBrew.jobs.push(_actualPhase.job);

  brewChanged();
};
<|endoftext|>


<commit_before>/**
 * @fileoverview Defines behavior of the settings page.
 */

(function() {

// Mapping from dashboard name to list of checkboxes (one for each tab).
let tabCheckLists = {};
// List of checkboxes (one for each dashboard).
let dashboardCheckboxes = [];
// The object to use to get information from updater.
let updater = chrome.extension.getBackgroundPage().Updater;

document.addEventListener('DOMContentLoaded', function() {
  /* extension navigation buttons */
  // Element representing the button to get back to the alerts page without
  // saving.
  let back = document.getElementById('back');
  back.addEventListener('click', backOnClick);
  // Element representing the button to save settings and return to the alerts
  // page.
  let save = document.getElementById('save');
  save.addEventListener('click', saveOnClick);

  // List of information requested from updater.js:
  // [dashboards, selectedDashboards, hasPermission]
  let info = updater.openSettings();

  /* add dashboards */
  // Element holding the Select Dashboards section header
  let selectDashboards = document.getElementById('select-dashboards');
  // Element representing the div that will contain the list of dashboards and
  // tabs.
  let dashboardsDiv = document.getElementById('dashboards-div');
  selectDashboards.addEventListener('click', function() {
    dashboardsDiv.classList.toggle('collapsed');
  });

  // Mapping from dashboard name to list of tab names for all dashboards.
  let dashboards = info[0];
  // Mapping from dashboard name to list of tab names for selected dashboards
  // and tabs.
  let selectedDashboards = info[1];
  // A boolean indicating whether the user is able to request data from
  // TestGrid. If not, the extension displays an error message instead of data.
  let hasPermission = info[2];
  if (hasPermission) {
    // Element representing the Expand All button in the Select Dashboards
    // section. Used to expand all dashboards to show all tabs.
    let expandButton = createButton('Expand All', dashboardsDiv);
    // Element representing the Collapse All button in the Select Dashboards
    // section. Used to collapse all dashboards to hide all tabs.
    let collapseButton = createButton('Collapse All', dashboardsDiv);
    // Array containing a list of divs for each dashboard and a list of
    // dictionaries for each dashboard containing the checkboxes for their tabs.
    let lists = addDashboards(dashboards, selectedDashboards, dashboardsDiv);
    expandButton.addEventListener('click', expandAll.bind(null, lists[0]));
    collapseButton.addEventListener('click', collapseAll.bind(null, lists[0]));
    dashboardCheckboxes = lists[1];
  } else {
    // Element to display error message if the user cannot access TestGrid.
    let error = document.createElement('p');
    error.textContent = 'You do not have permission to access TestGrid';
    dashboardsDiv.appendChild(error);
    console.log(dashboards.length);
  }

});

/**
 * Creates a button with the given text and adds it to the given div.
 * @param {string} text displayed on the button
 * @param {Element} div to add the button to
 * @return {Element} the button
 */
function createButton(text, div) {
  let button = document.createElement('button');
  button.type = 'button';
  button.textContent = text;
  div.appendChild(button);
  return button;
}

/**
 * Switches the current page to popup.html.
 */
function backOnClick() {
  document.location = 'popup.html';
}

/**
 * Send the currently selected settings information to updater.py and switches
 * the current page to popup.html.
 */
function saveOnClick() {
  // Mapping from dashboard name to list of tab names for selected dashboards
  // and tabs.
  let selectedDashboards = {};
  dashboardCheckboxes.forEach(function(tabCheckboxes) {
    // List of the names of selected tabs in this dashboard.
    let tabs = [];
    tabCheckboxes.tabs.forEach(function(checkbox) {
      if (checkbox.checked) {
        tabs.push(checkbox.id);
      }
    });
    if (tabs.length > 0) {
      selectedDashboards[tabCheckboxes.dashboard] = tabs;
    }
  });
  updater.saveSettings(selectedDashboards);
  document.location = 'popup.html';
}

/**
 * Adds dashboards and their tabs to the Select Dashboards section. Selected
 * tabs and dashboards with selected tabs in them start checked.
 * @param {Array} dashboards List of Dashboard objects.
 * @param {Object} selectedDashboards dictionary from dashboard name to list of
 *   tab names for for selected dashboards and tabs.
 * @param {Element} dashboardsDiv The div to add the dashboards and tabs to.
 * @return {Array} list of divs for each dashboard, list of dictionaries for
 *   each dashboard containing the checkboxes for their tabs
 */
function addDashboards(dashboards, selectedDashboards, dashboardsDiv) {
  // List of divs containing tabs, one for each dashboard
  let divList = [];
  let checkboxes = [];
  for (dashboardName in dashboards) {
    // Array containing the checkbox and text elements for the dashboard
    let dashboard = addLine(dashboardName, dashboardsDiv);
    dashboard[0].addEventListener(
        'click', toggleDashboard.bind(null, dashboardName));
    // Object containing the dashboard name and a list of tab checkboxes for
    // this dashboard.
    let boxes = {'dashboard': dashboardName, 'tabs': []};
    // Element representing the div that tabs in this dashboard are listed in.
    let tabs = document.createElement('div');
    tabs.id = dashboardName + '-div';
    tabs.classList.add('dashboard-div');
    tabs.classList.add('collapsed');
    dashboardsDiv.appendChild(tabs);
    divList.push(tabs);
    dashboard[1].addEventListener('click', function() {
      tabs.classList.toggle('collapsed');
    });
    dashboards[dashboardName].forEach(function(name) {
      // Array containing the checkbox and text elements for the tab
      let tab = addLine(name, tabs);
      boxes.tabs.push(tab[0]);
      if (dashboardName in selectedDashboards &&
          selectedDashboards[dashboardName].includes(name)) {
        tab[0].checked = true;
      }
    });
    if (dashboardName in selectedDashboards) {
      dashboard[0].checked = true;
    }
    checkboxes.push(boxes);
    tabCheckLists[dashboardName] = boxes.tabs;
  }
  return [divList, checkboxes];
}

/**
 * Adds a line with a checkbox and a label for the given name.
 *
 * The id of the checkbox is set to name.
 *
 * @param {string} name The name of the tab or dashboard being indicated.
 * @param {Element} div The div to add the line to.
 * @return {Array} An array containing the checkbox and the paragraph objects.
 */
function addLine(name, div) {
  // Div to hold the checkbox and text element.
  let line = document.createElement('div');
  div.appendChild(line);
  let box = document.createElement('input');
  box.id = name;
  box.type = 'checkbox';
  box.classList.add('line');
  line.appendChild(box);
  let text = document.createElement('p');
  text.textContent = name;
  text.classList.add('line');
  line.appendChild(text);
  return [box, text];
}

/**
 * Expands every div contained in divList.
 *
 * @param {Array} divList A list of div elements.
 */
function expandAll(divList) {
  divList.forEach(function(div) {
    div.classList.remove('collapsed');
  });
}

/**
 * Collapses every div contained in divList.
 *
 * @param {Array} divList A list of div elements.
 */
function collapseAll(divList) {
  divList.forEach(function(div) {
    div.classList.add('collapsed');
  });
}

/**
 * Sets every tab checkbox for the dashboard with the given dashboard name to
 * the same value as the dashboard's checkbox.
 *
 * @param {string} dashboardName The name of the dashboard whose tab's
 *   checkboxes should be toggled
 */
function toggleDashboard(dashboardName) {
  let set = document.getElementById(dashboardName).checked;
  tabCheckLists[dashboardName].forEach(function(tab) {
    tab.checked = set;
  });
}

})();
<commit_msg>fix some typos<commit_after>/**
 * @fileoverview Defines behavior of the settings page.
 */

(function() {

// Mapping from dashboard name to list of checkboxes (one for each tab).
let tabCheckLists = {};
// List of checkboxes (one for each dashboard).
let dashboardCheckboxes = [];
// The object to use to get information from updater.
let updater = chrome.extension.getBackgroundPage().Updater;

document.addEventListener('DOMContentLoaded', function() {
  /* extension navigation buttons */
  // Element representing the button to get back to the alerts page without
  // saving.
  let back = document.getElementById('back');
  back.addEventListener('click', backOnClick);
  // Element representing the button to save settings and return to the alerts
  // page.
  let save = document.getElementById('save');
  save.addEventListener('click', saveOnClick);

  // List of information requested from updater.js:
  // [dashboards, selectedDashboards, hasPermission]
  let info = updater.openSettings();

  /* add dashboards */
  // Element holding the Select Dashboards section header
  let selectDashboards = document.getElementById('select-dashboards');
  // Element representing the div that will contain the list of dashboards and
  // tabs.
  let dashboardsDiv = document.getElementById('dashboards-div');
  selectDashboards.addEventListener('click', function() {
    dashboardsDiv.classList.toggle('collapsed');
  });

  // Mapping from dashboard name to list of tab names for all dashboards.
  let dashboards = info[0];
  // Mapping from dashboard name to list of tab names for selected dashboards
  // and tabs.
  let selectedDashboards = info[1];
  // A boolean indicating whether the user is able to request data from
  // TestGrid. If not, the extension displays an error message instead of data.
  let hasPermission = info[2];
  if (hasPermission) {
    // Element representing the Expand All button in the Select Dashboards
    // section. Used to expand all dashboards to show all tabs.
    let expandButton = createButton('Expand All', dashboardsDiv);
    // Element representing the Collapse All button in the Select Dashboards
    // section. Used to collapse all dashboards to hide all tabs.
    let collapseButton = createButton('Collapse All', dashboardsDiv);
    // Array containing a list of divs for each dashboard and a list of
    // dictionaries for each dashboard containing the checkboxes for their tabs.
    let lists = addDashboards(dashboards, selectedDashboards, dashboardsDiv);
    expandButton.addEventListener('click', expandAll.bind(null, lists[0]));
    collapseButton.addEventListener('click', collapseAll.bind(null, lists[0]));
    dashboardCheckboxes = lists[1];
  } else {
    // Element to display error message if the user cannot access TestGrid.
    let error = document.createElement('p');
    error.textContent = 'You do not have permission to access TestGrid';
    dashboardsDiv.appendChild(error);
    console.log(dashboards.length);
  }

});

/**
 * Creates a button with the given text and adds it to the given div.
 * @param {string} text displayed on the button
 * @param {Element} div to add the button to
 * @return {Element} the button
 */
function createButton(text, div) {
  let button = document.createElement('button');
  button.type = 'button';
  button.textContent = text;
  div.appendChild(button);
  return button;
}

/**
 * Switches the current page to popup.html.
 */
function backOnClick() {
  document.location = 'popup.html';
}

/**
 * Send the currently selected settings information to updater.py and switches
 * the current page to popup.html.
 */
function saveOnClick() {
  // Mapping from dashboard name to list of tab names for selected dashboards
  // and tabs.
  let selectedDashboards = {};
  dashboardCheckboxes.forEach(function(tabCheckboxes) {
    // List of the names of selected tabs in this dashboard.
    let tabs = [];
    tabCheckboxes.tabs.forEach(function(checkbox) {
      if (checkbox.checked) {
        tabs.push(checkbox.id);
      }
    });
    if (tabs.length > 0) {
      selectedDashboards[tabCheckboxes.dashboard] = tabs;
    }
  });
  updater.saveSettings(selectedDashboards);
  document.location = 'popup.html';
}

/**
 * Adds dashboards and their tabs to the Select Dashboards section. Selected
 * tabs and dashboards with selected tabs in them start checked.
 * @param {Array} dashboards List of Dashboard objects.
 * @param {Object} selectedDashboards dictionary from dashboard name to list of
 *   tab names for selected dashboards and tabs.
 * @param {Element} dashboardsDiv The div to add the dashboards and tabs to.
 * @return {Array} list of divs for each dashboard, list of dictionaries for
 *   each dashboard containing the checkboxes for their tabs
 */
function addDashboards(dashboards, selectedDashboards, dashboardsDiv) {
  // List of divs containing tabs, one for each dashboard
  let divList = [];
  let checkboxes = [];
  for (dashboardName in dashboards) {
    // Array containing the checkbox and text elements for the dashboard
    let dashboard = addLine(dashboardName, dashboardsDiv);
    dashboard[0].addEventListener(
        'click', toggleDashboard.bind(null, dashboardName));
    // Object containing the dashboard name and a list of tab checkboxes for
    // this dashboard.
    let boxes = {'dashboard': dashboardName, 'tabs': []};
    // Element representing the div that tabs in this dashboard are listed in.
    let tabs = document.createElement('div');
    tabs.id = dashboardName + '-div';
    tabs.classList.add('dashboard-div');
    tabs.classList.add('collapsed');
    dashboardsDiv.appendChild(tabs);
    divList.push(tabs);
    dashboard[1].addEventListener('click', function() {
      tabs.classList.toggle('collapsed');
    });
    dashboards[dashboardName].forEach(function(name) {
      // Array containing the checkbox and text elements for the tab
      let tab = addLine(name, tabs);
      boxes.tabs.push(tab[0]);
      if (dashboardName in selectedDashboards &&
          selectedDashboards[dashboardName].includes(name)) {
        tab[0].checked = true;
      }
    });
    if (dashboardName in selectedDashboards) {
      dashboard[0].checked = true;
    }
    checkboxes.push(boxes);
    tabCheckLists[dashboardName] = boxes.tabs;
  }
  return [divList, checkboxes];
}

/**
 * Adds a line with a checkbox and a label for the given name.
 *
 * The id of the checkbox is set to name.
 *
 * @param {string} name The name of the tab or dashboard being indicated.
 * @param {Element} div The div to add the line to.
 * @return {Array} An array containing the checkbox and the paragraph objects.
 */
function addLine(name, div) {
  // Div to hold the checkbox and text element.
  let line = document.createElement('div');
  div.appendChild(line);
  let box = document.createElement('input');
  box.id = name;
  box.type = 'checkbox';
  box.classList.add('line');
  line.appendChild(box);
  let text = document.createElement('p');
  text.textContent = name;
  text.classList.add('line');
  line.appendChild(text);
  return [box, text];
}

/**
 * Expands every div contained in divList.
 *
 * @param {Array} divList A list of div elements.
 */
function expandAll(divList) {
  divList.forEach(function(div) {
    div.classList.remove('collapsed');
  });
}

/**
 * Collapses every div contained in divList.
 *
 * @param {Array} divList A list of div elements.
 */
function collapseAll(divList) {
  divList.forEach(function(div) {
    div.classList.add('collapsed');
  });
}

/**
 * Sets every tab checkbox for the dashboard with the given dashboard name to
 * the same value as the dashboard's checkbox.
 *
 * @param {string} dashboardName The name of the dashboard whose tab's
 *   checkboxes should be toggled
 */
function toggleDashboard(dashboardName) {
  let set = document.getElementById(dashboardName).checked;
  tabCheckLists[dashboardName].forEach(function(tab) {
    tab.checked = set;
  });
}

})();
<|endoftext|>


<commit_before>// special shoutout to Geoffrey Pursell for single-handedly making Pattern Lab Node Pattern Engines possible!
'use strict';
const path = require('path');
const diveSync = require('diveSync');
const chalk = require('chalk');
const engineMatcher = /^patternengine-node-(.*)$/;
const enginesDirectories = [
  {
    displayName: 'the core',
    path: path.resolve(__dirname, '..', '..', 'node_modules')
  },
  {
    displayName: 'the edition or test directory',
    path: path.join(process.cwd(), 'node_modules')
  }
];

// given a path: return the engine name if the path points to a valid engine
// module directory, or false if it doesn't
function isEngineModule(filePath) {
  const baseName = path.basename(filePath);
  const engineMatch = baseName.match(engineMatcher);

  if (engineMatch) { return engineMatch[1]; }
  return false;
}

function findEngineModulesInDirectory(dir) {
  const foundEngines = [];

  diveSync(dir, {
    recursive: false,
    directories: true
  }, function (err, filePath) {
    if (err) { throw err; }
    const foundEngineName = isEngineModule(filePath);
    if (foundEngineName) {
      foundEngines.push({
        name: foundEngineName,
        modulePath: filePath
      });
    }
  });

  return foundEngines;
}

//
// PatternEngines: the main export of this module
//
// It's an Object/hash of all loaded pattern engines, empty at first.  My
// intention here is to make this return an object that can be used to obtain
// any loaded PatternEngine by addressing them like this:
//
//   var PatternEngines = require('./pattern_engines/pattern_engines');
//   var Mustache = PatternEngines['mustache'];
//
// Object.create lets us create an object with a specified prototype. We want
// this here because we would like the object's "own properties" to include
// only the engine names so we can easily iterate over them; all the handy
// methods and properites below should therefore be on its prototype.

const PatternEngines = Object.create({

  loadAllEngines: function (patternLabConfig) {
    var self = this;

    // Try to load engines! We scan for engines at each path specified above. This
    // function is kind of a big deal.
    enginesDirectories.forEach(function (engineDirectory) {
      const enginesInThisDir = findEngineModulesInDirectory(engineDirectory.path);
      console.log(chalk.bold(`Loading engines from ${engineDirectory.displayName}...\n`));

      // find all engine-named things in this directory and try to load them,
      // unless it's already been loaded.
      enginesInThisDir.forEach(function (engineDiscovery) {
        let errorMessage;
        const successMessage = chalk.green("good to go");

        try {
          // Give it a try! load 'er up. But not if we already have,
          // of course.  Also pass the pattern lab config object into
          // the engine's closure scope so it can know things about
          // things.
          if (self[engineDiscovery.name]) {
            throw new Error("already loaded, skipping.");
          }
          self[engineDiscovery.name] = require(engineDiscovery.modulePath);
          if (typeof self[engineDiscovery.name].usePatternLabConfig === 'function') {
            self[engineDiscovery.name].usePatternLabConfig(patternLabConfig);
          }
          if (typeof self[engineDiscovery.name].spawnMeta === 'function') {
            self[engineDiscovery.name].spawnMeta(patternLabConfig);
          }
        } catch (err) {
          errorMessage = err.message;
        } finally {
          // report on the status of the engine, one way or another!
          console.log(`  ${engineDiscovery.name}:`, errorMessage ? chalk.red(errorMessage) : successMessage);
        }
      });
      console.log('');
    });

    // Complain if for some reason we haven't loaded any engines.
    if (Object.keys(self).length === 0) {
      throw new Error('No engines loaded! Something is seriously wrong.');
    }

    console.log(chalk.bold('Done loading engines.\n'));
  },

  getEngineNameForPattern: function (pattern) {
    // avoid circular dependency by putting this in here. TODO: is this slow?
    const of = require('./object_factory');
    if (pattern instanceof of.Pattern && typeof pattern.fileExtension === 'string' && pattern.fileExtension) {
      //loop through known engines and find the one that supports the pattern's fileExtension
      //TODO: support multiple extensions someday, such as .handlebars and .hbs
      const engineNames = Object.keys(this);
      for (let i = 0; i < engineNames.length; i++) {
        const engine = this[engineNames[i]];
        if (engine.engineFileExtension === pattern.fileExtension) {
          return engine.engineName;
        }
      }
    }

    // otherwise, assume it's a plain mustache template string and act
    // accordingly
    return 'mustache';
  },

  getEngineForPattern: function (pattern) {
    if (pattern.isPseudoPattern) {
      return this.getEngineForPattern(pattern.basePattern);
    } else {
      const engineName = this.getEngineNameForPattern(pattern);
      return this[engineName];
    }
  },

  getSupportedFileExtensions: function () {
    const engineNames = Object.keys(PatternEngines);
    return engineNames.map(function (engineName) {
      return PatternEngines[engineName].engineFileExtension;
    });
  },

  isFileExtensionSupported: function (fileExtension) {
    const supportedExtensions = PatternEngines.getSupportedFileExtensions();
    return (supportedExtensions.lastIndexOf(fileExtension) !== -1);
  },

  // given a filename, return a boolean: whether or not the filename indicates
  // that the file is pseudopattern JSON
  isPseudoPatternJSON: function (filename) {
    const extension = path.extname(filename);
    return (extension === '.json' && filename.indexOf('~') > -1);
  },

  // takes a filename string, not a full path; a basename (plus extension)
  // ignore _underscored patterns, dotfiles, and anything not recognized by a
  // loaded pattern engine. Pseudo-pattern .json files ARE considered to be
  // pattern files!
  isPatternFile: function (filename) {
    // skip hidden patterns/files without a second thought
    const extension = path.extname(filename);
    if (filename.charAt(0) === '.' ||
        (extension === '.json' && !PatternEngines.isPseudoPatternJSON(filename))) {
      return false;
    }

    // not a hidden pattern, let's dig deeper
    const supportedPatternFileExtensions = PatternEngines.getSupportedFileExtensions();
    return (supportedPatternFileExtensions.lastIndexOf(extension) !== -1 ||
            PatternEngines.isPseudoPatternJSON(filename));
  }
});

module.exports = PatternEngines;
<commit_msg>moved patternlab engine logs behind debug flag<commit_after>// special shoutout to Geoffrey Pursell for single-handedly making Pattern Lab Node Pattern Engines possible!
'use strict';
const path = require('path');
const diveSync = require('diveSync');
const chalk = require('chalk');
const engineMatcher = /^patternengine-node-(.*)$/;
const enginesDirectories = [
  {
    displayName: 'the core',
    path: path.resolve(__dirname, '..', '..', 'node_modules')
  },
  {
    displayName: 'the edition or test directory',
    path: path.join(process.cwd(), 'node_modules')
  }
];

// given a path: return the engine name if the path points to a valid engine
// module directory, or false if it doesn't
function isEngineModule(filePath) {
  const baseName = path.basename(filePath);
  const engineMatch = baseName.match(engineMatcher);

  if (engineMatch) { return engineMatch[1]; }
  return false;
}

function findEngineModulesInDirectory(dir) {
  const foundEngines = [];

  diveSync(dir, {
    recursive: false,
    directories: true
  }, function (err, filePath) {
    if (err) { throw err; }
    const foundEngineName = isEngineModule(filePath);
    if (foundEngineName) {
      foundEngines.push({
        name: foundEngineName,
        modulePath: filePath
      });
    }
  });

  return foundEngines;
}

//
// PatternEngines: the main export of this module
//
// It's an Object/hash of all loaded pattern engines, empty at first.  My
// intention here is to make this return an object that can be used to obtain
// any loaded PatternEngine by addressing them like this:
//
//   var PatternEngines = require('./pattern_engines/pattern_engines');
//   var Mustache = PatternEngines['mustache'];
//
// Object.create lets us create an object with a specified prototype. We want
// this here because we would like the object's "own properties" to include
// only the engine names so we can easily iterate over them; all the handy
// methods and properites below should therefore be on its prototype.

const PatternEngines = Object.create({

  loadAllEngines: function (patternLabConfig) {
    var self = this;

    // Try to load engines! We scan for engines at each path specified above. This
    // function is kind of a big deal.
    enginesDirectories.forEach(function (engineDirectory) {
      const enginesInThisDir = findEngineModulesInDirectory(engineDirectory.path);
      if (patternLabConfig.debug) {
        console.log(chalk.bold(`Loading engines from ${engineDirectory.displayName}...\n`));
      }

      // find all engine-named things in this directory and try to load them,
      // unless it's already been loaded.
      enginesInThisDir.forEach(function (engineDiscovery) {
        let errorMessage;
        const successMessage = "good to go";
        if (patternLabConfig.debug) {
          chalk.green(successMessage);
        }

        try {
          // Give it a try! load 'er up. But not if we already have,
          // of course.  Also pass the pattern lab config object into
          // the engine's closure scope so it can know things about
          // things.
          if (self[engineDiscovery.name]) {
            throw new Error("already loaded, skipping.");
          }
          self[engineDiscovery.name] = require(engineDiscovery.modulePath);
          if (typeof self[engineDiscovery.name].usePatternLabConfig === 'function') {
            self[engineDiscovery.name].usePatternLabConfig(patternLabConfig);
          }
          if (typeof self[engineDiscovery.name].spawnMeta === 'function') {
            self[engineDiscovery.name].spawnMeta(patternLabConfig);
          }
        } catch (err) {
          errorMessage = err.message;
        } finally {
          // report on the status of the engine, one way or another!
          if (patternLabConfig.debug) {
            console.log(`  ${engineDiscovery.name}:`, errorMessage ? chalk.red(errorMessage) : successMessage);
          }
        }
      });
      console.log('');
    });

    // Complain if for some reason we haven't loaded any engines.
    if (Object.keys(self).length === 0) {
      throw new Error('No engines loaded! Something is seriously wrong.');
    }
    if (patternLabConfig.debug) {
      console.log(chalk.bold('Done loading engines.\n'));
    }
  },

  getEngineNameForPattern: function (pattern) {
    // avoid circular dependency by putting this in here. TODO: is this slow?
    const of = require('./object_factory');
    if (pattern instanceof of.Pattern && typeof pattern.fileExtension === 'string' && pattern.fileExtension) {
      //loop through known engines and find the one that supports the pattern's fileExtension
      //TODO: support multiple extensions someday, such as .handlebars and .hbs
      const engineNames = Object.keys(this);
      for (let i = 0; i < engineNames.length; i++) {
        const engine = this[engineNames[i]];
        if (engine.engineFileExtension === pattern.fileExtension) {
          return engine.engineName;
        }
      }
    }

    // otherwise, assume it's a plain mustache template string and act
    // accordingly
    return 'mustache';
  },

  getEngineForPattern: function (pattern) {
    if (pattern.isPseudoPattern) {
      return this.getEngineForPattern(pattern.basePattern);
    } else {
      const engineName = this.getEngineNameForPattern(pattern);
      return this[engineName];
    }
  },

  getSupportedFileExtensions: function () {
    const engineNames = Object.keys(PatternEngines);
    return engineNames.map(function (engineName) {
      return PatternEngines[engineName].engineFileExtension;
    });
  },

  isFileExtensionSupported: function (fileExtension) {
    const supportedExtensions = PatternEngines.getSupportedFileExtensions();
    return (supportedExtensions.lastIndexOf(fileExtension) !== -1);
  },

  // given a filename, return a boolean: whether or not the filename indicates
  // that the file is pseudopattern JSON
  isPseudoPatternJSON: function (filename) {
    const extension = path.extname(filename);
    return (extension === '.json' && filename.indexOf('~') > -1);
  },

  // takes a filename string, not a full path; a basename (plus extension)
  // ignore _underscored patterns, dotfiles, and anything not recognized by a
  // loaded pattern engine. Pseudo-pattern .json files ARE considered to be
  // pattern files!
  isPatternFile: function (filename) {
    // skip hidden patterns/files without a second thought
    const extension = path.extname(filename);
    if (filename.charAt(0) === '.' ||
        (extension === '.json' && !PatternEngines.isPseudoPatternJSON(filename))) {
      return false;
    }

    // not a hidden pattern, let's dig deeper
    const supportedPatternFileExtensions = PatternEngines.getSupportedFileExtensions();
    return (supportedPatternFileExtensions.lastIndexOf(extension) !== -1 ||
            PatternEngines.isPseudoPatternJSON(filename));
  }
});

module.exports = PatternEngines;
<|endoftext|>