<commit_before>/* eslint-env mocha */
describe('L.esri.BasemapLayer', function () {
  function createMap () {
    // create container
    var container = document.createElement('div');

    // give container a width/height
    container.setAttribute('style', 'width:500px; height: 500px;');

    // add container to body
    document.body.appendChild(container);

    return L.map(container).setView([37.75, -122.45], 5);
  }

  var map;
  var server;
  var clock;
  var mockAttributions = {
    'contributors': [
      {
        'attribution': 'SkyNet',
        'coverageAreas': [
          {
            'zoomMax': 19,
            'zoomMin': 0,
            'score': 50,
            'bbox': [-84.94, -179.66, 84.94, 179.66]
          }
        ]
      },
      {
        'attribution': 'City & County of San Francisco',
        'coverageAreas': [
          {
            'zoomMax': 19,
            'zoomMin': 16,
            'score': 100,
            'bbox': [37.71, -122.51, 37.83, -122.36]
          }
        ]
      }
    ]
  };

  beforeEach(function () {
    clock = sinon.useFakeTimers();
    server = sinon.fakeServer.create();
    server.respondWith('GET', new RegExp(/.*/), JSON.stringify(mockAttributions));
    map = createMap();
  });

  afterEach(function () {
    clock.restore();
    server.restore();
    map.remove();
  });

  it('can return valid basemaps', function () {
    var testmaps = ['Streets', 'Topographic', 'NationalGeographic', 'Oceans', 'OceansLabels', 'DarkGray', 'DarkGrayLabels', 'Gray', 'GrayLabels', 'Imagery', 'ImageryLabels', 'ImageryTransportation', 'ShadedRelief', 'ShadedReliefLabels', 'Terrain', 'TerrainLabels', 'USATopo', 'ImageryClarity', 'ImageryFirefly', 'Physical'];

    for (var i = 0, len = testmaps.length; i < len; i++) {
      var name = testmaps[i];
      expect(L.esri.basemapLayer(name)).to.be.instanceof(L.esri.BasemapLayer);
      expect(L.esri.basemapLayer(name)._url).to.equal(L.esri.BasemapLayer.TILES[name].urlTemplate);
    }
  });

  it('can survive adding/removing basemaps w/ labels', function () {
    var moremaps = ['Oceans', 'DarkGray', 'Gray', 'Imagery', 'ShadedRelief', 'Terrain'];
    for (var i = 0, len = moremaps.length; i < len; i++) {
      var layer = L.esri.basemapLayer(moremaps[i]).addTo(map);
      var layerWithLabels = L.esri.basemapLayer(moremaps[i] + 'Labels').addTo(map);
      expect(map.hasLayer(layer)).to.equal(true);
      expect(map.hasLayer(layerWithLabels)).to.equal(true);

      map.removeLayer(layer);
      map.removeLayer(layerWithLabels);
      expect(map.hasLayer(layer)).to.equal(false);
      expect(map.hasLayer(layerWithLabels)).to.equal(false);
    }
  });

  it('can be added to the map as normal', function () {
    var baseLayer = L.esri.basemapLayer('Streets');
    map.addLayer(baseLayer);
    expect(map.hasLayer(baseLayer)).to.equal(true);
  });

  it('will append tokens when fetching tiles if necessary', function () {
    var baseLayer = L.esri.basemapLayer('Streets', {token: 'bogus'});
    map.addLayer(baseLayer);
    expect(baseLayer._url).to.contain('token=bogus');
  });

  it('will throw an error given invalid basemap name', function () {
    expect(function () {
      L.esri.basemapLayer('junk');
    }).to.throw(/Invalid parameter/);
  });

  it('should have an L.esri.basemapLayer alias', function () {
    expect(L.esri.basemapLayer('Topographic')).to.be.instanceof(L.esri.BasemapLayer);
  });

  // /*
  // need to figure out how to wire up the mockAttributions to
  // test display when map is panned beyond the dateline
  //
  // i dont understand how to spoof http responses for inner logic private function calls
  // like the jsonp request made inside L.esri.Util._getAttributionData();
  // */
  // it('can display attribution when panned beyond the dateline', function () {
  //   var baseLayer = L.esri.basemapLayer('Streets');
  //   map.addLayer(baseLayer);
  //   // L.esri.Util._getAttributionData(baseLayer.options.attributionUrl, map);
  //   // map.setView([ 37.30, 360], 10);
  //   clock.tick(151);
  //
  //   var check = false;
  //   for(var prop in map.attributionControl._attributions) {
  //       // console.log(prop);
  //       if (prop.match(/SkyNet/i) != null) {
  //         test = true;
  //       };
  //   }
  //
  //   expect(check).to.equal(true);
  // });
});
<commit_msg>Test for proxy to be applied<commit_after>/* eslint-env mocha */
describe('L.esri.BasemapLayer', function () {
  function createMap () {
    // create container
    var container = document.createElement('div');

    // give container a width/height
    container.setAttribute('style', 'width:500px; height: 500px;');

    // add container to body
    document.body.appendChild(container);

    return L.map(container).setView([37.75, -122.45], 5);
  }

  var map;
  var server;
  var clock;
  var mockAttributions = {
    'contributors': [
      {
        'attribution': 'SkyNet',
        'coverageAreas': [
          {
            'zoomMax': 19,
            'zoomMin': 0,
            'score': 50,
            'bbox': [-84.94, -179.66, 84.94, 179.66]
          }
        ]
      },
      {
        'attribution': 'City & County of San Francisco',
        'coverageAreas': [
          {
            'zoomMax': 19,
            'zoomMin': 16,
            'score': 100,
            'bbox': [37.71, -122.51, 37.83, -122.36]
          }
        ]
      }
    ]
  };

  beforeEach(function () {
    clock = sinon.useFakeTimers();
    server = sinon.fakeServer.create();
    server.respondWith('GET', new RegExp(/.*/), JSON.stringify(mockAttributions));
    map = createMap();
  });

  afterEach(function () {
    clock.restore();
    server.restore();
    map.remove();
  });

  it('can return valid basemaps', function () {
    var testmaps = ['Streets', 'Topographic', 'NationalGeographic', 'Oceans', 'OceansLabels', 'DarkGray', 'DarkGrayLabels', 'Gray', 'GrayLabels', 'Imagery', 'ImageryLabels', 'ImageryTransportation', 'ShadedRelief', 'ShadedReliefLabels', 'Terrain', 'TerrainLabels', 'USATopo', 'ImageryClarity', 'ImageryFirefly', 'Physical'];

    for (var i = 0, len = testmaps.length; i < len; i++) {
      var name = testmaps[i];
      expect(L.esri.basemapLayer(name)).to.be.instanceof(L.esri.BasemapLayer);
      expect(L.esri.basemapLayer(name)._url).to.equal(L.esri.BasemapLayer.TILES[name].urlTemplate);
    }
  });

  it('can survive adding/removing basemaps w/ labels', function () {
    var moremaps = ['Oceans', 'DarkGray', 'Gray', 'Imagery', 'ShadedRelief', 'Terrain'];
    for (var i = 0, len = moremaps.length; i < len; i++) {
      var layer = L.esri.basemapLayer(moremaps[i]).addTo(map);
      var layerWithLabels = L.esri.basemapLayer(moremaps[i] + 'Labels').addTo(map);
      expect(map.hasLayer(layer)).to.equal(true);
      expect(map.hasLayer(layerWithLabels)).to.equal(true);

      map.removeLayer(layer);
      map.removeLayer(layerWithLabels);
      expect(map.hasLayer(layer)).to.equal(false);
      expect(map.hasLayer(layerWithLabels)).to.equal(false);
    }
  });

  it('can be added to the map as normal', function () {
    var baseLayer = L.esri.basemapLayer('Streets');
    map.addLayer(baseLayer);
    expect(map.hasLayer(baseLayer)).to.equal(true);
  });

  it('will append tokens when fetching tiles if necessary', function () {
    var baseLayer = L.esri.basemapLayer('Streets', {token: 'bogus'});
    map.addLayer(baseLayer);
    expect(baseLayer._url).to.contain('token=bogus');
  });

  it('will prepend proxy when fetching tiles if necessary', function () {
    var proxyURL = 'http://example.proxy';
    var baseLayer = L.esri.basemapLayer('Streets', {proxy: proxyURL});
    map.addLayer(baseLayer);
    expect(baseLayer._url).to.contain(proxyURL);
  });

  it('will throw an error given invalid basemap name', function () {
    expect(function () {
      L.esri.basemapLayer('junk');
    }).to.throw(/Invalid parameter/);
  });

  it('should have an L.esri.basemapLayer alias', function () {
    expect(L.esri.basemapLayer('Topographic')).to.be.instanceof(L.esri.BasemapLayer);
  });

  // /*
  // need to figure out how to wire up the mockAttributions to
  // test display when map is panned beyond the dateline
  //
  // i dont understand how to spoof http responses for inner logic private function calls
  // like the jsonp request made inside L.esri.Util._getAttributionData();
  // */
  // it('can display attribution when panned beyond the dateline', function () {
  //   var baseLayer = L.esri.basemapLayer('Streets');
  //   map.addLayer(baseLayer);
  //   // L.esri.Util._getAttributionData(baseLayer.options.attributionUrl, map);
  //   // map.setView([ 37.30, 360], 10);
  //   clock.tick(151);
  //
  //   var check = false;
  //   for(var prop in map.attributionControl._attributions) {
  //       // console.log(prop);
  //       if (prop.match(/SkyNet/i) != null) {
  //         test = true;
  //       };
  //   }
  //
  //   expect(check).to.equal(true);
  // });
});
<|endoftext|>


