<commit_before>angular.module('DeviceWall')
  .controller(
    'MainController',
    function($rootScope, $scope, $window, $http, $timeout, DeviceList, lodash, appConfig, $log, socketConnect) {
      'use strict';
      var _ = lodash;
      var socket = socketConnect.connect('/devicewall');

      $log.debug('loading main controller');
      $scope.indicatorWaiting = {show: false};
      $scope.config = appConfig;
      $scope.deviceList = DeviceList.toArray();
      $scope.serverStatus = 'stopped';

      // Default device sort order
      $scope.predicate = 'label';
      $scope.reverse = false;

      // TODO move these somewhere
      $scope.userAgent = {selected: {}};
      $scope.userAgents = [
        {},
        {name: 'desktop (OS X)', value: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36"},
        {name: 'desktop (Windows)', value: "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"},
        {name: 'mobile (Android)', value: "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.36 (KHTML, like Gecko) Chrome/40.0.2194.2 Mobile Safari/535.36"},
        {name: 'mobile (iOS)', value: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"},
        {name: 'mobile (Lumia 900)', value: "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; NOKIA; Lumia 900)"},
        {name: 'mobile (Lumia 920)', value: "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)"},
        {name: 'tablet (Android)', value: "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus 7 Build/IMM76B) AppleWebKit/535.36 (KHTML, like Gecko) Chrome/40.0.2194.2 Safari/535.36"},
        {name: 'tablet 10 inch', value: "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus 10 Build/IMM76B) AppleWebKit/535.36 (KHTML, like Gecko) Chrome/40.0.2194.2 Safari/535.36"},
        {name: 'tablet (iPad)', value: "Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10"},
      ];
      // selected device uuid list? TODO refactor whole selected devices list feature, this is not nice, really.
      $scope.uuids = {};

      if (appConfig.singleUser) {
        $scope.user = {
          id: 'singleuser',
          displayName: 'singleuser'
        };
      } else {
        $scope.user = {
          id: $window.localStorage.getItem('name'),
          displayName: $window.localStorage.getItem('name')
        };
      }

      $scope.url = {
        value: '',
        visitedUrls: $window.localStorage.getItem('visitedUrls') ? JSON.parse($window.localStorage.getItem('visitedUrls')) : [],
        click: function() {
          if (!$scope.url.selectedValue || $scope.url.value === '' || !$scope.url.value) {
            $scope.url.value = 'http://';
          }
        },
        inputChanged: function(str) {
          // set the url model value to input text
          $scope.url.value = str;
        }
      };

      $scope.btnGo =              {show: true};
      $scope.btnStopAllTesting =  {show: false};
      $scope.btnStopTesting =     {show: false};

      $scope.tooltipError = {
        show: false,
        content: ''
      };

      $scope.popupWindow = null;

      $scope.submitUrl = function() {
        if ($scope.url.selectedValue) {
          // If user has selected an existing url from the autocomplete array, use that value
          $scope.url.value = $scope.url.selectedValue.originalObject.url;
        } else {
          // Add new url to visitedUrls array and to local storage
          $scope.url.visitedUrls.push({url: $scope.url.value});
          $window.localStorage.setItem('visitedUrls', JSON.stringify($scope.url.visitedUrls));
        }

        $log.debug('Submit url ', $scope.url.value);
        if ($scope.url.value.length <= 4) {
          $scope.tooltipError = {
            show: true,
            content: 'Invalid url'
          };
          return;
        }

        var formData = {
          url: $scope.url.value,
          labels: _.pluck(_.where($scope.deviceList, { selected: true }), 'label'),
          user: $scope.user,
          userAgentHeader: $scope.userAgent.selected.name ? $scope.userAgent.selected.value : false
        };

        $window.localStorage.setItem('url', $scope.url.value);
        $scope.tooltipError.show = false;
        $scope.tooltipError.content = '';
        socket.emit('start', formData);
        $scope.setButtonsStatus(false);
      };

      $scope.selectAll = function(status) {
        _.each(DeviceList.toArray(), function(device) {
          device.selected = status;
          DeviceList.update(device);
        });
        $scope.deviceList = DeviceList.toArray();
      };

      $scope.clientFieldChanged = function(model) {
        socket.emit('save', model);
      };

      $scope.stopTesting = function() {
        $log.debug('stop testing');
        function getUserDevices() {
          var userDevices = [];
          _.each($scope.deviceList, function(device) {
            if (device.userId === $scope.user.id) {
              userDevices.push(device.uuid);
            }
          });
          return userDevices;
        }
        socket.emit('stop', {user: $scope.user, labels: getUserDevices()});
        $scope.btnStopTesting.show = false;
      };

      $scope.stopAllTesting = function() {
        $log.debug('stop All testing');
        socket.emit('stopall');
      };

      $scope.removeDevice = function(device) {
        socket.emit('remove', {labels: [device.label]});
        DeviceList.remove(device);
        $scope.deviceList = DeviceList.toArray();
      };

      // Socket actions
      socket.on('connect',  function () {
        $scope.indicatorWaiting = {show: false};
        socket.emit('list', 'list', function(data) {
          $log.debug("socket::list", data);
          var instancesRunning = false;
          _.each(data, function(device) {
            // device selected by default
            device.selected = true;
            if (device.userId) {
              instancesRunning = true;
            }
            DeviceList.add(device);
          });
          $scope.deviceList = DeviceList.toArray();
          if (instancesRunning) {
            $scope.setButtonsStatus(false);
          }
        });
      });

      socket.on('rename', function(data) {
        $log.debug('rename', data);
        if (DeviceList.has({label: data.oldLabel})) {
          var device = DeviceList.get(data.oldLabel);
          DeviceList.remove(device);
          device.label = data.newLabel;
          DeviceList.add(device);
        }
      });
      socket.on('update', function (data) {
        $scope.$apply(function() {
          _.each(data, function(device) {
            if (DeviceList.has(device)) {
              DeviceList.update(device);
            } else {
              device.selected = true;
              DeviceList.add(device);
            }
          });
          $scope.deviceList = DeviceList.toArray();
        });
      });

      socket.on('start', function (data) {
        if (data.user.id === $scope.user.id) {
          $scope.setButtonsStatus(false);
          $scope.serverStatus = 'running';
          if ($scope.openUrl) {
            $log.debug('Opening a popup view');
            if ($scope.popupWindow && !$scope.popupWindow.closed) {
              $scope.popupWindow.location.href = data.url;
              $scope.popupWindow.focus();
            } else {
              $scope.popupWindow = $window.open(data.url, '_blank');
            }
          }
        }
      });

      socket.on('server-stop', function(data) {
        $log.debug('socket::server-stop');
        if (data.user.id === $scope.user.id) {
          $scope.serverStatus = 'stopped';
          $scope.setButtonsStatus(true);
        }
        if (data.reason) {
          $log.debug('some err', data.reason);
          $scope.tooltipError.content = data.reason;
          $scope.tooltipError.show = true;
        }
      });

      socket.on('stopall', function() {
        $log.debug('socket::stopall');
        $scope.serverStatus = 'stopped';
        $scope.setButtonsStatus(true);
      });

      // simple helper for buttons
      $scope.setButtonsStatus = function(status) {
        $scope.btnStopAllTesting.show = !status;
        $scope.btnStopTesting.show = !status;
        $scope.btnGo.show = true;
      };

      $scope.checkGoButtonStatus = function(oldVal, newVal, scope) {
        var disableGoButton = true;
        _.each(scope.deviceList, function(device) {
          if (device.selected) {
            disableGoButton = false;
          }
        });
        if (disableGoButton && scope.openUrl) {
          disableGoButton = false;
        }
        $scope.btnGo.disabled = disableGoButton;
      };

      $scope.showDeviceView = function() {
        $window.location.href = $window.location.protocol + '//' + $window.location.hostname + ':' +  appConfig.port;
      };

      $scope.$watch('deviceList', $scope.checkGoButtonStatus, true);
      $scope.$watch('openUrl', $scope.checkGoButtonStatus);

      $scope.$on('$destroy', function() {
        socket.removeAllListeners();
      });
    }
  );

<commit_msg>fix url value logic and only save same url once to localStorage<commit_after>angular.module('DeviceWall')
  .controller(
    'MainController',
    function($rootScope, $scope, $window, $http, $timeout, DeviceList, lodash, appConfig, $log, socketConnect) {
      'use strict';
      var _ = lodash;
      var socket = socketConnect.connect('/devicewall');

      $log.debug('loading main controller');
      $scope.indicatorWaiting = {show: false};
      $scope.config = appConfig;
      $scope.deviceList = DeviceList.toArray();
      $scope.serverStatus = 'stopped';

      // Default device sort order
      $scope.predicate = 'label';
      $scope.reverse = false;

      // TODO move these somewhere
      $scope.userAgent = {selected: {}};
      $scope.userAgents = [
        {},
        {name: 'desktop (OS X)', value: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36"},
        {name: 'desktop (Windows)', value: "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"},
        {name: 'mobile (Android)', value: "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.36 (KHTML, like Gecko) Chrome/40.0.2194.2 Mobile Safari/535.36"},
        {name: 'mobile (iOS)', value: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"},
        {name: 'mobile (Lumia 900)', value: "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; NOKIA; Lumia 900)"},
        {name: 'mobile (Lumia 920)', value: "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)"},
        {name: 'tablet (Android)', value: "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus 7 Build/IMM76B) AppleWebKit/535.36 (KHTML, like Gecko) Chrome/40.0.2194.2 Safari/535.36"},
        {name: 'tablet 10 inch', value: "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus 10 Build/IMM76B) AppleWebKit/535.36 (KHTML, like Gecko) Chrome/40.0.2194.2 Safari/535.36"},
        {name: 'tablet (iPad)', value: "Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10"},
      ];
      // selected device uuid list? TODO refactor whole selected devices list feature, this is not nice, really.
      $scope.uuids = {};

      if (appConfig.singleUser) {
        $scope.user = {
          id: 'singleuser',
          displayName: 'singleuser'
        };
      } else {
        $scope.user = {
          id: $window.localStorage.getItem('name'),
          displayName: $window.localStorage.getItem('name')
        };
      }

      $scope.url = {
        value: '',
        visitedUrls: $window.localStorage.getItem('visitedUrls') ? JSON.parse($window.localStorage.getItem('visitedUrls')) : [],
        click: function() {
          if (!$scope.url.selectedValue || $scope.url.value === '' || !$scope.url.value) {
            $scope.url.value = 'http://';
          }
        },
        inputChanged: function(str) {
          // set the url model value to input text
          $scope.url.value = str;
        }
      };

      $scope.btnGo =              {show: true};
      $scope.btnStopAllTesting =  {show: false};
      $scope.btnStopTesting =     {show: false};

      $scope.tooltipError = {
        show: false,
        content: ''
      };

      $scope.popupWindow = null;

      $scope.submitUrl = function() {
        if (
          _.has($scope.url, "selectedValue") &&
          _.has($scope.url.selectedValue, "originalObject") &&
          _.has($scope.url.selectedValue.originalObject, "url")
        ) {
          $scope.url.value = $scope.url.selectedValue.originalObject.url;
        } else if (_.has($scope.url.selectedValue, "originalObject")) {
          $scope.url.value = $scope.url.selectedValue.originalObject;
        }

        // Add new url to visitedUrls array and to local storage
        $scope.url.visitedUrls.push({url: $scope.url.value});
        $scope.url.visitedUrls = _.unique($scope.url.visitedUrls, function(obj) {
          return obj.url;
        });
        $window.localStorage.setItem('visitedUrls', JSON.stringify($scope.url.visitedUrls));

        $log.debug('Submit url ', $scope.url.value);

        if (!$scope.url.value || $scope.url.value.length <= 4) {
          $scope.tooltipError = {
            show: true,
            content: 'Invalid url'
          };
          return;
        }

        var formData = {
          url: $scope.url.value,
          labels: _.pluck(_.where($scope.deviceList, { selected: true }), 'label'),
          user: $scope.user,
          userAgentHeader: $scope.userAgent.selected.name ? $scope.userAgent.selected.value : false
        };

        $window.localStorage.setItem('url', $scope.url.value);
        $scope.tooltipError.show = false;
        $scope.tooltipError.content = '';
        socket.emit('start', formData);
        $scope.setButtonsStatus(false);
      };

      $scope.selectAll = function(status) {
        _.each(DeviceList.toArray(), function(device) {
          device.selected = status;
          DeviceList.update(device);
        });
        $scope.deviceList = DeviceList.toArray();
      };

      $scope.clientFieldChanged = function(model) {
        socket.emit('save', model);
      };

      $scope.stopTesting = function() {
        $log.debug('stop testing');
        function getUserDevices() {
          var userDevices = [];
          _.each($scope.deviceList, function(device) {
            if (device.userId === $scope.user.id) {
              userDevices.push(device.uuid);
            }
          });
          return userDevices;
        }
        socket.emit('stop', {user: $scope.user, labels: getUserDevices()});
        $scope.btnStopTesting.show = false;
      };

      $scope.stopAllTesting = function() {
        $log.debug('stop All testing');
        socket.emit('stopall');
      };

      $scope.removeDevice = function(device) {
        socket.emit('remove', {labels: [device.label]});
        DeviceList.remove(device);
        $scope.deviceList = DeviceList.toArray();
      };

      // Socket actions
      socket.on('connect',  function () {
        $scope.indicatorWaiting = {show: false};
        socket.emit('list', 'list', function(data) {
          $log.debug("socket::list", data);
          var instancesRunning = false;
          _.each(data, function(device) {
            // device selected by default
            device.selected = true;
            if (device.userId) {
              instancesRunning = true;
            }
            DeviceList.add(device);
          });
          $scope.deviceList = DeviceList.toArray();
          if (instancesRunning) {
            $scope.setButtonsStatus(false);
          }
        });
      });

      socket.on('rename', function(data) {
        $log.debug('rename', data);
        if (DeviceList.has({label: data.oldLabel})) {
          var device = DeviceList.get(data.oldLabel);
          DeviceList.remove(device);
          device.label = data.newLabel;
          DeviceList.add(device);
        }
      });
      socket.on('update', function (data) {
        $scope.$apply(function() {
          _.each(data, function(device) {
            if (DeviceList.has(device)) {
              DeviceList.update(device);
            } else {
              device.selected = true;
              DeviceList.add(device);
            }
          });
          $scope.deviceList = DeviceList.toArray();
        });
      });

      socket.on('start', function (data) {
        if (data.user.id === $scope.user.id) {
          $scope.setButtonsStatus(false);
          $scope.serverStatus = 'running';
          if ($scope.openUrl) {
            $log.debug('Opening a popup view');
            if ($scope.popupWindow && !$scope.popupWindow.closed) {
              $scope.popupWindow.location.href = data.url;
              $scope.popupWindow.focus();
            } else {
              $scope.popupWindow = $window.open(data.url, '_blank');
            }
          }
        }
      });

      socket.on('server-stop', function(data) {
        $log.debug('socket::server-stop');
        if (data.user.id === $scope.user.id) {
          $scope.serverStatus = 'stopped';
          $scope.setButtonsStatus(true);
        }
        if (data.reason) {
          $log.debug('some err', data.reason);
          $scope.tooltipError.content = data.reason;
          $scope.tooltipError.show = true;
        }
      });

      socket.on('stopall', function() {
        $log.debug('socket::stopall');
        $scope.serverStatus = 'stopped';
        $scope.setButtonsStatus(true);
      });

      // simple helper for buttons
      $scope.setButtonsStatus = function(status) {
        $scope.btnStopAllTesting.show = !status;
        $scope.btnStopTesting.show = !status;
        $scope.btnGo.show = true;
      };

      $scope.checkGoButtonStatus = function(oldVal, newVal, scope) {
        var disableGoButton = true;
        _.each(scope.deviceList, function(device) {
          if (device.selected) {
            disableGoButton = false;
          }
        });
        if (disableGoButton && scope.openUrl) {
          disableGoButton = false;
        }
        $scope.btnGo.disabled = disableGoButton;
      };

      $scope.showDeviceView = function() {
        $window.location.href = $window.location.protocol + '//' + $window.location.hostname + ':' +  appConfig.port;
      };

      $scope.$watch('deviceList', $scope.checkGoButtonStatus, true);
      $scope.$watch('openUrl', $scope.checkGoButtonStatus);

      $scope.$on('$destroy', function() {
        socket.removeAllListeners();
      });
    }
  );

<|endoftext|>


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

  angular.module('app.components')
    .factory('entitiesAPI', function(Restangular) {
      return Restangular.withConfig(function(RestangularConfigurer) {
      		//RestangularConfigurer.setBaseUrl('http://127.0.0.1:3000'); //Dev cache
      		//RestangularConfigurer.setBaseUrl('http://ec2-54-68-181-32.us-west-2.compute.amazonaws.com:8090/v1');
      		RestangularConfigurer.setBaseUrl('https://organicityassetsdiscovery-fpgpcgrrbc.now.sh/'); //Temp 30sec cached for speed
        });
    });

})();
<commit_msg>orion url changed<commit_after>(function() {
  'use strict';

  angular.module('app.components')
    .factory('entitiesAPI', function(Restangular) {
      return Restangular.withConfig(function(RestangularConfigurer) {
      		//RestangularConfigurer.setBaseUrl('http://127.0.0.1:3000'); //Dev cache
      		RestangularConfigurer.setBaseUrl('http://ec2-52-40-19-99.us-west-2.compute.amazonaws.com:8090/v1');
      		//RestangularConfigurer.setBaseUrl('https://organicityassetsdiscovery-fpgpcgrrbc.now.sh/'); //Temp 30sec cached for speed
        });
    });

})();
<|endoftext|>


<commit_before>test(
  'ComponentApisTest',

  [
    'ephox.agar.api.Logger',
    'ephox.agar.api.RawAssertions',
    'ephox.alloy.construct.ComponentApis',
    'ephox.alloy.test.ResultAssertions',
    'ephox.alloy.test.TestStore',
    'ephox.alloy.util.ExtraArgs',
    'ephox.compass.Arr',
    'ephox.compass.Obj',
    'ephox.peanut.Fun',
    'ephox.scullion.Struct'
  ],

  function (Logger, RawAssertions, ComponentApis, ResultAssertions, TestStore, ExtraArgs, Arr, Obj, Fun, Struct) {
    var behaviour = Struct.immutable('name', 'apis');

    var checkErr = function (expectedPart, info, behaviours) {
      ResultAssertions.checkErr(
        'Checking error of combined API',
        expectedPart,
        function () {
          return ComponentApis.combine(info, behaviours, [ ExtraArgs.eager('extra-args') ]);
        }
      );
    };

    var store = TestStore();

    var check = function (expected, info, behaviours) {
      ResultAssertions.checkVal(
        'Checking combined API',
        function () {
          store.clear();
          return ComponentApis.combine(info, behaviours, [ ExtraArgs.eager('extra-args') ]);
        },
        function (value) {
          var apis = Obj.keys(value).sort();
          Arr.each(apis, function (apiName) {
            value[apiName]();
          });

          store.assertEq('Checking combined api', expected);
        }
      );
    };

    var ao = function (apiOrder) {
      return {
        apiOrder: Fun.constant(apiOrder)
      };
    };

    var handler = function (message) {
      return function () {
        store.adder(message)();
      };
    };

    Logger.sync(
      'Test with no apis',
      function () {
        check([ ], ao({}), [ ]);
      }
    );

    Logger.sync(
      'Test with 1 api from 1 behaviour',
      function () {
        check([
          'isAlpha.action'
        ], ao({}), [
          behaviour('alpha', {
            'isAlpha': handler('isAlpha.action')
          })
        ]);
      }
    );

    Logger.sync(
      'Test with 1 api with 2 behaviours and no order',
      function () {
        checkErr(
          'API ordering',
          ao({}), [
          behaviour('alpha.1', {
            'isAlpha': handler('isAlpha.1.action')
          }),
          behaviour('alpha.2', {
            'isAlpha': handler('isAlpha.2.action')
          })
        ]);
      }
    );

    Logger.sync(
      'Test with 1 api with 2 behaviours and an *incomplete* order',
      function () {
        checkErr(
          'entry for alpha.1',
          ao({
            'isAlpha': [ 'alpha.2' ]
          }), [
          behaviour('alpha.1', {
            'isAlpha': handler('isAlpha.1.action')
          }),
          behaviour('alpha.2', {
            'isAlpha': handler('isAlpha.2.action')
          })
        ]);
      }
    );

    Logger.sync(
      'Test with 1 api with 2 behaviours and an order',
      function () {
        check(
          [
            'isAlpha.2.action',
            'isAlpha.1.action'
          ],
          ao({
            'isAlpha': [ 'alpha.2', 'alpha.1' ]
          }),
          [
            behaviour('alpha.1', {
              'isAlpha': handler('isAlpha.1.action')
            }),
            behaviour('alpha.2', {
              'isAlpha': handler('isAlpha.2.action')
            })
          ]
        );
      }
    );

    Logger.sync(
      'Complex Test with 2 apis and 3 behaviours and missing notmiddle order so *incomplete*',
      function () {
        checkErr(
          'API call (notmiddle)',
          ao({
            'all': [ 'b1', 'b3', 'b2' ],
            'middle': [ 'b2' ]
          }),
          [
            behaviour('b1', {
              'all': handler('b1.all'),
              'notmiddle': handler('b1.notmiddle')
            }),
            behaviour('b2', {
              'all': handler('b2.all'),
              'middle': handler('b2.middle')
            }),
            behaviour('b3', {
              'all': handler('b3.all'),
              'notmiddle': handler('b3.notmiddle')
            })
          ]
        );
      }
    );

    Logger.sync(
      'Complex Test with 2 apis and 3 behaviours and missing middle order but it only has one',
      function () {
        check(
          [
            'b1.all',
            'b3.all',
            'b2.all',

            'b2.middle',

            'b3.notmiddle',
            'b1.notmiddle'
          ],
          ao({
            'all': [ 'b1', 'b3', 'b2' ],
            'notmiddle': [ 'b3', 'b1' ]
          }),
          [
            behaviour('b1', {
              'all': handler('b1.all'),
              'notmiddle': handler('b1.notmiddle')
            }),
            behaviour('b2', {
              'all': handler('b2.all'),
              'middle': handler('b2.middle')
            }),
            behaviour('b3', {
              'all': handler('b3.all'),
              'notmiddle': handler('b3.notmiddle')
            })
          ]
        );
      }
    );

    Logger.sync(
      'Complex Test with 2 apis and 3 behaviours and correct order',
      function () {
        check(
          [
            'b1.all',
            'b3.all',
            'b2.all',

            'b2.middle',

            'b3.notmiddle',
            'b1.notmiddle'
          ],
          ao({
            'all': [ 'b1', 'b3', 'b2' ],
            'middle': [ 'b2' ],
            'notmiddle': [ 'b3', 'b1' ]
          }),
          [
            behaviour('b1', {
              'all': handler('b1.all'),
              'notmiddle': handler('b1.notmiddle')
            }),
            behaviour('b2', {
              'all': handler('b2.all'),
              'middle': handler('b2.middle')
            }),
            behaviour('b3', {
              'all': handler('b3.all'),
              'notmiddle': handler('b3.notmiddle')
            })
          ]
        );
      }
    );
  }
);<commit_msg>VAN-1: deliberate test failure<commit_after>test(
  'ComponentApisTest',

  [
    'ephox.agar.api.Logger',
    'ephox.agar.api.RawAssertions',
    'ephox.alloy.construct.ComponentApis',
    'ephox.alloy.test.ResultAssertions',
    'ephox.alloy.test.TestStore',
    'ephox.alloy.util.ExtraArgs',
    'ephox.compass.Arr',
    'ephox.compass.Obj',
    'ephox.peanut.Fun',
    'ephox.scullion.Struct'
  ],

  function (Logger, RawAssertions, ComponentApis, ResultAssertions, TestStore, ExtraArgs, Arr, Obj, Fun, Struct) {
    var behaviour = Struct.immutable('name', 'apis');

    var checkErr = function (expectedPart, info, behaviours) {
      ResultAssertions.checkErr(
        'Checking error of combined API',
        expectedPart,
        function () {
          return ComponentApis.combine(info, behaviours, [ ExtraArgs.eager('extra-args') ]);
        }
      );
    };

    var store = TestStore();

    var check = function (expected, info, behaviours) {
      ResultAssertions.checkVal(
        'Checking combined API',
        function () {
          store.clear();
          return ComponentApis.combine(info, behaviours, [ ExtraArgs.eager('extra-args') ]);
        },
        function (value) {
          var apis = Obj.keys(value).sort();
          Arr.each(apis, function (apiName) {
            value[apiName]();
          });

          store.assertEq('Checking combined api', expected);
        }
      );
    };

    var ao = function (apiOrder) {
      return {
        apiOrder: Fun.constant(apiOrder)
      };
    };

    var handler = function (message) {
      return function () {
        store.adder(message)();
      };
    };

    Logger.sync(
      'Test with no apis',
      function () {
        check([ ], ao({}), [ ]);
      }
    );

    Logger.sync(
      'Test with 1 api from 1 behaviour',
      function () {
        check([
          'isAlpha.actionz'
        ], ao({}), [
          behaviour('alpha', {
            'isAlpha': handler('isAlpha.action')
          })
        ]);
      }
    );

    Logger.sync(
      'Test with 1 api with 2 behaviours and no order',
      function () {
        checkErr(
          'API ordering',
          ao({}), [
          behaviour('alpha.1', {
            'isAlpha': handler('isAlpha.1.action')
          }),
          behaviour('alpha.2', {
            'isAlpha': handler('isAlpha.2.action')
          })
        ]);
      }
    );

    Logger.sync(
      'Test with 1 api with 2 behaviours and an *incomplete* order',
      function () {
        checkErr(
          'entry for alpha.1',
          ao({
            'isAlpha': [ 'alpha.2' ]
          }), [
          behaviour('alpha.1', {
            'isAlpha': handler('isAlpha.1.action')
          }),
          behaviour('alpha.2', {
            'isAlpha': handler('isAlpha.2.action')
          })
        ]);
      }
    );

    Logger.sync(
      'Test with 1 api with 2 behaviours and an order',
      function () {
        check(
          [
            'isAlpha.2.action',
            'isAlpha.1.action'
          ],
          ao({
            'isAlpha': [ 'alpha.2', 'alpha.1' ]
          }),
          [
            behaviour('alpha.1', {
              'isAlpha': handler('isAlpha.1.action')
            }),
            behaviour('alpha.2', {
              'isAlpha': handler('isAlpha.2.action')
            })
          ]
        );
      }
    );

    Logger.sync(
      'Complex Test with 2 apis and 3 behaviours and missing notmiddle order so *incomplete*',
      function () {
        checkErr(
          'API call (notmiddle)',
          ao({
            'all': [ 'b1', 'b3', 'b2' ],
            'middle': [ 'b2' ]
          }),
          [
            behaviour('b1', {
              'all': handler('b1.all'),
              'notmiddle': handler('b1.notmiddle')
            }),
            behaviour('b2', {
              'all': handler('b2.all'),
              'middle': handler('b2.middle')
            }),
            behaviour('b3', {
              'all': handler('b3.all'),
              'notmiddle': handler('b3.notmiddle')
            })
          ]
        );
      }
    );

    Logger.sync(
      'Complex Test with 2 apis and 3 behaviours and missing middle order but it only has one',
      function () {
        check(
          [
            'b1.all',
            'b3.all',
            'b2.all',

            'b2.middle',

            'b3.notmiddle',
            'b1.notmiddle'
          ],
          ao({
            'all': [ 'b1', 'b3', 'b2' ],
            'notmiddle': [ 'b3', 'b1' ]
          }),
          [
            behaviour('b1', {
              'all': handler('b1.all'),
              'notmiddle': handler('b1.notmiddle')
            }),
            behaviour('b2', {
              'all': handler('b2.all'),
              'middle': handler('b2.middle')
            }),
            behaviour('b3', {
              'all': handler('b3.all'),
              'notmiddle': handler('b3.notmiddle')
            })
          ]
        );
      }
    );

    Logger.sync(
      'Complex Test with 2 apis and 3 behaviours and correct order',
      function () {
        check(
          [
            'b1.all',
            'b3.all',
            'b2.all',

            'b2.middle',

            'b3.notmiddle',
            'b1.notmiddle'
          ],
          ao({
            'all': [ 'b1', 'b3', 'b2' ],
            'middle': [ 'b2' ],
            'notmiddle': [ 'b3', 'b1' ]
          }),
          [
            behaviour('b1', {
              'all': handler('b1.all'),
              'notmiddle': handler('b1.notmiddle')
            }),
            behaviour('b2', {
              'all': handler('b2.all'),
              'middle': handler('b2.middle')
            }),
            behaviour('b3', {
              'all': handler('b3.all'),
              'notmiddle': handler('b3.notmiddle')
            })
          ]
        );
      }
    );
  }
);<|endoftext|>


<commit_before>/**
 * Copyright 2018-present Facebook.
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * @format
 */

import type {
  ImageId,
  ImageData,
  ImagesList,
  ImagesListResponse,
  ImageEvent,
  FrescoDebugOverlayEvent,
  AndroidCloseableReferenceLeakEvent,
  CacheInfo,
} from './api.js';
import type {ImagesMap} from './ImagePool.js';
import type {MetricType, MiddlewareAPI} from 'flipper';
import React from 'react';
import ImagesCacheOverview from './ImagesCacheOverview.js';
import {
  FlipperPlugin,
  FlexRow,
  Text,
  DetailSidebar,
  colors,
  styled,
  isProduction,
} from 'flipper';
import ImagesSidebar from './ImagesSidebar.js';
import ImagePool from './ImagePool.js';

export type ImageEventWithId = ImageEvent & {eventId: number};

type PersistedState = {
  surfaceList: Set<string>,
  images: ImagesList,
  events: Array<ImageEventWithId>,
  imagesMap: ImagesMap,
};

type PluginState = {
  selectedSurface: string,
  selectedImage: ?ImageId,
  isDebugOverlayEnabled: boolean,
  isAutoRefreshEnabled: boolean,
  images: ImagesList,
  coldStartFilter: boolean,
};

const EmptySidebar = styled(FlexRow)({
  alignItems: 'center',
  justifyContent: 'center',
  color: colors.light30,
  padding: 15,
  fontSize: 16,
});

const surfaceDefaultText = 'SELECT ALL SURFACES';

const debugLog = (...args) => {
  if (!isProduction()) {
    // eslint-disable-next-line no-console
    console.log(...args);
  }
};

type ImagesMetaData = {|
  levels: ImagesListResponse,
  events: Array<ImageEventWithId>,
  imageDataList: Array<ImageData>,
|};

export default class extends FlipperPlugin<PluginState, *, PersistedState> {
  static defaultPersistedState: PersistedState = {
    images: [],
    events: [],
    imagesMap: {},
    surfaceList: new Set(),
  };

  static exportPersistedState = (
    callClient: (string, ?Object) => Promise<Object>,
    persistedState: ?PersistedState,
    store: ?MiddlewareAPI,
  ): Promise<?PersistedState> => {
    if (persistedState) {
      return Promise.resolve(persistedState);
    }
    const defaultPromise = Promise.resolve(persistedState);
    if (!store) {
      return defaultPromise;
    }
    return callClient('getAllImageData').then((data: ImagesMetaData) => {
      if (!data) {
        return;
      }
      const {levels, events, imageDataList} = data;
      let pluginData: PersistedState = {
        images: [...levels.levels],
        surfaceList: new Set(),
        events: [],
        imagesMap: {},
      };

      events.forEach((event: ImageEventWithId, index) => {
        const {attribution} = event;
        if (attribution instanceof Array && attribution.length > 0) {
          const surface = attribution[0].trim();
          if (surface.length > 0) {
            pluginData.surfaceList.add(surface);
          }
        }
        pluginData = {
          ...pluginData,
          events: [{eventId: index, ...event}, ...pluginData.events],
        };
      });

      imageDataList.forEach((imageData: ImageData) => {
        const {imageId} = imageData;
        pluginData.imagesMap[imageId] = imageData;
      });
      return pluginData;
    });
  };

  static metricsReducer = (
    persistedState: PersistedState,
  ): Promise<MetricType> => {
    const {events, imagesMap} = persistedState;
    let wastedBytes = 0;
    events.forEach(event => {
      const {viewport, imageIds} = event;
      if (!viewport) {
        return;
      }
      imageIds.forEach(imageID => {
        const imageData: ImageData = imagesMap[imageID];
        if (!imageData) {
          return;
        }
        const imageWidth: number = imageData.width;
        const imageHeight: number = imageData.height;
        const viewPortWidth: number = viewport.width;
        const viewPortHeight: number = viewport.height;
        const viewPortArea = viewPortWidth * viewPortHeight;
        const imageArea = imageWidth * imageHeight;
        wastedBytes += Math.max(0, imageArea - viewPortArea);
      });
    });
    return Promise.resolve({WASTED_BYTES: wastedBytes});
  };

  state: PluginState;
  imagePool: ImagePool;
  nextEventId: number = 1;

  state = {
    selectedSurface: surfaceDefaultText,
    selectedImage: null,
    isDebugOverlayEnabled: false,
    isAutoRefreshEnabled: false,
    images: [],
    coldStartFilter: false,
  };

  filterImages = (
    images: ImagesList,
    events: Array<ImageEventWithId>,
    surface: string,
    coldStart: boolean,
  ): ImagesList => {
    if (!surface || (surface === surfaceDefaultText && !coldStart)) {
      return images;
    }
    const imageList = images.map((image: CacheInfo) => {
      const imageIdList = image.imageIds.filter(imageID => {
        const filteredEvents = events.filter((event: ImageEventWithId) => {
          const output =
            event.attribution &&
            event.attribution.length > 0 &&
            event.imageIds &&
            event.imageIds.includes(imageID);

          if (surface === surfaceDefaultText) {
            return output && coldStart && event.coldStart;
          }
          return (
            (!coldStart || (coldStart && event.coldStart)) &&
            output &&
            event.attribution[0] == surface
          );
        });
        return filteredEvents.length > 0;
      });
      return {...image, imageIds: imageIdList};
    });
    return imageList;
  };

  init() {
    debugLog('init()');
    this.updateCaches('init');
    this.client.subscribe('events', (event: ImageEvent) => {
      const {surfaceList} = this.props.persistedState;
      const {attribution} = event;
      if (attribution instanceof Array && attribution.length > 0) {
        const surface = attribution[0].trim();
        if (surface.length > 0) {
          surfaceList.add(surface);
        }
      }
      this.props.setPersistedState({
        events: [
          {eventId: this.nextEventId, ...event},
          ...this.props.persistedState.events,
        ],
      });
      this.nextEventId++;
    });
    this.client.subscribe(
      'debug_overlay_event',
      (event: FrescoDebugOverlayEvent) => {
        this.setState({isDebugOverlayEnabled: event.enabled});
      },
    );
    this.client.subscribe(
      'closeable_reference_leak_event',
      (event: AndroidCloseableReferenceLeakEvent) => {
        // TODO(T45065440): Temporary log, to be turned into counter.
        console.warn('CloseableReference leak detected:', event);
      },
    );
    this.imagePool = new ImagePool(this.getImage, (images: ImagesMap) =>
      this.props.setPersistedState({imagesMap: images}),
    );

    let images = this.filterImages(
      this.props.persistedState.images,
      this.props.persistedState.events,
      this.state.selectedSurface,
      this.state.coldStartFilter,
    );
    this.setState({images});
  }

  teardown() {
    this.imagePool.clear();
  }

  updateImagesOnUI = (
    images: ImagesList,
    surface: string,
    coldStart: boolean,
  ) => {
    const filteredImages = this.filterImages(
      images,
      this.props.persistedState.events,
      surface,
      coldStart,
    );
    this.setState({
      selectedSurface: surface,
      images: filteredImages,
      coldStartFilter: coldStart,
    });
  };
  updateCaches = (reason: string) => {
    debugLog('Requesting images list (reason=' + reason + ')');
    this.client.call('listImages').then((response: ImagesListResponse) => {
      response.levels.forEach(data =>
        this.imagePool.fetchImages(data.imageIds),
      );
      this.props.setPersistedState({images: response.levels});
      this.updateImagesOnUI(
        this.props.persistedState.images,
        this.state.selectedSurface,
        this.state.coldStartFilter,
      );
    });
  };

  onClear = (type: string) => {
    this.client.call('clear', {type});
    setTimeout(() => this.updateCaches('onClear'), 1000);
  };

  onTrimMemory = () => {
    this.client.call('trimMemory', {});
    setTimeout(() => this.updateCaches('onTrimMemory'), 1000);
  };

  onEnableDebugOverlay = (enabled: boolean) => {
    this.client.call('enableDebugOverlay', {enabled});
  };

  onEnableAutoRefresh = (enabled: boolean) => {
    this.setState({isAutoRefreshEnabled: enabled});
    if (enabled) {
      // Delay the call just enough to allow the state change to complete.
      setTimeout(() => this.onAutoRefresh());
    }
  };

  onAutoRefresh = () => {
    this.updateCaches('auto-refresh');
    if (this.state.isAutoRefreshEnabled) {
      setTimeout(() => this.onAutoRefresh(), 1000);
    }
  };

  getImage = (imageId: string) => {
    debugLog('<- getImage requested for ' + imageId);
    this.client.call('getImage', {imageId}).then((image: ImageData) => {
      debugLog('-> getImage ' + imageId + ' returned');
      this.imagePool._fetchCompleted(image);
    });
  };

  onImageSelected = (selectedImage: ImageId) => this.setState({selectedImage});

  renderSidebar = () => {
    const {selectedImage} = this.state;

    if (selectedImage == null) {
      return (
        <EmptySidebar grow={true}>
          <Text align="center">
            Select an image to see the events associated with it.
          </Text>
        </EmptySidebar>
      );
    }

    const maybeImage = this.props.persistedState.imagesMap[selectedImage];
    const events = this.props.persistedState.events.filter(e =>
      e.imageIds.includes(selectedImage),
    );
    return <ImagesSidebar image={maybeImage} events={events} />;
  };

  onSurfaceChange = (surface: string) => {
    this.updateImagesOnUI(
      this.props.persistedState.images,
      surface,
      this.state.coldStartFilter,
    );
  };

  onColdStartChange = (checked: boolean) => {
    this.updateImagesOnUI(
      this.props.persistedState.images,
      this.state.selectedSurface,
      checked,
    );
  };

  render() {
    const options = [...this.props.persistedState.surfaceList].reduce(
      (acc, item) => {
        return {...acc, [item]: item};
      },
      {[surfaceDefaultText]: surfaceDefaultText},
    );
    return (
      <React.Fragment>
        <ImagesCacheOverview
          surfaceOptions={options}
          selectedSurface={this.state.selectedSurface}
          onChangeSurface={this.onSurfaceChange}
          coldStartFilter={this.state.coldStartFilter}
          onColdStartChange={this.onColdStartChange}
          images={this.state.images}
          onClear={this.onClear}
          onTrimMemory={this.onTrimMemory}
          onRefresh={() => this.updateCaches('refresh')}
          onEnableDebugOverlay={this.onEnableDebugOverlay}
          onEnableAutoRefresh={this.onEnableAutoRefresh}
          isDebugOverlayEnabled={this.state.isDebugOverlayEnabled}
          isAutoRefreshEnabled={this.state.isAutoRefreshEnabled}
          onImageSelected={this.onImageSelected}
          imagesMap={this.props.persistedState.imagesMap}
          events={this.props.persistedState.events}
        />
        <DetailSidebar>{this.renderSidebar()}</DetailSidebar>
      </React.Fragment>
    );
  }
}
<commit_msg>Reduce in metrics reducer<commit_after>/**
 * Copyright 2018-present Facebook.
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * @format
 */

import type {
  ImageId,
  ImageData,
  ImagesList,
  ImagesListResponse,
  ImageEvent,
  FrescoDebugOverlayEvent,
  AndroidCloseableReferenceLeakEvent,
  CacheInfo,
} from './api.js';
import type {ImagesMap} from './ImagePool.js';
import type {MetricType, MiddlewareAPI} from 'flipper';
import React from 'react';
import ImagesCacheOverview from './ImagesCacheOverview.js';
import {
  FlipperPlugin,
  FlexRow,
  Text,
  DetailSidebar,
  colors,
  styled,
  isProduction,
} from 'flipper';
import ImagesSidebar from './ImagesSidebar.js';
import ImagePool from './ImagePool.js';

export type ImageEventWithId = ImageEvent & {eventId: number};

type PersistedState = {
  surfaceList: Set<string>,
  images: ImagesList,
  events: Array<ImageEventWithId>,
  imagesMap: ImagesMap,
};

type PluginState = {
  selectedSurface: string,
  selectedImage: ?ImageId,
  isDebugOverlayEnabled: boolean,
  isAutoRefreshEnabled: boolean,
  images: ImagesList,
  coldStartFilter: boolean,
};

const EmptySidebar = styled(FlexRow)({
  alignItems: 'center',
  justifyContent: 'center',
  color: colors.light30,
  padding: 15,
  fontSize: 16,
});

const surfaceDefaultText = 'SELECT ALL SURFACES';

const debugLog = (...args) => {
  if (!isProduction()) {
    // eslint-disable-next-line no-console
    console.log(...args);
  }
};

type ImagesMetaData = {|
  levels: ImagesListResponse,
  events: Array<ImageEventWithId>,
  imageDataList: Array<ImageData>,
|};

export default class extends FlipperPlugin<PluginState, *, PersistedState> {
  static defaultPersistedState: PersistedState = {
    images: [],
    events: [],
    imagesMap: {},
    surfaceList: new Set(),
  };

  static exportPersistedState = (
    callClient: (string, ?Object) => Promise<Object>,
    persistedState: ?PersistedState,
    store: ?MiddlewareAPI,
  ): Promise<?PersistedState> => {
    if (persistedState) {
      return Promise.resolve(persistedState);
    }
    const defaultPromise = Promise.resolve(persistedState);
    if (!store) {
      return defaultPromise;
    }
    return callClient('getAllImageData').then((data: ImagesMetaData) => {
      if (!data) {
        return;
      }
      const {levels, events, imageDataList} = data;
      let pluginData: PersistedState = {
        images: [...levels.levels],
        surfaceList: new Set(),
        events: [],
        imagesMap: {},
      };

      events.forEach((event: ImageEventWithId, index) => {
        const {attribution} = event;
        if (attribution instanceof Array && attribution.length > 0) {
          const surface = attribution[0].trim();
          if (surface.length > 0) {
            pluginData.surfaceList.add(surface);
          }
        }
        pluginData = {
          ...pluginData,
          events: [{eventId: index, ...event}, ...pluginData.events],
        };
      });

      imageDataList.forEach((imageData: ImageData) => {
        const {imageId} = imageData;
        pluginData.imagesMap[imageId] = imageData;
      });
      return pluginData;
    });
  };

  static metricsReducer = (
    persistedState: PersistedState,
  ): Promise<MetricType> => {
    const {events, imagesMap} = persistedState;
    let wastedBytes = events.reduce((acc, event) => {
      const {viewport, imageIds} = event;
      if (!viewport) {
        return acc;
      }
      return (
        acc +
        imageIds.reduce((innerAcc, imageID) => {
          const imageData: ImageData = imagesMap[imageID];
          if (!imageData) {
            return innerAcc;
          }
          const imageWidth: number = imageData.width;
          const imageHeight: number = imageData.height;
          const viewPortWidth: number = viewport.width;
          const viewPortHeight: number = viewport.height;
          const viewPortArea = viewPortWidth * viewPortHeight;
          const imageArea = imageWidth * imageHeight;
          return innerAcc + Math.max(0, imageArea - viewPortArea);
        }, acc)
      );
    }, 0);
    return Promise.resolve({WASTED_BYTES: wastedBytes});
  };

  state: PluginState;
  imagePool: ImagePool;
  nextEventId: number = 1;

  state = {
    selectedSurface: surfaceDefaultText,
    selectedImage: null,
    isDebugOverlayEnabled: false,
    isAutoRefreshEnabled: false,
    images: [],
    coldStartFilter: false,
  };

  filterImages = (
    images: ImagesList,
    events: Array<ImageEventWithId>,
    surface: string,
    coldStart: boolean,
  ): ImagesList => {
    if (!surface || (surface === surfaceDefaultText && !coldStart)) {
      return images;
    }
    const imageList = images.map((image: CacheInfo) => {
      const imageIdList = image.imageIds.filter(imageID => {
        const filteredEvents = events.filter((event: ImageEventWithId) => {
          const output =
            event.attribution &&
            event.attribution.length > 0 &&
            event.imageIds &&
            event.imageIds.includes(imageID);

          if (surface === surfaceDefaultText) {
            return output && coldStart && event.coldStart;
          }
          return (
            (!coldStart || (coldStart && event.coldStart)) &&
            output &&
            event.attribution[0] == surface
          );
        });
        return filteredEvents.length > 0;
      });
      return {...image, imageIds: imageIdList};
    });
    return imageList;
  };

  init() {
    debugLog('init()');
    this.updateCaches('init');
    this.client.subscribe('events', (event: ImageEvent) => {
      const {surfaceList} = this.props.persistedState;
      const {attribution} = event;
      if (attribution instanceof Array && attribution.length > 0) {
        const surface = attribution[0].trim();
        if (surface.length > 0) {
          surfaceList.add(surface);
        }
      }
      this.props.setPersistedState({
        events: [
          {eventId: this.nextEventId, ...event},
          ...this.props.persistedState.events,
        ],
      });
      this.nextEventId++;
    });
    this.client.subscribe(
      'debug_overlay_event',
      (event: FrescoDebugOverlayEvent) => {
        this.setState({isDebugOverlayEnabled: event.enabled});
      },
    );
    this.client.subscribe(
      'closeable_reference_leak_event',
      (event: AndroidCloseableReferenceLeakEvent) => {
        // TODO(T45065440): Temporary log, to be turned into counter.
        console.warn('CloseableReference leak detected:', event);
      },
    );
    this.imagePool = new ImagePool(this.getImage, (images: ImagesMap) =>
      this.props.setPersistedState({imagesMap: images}),
    );

    let images = this.filterImages(
      this.props.persistedState.images,
      this.props.persistedState.events,
      this.state.selectedSurface,
      this.state.coldStartFilter,
    );
    this.setState({images});
  }

  teardown() {
    this.imagePool.clear();
  }

  updateImagesOnUI = (
    images: ImagesList,
    surface: string,
    coldStart: boolean,
  ) => {
    const filteredImages = this.filterImages(
      images,
      this.props.persistedState.events,
      surface,
      coldStart,
    );
    this.setState({
      selectedSurface: surface,
      images: filteredImages,
      coldStartFilter: coldStart,
    });
  };
  updateCaches = (reason: string) => {
    debugLog('Requesting images list (reason=' + reason + ')');
    this.client.call('listImages').then((response: ImagesListResponse) => {
      response.levels.forEach(data =>
        this.imagePool.fetchImages(data.imageIds),
      );
      this.props.setPersistedState({images: response.levels});
      this.updateImagesOnUI(
        this.props.persistedState.images,
        this.state.selectedSurface,
        this.state.coldStartFilter,
      );
    });
  };

  onClear = (type: string) => {
    this.client.call('clear', {type});
    setTimeout(() => this.updateCaches('onClear'), 1000);
  };

  onTrimMemory = () => {
    this.client.call('trimMemory', {});
    setTimeout(() => this.updateCaches('onTrimMemory'), 1000);
  };

  onEnableDebugOverlay = (enabled: boolean) => {
    this.client.call('enableDebugOverlay', {enabled});
  };

  onEnableAutoRefresh = (enabled: boolean) => {
    this.setState({isAutoRefreshEnabled: enabled});
    if (enabled) {
      // Delay the call just enough to allow the state change to complete.
      setTimeout(() => this.onAutoRefresh());
    }
  };

  onAutoRefresh = () => {
    this.updateCaches('auto-refresh');
    if (this.state.isAutoRefreshEnabled) {
      setTimeout(() => this.onAutoRefresh(), 1000);
    }
  };

  getImage = (imageId: string) => {
    debugLog('<- getImage requested for ' + imageId);
    this.client.call('getImage', {imageId}).then((image: ImageData) => {
      debugLog('-> getImage ' + imageId + ' returned');
      this.imagePool._fetchCompleted(image);
    });
  };

  onImageSelected = (selectedImage: ImageId) => this.setState({selectedImage});

  renderSidebar = () => {
    const {selectedImage} = this.state;

    if (selectedImage == null) {
      return (
        <EmptySidebar grow={true}>
          <Text align="center">
            Select an image to see the events associated with it.
          </Text>
        </EmptySidebar>
      );
    }

    const maybeImage = this.props.persistedState.imagesMap[selectedImage];
    const events = this.props.persistedState.events.filter(e =>
      e.imageIds.includes(selectedImage),
    );
    return <ImagesSidebar image={maybeImage} events={events} />;
  };

  onSurfaceChange = (surface: string) => {
    this.updateImagesOnUI(
      this.props.persistedState.images,
      surface,
      this.state.coldStartFilter,
    );
  };

  onColdStartChange = (checked: boolean) => {
    this.updateImagesOnUI(
      this.props.persistedState.images,
      this.state.selectedSurface,
      checked,
    );
  };

  render() {
    const options = [...this.props.persistedState.surfaceList].reduce(
      (acc, item) => {
        return {...acc, [item]: item};
      },
      {[surfaceDefaultText]: surfaceDefaultText},
    );
    return (
      <React.Fragment>
        <ImagesCacheOverview
          surfaceOptions={options}
          selectedSurface={this.state.selectedSurface}
          onChangeSurface={this.onSurfaceChange}
          coldStartFilter={this.state.coldStartFilter}
          onColdStartChange={this.onColdStartChange}
          images={this.state.images}
          onClear={this.onClear}
          onTrimMemory={this.onTrimMemory}
          onRefresh={() => this.updateCaches('refresh')}
          onEnableDebugOverlay={this.onEnableDebugOverlay}
          onEnableAutoRefresh={this.onEnableAutoRefresh}
          isDebugOverlayEnabled={this.state.isDebugOverlayEnabled}
          isAutoRefreshEnabled={this.state.isAutoRefreshEnabled}
          onImageSelected={this.onImageSelected}
          imagesMap={this.props.persistedState.imagesMap}
          events={this.props.persistedState.events}
        />
        <DetailSidebar>{this.renderSidebar()}</DetailSidebar>
      </React.Fragment>
    );
  }
}
<|endoftext|>


<commit_before>'use strict';

var Backbone = require('backbone');
var _ = require('underscore');
var $ = Backbone.$ = require('jquery');

var fastdom = require('fastdom');

var Cache = require('../util/simple-cache');

var animEndEventNames = {
  'WebkitAnimation': 'webkitAnimationEnd',
  'OAnimation': 'oAnimationEnd',
  'msAnimation': 'MSAnimationEnd',
  'animation': 'animationend'
};

var outClass = 'layer page-scaleDown';
var inClass = 'layer page-moveFromRight page-ontop';

//var cssTweenEvent = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd';
var animEndEventName = animEndEventNames[window.Modernizr.prefixed('animation')];

var cache = new Cache();

var AppRouter = module.exports = Backbone.Router.extend({

  currentPage: null,

  currentRoute: 'index',

  routes: {
    'index.html': 'index',
    '': 'index',
    'about.html': 'about',
    'about': 'about'
  },

  initialize: function() {

    console.log(window.location.pathname);

    var ctx = this;

    this.on('route', this.onRoute.bind(this));

    $(document).on('click', 'a[href]', this.onRequestNavigation.bind(this));

  },

  onRoute: function(name) {
    this.currentRoute = name;
  },

  onRequestNavigation: function(event) {

    var link = event.currentTarget;

    if (link.tagName.toUpperCase() !== 'A') {
      throw "requires an anchor element";
    }

    var passThrough = link.hasAttribute('data-external-link');

    // Middle click, cmd click, and ctrl click should open
    // links in a new tab as normal.
    if (passThrough || event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
      return;
    }

    // Ignore cross origin links
    if (location.protocol !== link.protocol || location.hostname !== link.hostname) {
      return;
    }

    // Ignore anchors on the same page
    if (link.hash && link.href.replace(link.hash, '') === location.href.replace(location.hash, '')) {
      return;
    }

    // Ignore empty anchor "foo.html#"
    if (link.href === location.href + '#') {
      return;
    }

    // Ignore event with default prevented
    if (event.isDefaultPrevented()) {
      return;
    }

    event.preventDefault();

    var href = link.getAttribute('href');
    var url = href.replace(/^\//, '').replace('\#\!\/', '');

    this.navigate(url, {
      trigger: true,
      replace: false
    });

    return false;

  },

  showPage: function(url) {

    if (!cache.has(url)) {
      this.loadPage(url);
    } else {

      if (this.currentPage === cache.get(url).page) {
        return;
      }

      var ctx = this;

      var lastPage = ctx.currentPage ||  $('.page').first();
      var title = cache.get(url).title;
      ctx.currentPage = cache.get(url).page;

      if (ctx.currentPage.parent().length === 0) {
        ctx.currentPage.appendTo(document.body);
      }

      fastdom.write(function() {

        lastPage.on(animEndEventName, function(event) {
            $('head').find('title').text(title);
            $(this).off(animEndEventName).removeClass(outClass + ' page-current');
            // $(this).off(animEndEventName).removeClass(outClass + ' page-current').remove();
          })
          .addClass(outClass);

        ctx.currentPage.removeClass(outClass).addClass(inClass + ' page-current')
          .on(animEndEventName, function(event) {
            $(this).off(animEndEventName).removeClass(inClass);
          });

      });

    }
  },

  loadPage: function(url) {

    var ctx = this;

    $.ajax({
        url: url,
        processData: false,
        dataType: 'html',
        context: document.body,
      })
      .done(function(data) {
        var $content = $('<html>').append($.parseHTML(data, document, true));
        var page = $content.find('.page').first().clone();

        var nextPage = {
          page: page,
          title: $content.find('title').text()
        };

        cache.set(url, nextPage);
        ctx.showPage(url);

        $content = null;

      })
      .error(function(e) {
        console.error(e);
      });
  },

  index: function() {
    if (this.currentRoute !== 'index') {
      this.showPage('/index.html');
    }
  },

  about: function() {
    if (this.currentRoute !== 'about') {
      this.showPage('/about.html');
    }
  }

});
<commit_msg>mv " '<commit_after>'use strict';

var Backbone = require('backbone');
var _ = require('underscore');
var $ = Backbone.$ = require('jquery');

var fastdom = require('fastdom');

var Cache = require('../util/simple-cache');

var animEndEventNames = {
  'WebkitAnimation': 'webkitAnimationEnd',
  'OAnimation': 'oAnimationEnd',
  'msAnimation': 'MSAnimationEnd',
  'animation': 'animationend'
};

var outClass = 'layer page-scaleDown';
var inClass = 'layer page-moveFromRight page-ontop';

//var cssTweenEvent = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd';
var animEndEventName = animEndEventNames[window.Modernizr.prefixed('animation')];

var cache = new Cache();

var AppRouter = module.exports = Backbone.Router.extend({

  currentPage: null,

  currentRoute: 'index',

  routes: {
    'index.html': 'index',
    '': 'index',
    'about.html': 'about',
    'about': 'about'
  },

  initialize: function() {

    console.log(window.location.pathname);

    var ctx = this;

    this.on('route', this.onRoute.bind(this));

    $(document).on('click', 'a[href]', this.onRequestNavigation.bind(this));

  },

  onRoute: function(name) {
    this.currentRoute = name;
  },

  onRequestNavigation: function(event) {

    var link = event.currentTarget;

    if (link.tagName.toUpperCase() !== 'A') {
      throw 'requires an anchor element';
    }

    var passThrough = link.hasAttribute('data-external-link');

    // Middle click, cmd click, and ctrl click should open
    // links in a new tab as normal.
    if (passThrough || event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
      return;
    }

    // Ignore cross origin links
    if (location.protocol !== link.protocol || location.hostname !== link.hostname) {
      return;
    }

    // Ignore anchors on the same page
    if (link.hash && link.href.replace(link.hash, '') === location.href.replace(location.hash, '')) {
      return;
    }

    // Ignore empty anchor 'foo.html#'
    if (link.href === location.href + '#') {
      return;
    }

    // Ignore event with default prevented
    if (event.isDefaultPrevented()) {
      return;
    }

    event.preventDefault();

    var href = link.getAttribute('href');
    var url = href.replace(/^\//, '').replace('\#\!\/', '');

    this.navigate(url, {
      trigger: true,
      replace: false
    });

    return false;

  },

  showPage: function(url) {

    if (!cache.has(url)) {
      this.loadPage(url);
    } else {

      if (this.currentPage === cache.get(url).page) {
        return;
      }

      var ctx = this;

      var lastPage = ctx.currentPage ||  $('.page').first();
      var title = cache.get(url).title;
      ctx.currentPage = cache.get(url).page;

      if (ctx.currentPage.parent().length === 0) {
        ctx.currentPage.appendTo(document.body);
      }

      fastdom.write(function() {

        lastPage.on(animEndEventName, function(event) {
            $('head').find('title').text(title);
            $(this).off(animEndEventName).removeClass(outClass + ' page-current');
            // $(this).off(animEndEventName).removeClass(outClass + ' page-current').remove();
          })
          .addClass(outClass);

        ctx.currentPage.removeClass(outClass).addClass(inClass + ' page-current')
          .on(animEndEventName, function(event) {
            $(this).off(animEndEventName).removeClass(inClass);
          });

      });

    }
  },

  loadPage: function(url) {

    var ctx = this;

    $.ajax({
        url: url,
        processData: false,
        dataType: 'html',
        context: document.body,
      })
      .done(function(data) {
        var $content = $('<html>').append($.parseHTML(data, document, true));
        var page = $content.find('.page').first().clone();

        var nextPage = {
          page: page,
          title: $content.find('title').text()
        };

        cache.set(url, nextPage);
        ctx.showPage(url);

        $content = null;

      })
      .error(function(e) {
        console.error(e);
      });
  },

  index: function() {
    if (this.currentRoute !== 'index') {
      this.showPage('/index.html');
    }
  },

  about: function() {
    if (this.currentRoute !== 'about') {
      this.showPage('/about.html');
    }
  }

});
<|endoftext|>


<commit_before>import macro from 'vtk.js/Sources/macro';
import vtkCameraManipulator from 'vtk.js/Sources/Interaction/Manipulators/CameraManipulator';

const { vtkWarningMacro } = macro;
const DEFAULT_POSITION = { x: 0, y: 0 };

// ----------------------------------------------------------------------------
// vtkTrackballZoom methods
// ----------------------------------------------------------------------------

function vtkTrackballZoom(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkTrackballZoom');

  publicAPI.onButtonDown = (interactor) => {
    const size = interactor.getView().getSize();

    try {
      const { x, y } =
        interactor.getAnimationEventPosition(interactor.getPointerIndex()) ||
        DEFAULT_POSITION;
      const interactorStyle = interactor.getInteractorStyle();
      const renderer =
        interactorStyle.getCurrentRenderer() ||
        interactor.findPokedRenderer(x, y);
      const camera = renderer.getActiveCamera();
      if (camera.getParallelProjection()) {
        model.zoomScale = 1.5 / size[1];
      } else {
        const range = camera.getClippingRange();
        model.zoomScale = 1.5 * (range[1] / size[1]);
      }
    } catch (e) {
      vtkWarningMacro('Unable to set model.zoomScale');
    }
  };

  publicAPI.onAnimation = (interactor, renderer) => {
    const lastPtr = interactor.getPointerIndex();
    const pos = interactor.getAnimationEventPosition(lastPtr);
    const lastPos = interactor.getLastAnimationEventPosition(lastPtr);

    if (!pos || !lastPos || !renderer) {
      return;
    }

    const dy = lastPos.y - pos.y;
    const camera = renderer.getActiveCamera();

    if (camera.getParallelProjection()) {
      const k = dy * model.zoomScale;
      camera.setParallelScale((1.0 - k) * camera.getParallelScale());
    } else {
      const cameraPos = camera.getPosition();
      const cameraFp = camera.getFocalPoint();
      const norm = camera.getDirectionOfProjection();
      const k = dy * model.zoomScale;

      let tmp = k * norm[0];
      cameraPos[0] += tmp;
      cameraFp[0] += tmp;

      tmp = k * norm[1];
      cameraPos[1] += tmp;
      cameraFp[1] += tmp;

      tmp = k * norm[2];
      cameraPos[2] += tmp;
      cameraFp[2] += tmp;

      if (!camera.getFreezeFocalPoint()) {
        camera.setFocalPoint(cameraFp[0], cameraFp[1], cameraFp[2]);
      }

      camera.setPosition(cameraPos[0], cameraPos[1], cameraPos[2]);
      renderer.resetCameraClippingRange();
    }
  };
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  zoomScale: 0.0,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Inheritance
  vtkCameraManipulator.extend(publicAPI, model, initialValues);

  // Object specific methods
  vtkTrackballZoom(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkTrackballZoom');

// ----------------------------------------------------------------------------

export default Object.assign({ newInstance, extend });
<commit_msg>feat(TrackballZoom): allow zoom on pinch in manipulator<commit_after>import macro from 'vtk.js/Sources/macro';
import vtkCameraManipulator from 'vtk.js/Sources/Interaction/Manipulators/CameraManipulator';

const { vtkWarningMacro } = macro;
const DEFAULT_POSITION = { x: 0, y: 0 };

// ----------------------------------------------------------------------------
// vtkTrackballZoom methods
// ----------------------------------------------------------------------------

function vtkTrackballZoom(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkTrackballZoom');

  publicAPI.onButtonDown = (interactor) => {
    const size = interactor.getView().getSize();

    try {
      const { x, y } =
        interactor.getAnimationEventPosition(interactor.getPointerIndex()) ||
        DEFAULT_POSITION;
      const interactorStyle = interactor.getInteractorStyle();
      const renderer =
        interactorStyle.getCurrentRenderer() ||
        interactor.findPokedRenderer(x, y);
      const camera = renderer.getActiveCamera();
      if (camera.getParallelProjection()) {
        model.zoomScale = 1.5 / size[1];
      } else {
        const range = camera.getClippingRange();
        model.zoomScale = 1.5 * (range[1] / size[1]);
      }
    } catch (e) {
      vtkWarningMacro('Unable to set model.zoomScale');
    }
  };

  publicAPI.onAnimation = (interactor, renderer) => {
    const lastPtr = interactor.getPointerIndex();
    const pos = interactor.getAnimationEventPosition(lastPtr);
    const lastPos = interactor.getLastAnimationEventPosition(lastPtr);

    if (!pos || !lastPos || !renderer) {
      return;
    }

    const dy = lastPos.y - pos.y;
    const camera = renderer.getActiveCamera();

    if (camera.getParallelProjection()) {
      const k = dy * model.zoomScale;
      camera.setParallelScale((1.0 - k) * camera.getParallelScale());
    } else {
      const cameraPos = camera.getPosition();
      const cameraFp = camera.getFocalPoint();
      const norm = camera.getDirectionOfProjection();
      const k = dy * model.zoomScale;

      let tmp = k * norm[0];
      cameraPos[0] += tmp;
      cameraFp[0] += tmp;

      tmp = k * norm[1];
      cameraPos[1] += tmp;
      cameraFp[1] += tmp;

      tmp = k * norm[2];
      cameraPos[2] += tmp;
      cameraFp[2] += tmp;

      if (!camera.getFreezeFocalPoint()) {
        camera.setFocalPoint(cameraFp[0], cameraFp[1], cameraFp[2]);
      }

      camera.setPosition(cameraPos[0], cameraPos[1], cameraPos[2]);
      renderer.resetCameraClippingRange();
    }
  };

  publicAPI.onPinch = (interactor) => {
    const { x, y } =
      interactor.getAnimationEventPosition(interactor.getPointerIndex()) ||
      DEFAULT_POSITION;
    const interactorStyle = interactor.getInteractorStyle();
    const renderer =
      interactorStyle.getCurrentRenderer() ||
      interactor.findPokedRenderer(x, y);

    const camera = renderer.getActiveCamera();

    const dyf = interactor.getScale() / interactor.getLastScale();

    if (camera.getParallelProjection()) {
      camera.setParallelScale(camera.getParallelScale() / dyf);
    } else {
      camera.dolly(dyf);
      renderer.resetCameraClippingRange();
    }

    if (interactor.getLightFollowCamera()) {
      renderer.updateLightsGeometryToFollowCamera();
    }
  };
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  zoomScale: 0.0,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Inheritance
  vtkCameraManipulator.extend(publicAPI, model, initialValues);

  // Object specific methods
  vtkTrackballZoom(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkTrackballZoom');

// ----------------------------------------------------------------------------

export default Object.assign({ newInstance, extend });
<|endoftext|>


<commit_before>/*jshint browser:true, jquery:true */

var jenkinsHost = 'http://alembic.local:7000';

var APIURL = jenkinsHost +
        '/api/json?' +
        '&jsonp=?';

var app = {};

app.status = {};

var jsonpHelper = function(data) {
    return data;
};

var publishItem = function(i, item){

    $('
')
        .html('
' +
              '' +
              ' ' +
              item.name +
             '' +
              '
')
        .appendTo( '#list' );

    return true;
};

var conf = {
    job : 'example',
    host: 'http://alembic.local:7000'
};

var formatScriptTag = function(src){
    $.ajax({
        url: src,
        dataType: "jsonp"
    });
};

var makeBuildButton = function(selector, jobName, hostName){
    $(selector)
        .click(function(){

            var buildURL = hostName +
                    '/job/' +
                    jobName +
                    '/build?delay=0sec';

            $.ajax({
                url: buildURL,
                dataType: "jsonp"
            });

        });

};

var noisy_log = function(message){
    console.debug('[FILTER] ' + message);
};

var noisy_assert = function(expression, message){
    console.debug('[ASSERT] ' + message);
    console.assert(expression, message);
};

var updateWith = function(responseDocument){
    app.status.jenkinsAPIResponse = responseDocument;

    $('#list').html('');

    noisy_assert(_.isArray(responseDocument.jobs),
                 'I see a list of Jenkins jobs.');

    var rawList = _.
            reject(responseDocument.jobs,
                   function(item){
                       var input = item.color;
                       var result = (/^disabled$/).test(input);

                       noisy_log('Are any Jenkins jobs disabled?');

                       return result;
                   });

    $.each(rawList,
           publishItem);

    /* analysis */

    var widgets = $('#list li').length,
        passing = _.where(app.status.jenkinsAPIResponse.jobs, {color: "blue"}).length,
        failing = _.where(app.status.jenkinsAPIResponse.jobs, {color: "red"}).length;;

    $('p.description').html(widgets + ' jobs displayed.
' +
                            passing + ' succeeded,
' +
                            failing + ' falled.
');

};

var pollRemoteHost = function(){
    $.getJSON(APIURL, updateWith);
};

setInterval(pollRemoteHost, 500);
<commit_msg>remove superfluous old file<commit_after><|endoftext|>


<commit_before>/* @flow */
import React, { PureComponent } from 'react';
import { FlatList } from 'react-native';

import type { GlobalState, SubscriptionsState } from '../types';
import connectWithActions from '../connectWithActions';
import { Popup } from '../common';
import { getSubscribedStreams } from '../subscriptions/subscriptionSelectors';
import StreamItem from '../streams/StreamItem';

type Props = {
  filter: string,
  onAutocomplete: (name: string) => void,
  subscriptions: SubscriptionsState,
};

class StreamAutocomplete extends PureComponent<Props> {
  props: Props;

  render() {
    const { filter, subscriptions, onAutocomplete } = this.props;
    const streams = subscriptions.filter(x =>
      x.name.toLowerCase().startsWith(filter.toLowerCase()),
    );

    if (streams.length === 0) {
      return null;
    }

    return (
      <Popup>
        <FlatList
          keyboardShouldPersistTaps="always"
          initialNumToRender={streams.length}
          data={streams}
          keyExtractor={item => item.stream_id.toString()}
          renderItem={({ item }) => (
            <StreamItem
              name={item.name}
              isMuted={!item.in_home_view}
              isPrivate={item.invite_only}
              iconSize={12}
              color={item.color}
              onPress={() => onAutocomplete(`**${item.name}**`)}
            />
          )}
        />
      </Popup>
    );
  }
}

export default connectWithActions((state: GlobalState) => ({
  subscriptions: getSubscribedStreams(state),
}))(StreamAutocomplete);
<commit_msg>stream autocomplete: Use standard Redux dispatch.<commit_after>/* @flow */
import { connect } from 'react-redux';

import React, { PureComponent } from 'react';
import { FlatList } from 'react-native';

import type { GlobalState, SubscriptionsState } from '../types';
import { Popup } from '../common';
import { getSubscribedStreams } from '../subscriptions/subscriptionSelectors';
import StreamItem from '../streams/StreamItem';

type Props = {
  filter: string,
  onAutocomplete: (name: string) => void,
  subscriptions: SubscriptionsState,
};

class StreamAutocomplete extends PureComponent<Props> {
  props: Props;

  render() {
    const { filter, subscriptions, onAutocomplete } = this.props;
    const streams = subscriptions.filter(x =>
      x.name.toLowerCase().startsWith(filter.toLowerCase()),
    );

    if (streams.length === 0) {
      return null;
    }

    return (
      <Popup>
        <FlatList
          keyboardShouldPersistTaps="always"
          initialNumToRender={streams.length}
          data={streams}
          keyExtractor={item => item.stream_id.toString()}
          renderItem={({ item }) => (
            <StreamItem
              name={item.name}
              isMuted={!item.in_home_view}
              isPrivate={item.invite_only}
              iconSize={12}
              color={item.color}
              onPress={() => onAutocomplete(`**${item.name}**`)}
            />
          )}
        />
      </Popup>
    );
  }
}

export default connect((state: GlobalState) => ({
  subscriptions: getSubscribedStreams(state),
}))(StreamAutocomplete);
<|endoftext|>