<commit_before>export default function(ngapp, xelib) {
    var mainTreeViewController = function($scope, xelibService, mainTreeViewFactory) {
        // TODO: Load this from disk
        $scope.columns = [
            {
                label: "FormID",
                width: '315px',
                getData: function(node) {
                    switch (node.element_type) {
                        case 'etFile':
                            return xelib.DisplayName(node.handle);
                        case 'etGroupRecord':
                            // TODO: include signature as well based on setting
                            return xelib.Name(node.handle);
                        case 'etMainRecord':
                            if (node.fid == 0) {
                                return 'File Header';
                            } else {
                                return xelibService.intToHex(node.fid, 8);
                            }
                    }
                }
            },
            {
                label: "EditorID",
                width: '150px',
                getData: function(node) {
                    if (node.element_type === 'etMainRecord' && node.fid > 0) {
                        return xelib.EditorID(node.handle);
                    }
                }
            },
            {
                label: "Name",
                getData: function(node) {
                    if (node.element_type === 'etMainRecord' && node.fid > 0) {
                        if (xelib.HasElement(node.handle, 'FULL')) {
                            return xelib.FullName(node.handle);
                        }
                    }
                }
            }
        ];

        $scope.sort = {
            index: 0,
            reverse: false
        };

        $scope.getNodeStatus = function(node) {
            let status = {
                modified: false //xelib.GetModified(handle)
            };
            if (node.element_type === 'etMainRecord') {
                status.override = xelib.IsOverride(node.handle);
                status.injected = xelib.IsInjected(node.handle);
                status.conflict = 'ctNoConflict'; //xelib.ConflictThis(node.handle);
            }
            node.status = status;
        };

        $scope.getChildrenCount = function(node) {
            if (node.element_type === 'etMainRecord') {
                node.children_count = 0;
            } else {
                node.children_count = xelib.ElementCount(node.handle);
            }
        };

        $scope.buildColumnValues = function(node) {
            node.column_values = $scope.columns.map(function(column) {
                try {
                    return column.getData(node) || "";
                } catch (x) {
                    console.log(x);
                    return "";
                }
            }).trimFalsy();
        };

        $scope.buildNode = function(handle, depth) {
            let node = {
                handle: handle,
                element_type: xelib.ElementType(handle),
                depth: depth + 1
            };
            if (node.element_type === 'etMainRecord') {
                node.fid = xelib.GetFormID(node.handle);
                node.is_record = true;
            }
            $scope.getNodeStatus(node);
            $scope.getChildrenCount(node);
            $scope.buildColumnValues(node);
            return node;
        };

        $scope.sortNodes = function(nodes) {
            nodes.sort(function(a, b) {
                if (a.fid === 0) return -1;
                if (b.fid === 0) return 1;
                let valueA = a.column_values[$scope.sort.index];
                let valueB = b.column_values[$scope.sort.index];
                if (valueA < valueB) return -1;
                if (valueA > valueB) return 1;
                return 0;
            });
            nodes.forEach(function(node, index) {
                node.index = index;
            });
            if ($scope.sort.reverse) nodes.reverse();
        };

        $scope.buildNodes = function(handle, depth = -1) {
            let nodes = xelib.GetElements(handle).map(function(handle) {
                return $scope.buildNode(handle, depth);
            });
            $scope.sortNodes(nodes);
            return nodes;
        };

        $scope.expandNode = function(node) {
            node.expanded = true;
            node.children = $scope.buildNodes(node.handle, node.depth);
            node.children.forEach(function(child) {
                child.parent = node;
            });
            if (!node.children.length) {
                node.children_count = 0;
                $scope.collapseNode(node);
            }
        };

        $scope.collapseNode = function(node) {
            if (node.expanded) delete node.expanded;
            if (node.children) {
                mainTreeViewFactory.releaseChildren(node);
                delete node.children;
            }
        };

        $scope.toggleNode = function(e, node) {
            if (node.expanded) {
                $scope.collapseNode(node);
            } else {
                $scope.expandNode(node);
            }
            e.stopPropagation();
        };

        var selectedNodes = [];
        $scope.clearSelection = function() {
            selectedNodes.forEach(function(node) {
                node.selected = false;
            });
            selectedNodes = [];
        };

        var getFirstNode = function(n1, n2) {
            let m1 = n1;
            let m2 = n2;
            while (m1.parent !== m2.parent) {
                m1 = m1.parent;
                m2 = m2.parent;
            }
            return (m1.index > m2.index) ? m2 : m1;
        };

        var selectRange = function(n1, n2) {
            if (n1.depth !== n2.depth || n1 === n2) return;
            let firstNode = getFirstNode(n1, n2);
            let lastNode = firstNode === n1 ? n2 : n1;
            // TODO
        };

        var selectSingle = function(node) {
            if (selectedNodes.length > 0 && node.depth != prevNode.depth) return;
            node.selected = !node.selected;
            prevNode = node;
            selectedNodes[node.selected ? 'push' : 'remove'](node);
        };

        var prevNode;
        $scope.selectNode = function(e, node) {
            if (!e || !e.ctrlKey) $scope.clearSelection();
            if (e && e.shiftKey && prevNode) {
                selectRange(node, prevNode);
            } else {
                selectSingle(node);
            }
            e && e.stopPropagation();
        };

        // expand node or navigate to first child when right arrow is pressed
        var handleRightArrow = function() {
            let node = selectedNodes.last();
            if (!node || !node.children_count) return;
            if (!node.expanded) {
                $scope.clearSelection();
                $scope.expandNode(node);
                node.selected = true;
                selectedNodes.push(node);
            } else {
                $scope.selectNode(null, node.children[0]);
            }
        };

        // navigate to parent or collapse node when left arrow is pressed
        var handleLeftArrow = function() {
            let node = selectedNodes.last();
            if (!node) return;
            if (node.expanded) {
                $scope.clearSelection();
                $scope.collapseNode(node);
                node.selected = true;
                selectedNodes.push(node);
            } else {
                node = node.parent;
                $scope.selectNode(null, node, node.index);
            }
        };

        var selectNextNode = function(node) {
            let nextIndex = node.index + 1;
            let collection = node.parent && node.parent.children || $scope.data.tree;
            if (nextIndex < collection.length) {
                $scope.selectNode(null, collection[nextIndex]);
            } else if (node.parent) {
                selectNextNode(node.parent);
            }
        };

        //navigate down a node or to first child when down arrow is pressed
        var handleDownArrow = function() {
            let node = selectedNodes.last();
            if (!node) return;
            if (node.expanded) {
                $scope.selectNode(null, node.children[0]);
            } else {
                selectNextNode(node);
            }
        };

        var selectPreviousNode = function(node) {
            let prevIndex = node.index - 1;
            let collection = node.parent && node.parent.children || $scope.data.tree;
            if (prevIndex > -1) {
                let prevNode = collection[prevIndex];
                while (prevNode.expanded) {
                    prevNode = prevNode.children.last();
                }
                $scope.selectNode(null, prevNode);
            } else if (node.parent) {
                $scope.selectNode(null, node.parent);
            }
        };

        //navigate down a node or to first child when down arrow is pressed
        var handleUpArrow = function() {
            let node = selectedNodes.last();
            if (!node) return;
            selectPreviousNode(node);
        };

        $scope.onKeyDown = function(e) {
            if (e.keyCode == 39) {
                handleRightArrow();
            } else if (e.keyCode == 37) {
                handleLeftArrow();
            } else if (e.keyCode == 40) {
                handleDownArrow()
            } else if (e.keyCode == 38) {
                handleUpArrow();
            } else {
                return;
            }
            e.stopPropagation();
            e.preventDefault();
        };

        // initialize tree
        $scope.data = $scope.$parent.tab.data;
        $scope.data.tree = $scope.buildNodes(0);
    };

    ngapp.service('mainTreeViewFactory', function() {
        let factory = this;

        this.releaseChildren = function(node) {
            node.children.forEach(function(child) {
                if (child.children) factory.releaseChildren(child);
                xelib.Release(child.handle);
            });
        };

        this.destroy = function(view) {
            factory.releaseChildren(view.data.tree);
        };

        this.new = function() {
            return {
                templateUrl: 'partials/mainTreeView.html',
                controller: mainTreeViewController,
                class: 'main-tree-view',
                data: {
                    tabLabel: 'Tree View',
                    tree: {}
                },
                destroy: factory.destroy
            }
        };
    });

    ngapp.run(function(viewFactory, mainTreeViewFactory) {
        viewFactory.registerView('mainTreeView', mainTreeViewFactory.new);
    });
};<commit_msg>added shift-click multi selection to main tree view<commit_after>export default function(ngapp, xelib) {
    var mainTreeViewController = function($scope, xelibService, mainTreeViewFactory) {
        // TODO: Load this from disk
        $scope.columns = [
            {
                label: "FormID",
                width: '315px',
                getData: function(node) {
                    switch (node.element_type) {
                        case 'etFile':
                            return xelib.DisplayName(node.handle);
                        case 'etGroupRecord':
                            // TODO: include signature as well based on setting
                            return xelib.Name(node.handle);
                        case 'etMainRecord':
                            if (node.fid == 0) {
                                return 'File Header';
                            } else {
                                return xelibService.intToHex(node.fid, 8);
                            }
                    }
                }
            },
            {
                label: "EditorID",
                width: '150px',
                getData: function(node) {
                    if (node.element_type === 'etMainRecord' && node.fid > 0) {
                        return xelib.EditorID(node.handle);
                    }
                }
            },
            {
                label: "Name",
                getData: function(node) {
                    if (node.element_type === 'etMainRecord' && node.fid > 0) {
                        if (xelib.HasElement(node.handle, 'FULL')) {
                            return xelib.FullName(node.handle);
                        }
                    }
                }
            }
        ];

        $scope.sort = {
            index: 0,
            reverse: false
        };

        $scope.getNodeStatus = function(node) {
            let status = {
                modified: false //xelib.GetModified(handle)
            };
            if (node.element_type === 'etMainRecord') {
                status.override = xelib.IsOverride(node.handle);
                status.injected = xelib.IsInjected(node.handle);
                status.conflict = 'ctNoConflict'; //xelib.ConflictThis(node.handle);
            }
            node.status = status;
        };

        $scope.getChildrenCount = function(node) {
            if (node.element_type === 'etMainRecord') {
                node.children_count = 0;
            } else {
                node.children_count = xelib.ElementCount(node.handle);
            }
        };

        $scope.buildColumnValues = function(node) {
            node.column_values = $scope.columns.map(function(column) {
                try {
                    return column.getData(node) || "";
                } catch (x) {
                    console.log(x);
                    return "";
                }
            }).trimFalsy();
        };

        $scope.buildNode = function(handle, depth) {
            let node = {
                handle: handle,
                element_type: xelib.ElementType(handle),
                depth: depth + 1
            };
            if (node.element_type === 'etMainRecord') {
                node.fid = xelib.GetFormID(node.handle);
                node.is_record = true;
            }
            $scope.getNodeStatus(node);
            $scope.getChildrenCount(node);
            $scope.buildColumnValues(node);
            return node;
        };

        $scope.sortNodes = function(nodes) {
            nodes.sort(function(a, b) {
                if (a.fid === 0) return -1;
                if (b.fid === 0) return 1;
                let valueA = a.column_values[$scope.sort.index];
                let valueB = b.column_values[$scope.sort.index];
                if (valueA < valueB) return -1;
                if (valueA > valueB) return 1;
                return 0;
            });
            nodes.forEach(function(node, index) {
                node.index = index;
            });
            if ($scope.sort.reverse) nodes.reverse();
        };

        $scope.buildNodes = function(handle, depth = -1) {
            let nodes = xelib.GetElements(handle).map(function(handle) {
                return $scope.buildNode(handle, depth);
            });
            $scope.sortNodes(nodes);
            return nodes;
        };

        $scope.expandNode = function(node) {
            node.expanded = true;
            node.children = $scope.buildNodes(node.handle, node.depth);
            node.children.forEach(function(child) {
                child.parent = node;
            });
            if (!node.children.length) {
                node.children_count = 0;
                $scope.collapseNode(node);
            }
        };

        $scope.collapseNode = function(node) {
            if (node.expanded) delete node.expanded;
            if (node.children) {
                mainTreeViewFactory.releaseChildren(node);
                delete node.children;
            }
        };

        $scope.toggleNode = function(e, node) {
            if (node.expanded) {
                $scope.collapseNode(node);
            } else {
                $scope.expandNode(node);
            }
            e.stopPropagation();
        };

        var selectedNodes = [];
        $scope.clearSelection = function() {
            selectedNodes.forEach(function(node) {
                node.selected = false;
            });
            selectedNodes = [];
        };

        var getFirstNode = function(n1, n2) {
            let m1 = n1;
            let m2 = n2;
            while (m1.parent !== m2.parent) {
                m1 = m1.parent;
                m2 = m2.parent;
            }
            return (m1.index > m2.index) ? m2 : m1;
        };

        var selectChildren = function(node, targetDepth, lastNode, minIndex = -1) {
            if (!node.expanded) return;
            for (let i = 0; i < node.children.length; i++) {
                let child = node.children[i];
                if (child.index < minIndex) continue;
                if (child.depth == targetDepth) {
                    if (!child.selected) {
                        child.selected = true;
                        selectedNodes.push(child);
                    }
                    if (child == lastNode) return true;
                } else if (child.depth < targetDepth) {
                    if (selectChildren(child, targetDepth, lastNode)) return true;
                }
            }
        };

        var selectNodesBetween = function(n1, n2) {
            let context = n1.parent && n1.parent.children || $scope.data.tree;
            for (let i = n1.index; i <= n2.index; i++) {
                let node = context[i];
                if (!node.selected) {
                    node.selected = true;
                    selectedNodes.push(node);
                }
            }
        };

        var selectRange = function(n1, n2) {
            if (n1.depth !== n2.depth || n1 === n2) return;
            let firstNode = getFirstNode(n1, n2);
            let lastNode = firstNode === n1 ? n2 : n1;
            let targetDepth = n1.depth;
            let current = firstNode.parent;
            let prev = firstNode;
            if (firstNode.parent === lastNode.parent) {
                selectNodesBetween(firstNode, lastNode);
            } else {
                while (current) {
                    if (selectChildren(current, targetDepth, lastNode, prev.index)) return;
                    prev = current;
                    current = current.parent;
                }
            }
        };

        var selectSingle = function(node) {
            if (selectedNodes.length > 0 && node.depth != prevNode.depth) return;
            node.selected = !node.selected;
            selectedNodes[node.selected ? 'push' : 'remove'](node);
        };

        var prevNode;
        $scope.selectNode = function(e, node) {
            if (!e.ctrlKey) $scope.clearSelection();
            if (e.shiftKey && prevNode) {
                selectRange(node, prevNode);
            } else {
                prevNode = node;
                selectSingle(node);
            }
            e.stopPropagation();
        };

        // expand node or navigate to first child when right arrow is pressed
        var handleRightArrow = function() {
            let node = selectedNodes.last();
            if (!node || !node.children_count) return;
            if (!node.expanded) {
                $scope.clearSelection();
                $scope.expandNode(node);
                node.selected = true;
                selectedNodes.push(node);
            } else {
                $scope.selectNode(null, node.children[0]);
            }
        };

        // navigate to parent or collapse node when left arrow is pressed
        var handleLeftArrow = function() {
            let node = selectedNodes.last();
            if (!node) return;
            if (node.expanded) {
                $scope.clearSelection();
                $scope.collapseNode(node);
                node.selected = true;
                selectedNodes.push(node);
            } else {
                node = node.parent;
                $scope.selectNode(null, node, node.index);
            }
        };

        var selectNextNode = function(node) {
            let nextIndex = node.index + 1;
            let collection = node.parent && node.parent.children || $scope.data.tree;
            if (nextIndex < collection.length) {
                $scope.selectNode(null, collection[nextIndex]);
            } else if (node.parent) {
                selectNextNode(node.parent);
            }
        };

        //navigate down a node or to first child when down arrow is pressed
        var handleDownArrow = function() {
            let node = selectedNodes.last();
            if (!node) return;
            if (node.expanded) {
                $scope.selectNode(null, node.children[0]);
            } else {
                selectNextNode(node);
            }
        };

        var selectPreviousNode = function(node) {
            let prevIndex = node.index - 1;
            let collection = node.parent && node.parent.children || $scope.data.tree;
            if (prevIndex > -1) {
                let prevNode = collection[prevIndex];
                while (prevNode.expanded) {
                    prevNode = prevNode.children.last();
                }
                $scope.selectNode(null, prevNode);
            } else if (node.parent) {
                $scope.selectNode(null, node.parent);
            }
        };

        //navigate down a node or to first child when down arrow is pressed
        var handleUpArrow = function() {
            let node = selectedNodes.last();
            if (!node) return;
            selectPreviousNode(node);
        };

        $scope.onKeyDown = function(e) {
            if (e.keyCode == 39) {
                handleRightArrow();
            } else if (e.keyCode == 37) {
                handleLeftArrow();
            } else if (e.keyCode == 40) {
                handleDownArrow()
            } else if (e.keyCode == 38) {
                handleUpArrow();
            } else {
                return;
            }
            e.stopPropagation();
            e.preventDefault();
        };

        // initialize tree
        $scope.data = $scope.$parent.tab.data;
        $scope.data.tree = $scope.buildNodes(0);
    };

    ngapp.service('mainTreeViewFactory', function() {
        let factory = this;

        this.releaseChildren = function(node) {
            node.children.forEach(function(child) {
                if (child.children) factory.releaseChildren(child);
                xelib.Release(child.handle);
            });
        };

        this.destroy = function(view) {
            factory.releaseChildren(view.data.tree);
        };

        this.new = function() {
            return {
                templateUrl: 'partials/mainTreeView.html',
                controller: mainTreeViewController,
                class: 'main-tree-view',
                data: {
                    tabLabel: 'Tree View',
                    tree: {}
                },
                destroy: factory.destroy
            }
        };
    });

    ngapp.run(function(viewFactory, mainTreeViewFactory) {
        viewFactory.registerView('mainTreeView', mainTreeViewFactory.new);
    });
};<|endoftext|>


<commit_before>const EthereumTx = require('ethereumjs-tx');
const Web3 = require('web3-eth');
const config = require('bitcore-node/lib/config');

export default class ETHTxProvder {
  constructor(chain) {
    this.chain = chain || 'ETH';
    this.chain = this.chain.toUpperCase();
    this.config = config.chains[this.chain];
  }
  web3For(network) {
    const networkConfig = this.config[network];
    const provider = networkConfig.provider;
    const portString = provider.port ? `:${provider.port}` : '';
    const connUrl = `${provider.protocool}://${provider.host}${portString}`;
    let ProviderType;
    switch (provider.protocool) {
      case 'wss':
        ProviderType = Web3.providers.WebsocketProvider;
        break;
      default:
        ProviderType = Web3.providers.HttpProvider;
        break;
    }
    return new Web3(new ProviderType(connUrl));
  }

  async create({ network, addresses, amount }) {
    var nonce = await this.web3For(network).eth.getTransactionCountAsync(
      addresses
    );
    var tx = new Transaction({
      to: addresses,
      value: amount,
      nonce: nonce,
      gasLimit: 2000000
    });
    return tx;
  }
}
<commit_msg>Removing eth, that'll be in another branch<commit_after><|endoftext|>


<commit_before>import isArray from '../understreck.isarray'
import isFunction from '../understreck.isfunction'

function some (collection, predicate) { //, thisArg
  const length = collection ? collection.length : 0
  if (isArray(collection) && isFunction(predicate)) {
    let index = -1
    while (++index < length) {
      if (predicate(collection[index])) {
        return true
      }
    }
    return false
  }
  return false
}

export default some
<commit_msg>update some. remove unnecessary return statement<commit_after>import isArray from '../understreck.isarray'
import isFunction from '../understreck.isfunction'

function some (collection, predicate) { //, thisArg
  const length = collection ? collection.length : 0
  if (isArray(collection) && isFunction(predicate)) {
    let index = -1
    while (++index < length) {
      if (predicate(collection[index])) {
        return true
      }
    }
  }
  return false
}

export default some
<|endoftext|>


<commit_before>import React from "react";
import { connect } from "react-redux";
import queryString from "query-string";
import { changePage, EXPERIMENTAL_showMainDisplayMarkdown } from "../../actions/navigation";
import { TOGGLE_NARRATIVE } from "../../actions/types";
import {
  linkStyles,
  MobileBannerTop,
  MobileBannerBottom,
  MobileContentContainer,
  EndOfNarrative
} from "./styles";
import Tree from "../tree";
import Map from "../map/map";
import MainDisplayMarkdown from "./MainDisplayMarkdown";

const BANNER_HEIGHT = 50;
/**
 * A React component which takes up the entire screen and displays narratives in
 * "mobile" format. Instead of each narrative page displayed as narrative-text in
 * the sidebar & viz on the right hand section of the page, as we do for laptops
 * we render both in a "scrolly" fashion. Banners at the top/bottom of the page
 * allow you to navigate between pages.
 *
 * TODO: a lot of code is duplicated between this and `narratives/index.js` +
 * `main/index.js`. These should be brought out into functions. Same with
 * styling.
 * TODO: entropy / frequencies panels
 */
@connect((state) => ({
  loaded: state.narrative.loaded,
  blocks: state.narrative.blocks,
  currentInFocusBlockIdx: state.narrative.blockIdx,
  panelsToDisplay: state.controls.panelsToDisplay
}))
class MobileNarrativeDisplay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      showingEndOfNarrativePage: false,
      bannerHeight: BANNER_HEIGHT,
      contentHeight: window.innerHeight - 2*BANNER_HEIGHT
    };

    this.exitNarrativeMode = () => {
      this.props.dispatch({type: TOGGLE_NARRATIVE, display: false});
    };

    this.goToNextPage = () => {
      if (this.state.showingEndOfNarrativePage) return; // no-op

      if (this.props.currentInFocusBlockIdx+1 === this.props.blocks.length) {
        this.setState({showingEndOfNarrativePage: true});
        return;
      }

      this._goToPage(this.props.currentInFocusBlockIdx+1);
    };

    this.goToPreviousPage = () => {
      if (this.props.currentInFocusBlockIdx === 0) return; // no-op
      this._goToPage(this.props.currentInFocusBlockIdx-1);
    };

    this._goToPage = (idx) => {
      // TODO: this `if` statement should be moved to the `changePage` function or similar
      if (this.props.blocks[idx] && this.props.blocks[idx].mainDisplayMarkdown) {
        this.props.dispatch(EXPERIMENTAL_showMainDisplayMarkdown({
          query: queryString.parse(this.props.blocks[idx].query),
          queryToDisplay: {n: idx}
        }));
        return;
      }

      this.props.dispatch(changePage({
        changeDataset: false,
        query: queryString.parse(this.props.blocks[idx].query),
        queryToDisplay: {n: idx},
        push: true
      }));
    };
    // TODO: bind down & up arrows (is this ok since we also have scollable content?)
  }

  pageNarrativeContent() {
    if (this.props.blocks[this.props.currentInFocusBlockIdx].mainDisplayMarkdown) {
      /* don't display normal narrative content if the block defines `mainDisplayMarkdown` */
      return null;
    }
    const block = this.props.blocks[this.props.currentInFocusBlockIdx];
    return (
      <div
        id={`MobileNarrativeBlock_${this.props.currentInFocusBlockIdx}`}
        key={block.__html}
        style={{
          padding: "10px 20px",
          height: "inherit",
          overflow: "hidden"
        }}
        dangerouslySetInnerHTML={block}
      />
    );
  }

  renderMainMarkdown() {
    if (this.props.panelsToDisplay.includes("EXPERIMENTAL_MainDisplayMarkdown")) {
      return <MainDisplayMarkdown width={window.innerWidth}/>;
    }
    return null;
  }

  renderVizCards(height) {
    if (this.props.blocks[this.props.currentInFocusBlockIdx].mainDisplayMarkdown) {
      /* don't display normal narrative content if the block defines `mainDisplayMarkdown` */
      return null;
    }
    if (this.props.currentInFocusBlockIdx === 0) {
      /* don't display viz panels for intro page */
      return null;
    }
    const width = window.innerWidth - 50; // TODO
    return (
      <>
        {this.props.panelsToDisplay.includes("tree")
          ? <Tree width={width} height={height} /> : null}
        {this.props.panelsToDisplay.includes("map")
          ? <Map width={width} height={height} justGotNewDatasetRenderNewMap={false} /> : null}
      </>
    );
  }

  renderEndOfNarrative() {
    return (
      <>
        <this.PreviousButton>
          Previous
        </this.PreviousButton>
        <MobileContentContainer height={this.state.contentHeight+this.state.bannerHeight}>
          <EndOfNarrative>

End of Narrative

             this._goToPage(0)}
            >
              Jump to the beginning

              Leave the narrative & explore the data yourself

          </EndOfNarrative>
        </MobileContentContainer>
      </>
    );
  }

  renderStartOfNarrative() {
    return (
      <>
        <MobileContentContainer height={this.state.contentHeight + this.state.bannerHeight}>
          {this.pageNarrativeContent()}
          {this.renderVizCards(this.state.contentHeight)}
          {this.renderMainMarkdown()}
        </MobileContentContainer>
        <this.NextButton>
          Next
        </this.NextButton>
      </>
    );
  }

  renderMiddleOfNarrative() {
    return (
      <>
        <this.PreviousButton>
          Previous
        </this.PreviousButton>
        <MobileContentContainer height={this.state.contentHeight}>
          {this.pageNarrativeContent()}
          {this.renderVizCards(this.state.contentHeight)}
          {this.renderMainMarkdown()}
        </MobileContentContainer>
        <this.NextButton>
          Next
        </this.NextButton>
      </>
    );
  }

  NextButton = (props) => (
    <MobileBannerBottom height={this.state.bannerHeight}
      onClick={this.goToNextPage}
    >
      {props.children}
    </MobileBannerBottom>
  )

  PreviousButton = (props) => (
    <MobileBannerTop height={this.state.bannerHeight}
      onClick={this.goToPreviousPage}
    >
      {props.children}
    </MobileBannerTop>
  )

  render() {

    if (this.state.showingEndOfNarrativePage) {
      return this.renderEndOfNarrative();

    } else if (this.props.currentInFocusBlockIdx === 0) {
      return this.renderStartOfNarrative();
    }

    return this.renderMiddleOfNarrative();
  }
}

export default MobileNarrativeDisplay;
<commit_msg>narratives: Scroll to top after hitting button<commit_after>import React from "react";
import { connect } from "react-redux";
import queryString from "query-string";
import { changePage, EXPERIMENTAL_showMainDisplayMarkdown } from "../../actions/navigation";
import { TOGGLE_NARRATIVE } from "../../actions/types";
import {
  linkStyles,
  MobileBannerTop,
  MobileBannerBottom,
  MobileContentContainer,
  EndOfNarrative
} from "./styles";
import Tree from "../tree";
import Map from "../map/map";
import MainDisplayMarkdown from "./MainDisplayMarkdown";

const BANNER_HEIGHT = 50;

const scrollToTop = () => {
  document.getElementById('MobileNarrativeBlock').scrollIntoView();
};

/**
 * A React component which takes up the entire screen and displays narratives in
 * "mobile" format. Instead of each narrative page displayed as narrative-text in
 * the sidebar & viz on the right hand section of the page, as we do for laptops
 * we render both in a "scrolly" fashion. Banners at the top/bottom of the page
 * allow you to navigate between pages.
 *
 * TODO: a lot of code is duplicated between this and `narratives/index.js` +
 * `main/index.js`. These should be brought out into functions. Same with
 * styling.
 * TODO: entropy / frequencies panels
 */
@connect((state) => ({
  loaded: state.narrative.loaded,
  blocks: state.narrative.blocks,
  currentInFocusBlockIdx: state.narrative.blockIdx,
  panelsToDisplay: state.controls.panelsToDisplay
}))
class MobileNarrativeDisplay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      showingEndOfNarrativePage: false,
      bannerHeight: BANNER_HEIGHT,
      contentHeight: window.innerHeight - 2*BANNER_HEIGHT
    };

    this.exitNarrativeMode = () => {
      this.props.dispatch({type: TOGGLE_NARRATIVE, display: false});
    };

    this.goToNextPage = () => {
      if (this.state.showingEndOfNarrativePage) return; // no-op

      if (this.props.currentInFocusBlockIdx+1 === this.props.blocks.length) {
        this.setState({showingEndOfNarrativePage: true});
        return;
      }

      this._goToPage(this.props.currentInFocusBlockIdx+1);
    };

    this.goToPreviousPage = () => {
      if (this.props.currentInFocusBlockIdx === 0) return; // no-op
      this._goToPage(this.props.currentInFocusBlockIdx-1);
    };

    this._goToPage = (idx) => {
      // TODO: this `if` statement should be moved to the `changePage` function or similar
      if (this.props.blocks[idx] && this.props.blocks[idx].mainDisplayMarkdown) {
        this.props.dispatch(EXPERIMENTAL_showMainDisplayMarkdown({
          query: queryString.parse(this.props.blocks[idx].query),
          queryToDisplay: {n: idx}
        }));
        return;
      }

      this.props.dispatch(changePage({
        changeDataset: false,
        query: queryString.parse(this.props.blocks[idx].query),
        queryToDisplay: {n: idx},
        push: true
      }));

      scrollToTop();
    };
    // TODO: bind down & up arrows (is this ok since we also have scollable content?)
  }

  pageNarrativeContent() {
    if (this.props.blocks[this.props.currentInFocusBlockIdx].mainDisplayMarkdown) {
      /* don't display normal narrative content if the block defines `mainDisplayMarkdown` */
      return null;
    }
    const block = this.props.blocks[this.props.currentInFocusBlockIdx];
    return (
      <div
        id={`MobileNarrativeBlock_${this.props.currentInFocusBlockIdx}`}
        key={block.__html}
        style={{
          padding: "10px 20px",
          height: "inherit",
          overflow: "hidden"
        }}
        dangerouslySetInnerHTML={block}
      />
    );
  }

  renderMainMarkdown() {
    if (this.props.panelsToDisplay.includes("EXPERIMENTAL_MainDisplayMarkdown")) {
      return <MainDisplayMarkdown width={window.innerWidth}/>;
    }
    return null;
  }

  renderVizCards(height) {
    if (this.props.blocks[this.props.currentInFocusBlockIdx].mainDisplayMarkdown) {
      /* don't display normal narrative content if the block defines `mainDisplayMarkdown` */
      return null;
    }
    if (this.props.currentInFocusBlockIdx === 0) {
      /* don't display viz panels for intro page */
      return null;
    }
    const width = window.innerWidth - 50; // TODO
    return (
      <>
        {this.props.panelsToDisplay.includes("tree")
          ? <Tree width={width} height={height} /> : null}
        {this.props.panelsToDisplay.includes("map")
          ? <Map width={width} height={height} justGotNewDatasetRenderNewMap={false} /> : null}
      </>
    );
  }

  renderEndOfNarrative() {
    return (
      <>
        <this.PreviousButton>
          Previous
        </this.PreviousButton>
        <MobileContentContainer height={this.state.contentHeight+this.state.bannerHeight}>
          <EndOfNarrative>

End of Narrative

             this._goToPage(0)}
            >
              Jump to the beginning

              Leave the narrative & explore the data yourself

          </EndOfNarrative>
        </MobileContentContainer>
      </>
    );
  }

  renderStartOfNarrative() {
    return (
      <>
        <MobileContentContainer height={this.state.contentHeight + this.state.bannerHeight}>
          {this.pageNarrativeContent()}
          {this.renderVizCards(this.state.contentHeight)}
          {this.renderMainMarkdown()}
        </MobileContentContainer>
        <this.NextButton>
          Next
        </this.NextButton>
      </>
    );
  }

  renderMiddleOfNarrative() {
    return (
      <>
        <this.PreviousButton>
          Previous
        </this.PreviousButton>
        <MobileContentContainer height={this.state.contentHeight}>
          {this.pageNarrativeContent()}
          {this.renderVizCards(this.state.contentHeight)}
          {this.renderMainMarkdown()}
        </MobileContentContainer>
        <this.NextButton>
          Next
        </this.NextButton>
      </>
    );
  }

  NextButton = (props) => (
    <MobileBannerBottom height={this.state.bannerHeight}
      onClick={this.goToNextPage}
    >
      {props.children}
    </MobileBannerBottom>
  )

  PreviousButton = (props) => (
    <MobileBannerTop height={this.state.bannerHeight}
      onClick={this.goToPreviousPage}
    >
      {props.children}
    </MobileBannerTop>
  )

  render() {

    if (this.state.showingEndOfNarrativePage) {
      return this.renderEndOfNarrative();

    } else if (this.props.currentInFocusBlockIdx === 0) {
      return this.renderStartOfNarrative();
    }

    return this.renderMiddleOfNarrative();
  }
}

export default MobileNarrativeDisplay;
<|endoftext|>


<commit_before>;(function($,window,undefined) {
  var pluginName = 'shapeshift',
      document = window.document,
      defaults = {
        // Features
        centerGrid: true,
        enableAnimation: true,
        enableAutoHeight: true,
        enableDrag: true,
        enableDragAnimation: true,
        enableResize: true,

        // Options
        animateSpeed: 150,
        childWidth: null,
        columns: null,
        dragBlacklist: null,
        dragClone: false,
        dropCutoff: 0,
        dropWhitelist: "*",
        gutterX: 10,
        gutterY: 10,
        paddingY: 0,
        paddingX: 0,
        selector: ""
      };

  function Plugin(element, options) {
    var ss = this;
    ss.element = element;
    ss.container = $(element);
    ss.options = $.extend({}, defaults, options);
    ss.init();
  }

  Plugin.prototype.init = function() {
    var ss = this,
        $container = $(ss.element);

    // Create the trigger events
    ss.eventSetup();

    // The initial shapeshift arrangement
    $container.trigger("ss-event-arrange");
  };

  Plugin.prototype.eventSetup = function() {
    var ss = this,
        options = ss.options,
        $container = $(ss.element);

    $container.off("ss-event-arrange").on("ss-event-arrange", function() { ss.arrange(); });
    $container.off("ss-event-dragreset").on("ss-event-dragreset", function() {
      $container.droppable().droppable('destroy');
      $container.children().draggable().draggable('destroy');
      ss.drag();
    });

    $container.droppable().droppable('destroy');
    $container.children().draggable().draggable('destroy');
    if(options.enableDrag) { ss.drag(); }
    if(options.enableResize) { ss.resize(); };
  }

  Plugin.prototype.arrange = function() {
    var ss = this,
        options = ss.options,
        $container = $(ss.element),
        $objects = $container.children(options.selector).filter(':visible'),
        animated = true;

    if($objects.filter(".ss-moving")[0]) {
      animated = options.enableDragAnimation;
    } else {
      animated = options.enableAnimation;
    }

    // Calculate the positions for each element
    positions = ss.getPositions($container, false);

    // Animate / Move each object into place
    for(var obj_i=0; obj_i < $objects.length; obj_i++) {
      var $obj = $($objects[obj_i]),
          attributes = positions[obj_i];
      // Never animate the currently dragged item
      if(!$obj.hasClass("ss-moving")) {
        if(animated) {
          $obj.stop(true, false).animate(attributes, options.animateSpeed);
        } else {
          $obj.css(attributes);
        }
      }
    }

    // Set the container height to match the tallest column
    $container.css("height", options.containerHeight);
  }

  Plugin.prototype.drag = function () {
    var ss = this,
        options = ss.options,
        $container = $curContainer = $(ss.element),
        $objects = $container.children(options.selector),
        $selected = null,
        dragging = false;

    // Dragging
    $objects.filter(options.dropWhitelist).filter(":not("+options.dragBlacklist+")").draggable({
      addClasses: false,
      containment: 'document',
      zIndex: 9999,
      drag: function(e, ui) { drag(e, ui); },
      start: function(e, ui) { start(e, ui); },
      stop: function(e, ui) { stop(e); }
    });

    function start(e, ui) {
      $selected = $(e.target);
      if(options.dragClone) {
        $clone = $selected.clone().insertBefore($selected).addClass("ss-clone");
      }
      $selected.addClass("ss-moving");
    }

    function stop(e) {
      $selected = $(e.target).removeClass("ss-moving");
      if($selected[0]) {
        $selected.parent().trigger("ss-event-arrange");
      }
    }

    function drag(e, ui) {
      if(!dragging) {
        dragging = true;
        position = ss.getIntendedPosition(e);
        $objects = $curContainer.children(":not(.ss-moving):visible");
        if(position != $objects.size()) {
          $target = $objects.get(position);
          $selected.insertBefore($target);
        } else {
          $target = $objects.get(position - 1);
          $selected.insertAfter($target);
        }

        $curContainer.trigger("ss-event-arrange");
        $(".ss-prev-container").trigger("ss-event-arrange");

        window.setTimeout(function() {
          dragging = false;
        }, options.animateSpeed / 3)
      }
      // Manually override the elements position
      ui.position.left = e.pageX - $(e.target).parent().offset().left - (options.childWidth / 2);
      ui.position.top = e.pageY - $(e.target).parent().offset().top - ($selected.outerHeight() / 2);
    }

    // Dropping
    $container.droppable({
      accept: options.dropWhitelist,
      drop: function(e) { drop(e); },
      over: function(e) { over(e); }
    });

    function drop(e) {
      $selected = $(".ss-moving").removeClass("ss-moving");
      $selectedContainer = $selected.parent();
      $clone = $(".ss-clone");
      if($clone[0]) {
        $cloneContainer = $clone.parent();
        if($cloneContainer[0] === $selectedContainer[0]) {
          $clone.remove();
        } else {
          $clone.removeClass("ss-clone");
          $cloneContainer.trigger("ss-event-dragreset");
          $selected.parent().trigger("ss-event-dragreset");
        }
      }
      $selectedContainer.trigger("ss-event-arrange").trigger("ss-event-dropped", $selected);
    }

    function over(e) {
      $curContainer.addClass("ss-prev-container");
      $curContainer = $(e.target).removeClass("ss-prev-container");
    }
  }

  Plugin.prototype.getPositions = function($container, ignoreSelected) {
    var ss = this,
        options = ss.options,
        $objects = $container.children(options.selector).filter(":visible"),
        columns = options.columns,
        colHeights = [],
        colWidth = null,
        gridOffset = options.paddingX,
        positions = [];

    if(ignoreSelected) {
      $objects = $objects.not(".ss-moving");
    }

    // Determine the width of each element.
    if(!options.childWidth) { options.childWidth = $objects.first().outerWidth(true); }

    // Determine the column width.
    colWidth = options.childWidth + options.gutterX;

    // Determine how many columns are currently active
    if(!columns) { columns = Math.floor($container.innerWidth() / colWidth); }

    // Offset the grid to center it.
    if(options.centerGrid) {
      gridOffset = Math.floor((($container.innerWidth() / colWidth) % 1 * colWidth) / 2) + (options.gutterX / 2);
    }

    // Create an array element for each column, which is then
    // used to store that columns current height.
    for(var i=0;i<columns;i++) {colHeights.push(options.paddingY);}

    // Loop over each element and determine what column it fits into
    for(var obj_i=0;obj_i<$objects.length;obj_i++) {
      var $obj = $($objects[obj_i]),
          col = $.inArray(Math.min.apply(window,colHeights), colHeights),
          height = $obj.outerHeight(true) + options.gutterY,
          offsetX = (colWidth * col) + gridOffset,
          offsetY = colHeights[col];

      // Store the position to animate into place later
      attributes = { left: offsetX, top: offsetY };
      positions[obj_i] = attributes;

      // Increase the calculated total height of the current column
      colHeights[col] += height;
    }
    // Store the height of the tallest column
    if(options.enableAutoHeight){
      options.containerHeight = Math.max.apply(Math,colHeights);
    }
    return positions;
  }

  Plugin.prototype.getIntendedPosition = function(e) {
    var ss = this,
        options = ss.options,
        $selected = $(".ss-moving"),
        $container = $selected.parent(),
        chosenIndex = 0,
        selectedX = $selected.position().left + (options.childWidth / 2),
        selectedY = $selected.position().top + ($selected.outerHeight() / 2),
        shortestDistance = 9999,
        positions = ss.getPositions($container, true),
        endCap = positions.length - options.dropCutoff;

    // Go over all of those positions and figure out
    // which is the closest to the cursor.
    for(hov_i=0;hov_i<positions.length;hov_i++) {
      if(hov_i < endCap) {
        attributes = positions[hov_i];
        if(selectedX > attributes.left && selectedY > attributes.top) {
          xDist = selectedX - attributes.left;
          yDist = selectedY - attributes.top;

          distance = Math.sqrt((xDist * xDist) + (yDist * yDist));
          if(distance < shortestDistance) {
            shortestDistance = distance;
            chosenIndex = hov_i;

            if(hov_i === positions.length - 1) {
              $object = $container.children().not(".ss-moving");
              if(yDist > ($object.outerHeight() * .9) || xDist > options.childWidth * .9) {
                chosenIndex++;
              }
            }
          }
        }
      }
    }
    return chosenIndex;
  }

  Plugin.prototype.resize = function () {
    var ss = this,
        $container = $(ss.element),
        resizing = false;

    $(window).on("resize", function() {
      if(!resizing) {
        resizing = true;
        $container.trigger("ss-event-arrange");
        setTimeout(function() {
          resizing = false;
        }, 200);
      }
    });
  }

  // Prevent against multiple instantiations
  $.fn[pluginName] = function (options) {
    return this.each(function () {
      $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
    });
  }

}(jQuery, window));<commit_msg>Some cleanup<commit_after>;(function($,window,undefined) {
  var pluginName = 'shapeshift',
      document = window.document,
      defaults = {
        // Features
        centerGrid: true,
        enableAnimation: true,
        enableAutoHeight: true,
        enableDrag: true,
        enableDragAnimation: true,
        enableResize: true,

        // Options
        animateSpeed: 150,
        childWidth: null,
        columns: null,
        dragBlacklist: null,
        dragClone: false,
        dropCutoff: 0,
        dropWhitelist: "*",
        gutterX: 10,
        gutterY: 10,
        paddingY: 0,
        paddingX: 0,
        selector: ""
      };

  function Plugin(element, options) {
    var ss = this;
    ss.element = element;
    ss.container = $(element);
    ss.options = $.extend({}, defaults, options);
    ss.init();
  }

  Plugin.prototype.init = function() {
    var ss = this;
    ss.eventSetup();
    $(ss.element).trigger("ss-event-arrange");
  };

  Plugin.prototype.eventSetup = function() {
    var ss = this,
        options = ss.options,
        $container = $(ss.element);

    $container.off("ss-event-arrange").on("ss-event-arrange", function() { ss.arrange(); });
    $container.off("ss-event-dragreset").on("ss-event-dragreset", function() { ss.dragClear(); ss.drag(); });

    ss.dragClear();
    if(options.enableDrag) { ss.drag(); }
    if(options.enableResize) { ss.resize(); }
  }

  Plugin.prototype.arrange = function() {
    var ss = this,
        options = ss.options,
        $container = $(ss.element),
        $objects = $container.children(options.selector).filter(':visible'),
        animated = true;

    if($objects.filter(".ss-moving")[0]) {
      animated = options.enableDragAnimation;
    } else {
      animated = options.enableAnimation;
    }

    // Calculate the positions for each element
    positions = ss.getPositions($container, false);

    // Animate / Move each object into place
    for(var obj_i=0; obj_i < $objects.length; obj_i++) {
      var $obj = $($objects[obj_i]);
      // Never animate the currently dragged item
      if(!$obj.hasClass("ss-moving")) {
        if(animated) {
          $obj.stop(true, false).animate(positions[obj_i], options.animateSpeed);
        } else {
          $obj.css(positions[obj_i]);
        }
      }
    }

    // Set the container height to match the tallest column
    $container.css("height", options.containerHeight);
  }

  Plugin.prototype.drag = function () {
    var ss = this,
        options = ss.options,
        $container = $curContainer = $(ss.element),
        $objects = $container.children(options.selector),
        $selected = null,
        dragging = false;

    // Dragging
    $objects.filter(options.dropWhitelist).filter(":not("+options.dragBlacklist+")").draggable({
      addClasses: false,
      containment: 'document',
      zIndex: 9999,
      drag: function(e, ui) { drag(e, ui); },
      start: function(e, ui) { start(e, ui); },
      stop: function(e, ui) { stop(e); }
    });

    function start(e, ui) {
      $selected = $(e.target);
      if(options.dragClone) {
        $clone = $selected.clone().insertBefore($selected).addClass("ss-clone");
      }
      $selected.addClass("ss-moving");
    }

    function stop(e) {
      $selected = $(e.target).removeClass("ss-moving");
      if($selected[0]) {
        $selected.parent().trigger("ss-event-arrange");
      }
    }

    function drag(e, ui) {
      if(!dragging) {
        dragging = true;
        position = ss.getIntendedPosition(e);
        $objects = $curContainer.children(":not(.ss-moving):visible");
        if(position != $objects.size()) {
          $target = $objects.get(position);
          $selected.insertBefore($target);
        } else {
          $target = $objects.get(position - 1);
          $selected.insertAfter($target);
        }

        $curContainer.trigger("ss-event-arrange");
        $(".ss-prev-container").trigger("ss-event-arrange");

        window.setTimeout(function() {
          dragging = false;
        }, options.animateSpeed / 3)
      }
      // Manually override the elements position
      ui.position.left = e.pageX - $(e.target).parent().offset().left - (options.childWidth / 2);
      ui.position.top = e.pageY - $(e.target).parent().offset().top - ($selected.outerHeight() / 2);
    }

    // Dropping
    $container.droppable({
      accept: options.dropWhitelist,
      drop: function(e) { drop(e); },
      over: function(e) { over(e); }
    });

    function drop(e) {
      $selected = $(".ss-moving").removeClass("ss-moving");
      $selectedContainer = $selected.parent();
      $clone = $(".ss-clone");
      if($clone[0]) {
        $cloneContainer = $clone.parent();
        if($cloneContainer[0] === $selectedContainer[0]) {
          $clone.remove();
        } else {
          $clone.removeClass("ss-clone");
          $cloneContainer.trigger("ss-event-dragreset");
          $selected.parent().trigger("ss-event-dragreset");
        }
      }
      $selectedContainer.trigger("ss-event-arrange").trigger("ss-event-dropped", $selected);
    }

    function over(e) {
      $curContainer.addClass("ss-prev-container");
      $curContainer = $(e.target).removeClass("ss-prev-container");
    }
  }

  Plugin.prototype.dragClear = function() {
    var ss = this,
        $container = $(ss.container);
    $container.droppable().droppable('destroy');
    $container.children().draggable().draggable('destroy');
  }

  Plugin.prototype.getPositions = function($container, ignoreSelected) {
    var ss = this,
        options = ss.options,
        $objects = $container.children(options.selector).filter(":visible"),
        columns = options.columns,
        colHeights = [],
        colWidth = null,
        gridOffset = options.paddingX,
        positions = [];

    // If we want to get the positions for all items excluding the
    // one currently being dragged.
    if(ignoreSelected) { $objects = $objects.not(".ss-moving"); }

    // Determine the width of each element.
    if(!options.childWidth) { options.childWidth = $objects.first().outerWidth(true); }

    // Determine the column width.
    colWidth = options.childWidth + options.gutterX;

    // Determine how many columns are currently active
    if(!columns) { columns = Math.floor($container.innerWidth() / colWidth); }

    // Offset the grid to center it.
    if(options.centerGrid) {
      gridOffset = Math.floor((($container.innerWidth() / colWidth) % 1 * colWidth) / 2) + (options.gutterX / 2);
    }

    // Create an array element for each column, which is then
    // used to store that columns current height.
    for(var i=0;i<columns;i++) {colHeights.push(options.paddingY);}

    // Loop over each element and determine what column it fits into
    for(var obj_i=0;obj_i<$objects.length;obj_i++) {
      var $obj = $($objects[obj_i]),
          col = $.inArray(Math.min.apply(window,colHeights), colHeights),
          height = $obj.outerHeight(true) + options.gutterY,
          offsetX = (colWidth * col) + gridOffset,
          offsetY = colHeights[col];

      // Store the position to animate into place later
      attributes = { left: offsetX, top: offsetY };
      positions[obj_i] = attributes;

      // Increase the calculated total height of the current column
      colHeights[col] += height;
    }
    // Store the height of the tallest column
    if(options.enableAutoHeight){
      options.containerHeight = Math.max.apply(Math,colHeights);
    }
    return positions;
  }

  Plugin.prototype.getIntendedPosition = function(e) {
    var ss = this,
        options = ss.options,
        $selected = $(".ss-moving"),
        $container = $selected.parent(),
        chosenIndex = 0,
        selectedX = $selected.position().left + (options.childWidth / 2),
        selectedY = $selected.position().top + ($selected.outerHeight() / 2),
        shortestDistance = 9999,
        positions = ss.getPositions($container, true),
        endCap = positions.length - options.dropCutoff;

    // Go over all of those positions and figure out
    // which is the closest to the cursor.
    for(hov_i=0;hov_i<positions.length;hov_i++) {

      // If we are able to insert at this index position
      if(hov_i < endCap) {
        attributes = positions[hov_i];

        // If the current item is to the bottom right of the current object position
        if(selectedX > attributes.left && selectedY > attributes.top) {
          var xDist = selectedX - attributes.left,
              yDist = selectedY - attributes.top;

          distance = Math.sqrt((xDist * xDist) + (yDist * yDist));

          // If this is the shortest distance so far
          if(distance < shortestDistance) {
            shortestDistance = distance;
            chosenIndex = hov_i;

            // If this is the last item, and we are below it or to the right,
            // then we may want to insert it as the last item.
            if(hov_i === positions.length - 1) {
              var $object = $container.children().not(".ss-moving").last();
              if(yDist > ($object.outerHeight() * .9) || xDist > options.childWidth * .9) {
                chosenIndex++;
              }
            }
          }
        }
      }
    }
    // Return the intended index position
    return chosenIndex;
  }

  Plugin.prototype.resize = function () {
    var ss = this,
        resizing = false;

    $(window).on("resize", function() {
      if(!resizing) {
        resizing = true;
        $(ss.element).trigger("ss-event-arrange");
        setTimeout(function() {
          resizing = false;
        }, 200);
      }
    });
  }

  // Prevent against multiple instantiations
  $.fn[pluginName] = function (options) {
    return this.each(function () {
      $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
    });
  }

}(jQuery, window));<|endoftext|>


<commit_before>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit   */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.")
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {
    return move || getInput();
}

function getComputerMove(move) {
    return move || randomPlay();
}

function getWinner(playerMove,computerMove) {
    var winner;
    if ((playerMove === 'rock' && computerMove === 'scissors') || (playerMove === 'scissors' && computerMove === 'paper') || (playerMove === 'paper' && computerMove === 'rock')) {
        winner = 'player';
    } else if ((computerMove === 'rock' && playerMove === 'scissors') || (computerMove === 'scissors' && playerMove === 'paper') || (computerMove === 'paper' && playerMove === 'rock')) {
        winner = 'computer';
    } else {
        winner = 'tie';
    }
    return winner;
}

function playToFive() {
    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    // Write code that plays 'Rock, Paper, Scissors' until either the player or the computer has won five times.
    /* YOUR CODE HERE */
    return [playerWins, computerWins];
}

<commit_msg>Completed project.<commit_after>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit   */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.")
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {
    return move || getInput();
}

function getComputerMove(move) {
    return move || randomPlay();
}

function getWinner(playerMove,computerMove) {
    var winner;
    if ((playerMove === "rock" && computerMove === "scissors") || (playerMove === "scissors" && computerMove === "paper") || (playerMove === "paper" && computerMove === "rock")) {
        winner = "player";
    } else if ((computerMove === "rock" && playerMove === "scissors") || (computerMove === "scissors" && playerMove === "paper") || (computerMove === "paper" && playerMove === "rock")) {
        winner = "computer";
    } else {
        winner = "tie";
    }
    return winner;
}

function playToFive() {
    console.log("Let's play Rock, Paper, Scissors!");
    var playerWins = 0;
    var computerWins = 0;
    while (playerWins < 5 && computerWins < 5) {
        var playerMove = getPlayerMove();
        var computerMove = getComputerMove();
        var winner = getWinner(playerMove, computerMove);
        if (winner === "player") {
            playerWins ++;
            console.log("You win!");
        } else if (winner === "computer") {
            computerWins ++;
            console.log("Computer wins.");
        } else {
            console.log("It's a tie!");
        }
        console.log("You chose " + playerMove + ", while Computer chose " + computerMove + ".");
        console.log("The score is currently " + playerWins + " to " + computerWins + ".");
    }
    if (playerWins > computerWins) {
        console.log("Bravo! You win the game! Please play again.")
    } else {
        console.log("Computer wins the game. Please try again.")
    }
    return [playerWins, computerWins];
}

function playTo(x) {
    console.log("Let's play Rock, Paper, Scissors!");
    var playerWins = 0;
    var computerWins = 0;
    while (playerWins < x && computerWins < x) {
        var playerMove = getPlayerMove();
        var computerMove = getComputerMove();
        var winner = getWinner(playerMove, computerMove);
        if (winner === "player") {
            playerWins ++;
            console.log("You win!");
        } else if (winner === "computer") {
            computerWins ++;
            console.log("Computer wins.");
        } else {
            console.log("It's a tie!");
        }
        console.log("You chose " + playerMove + ", while Computer chose " + computerMove + ".");
        console.log("The score is currently " + playerWins + " to " + computerWins + ".");
    }
    if (playerWins > computerWins) {
        console.log("Bravo! You win the game! Please play again.")
    } else {
        console.log("Computer wins the game. Please try again.")
    }
    return [playerWins, computerWins];
}

playTo(7)<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import dis from './dispatcher';

const MIN_DISPATCH_INTERVAL_MS = 500;
const CURRENTLY_ACTIVE_THRESHOLD_MS = 2000;

/**
 * This class watches for user activity (moving the mouse or pressing a key)
 * and dispatches the user_activity action at times when the user is interacting
 * with the app (but at a much lower frequency than mouse move events)
 */
class UserActivity {
    /**
     * Start listening to user activity
     */
    start() {
        document.onmousedown = this._onUserActivity.bind(this);
        document.onmousemove = this._onUserActivity.bind(this);
        document.onkeydown = this._onUserActivity.bind(this);
        // can't use document.scroll here because that's only the document
        // itself being scrolled. Need to use addEventListener's useCapture.
        // also this needs to be the wheel event, not scroll, as scroll is
        // fired when the view scrolls down for a new message.
        window.addEventListener('wheel', this._onUserActivity.bind(this),
                                { passive: true, capture: true });
        this.lastActivityAtTs = new Date().getTime();
        this.lastDispatchAtTs = 0;
        this.activityEndTimer = undefined;
    }

    /**
     * Stop tracking user activity
     */
    stop() {
        document.onmousedown = undefined;
        document.onmousemove = undefined;
        document.onkeydown = undefined;
        window.removeEventListener('wheel', this._onUserActivity.bind(this),
                                   { passive: true, capture: true });
    }

    /**
     * Return true if there has been user activity very recently
     * (ie. within a few seconds)
     * @returns {boolean} true if user is currently/very recently active
     */
    userCurrentlyActive() {
        return this.lastActivityAtTs > new Date().getTime() - CURRENTLY_ACTIVE_THRESHOLD_MS;
    }

    _onUserActivity(event) {
        if (event.screenX && event.type === "mousemove") {
            if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) {
                // mouse hasn't actually moved
                return;
            }
            this.lastScreenX = event.screenX;
            this.lastScreenY = event.screenY;
        }

        this.lastActivityAtTs = new Date().getTime();
        if (this.lastDispatchAtTs < this.lastActivityAtTs - MIN_DISPATCH_INTERVAL_MS) {
            this.lastDispatchAtTs = this.lastActivityAtTs;
            dis.dispatch({
                action: 'user_activity',
            });
            if (!this.activityEndTimer) {
                this.activityEndTimer = setTimeout(this._onActivityEndTimer.bind(this), MIN_DISPATCH_INTERVAL_MS);
            }
        }
    }

    _onActivityEndTimer() {
        const now = new Date().getTime();
        const targetTime = this.lastActivityAtTs + MIN_DISPATCH_INTERVAL_MS;
        if (now >= targetTime) {
            dis.dispatch({
                action: 'user_activity_end',
            });
            this.activityEndTimer = undefined;
        } else {
            this.activityEndTimer = setTimeout(this._onActivityEndTimer.bind(this), targetTime - now);
        }
    }
}

module.exports = new UserActivity();
<commit_msg>Timer in UserActivity, change semantics to "user probably looks at app"<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import dis from './dispatcher';
import Timer from './utils/Timer';

// important this is larger than the timeouts of timers
// used with UserActivity.timeWhileActive,
// such as READ_MARKER_INVIEW_THRESHOLD_MS,
// READ_MARKER_OUTOFVIEW_THRESHOLD_MS,
// READ_RECEIPT_INTERVAL_MS in TimelinePanel
const CURRENTLY_ACTIVE_THRESHOLD_MS = 2 * 60 * 1000;

/**
 * This class watches for user activity (moving the mouse or pressing a key)
 * and starts/stops attached timers while the user is active.
 */
class UserActivity {
    constructor() {
        this._attachedTimers = [];
        this._activityTimeout = new Timer(CURRENTLY_ACTIVE_THRESHOLD_MS);
        this._onUserActivity = this._onUserActivity.bind(this);
        this._onDocumentBlurred = this._onDocumentBlurred.bind(this);
        this._onPageVisibilityChanged = this._onPageVisibilityChanged.bind(this);
        this.lastScreenX = 0;
        this.lastScreenY = 0;
    }

    /**
     * Runs the given timer while the user is active, aborting when the user becomes inactive.
     * Can be called multiple times with the same already running timer, which is a NO-OP.
     * Can be called before the user becomes active, in which case it is only started
     * later on when the user does become active.
     */
    timeWhileActive(timer) {
        // important this happens first
        const index = this._attachedTimers.indexOf(timer);
        if (index === -1) {
            this._attachedTimers.push(timer);
            // remove when done or aborted
            timer.finished().finally(() => {
                const index = this._attachedTimers.indexOf(timer);
                if (index !== -1) { // should never be -1
                    this._attachedTimers.splice(index, 1);
                }
            // as we fork the promise here,
            // avoid unhandled rejection warnings
            }).catch((err) => {});
        }
        if (this.userCurrentlyActive()) {
            timer.start();
        }
    }

    /**
     * Start listening to user activity
     */
    start() {
        document.onmousedown = this._onUserActivity;
        document.onmousemove = this._onUserActivity;
        document.onkeydown = this._onUserActivity;
        document.addEventListener("visibilitychange", this._onPageVisibilityChanged);
        document.addEventListener("blur", this._onDocumentBlurred);
        document.addEventListener("focus", this._onUserActivity);
        // can't use document.scroll here because that's only the document
        // itself being scrolled. Need to use addEventListener's useCapture.
        // also this needs to be the wheel event, not scroll, as scroll is
        // fired when the view scrolls down for a new message.
        window.addEventListener('wheel', this._onUserActivity,
                                { passive: true, capture: true });
    }

    /**
     * Stop tracking user activity
     */
    stop() {
        document.onmousedown = undefined;
        document.onmousemove = undefined;
        document.onkeydown = undefined;
        window.removeEventListener('wheel', this._onUserActivity,
                                   { passive: true, capture: true });

        document.removeEventListener("visibilitychange", this._onPageVisibilityChanged);
        document.removeEventListener("blur", this._onDocumentBlurred);
        document.removeEventListener("focus", this._onUserActivity);
    }

    /**
     * Return true if there has been user activity very recently
     * (ie. within a few seconds)
     * @returns {boolean} true if user is currently/very recently active
     */
    userCurrentlyActive() {
        return this._activityTimeout.isRunning();
    }

    _onPageVisibilityChanged(e) {
        if (document.visibilityState === "hidden") {
            this._activityTimeout.abort();
        } else {
            this._onUserActivity(e);
        }
    }

    _onDocumentBlurred() {
        this._activityTimeout.abort();
    }

    async _onUserActivity(event) {
        if (event.screenX && event.type === "mousemove") {
            if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) {
                // mouse hasn't actually moved
                return;
            }
            this.lastScreenX = event.screenX;
            this.lastScreenY = event.screenY;
        }

        if (!this._activityTimeout.isRunning()) {
            this._activityTimeout.start();
            dis.dispatch({action: 'user_activity_start'});
            this._attachedTimers.forEach((t) => t.start());
            try {
                await this._activityTimeout.finished();
            } catch (_e) { /* aborted */ }
            this._attachedTimers.forEach((t) => t.abort());
        } else {
            this._activityTimeout.restart();
        }
    }
}

module.exports = new UserActivity();
<|endoftext|>


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

/**
 * An action that can be sent to the data stream, and optionally recorded for playback.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const ActionIO = require( 'AXON/ActionIO' );
  const axon = require( 'AXON/axon' );
  const PhetioObject = require( 'TANDEM/PhetioObject' );
  const Tandem = require( 'TANDEM/Tandem' );
  const ValidatorDef = require( 'AXON/ValidatorDef' );
  const validate = require( 'AXON/validate' );

  // constants
  const ActionIOWithNoArgs = ActionIO( [] );

  // Simulations have thousands of Emitters, so we re-use objects where possible.
  const EMPTY_ARRAY = [];
  assert && Object.freeze( EMPTY_ARRAY );

  class Action extends PhetioObject {

    /**
     * @param {function} action - the function that is called when this Action occurs
     * @param {Object} [options]
     */
    constructor( action, options ) {

      const phetioTypeSupplied = options && options.hasOwnProperty( 'phetioType' );
      const validatorsSupplied = options && options.hasOwnProperty( 'validators' );

      // ActionIO that have 0 args should use the built-in ActionIO([]) default.  But we must support EmitterIO([]),
      // so we guard based on the type name.
      if ( assert && phetioTypeSupplied && options.phetioType.typeName.indexOf( 'ActionIO' ) === 0 ) {
        assert( options.phetioType.parameterTypes.length > 0, 'do not specify phetioType that is the same as the default' );
      }

      options = _.extend( {

        // {Array.<Object>} - array of "validators" as defined by ValidatorDef.js
        validators: EMPTY_ARRAY,

        // {boolean} @deprecated, only to support legacy emit1, emit2, emit3 calls.
        validationEnabled: true,

        // phet-io - see PhetioObject.js for doc
        tandem: Tandem.optional,
        phetioState: false,
        phetioType: ActionIOWithNoArgs, // subtypes can override with ActionIO([...]), see ActionIO.js
        phetioPlayback: PhetioObject.DEFAULT_OPTIONS.phetioPlayback,
        phetioEventMetadata: PhetioObject.DEFAULT_OPTIONS.phetioEventMetadata
      }, options );

      const optionsSetByAction = {};

      // phetioPlayback events need to know the order the arguments occur in order to call EmitterIO.emit()
      // Indicate whether the event is for playback, but leave this "sparse"--only indicate when this happens to be true
      if ( options.phetioPlayback ) {
        optionsSetByAction.phetioEventMetadata = options.phetioEventMetadata || {};
        assert && assert( !optionsSetByAction.phetioEventMetadata.hasOwnProperty( 'dataKeys' ), 'dataKeys should be supplied by Action, not elsewhere' );
        optionsSetByAction.phetioEventMetadata.dataKeys = options.phetioType.elements.map( element => element.name );
      }

      // important to be before super call. OK to supply neither or one or the other, but not both.  That is a NAND.
      assert && assert( !( phetioTypeSupplied && validatorsSupplied ),
        'use either phetioType or validators, not both, see EmitterIO to set validators on an instrumented Action'
      );

      if ( options.tandem.supplied ) {
        assert && assert( !validatorsSupplied, 'when specifying tandem, use phetioType instead of validators' );
      }

      // use the phetioType's validators if provided, we know we aren't overwriting here because of the above assertion
      if ( phetioTypeSupplied ) {
        optionsSetByAction.validators = options.phetioType.validators;
      }

      // extend options with values set by Action, only if there are any to set
      if ( Object.keys( optionsSetByAction ).length > 0 ) {
        options = _.extend( {}, options, optionsSetByAction );
      }

      super( options );

      validate( options.validators, { valueType: Array } );

      // @public (only for testing) - Note: one test indicates stripping this out via assert && in builds may save around 300kb heap
      this.validators = options.validators;

      // @private - opt out of validation. Can be removed when deprecated emit functions are gone.
      this.validationEnabled = options.validationEnabled;

      if ( assert ) {

        // Iterate through each validator and make sure that it won't validate options on validating value. This is
        // mainly done for performance
        options.validators.forEach( validator => {
          assert(
            validator.validateOptionsOnValidateValue !== true,
            'Action sets its own validateOptionsOnValidateValue for each argument type'
          );
          validator.validateOptionsOnValidateValue = false;

          // Changing the validator options after construction indicates a logic error, except that many EmitterIOs
          // are shared between instances. Don't assume we "own" the validator if it came from the TypeIO.
          !phetioTypeSupplied && Object.freeze( validator );

          // validate the options passed in to validate each Action argument
          ValidatorDef.validateValidator( validator );
        } );

        // Changing after construction indicates a logic error, except that many EmitterIOs are shared between instances.
        // Don't assume we "own" the validator if it came from the TypeIO.
        !phetioTypeSupplied && Object.freeze( options.validators );
      }

      assert && assert( typeof action === 'function', 'action should be a function' );

      // @private {function}
      this._action = action;
    }

    /**
     * Gets the data that will be emitted to the PhET-iO data stream, for an instrumented simulation.
     * @returns {*}
     * @private
     */
    getPhetioData() {

      // null if there are no arguments.  dataStream.js omits null values for data
      let data = null;
      if ( this.phetioType.elements.length > 0 ) {

        // Enumerate named argsObject for the data stream.
        data = {};
        for ( let i = 0; i < this.phetioType.elements.length; i++ ) {
          const element = this.phetioType.elements[ i ];
          data[ element.name ] = element.type.toStateObject( arguments[ i ] );
        }
      }
      return data;
    }

    /**
     * Emits a single event.  This method is called many times in a simulation and must be well-optimized.  Listeners
     * are notified in the order they were added via addListener, though it is poor practice to rely on the order
     * of listener notifications.
     * @params - expected parameters are based on options.validators, see constructor
     * @public
     */
    emit() {
      if ( assert && this.validationEnabled ) {
        assert( arguments.length === this.validators.length,
          `Emitted unexpected number of args. Expected: ${this.validators.length} and received ${arguments.length}`
        );
        for ( let i = 0; i < this.validators.length; i++ ) {
          validate( arguments[ i ], this.validators[ i ] );
        }
      }

      // handle phet-io data stream for the emitted event
      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', this.getPhetioData.apply( this, arguments ) );

      this._action.apply( null, arguments );

      this.isPhetioInstrumented() && this.phetioEndEvent();
    }
  }

  return axon.register( 'Action', Action );
} );<commit_msg>reorganize assertions, factor out validator validating to function, #222<commit_after>// Copyright 2019, University of Colorado Boulder

/**
 * An action that can be executed and sent to the PhET-iO data stream, and optionally recorded for playback. This type
 * will also validate the argument types passed to the action function
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const ActionIO = require( 'AXON/ActionIO' );
  const axon = require( 'AXON/axon' );
  const PhetioObject = require( 'TANDEM/PhetioObject' );
  const Tandem = require( 'TANDEM/Tandem' );
  const ValidatorDef = require( 'AXON/ValidatorDef' );
  const validate = require( 'AXON/validate' );

  // constants
  const ActionIOWithNoArgs = ActionIO( [] );

  // Simulations have thousands of Emitters, so we re-use objects where possible.
  const EMPTY_ARRAY = [];
  assert && Object.freeze( EMPTY_ARRAY );

  class Action extends PhetioObject {

    /**
     * @param {function} action - the function that is called when this Action occurs
     * @param {Object} [options]
     */
    constructor( action, options ) {

      // It is important to know if the following options were provided by the client
      const phetioTypeSupplied = options && options.hasOwnProperty( 'phetioType' );
      const validatorsSupplied = options && options.hasOwnProperty( 'validators' );

      // Important to be before super call. OK to supply either or one or the other, but not both. This is a NAND operator.
      assert && assert( !( phetioTypeSupplied && validatorsSupplied ),
        'use either phetioType or validators, not both, see EmitterIO to set validators on an instrumented Action'
      );

      // ActionIO that have 0 args should use the built-in ActionIO([]) default.  But we must support EmitterIO([]),
      // so we guard based on the type name.
      if ( assert && phetioTypeSupplied && options.phetioType.typeName.indexOf( 'ActionIO' ) === 0 ) {
        assert( options.phetioType.parameterTypes.length > 0, 'do not specify phetioType that is the same as the default' );
      }

      options = _.extend( {

        // {ValidatorDef[]}
        validators: EMPTY_ARRAY,

        // {boolean} @deprecated, only to support legacy emit1, emit2, emit3 calls.
        validationEnabled: true,

        // phet-io - see PhetioObject.js for doc
        tandem: Tandem.optional,
        phetioState: false,
        phetioType: ActionIOWithNoArgs, // subtypes can override with ActionIO([...]), see ActionIO.js
        phetioPlayback: PhetioObject.DEFAULT_OPTIONS.phetioPlayback,
        phetioEventMetadata: PhetioObject.DEFAULT_OPTIONS.phetioEventMetadata
      }, options );

      // Use a separate object so as to not mutate passed in options object.
      const optionsSetByAction = {};

      // Use the phetioType's validators if provided, we know we aren't overwriting here because of the above assertion
      if ( phetioTypeSupplied ) {
        optionsSetByAction.validators = options.phetioType.validators;
      }

      // phetioPlayback events need to know the order the arguments occur in order to call EmitterIO.emit()
      // Indicate whether the event is for playback, but leave this "sparse"--only indicate when this happens to be true
      if ( options.phetioPlayback ) {
        optionsSetByAction.phetioEventMetadata = options.phetioEventMetadata || {};

        assert && assert( !optionsSetByAction.phetioEventMetadata.hasOwnProperty( 'dataKeys' ),
          'dataKeys should be supplied by Action, not elsewhere' );

        optionsSetByAction.phetioEventMetadata.dataKeys = options.phetioType.elements.map( element => element.name );
      }

      // Extend options with values set by Action, only if there are any to set to save an object declaration.
      if ( Object.keys( optionsSetByAction ).length > 0 ) {
        options = _.extend( {}, options, optionsSetByAction );
      }

      super( options );

      assert && this.validateValidators( options.validators, validatorsSupplied, phetioTypeSupplied );

      // @public (only for testing) - Note: one test indicates stripping this out via assert && in builds may save around 300kb heap
      this.validators = options.validators;

      // @private - opt out of validation. Can be removed when deprecated emit functions are gone.
      this.validationEnabled = options.validationEnabled;

      assert && assert( typeof action === 'function', 'action should be a function' );

      // @private {function}
      this._action = action;
    }

    /**
     * @private
     * @param {ValidatorDef[]} validators
     * @param {boolean} validatorsSuppliedByClient - true if the validators option was passed into the constructor via options,
     *                                               false if using the default from the options extend call
     * @param phetioTypeSuppliedByClient - true if the phetioType option was passed into the constructor via options,
     *                                     false if using the default from the options extend call
     */
    validateValidators( validators, validatorsSuppliedByClient, phetioTypeSuppliedByClient ) {

      // validate the validators object
      validate( validators, { valueType: Array } );

      this.isPhetioInstrumented() && assert( !validatorsSuppliedByClient, 'when specifying tandem, use phetioType instead of validators' );

      // Iterate through each validator and make sure that it won't validate options on validating value. This is
      // mainly done for performance
      validators.forEach( validator => {
        assert(
          validator.validateOptionsOnValidateValue !== true,
          'Action sets its own validateOptionsOnValidateValue for each argument type'
        );
        validator.validateOptionsOnValidateValue = false;

        // Changing the validator options after construction indicates a logic error, except that many EmitterIOs
        // are shared between instances. Don't assume we "own" the validator if it came from the TypeIO.
        !phetioTypeSuppliedByClient && Object.freeze( validator );

        // validate the options passed in to validate each Action argument
        ValidatorDef.validateValidator( validator );
      } );

      // Changing after construction indicates a logic error, except that many EmitterIOs are shared between instances.
      // Don't assume we "own" the validator if it came from the TypeIO.
      !phetioTypeSuppliedByClient && Object.freeze( validators );
    }

    /**
     * Gets the data that will be emitted to the PhET-iO data stream, for an instrumented simulation.
     * @returns {*}
     * @private
     */
    getPhetioData() {

      // null if there are no arguments.  dataStream.js omits null values for data
      let data = null;
      if ( this.phetioType.elements.length > 0 ) {

        // Enumerate named argsObject for the data stream.
        data = {};
        for ( let i = 0; i < this.phetioType.elements.length; i++ ) {
          const element = this.phetioType.elements[ i ];
          data[ element.name ] = element.type.toStateObject( arguments[ i ] );
        }
      }
      return data;
    }

    /**
     * Emits a single event.  This method is called many times in a simulation and must be well-optimized.  Listeners
     * are notified in the order they were added via addListener, though it is poor practice to rely on the order
     * of listener notifications.
     * @params - expected parameters are based on options.validators, see constructor
     * @public
     */
    emit() {
      if ( assert && this.validationEnabled ) {
        assert( arguments.length === this.validators.length,
          `Emitted unexpected number of args. Expected: ${this.validators.length} and received ${arguments.length}`
        );
        for ( let i = 0; i < this.validators.length; i++ ) {
          validate( arguments[ i ], this.validators[ i ] );
        }
      }

      // handle phet-io data stream for the emitted event
      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', this.getPhetioData.apply( this, arguments ) );

      this._action.apply( null, arguments );

      this.isPhetioInstrumented() && this.phetioEndEvent();
    }
  }

  return axon.register( 'Action', Action );
} );<|endoftext|>