<commit_before>/*global defineSuite*/
defineSuite([
         'Scene/GoogleEarthImageryProvider',
         'Core/DefaultProxy',
         'Core/FeatureDetection',
         'Core/jsonp',
         'Core/loadImage',
         'Scene/DiscardMissingTileImagePolicy',
         'Scene/Imagery',
         'Scene/ImageryLayer',
         'Scene/ImageryProvider',
         'Scene/ImageryState',
         'Scene/NeverTileDiscardPolicy',
         'Scene/WebMercatorTilingScheme',
         'ThirdParty/when'
     ], function(
         GoogleEarthImageryProvider,
         DefaultProxy,
         FeatureDetection,
         jsonp,
         loadImage,
         DiscardMissingTileImagePolicy,
         Imagery,
         ImageryLayer,
         ImageryProvider,
         ImageryState,
         NeverTileDiscardPolicy,
         WebMercatorTilingScheme,
         when) {
    "use strict";
    /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/

    afterEach(function() {
        jsonp.loadAndExecuteScript = jsonp.defaultLoadAndExecuteScript;
        loadImage.createImage = loadImage.defaultCreateImage;
    });

    it('conforms to ImageryProvider interface', function() {
        expect(GoogleEarthImageryProvider).toConformToInterface(ImageryProvider);
    });

    it('constructor throws when url is not specified', function() {
        function constructWithoutServer() {
            return new GoogleEarthImageryProvider({
              channel: 1234
            });
        }
        expect(constructWithoutServer).toThrow();
    });

    it('constructor throws when channel is not specified', function() {
        function constructWithoutServer() {
            return new GoogleEarthImageryProvider({
              url: 'http://foo.bar.net'
            });
        }
        expect(constructWithoutServer).toThrow();
    });

    it('can provide a root tile', function() {
        var url = 'http://fake.fake.net';
        var metadataUrl = url + '/query?request=Json&vars=geeServerDefs&is2d=t'
        var channel = 1234;

        jsonp.loadAndExecuteScript = function(url, functionName) {
            expect(url.indexOf(metadataUrl) === 0).toEqual(true);
            setTimeout(function() {
                window[functionName]({
                  "isAuthenticated": true,
                  "layers": [
                    {
                      "icon": "icons/773_l.png",
                      "id": 1234,
                      "initialState": true,
                      "isPng": false,
                      "label": "Imagery",
                      "lookAt": "none",
                      "opacity": 1,
                      "requestType": "ImageryMaps",
                      "version": 1
                    },{
                      "icon": "icons/773_l.png",
                      "id": 1007,
                      "initialState": true,
                      "isPng": true,
                      "label": "Labels",
                      "lookAt": "none",
                      "opacity": 1,
                      "requestType": "VectorMapsRaster",
                      "version": 8
                    }
                  ],
                  "serverUrl": "https://fake.com",
                  "useGoogleLayers": false
                });
            }, 1);
        };

        var provider = new GoogleEarthImageryProvider({
            url : url,
            channel: channel
        });

        expect(provider.getUrl()).toEqual(url);
        expect(provider.getChannel()).toEqual(channel);

        waitsFor(function() {
            return provider.isReady();
        }, 'imagery provider to become ready');

        var tile000Image;

        runs(function() {
            expect(provider.getTileWidth()).toEqual(256);
            expect(provider.getTileHeight()).toEqual(256);
            expect(provider.getMaximumLevel()).toEqual(23);
            expect(provider.getTilingScheme()).toBeInstanceOf(WebMercatorTilingScheme);
            expect(provider.getTileDiscardPolicy()).toBeInstanceOf(DiscardMissingTileImagePolicy);
            expect(provider.getExtent()).toEqual(new WebMercatorTilingScheme().getExtent());
        });

        waitsFor(function() {
            return typeof provider.getLogo() !== 'undefined';
        }, 'logo to become ready');

        runs(function() {
            expect(provider.getLogo()).toBeInstanceOf(Image);

            loadImage.createImage = function(url, crossOrigin, deferred) {
                expect(url).toEqual('http://fake.fake.net/query?request=ImageryMaps&channel=1234&version=1&x=0&y=0&z=1');

                // Just return any old image.
                return loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
            };

            when(provider.requestImage(0, 0, 0), function(image) {
                tile000Image = image;
            });
        });

        waitsFor(function() {
            return typeof tile000Image !== 'undefined';
        }, 'requested tile to be loaded');

        runs(function() {
            expect(tile000Image).toBeInstanceOf(Image);
        });
    });

    it('routes requests through a proxy if one is specified', function() {
        var url = 'http://foo.bar.net';
        var metadataUrl = url + '/query?request=Json&vars=geeServerDefs&is2d=t'
        var proxy = new DefaultProxy('/proxy/');

        jsonp.loadAndExecuteScript = function(url, functionName) {
            expect(url.indexOf(proxy.getURL(metadataUrl)) === 0).toEqual(true);
            setTimeout(function() {
                window[functionName]({
                  "isAuthenticated": true,
                  "layers": [
                    {
                      "icon": "icons/773_l.png",
                      "id": 1234,
                      "initialState": true,
                      "isPng": false,
                      "label": "Imagery",
                      "lookAt": "none",
                      "opacity": 1,
                      "requestType": "ImageryMaps",
                      "version": 1
                    },{
                      "icon": "icons/773_l.png",
                      "id": 1007,
                      "initialState": true,
                      "isPng": true,
                      "label": "Labels",
                      "lookAt": "none",
                      "opacity": 1,
                      "requestType": "VectorMapsRaster",
                      "version": 8
                    }
                  ],
                  "serverUrl": "https://fake.com",
                  "useGoogleLayers": false
                });
            }, 1);
        };

        var provider = new GoogleEarthImageryProvider({
            url : 'http://foo.bar.net',
            channel: 1234,
            proxy : proxy
        });

        expect(provider.getUrl()).toEqual(url);
        expect(provider.getProxy()).toEqual(proxy);

        waitsFor(function() {
            return provider.isReady();
        }, 'imagery provider to become ready');

        var tile000Image;

        runs(function() {
            loadImage.createImage = function(url, crossOrigin, deferred) {
                expect(url).toEqual(proxy.getURL('http://foo.bar.net/query?request=ImageryMaps&channel=1234&version=1&x=0&y=0&z=1'));

                // Just return any old image.
                return loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
            };

            when(provider.requestImage(0, 0, 0), function(image) {
                tile000Image = image;
            });
        });

        waitsFor(function() {
            return typeof tile000Image !== 'undefined';
        }, 'requested tile to be loaded');

        runs(function() {
            expect(tile000Image).toBeInstanceOf(Image);
        });
    });

    it('raises error on invalid url', function() {
        var url = 'invalid.localhost';
        var provider = new GoogleEarthImageryProvider({
            url : url,
            channel: 1234
        });

        var errorEventRaised = false;
        provider.getErrorEvent().addEventListener(function(error) {
            expect(error.message.indexOf(url) >= 0).toEqual(true);
            errorEventRaised = true;
        });

        waitsFor(function() {
            return provider.isReady() || errorEventRaised;
        }, 'imagery provider to become ready or raise error event');

        runs(function() {
            expect(provider.isReady()).toEqual(false);
            expect(errorEventRaised).toEqual(true);
        });
    });

    it('raises error event when image cannot be loaded', function() {
        jsonp.loadAndExecuteScript = function(url, functionName) {
            window[functionName]({
              "isAuthenticated": true,
              "layers": [
                {
                  "icon": "icons/773_l.png",
                  "id": 1234,
                  "initialState": true,
                  "isPng": false,
                  "label": "Imagery",
                  "lookAt": "none",
                  "opacity": 1,
                  "requestType": "ImageryMaps",
                  "version": 1
                },{
                  "icon": "icons/773_l.png",
                  "id": 1007,
                  "initialState": true,
                  "isPng": true,
                  "label": "Labels",
                  "lookAt": "none",
                  "opacity": 1,
                  "requestType": "VectorMapsRaster",
                  "version": 8
                }
              ],
              "serverUrl": "https://fake.com",
              "useGoogleLayers": false
            });
        };

        var provider = new GoogleEarthImageryProvider({
            url : 'invalid.localhost',
            channel: 1234
        });

        var layer = new ImageryLayer(provider);

        var tries = 0;
        provider.getErrorEvent().addEventListener(function(error) {
            expect(error.timesRetried).toEqual(tries);
            ++tries;
            if (tries < 3) {
                error.retry = true;
            }
        });

        loadImage.createImage = function(url, crossOrigin, deferred) {
            // Succeed after 2 tries
            if (tries === 2) {
                // valid URL
                return loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
            }

            // invalid URL
            return loadImage.defaultCreateImage(url, crossOrigin, deferred);
        };

        waitsFor(function() {
            return provider.isReady();
        }, 'imagery provider to become ready');

        var imagery;
        runs(function() {
            imagery = new Imagery(layer, 0, 0, 0);
            imagery.addReference();
            layer._requestImagery(imagery);
        });

        waitsFor(function() {
            return imagery.state === ImageryState.RECEIVED;
        }, 'image to load');

        runs(function() {
            expect(imagery.image).toBeInstanceOf(Image);
            expect(tries).toEqual(2);
            imagery.releaseReference();
        });
    });
});
<commit_msg>Added missing semicolons<commit_after>/*global defineSuite*/
defineSuite([
         'Scene/GoogleEarthImageryProvider',
         'Core/DefaultProxy',
         'Core/FeatureDetection',
         'Core/jsonp',
         'Core/loadImage',
         'Scene/DiscardMissingTileImagePolicy',
         'Scene/Imagery',
         'Scene/ImageryLayer',
         'Scene/ImageryProvider',
         'Scene/ImageryState',
         'Scene/NeverTileDiscardPolicy',
         'Scene/WebMercatorTilingScheme',
         'ThirdParty/when'
     ], function(
         GoogleEarthImageryProvider,
         DefaultProxy,
         FeatureDetection,
         jsonp,
         loadImage,
         DiscardMissingTileImagePolicy,
         Imagery,
         ImageryLayer,
         ImageryProvider,
         ImageryState,
         NeverTileDiscardPolicy,
         WebMercatorTilingScheme,
         when) {
    "use strict";
    /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/

    afterEach(function() {
        jsonp.loadAndExecuteScript = jsonp.defaultLoadAndExecuteScript;
        loadImage.createImage = loadImage.defaultCreateImage;
    });

    it('conforms to ImageryProvider interface', function() {
        expect(GoogleEarthImageryProvider).toConformToInterface(ImageryProvider);
    });

    it('constructor throws when url is not specified', function() {
        function constructWithoutServer() {
            return new GoogleEarthImageryProvider({
              channel: 1234
            });
        }
        expect(constructWithoutServer).toThrow();
    });

    it('constructor throws when channel is not specified', function() {
        function constructWithoutServer() {
            return new GoogleEarthImageryProvider({
              url: 'http://foo.bar.net'
            });
        }
        expect(constructWithoutServer).toThrow();
    });

    it('can provide a root tile', function() {
        var url = 'http://fake.fake.net';
        var metadataUrl = url + '/query?request=Json&vars=geeServerDefs&is2d=t';
        var channel = 1234;

        jsonp.loadAndExecuteScript = function(url, functionName) {
            expect(url.indexOf(metadataUrl) === 0).toEqual(true);
            setTimeout(function() {
                window[functionName]({
                  "isAuthenticated": true,
                  "layers": [
                    {
                      "icon": "icons/773_l.png",
                      "id": 1234,
                      "initialState": true,
                      "isPng": false,
                      "label": "Imagery",
                      "lookAt": "none",
                      "opacity": 1,
                      "requestType": "ImageryMaps",
                      "version": 1
                    },{
                      "icon": "icons/773_l.png",
                      "id": 1007,
                      "initialState": true,
                      "isPng": true,
                      "label": "Labels",
                      "lookAt": "none",
                      "opacity": 1,
                      "requestType": "VectorMapsRaster",
                      "version": 8
                    }
                  ],
                  "serverUrl": "https://fake.com",
                  "useGoogleLayers": false
                });
            }, 1);
        };

        var provider = new GoogleEarthImageryProvider({
            url : url,
            channel: channel
        });

        expect(provider.getUrl()).toEqual(url);
        expect(provider.getChannel()).toEqual(channel);

        waitsFor(function() {
            return provider.isReady();
        }, 'imagery provider to become ready');

        var tile000Image;

        runs(function() {
            expect(provider.getTileWidth()).toEqual(256);
            expect(provider.getTileHeight()).toEqual(256);
            expect(provider.getMaximumLevel()).toEqual(23);
            expect(provider.getTilingScheme()).toBeInstanceOf(WebMercatorTilingScheme);
            expect(provider.getTileDiscardPolicy()).toBeInstanceOf(DiscardMissingTileImagePolicy);
            expect(provider.getExtent()).toEqual(new WebMercatorTilingScheme().getExtent());
        });

        waitsFor(function() {
            return typeof provider.getLogo() !== 'undefined';
        }, 'logo to become ready');

        runs(function() {
            expect(provider.getLogo()).toBeInstanceOf(Image);

            loadImage.createImage = function(url, crossOrigin, deferred) {
                expect(url).toEqual('http://fake.fake.net/query?request=ImageryMaps&channel=1234&version=1&x=0&y=0&z=1');

                // Just return any old image.
                return loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
            };

            when(provider.requestImage(0, 0, 0), function(image) {
                tile000Image = image;
            });
        });

        waitsFor(function() {
            return typeof tile000Image !== 'undefined';
        }, 'requested tile to be loaded');

        runs(function() {
            expect(tile000Image).toBeInstanceOf(Image);
        });
    });

    it('routes requests through a proxy if one is specified', function() {
        var url = 'http://foo.bar.net';
        var metadataUrl = url + '/query?request=Json&vars=geeServerDefs&is2d=t';
        var proxy = new DefaultProxy('/proxy/');

        jsonp.loadAndExecuteScript = function(url, functionName) {
            expect(url.indexOf(proxy.getURL(metadataUrl)) === 0).toEqual(true);
            setTimeout(function() {
                window[functionName]({
                  "isAuthenticated": true,
                  "layers": [
                    {
                      "icon": "icons/773_l.png",
                      "id": 1234,
                      "initialState": true,
                      "isPng": false,
                      "label": "Imagery",
                      "lookAt": "none",
                      "opacity": 1,
                      "requestType": "ImageryMaps",
                      "version": 1
                    },{
                      "icon": "icons/773_l.png",
                      "id": 1007,
                      "initialState": true,
                      "isPng": true,
                      "label": "Labels",
                      "lookAt": "none",
                      "opacity": 1,
                      "requestType": "VectorMapsRaster",
                      "version": 8
                    }
                  ],
                  "serverUrl": "https://fake.com",
                  "useGoogleLayers": false
                });
            }, 1);
        };

        var provider = new GoogleEarthImageryProvider({
            url : 'http://foo.bar.net',
            channel: 1234,
            proxy : proxy
        });

        expect(provider.getUrl()).toEqual(url);
        expect(provider.getProxy()).toEqual(proxy);

        waitsFor(function() {
            return provider.isReady();
        }, 'imagery provider to become ready');

        var tile000Image;

        runs(function() {
            loadImage.createImage = function(url, crossOrigin, deferred) {
                expect(url).toEqual(proxy.getURL('http://foo.bar.net/query?request=ImageryMaps&channel=1234&version=1&x=0&y=0&z=1'));

                // Just return any old image.
                return loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
            };

            when(provider.requestImage(0, 0, 0), function(image) {
                tile000Image = image;
            });
        });

        waitsFor(function() {
            return typeof tile000Image !== 'undefined';
        }, 'requested tile to be loaded');

        runs(function() {
            expect(tile000Image).toBeInstanceOf(Image);
        });
    });

    it('raises error on invalid url', function() {
        var url = 'invalid.localhost';
        var provider = new GoogleEarthImageryProvider({
            url : url,
            channel: 1234
        });

        var errorEventRaised = false;
        provider.getErrorEvent().addEventListener(function(error) {
            expect(error.message.indexOf(url) >= 0).toEqual(true);
            errorEventRaised = true;
        });

        waitsFor(function() {
            return provider.isReady() || errorEventRaised;
        }, 'imagery provider to become ready or raise error event');

        runs(function() {
            expect(provider.isReady()).toEqual(false);
            expect(errorEventRaised).toEqual(true);
        });
    });

    it('raises error event when image cannot be loaded', function() {
        jsonp.loadAndExecuteScript = function(url, functionName) {
            window[functionName]({
              "isAuthenticated": true,
              "layers": [
                {
                  "icon": "icons/773_l.png",
                  "id": 1234,
                  "initialState": true,
                  "isPng": false,
                  "label": "Imagery",
                  "lookAt": "none",
                  "opacity": 1,
                  "requestType": "ImageryMaps",
                  "version": 1
                },{
                  "icon": "icons/773_l.png",
                  "id": 1007,
                  "initialState": true,
                  "isPng": true,
                  "label": "Labels",
                  "lookAt": "none",
                  "opacity": 1,
                  "requestType": "VectorMapsRaster",
                  "version": 8
                }
              ],
              "serverUrl": "https://fake.com",
              "useGoogleLayers": false
            });
        };

        var provider = new GoogleEarthImageryProvider({
            url : 'invalid.localhost',
            channel: 1234
        });

        var layer = new ImageryLayer(provider);

        var tries = 0;
        provider.getErrorEvent().addEventListener(function(error) {
            expect(error.timesRetried).toEqual(tries);
            ++tries;
            if (tries < 3) {
                error.retry = true;
            }
        });

        loadImage.createImage = function(url, crossOrigin, deferred) {
            // Succeed after 2 tries
            if (tries === 2) {
                // valid URL
                return loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
            }

            // invalid URL
            return loadImage.defaultCreateImage(url, crossOrigin, deferred);
        };

        waitsFor(function() {
            return provider.isReady();
        }, 'imagery provider to become ready');

        var imagery;
        runs(function() {
            imagery = new Imagery(layer, 0, 0, 0);
            imagery.addReference();
            layer._requestImagery(imagery);
        });

        waitsFor(function() {
            return imagery.state === ImageryState.RECEIVED;
        }, 'image to load');

        runs(function() {
            expect(imagery.image).toBeInstanceOf(Image);
            expect(tries).toEqual(2);
            imagery.releaseReference();
        });
    });
});
<|endoftext|>


<commit_before>/**
 * angular-permission
 * Route permission and access control as simple as it can get
 * @version v0.2.0 - 2015-03-02
 * @link http://www.rafaelvidaurre.com
 * @author Rafael Vidaurre <narzerus@gmail.com>
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */

(function () {
  'use strict';

  angular.module('permission', ['ui.router'])
    .run(['$rootScope', 'Permission', '$state', function ($rootScope, Permission, $state) {
      $rootScope.$on('$stateChangeStart',
      function (event, toState, toParams, fromState, fromParams) {
        if (toState.$$finishAuthorize) {
          return;
        }

        // If there are permissions set then prevent default and attempt to authorize
        var permissions;
        if (toState.data && toState.data.permissions) {
          permissions = toState.data.permissions;
        } else if (toState.permissions) {
          /**
          * This way of defining permissions will be depracated in v1. Should use
          * `data` key instead
          */
          console.log('Deprecation Warning: permissions should be set inside the `data` key ');
          console.log('Setting permissions for a state outside `data` will be depracated in' +
            ' version 1');
          permissions = toState.permissions;
        }

        if (permissions) {
          event.preventDefault();
          toState = angular.extend({'$$finishAuthorize': true}, toState);

          Permission.authorize(permissions, toParams).then(function () {
            // If authorized, use call state.go without triggering the event.
            // Then trigger $stateChangeSuccess manually to resume the rest of the process
            // Note: This is a pseudo-hacky fix which should be fixed in future ui-router versions
            if (!$rootScope.$broadcast('$stateChangeStart', toState, toParams, fromState, fromParams).defaultPrevented) {
              $rootScope.$broadcast('$stateChangePermissionAccepted', toState, toParams);

              $state.go(toState.name, toParams, {notify: false}).then(function() {
                $rootScope
                  .$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
              });
            }
          }, function () {
            if (!$rootScope.$broadcast('$stateChangeStart', toState, toParams, fromState, fromParams).defaultPrevented) {
              $rootScope.$broadcast('$stateChangePermissionDenied', toState, toParams);

              // If not authorized, redirect to wherever the route has defined, if defined at all
              var redirectTo = permissions.redirectTo;
              if (redirectTo) {
                $state.go(redirectTo, toParams);
              }
            }
          });
        }
      });
    }]);
}());

(function () {
  'use strict';

  angular.module('permission')
    .provider('Permission', function () {
      var roleValidationConfig = {};
      var validateRoleDefinitionParams = function (roleName, validationFunction) {
        if (!angular.isString(roleName)) {
          throw new Error('Role name must be a string');
        }
        if (!angular.isFunction(validationFunction)) {
          throw new Error('Validation function not provided correctly');
        }
      };

      this.defineRole = function (roleName, validationFunction) {
        /**
          This method is only available in config-time, and cannot access services, as they are
          not yet injected anywere which makes this kinda useless.
          Should remove if we cannot find a use for it.
        **/
        validateRoleDefinitionParams(roleName, validationFunction);
        roleValidationConfig[roleName] = validationFunction;

        return this;
      };

      this.$get = ['$q', function ($q) {
        var Permission = {
          _promiseify: function (value) {
            /**
              Converts a value into a promise, if the value is truthy it resolves it, otherwise
              it rejects it
            **/
            if (value && angular.isFunction(value.then)) {
              return value;
            }

            var deferred = $q.defer();
            if (value) {
              deferred.resolve();
            } else {
              deferred.reject();
            }
            return deferred.promise;
          },
          _validateRoleMap: function (roleMap) {
            if (typeof(roleMap) !== 'object' || roleMap instanceof Array) {
              throw new Error('Role map has to be an object');
            }
            if (roleMap.only === undefined && roleMap.except === undefined) {
              throw new Error('Either "only" or "except" keys must me defined');
            }
            if (roleMap.only) {
              if (!(roleMap.only instanceof Array)) {
                throw new Error('Array of roles expected');
              }
            } else if (roleMap.except) {
              if (!(roleMap.except instanceof Array)) {
                throw new Error('Array of roles expected');
              }
            }
          },
          _findMatchingRole: function (rolesArray, toParams) {
            var roles = angular.copy(rolesArray);
            var deferred = $q.defer();
            var currentRole = roles.shift();

            // If no roles left to validate reject promise
            if (!currentRole) {
              deferred.reject();
              return deferred.promise;
            }
            // Validate role definition exists
            if (!angular.isFunction(Permission.roleValidations[currentRole])) {
              throw new Error('undefined role or invalid role validation');
            }

            var validatingRole = Permission.roleValidations[currentRole](toParams);
            validatingRole = Permission._promiseify(validatingRole);

            validatingRole.then(function () {
              deferred.resolve();
            }, function () {
              Permission._findMatchingRole(roles, toParams).then(function () {
                deferred.resolve();
              }, function () {
                deferred.reject();
              });
            });

            return deferred.promise;
          },
          defineRole: function (roleName, validationFunction) {
            /**
              Service-available version of defineRole, the callback passed here lives in the
              scope where it is defined and therefore can interact with other modules
            **/
            validateRoleDefinitionParams(roleName, validationFunction);
            roleValidationConfig[roleName] = validationFunction;

            return Permission;
          },
          resolveIfMatch: function (rolesArray, toParams) {
            var roles = angular.copy(rolesArray);
            var deferred = $q.defer();
            Permission._findMatchingRole(roles, toParams).then(function () {
              // Found role match
              deferred.resolve();
            }, function () {
              // No match
              deferred.reject();
            });
            return deferred.promise;
          },
          rejectIfMatch: function (roles, toParams) {
            var deferred = $q.defer();
            Permission._findMatchingRole(roles, toParams).then(function () {
              // Role found
              deferred.reject();
            }, function () {
              // Role not found
              deferred.resolve();
            });
            return deferred.promise;
          },
          roleValidations: roleValidationConfig,
          authorize: function (roleMap, toParams) {
            // Validate input
            Permission._validateRoleMap(roleMap);

            var authorizing;

            if (roleMap.only) {
              authorizing = Permission.resolveIfMatch(roleMap.only, toParams);
            } else {
              authorizing = Permission.rejectIfMatch(roleMap.except, toParams);
            }

            return authorizing;
          }
        };

        return Permission;
      }];
    });

}());
<commit_msg>reject passing redirect state when function is calling<commit_after>/**
 * angular-permission
 * Route permission and access control as simple as it can get
 * @version v0.2.0 - 2015-03-02
 * @link http://www.rafaelvidaurre.com
 * @author Rafael Vidaurre <narzerus@gmail.com>
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */

(function () {
  'use strict';

  angular.module('permission', ['ui.router'])
    .run(['$rootScope', 'Permission', '$state', function ($rootScope, Permission, $state) {
      $rootScope.$on('$stateChangeStart',
      function (event, toState, toParams, fromState, fromParams) {
        if (toState.$$finishAuthorize) {
          return;
        }

        // If there are permissions set then prevent default and attempt to authorize
        var permissions;
        if (toState.data && toState.data.permissions) {
          permissions = toState.data.permissions;
        } else if (toState.permissions) {
          /**
          * This way of defining permissions will be depracated in v1. Should use
          * `data` key instead
          */
          console.log('Deprecation Warning: permissions should be set inside the `data` key ');
          console.log('Setting permissions for a state outside `data` will be depracated in' +
            ' version 1');
          permissions = toState.permissions;
        }

        if (permissions) {
          event.preventDefault();
          toState = angular.extend({'$$finishAuthorize': true}, toState);

          Permission.authorize(permissions, toParams).then(function () {
            // If authorized, use call state.go without triggering the event.
            // Then trigger $stateChangeSuccess manually to resume the rest of the process
            // Note: This is a pseudo-hacky fix which should be fixed in future ui-router versions
            if (!$rootScope.$broadcast('$stateChangeStart', toState, toParams, fromState, fromParams).defaultPrevented) {
              $rootScope.$broadcast('$stateChangePermissionAccepted', toState, toParams);

              $state.go(toState.name, toParams, {notify: false}).then(function() {
                $rootScope
                  .$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
              });
            }
          }, function (reject) {
            if (!$rootScope.$broadcast('$stateChangeStart', toState, toParams, fromState, fromParams).defaultPrevented) {
              $rootScope.$broadcast('$stateChangePermissionDenied', toState, toParams);

              // If not authorized, redirect to wherever the route has defined, if defined at all
              var redirectTo = permissions.redirectTo;
              if (angular.isString(redirectTo)) {
                $state.go(redirectTo, toParams);
              }

              if (angular.isFunction(redirectTo)) {
                $state.go(redirectTo(redirectTo(reject), toParams);
              }
            }
          });
        }
      });
    }]);
}());

(function () {
  'use strict';

  angular.module('permission')
    .provider('Permission', function () {
      var roleValidationConfig = {};
      var validateRoleDefinitionParams = function (roleName, validationFunction) {
        if (!angular.isString(roleName)) {
          throw new Error('Role name must be a string');
        }
        if (!angular.isFunction(validationFunction)) {
          throw new Error('Validation function not provided correctly');
        }
      };

      this.defineRole = function (roleName, validationFunction) {
        /**
          This method is only available in config-time, and cannot access services, as they are
          not yet injected anywere which makes this kinda useless.
          Should remove if we cannot find a use for it.
        **/
        validateRoleDefinitionParams(roleName, validationFunction);
        roleValidationConfig[roleName] = validationFunction;

        return this;
      };

      this.$get = ['$q', function ($q) {
        var Permission = {
          _promiseify: function (value) {
            /**
              Converts a value into a promise, if the value is truthy it resolves it, otherwise
              it rejects it
            **/
            if (value && angular.isFunction(value.then)) {
              return value;
            }

            var deferred = $q.defer();
            if (value) {
              deferred.resolve();
            } else {
              deferred.reject();
            }
            return deferred.promise;
          },
          _validateRoleMap: function (roleMap) {
            if (typeof(roleMap) !== 'object' || roleMap instanceof Array) {
              throw new Error('Role map has to be an object');
            }
            if (roleMap.only === undefined && roleMap.except === undefined) {
              throw new Error('Either "only" or "except" keys must me defined');
            }
            if (roleMap.only) {
              if (!(roleMap.only instanceof Array)) {
                throw new Error('Array of roles expected');
              }
            } else if (roleMap.except) {
              if (!(roleMap.except instanceof Array)) {
                throw new Error('Array of roles expected');
              }
            }
          },
          _findMatchingRole: function (rolesArray, toParams) {
            var roles = angular.copy(rolesArray);
            var deferred = $q.defer();
            var currentRole = roles.shift();

            // If no roles left to validate reject promise
            if (!currentRole) {
              deferred.reject();
              return deferred.promise;
            }
            // Validate role definition exists
            if (!angular.isFunction(Permission.roleValidations[currentRole])) {
              throw new Error('undefined role or invalid role validation');
            }

            var validatingRole = Permission.roleValidations[currentRole](toParams);
            validatingRole = Permission._promiseify(validatingRole);

            validatingRole.then(function () {
              deferred.resolve();
            }, function () {
              Permission._findMatchingRole(roles, toParams).then(function () {
                deferred.resolve();
              }, function () {
                deferred.reject();
              });
            });

            return deferred.promise;
          },
          defineRole: function (roleName, validationFunction) {
            /**
              Service-available version of defineRole, the callback passed here lives in the
              scope where it is defined and therefore can interact with other modules
            **/
            validateRoleDefinitionParams(roleName, validationFunction);
            roleValidationConfig[roleName] = validationFunction;

            return Permission;
          },
          resolveIfMatch: function (rolesArray, toParams) {
            var roles = angular.copy(rolesArray);
            var deferred = $q.defer();
            Permission._findMatchingRole(roles, toParams).then(function () {
              // Found role match
              deferred.resolve();
            }, function (reject) {
              // No match
              deferred.reject(reject);
            });
            return deferred.promise;
          },
          rejectIfMatch: function (roles, toParams) {
            var deferred = $q.defer();
            Permission._findMatchingRole(roles, toParams).then(function () {
              // Role found
              deferred.reject();
            }, function () {
              // Role not found
              deferred.resolve();
            });
            return deferred.promise;
          },
          roleValidations: roleValidationConfig,
          authorize: function (roleMap, toParams) {
            // Validate input
            Permission._validateRoleMap(roleMap);

            var authorizing;

            if (roleMap.only) {
              authorizing = Permission.resolveIfMatch(roleMap.only, toParams);
            } else {
              authorizing = Permission.rejectIfMatch(roleMap.except, toParams);
            }

            return authorizing;
          }
        };

        return Permission;
      }];
    });

}());
<|endoftext|>


<commit_before>import React, { Component } from 'react';

import {
	addComponent,
	removeComponent,
	watchComponent,
	updateQuery,
	setQueryListener,
	setQueryOptions,
	setComponentProps,
	updateComponentProps,
} from '@appbaseio/reactivecore/lib/actions';
import hoistNonReactStatics from 'hoist-non-react-statics';
import {
	isEqual,
	checkValueChange,
	checkPropChange,
	checkSomePropChange,
	getClassName,
	getOptionsFromQuery,
} from '@appbaseio/reactivecore/lib/utils/helper';
import types from '@appbaseio/reactivecore/lib/utils/types';
import { componentTypes } from '@appbaseio/reactivecore/lib/utils/constants';

import Title from '../../styles/Title';
import Container from '../../styles/Container';
import Button, { toggleButtons } from '../../styles/Button';
import { connect, getValidPropsKeys } from '../../utils';

class ToggleButton extends Component {
	constructor(props) {
		super(props);

		const value = props.selectedValue || props.value || props.defaultValue || [];
		const currentValue = ToggleButton.parseValue(value, props);

		this.state = {
			currentValue,
		};
		this.locked = false;

		props.addComponent(props.componentId);
		props.setComponentProps(props.componentId, {
			...props,
			componentType: componentTypes.toggleButton,
		});
		props.setQueryListener(props.componentId, props.onQueryChange, null);
		this.setReact(props);
		const hasMounted = false;

		if (currentValue.length) {
			this.handleToggle(currentValue, true, props, hasMounted);
		}
	}

	componentDidUpdate(prevProps) {
		checkSomePropChange(this.props, prevProps, getValidPropsKeys(this.props), () => {
			this.props.updateComponentProps(this.props.componentId, this.props);
		});
		checkPropChange(this.props.react, prevProps.react, () => {
			this.setReact(this.props);
		});

		checkSomePropChange(this.props, prevProps, ['dataField', 'nestedField'], () => {
			this.updateQuery(this.state.currentValue, this.props);
		});

		if (!isEqual(this.props.value, prevProps.value)) {
			this.handleToggle(this.props.value, true, this.props);
		} else if (this.props.multiSelect) {
			// for multiselect selectedValue will be an array
			if (
				!isEqual(this.state.currentValue, this.props.selectedValue)
				&& !isEqual(prevProps.selectedValue, this.props.selectedValue)
			) {
				const { value, onChange } = this.props;
				if (value === undefined) {
					this.handleToggle(this.props.selectedValue || [], true, this.props);
				} else if (onChange) {
					// value prop exists
					onChange(this.props.selectedValue || '');
				} else {
					// value prop exists and onChange is not defined:
					// we need to put the current value back into the store
					// if the clear action was triggered by interacting with
					// selected-filters component
					this.handleToggle(this.state.currentValue, true, this.props);
				}
			}
		} else {
			// else selectedValue will be a string
			const currentValue = this.state.currentValue[0]
				? this.state.currentValue[0].value
				: null;
			if (
				!isEqual(currentValue, this.props.selectedValue)
				&& !isEqual(prevProps.selectedValue, this.props.selectedValue)
			) {
				const { value, onChange } = this.props;
				if (value === undefined) {
					this.handleToggle(this.props.selectedValue || [], true, this.props);
				} else if (onChange) {
					// value prop exists
					onChange(this.props.selectedValue || '');
				} else {
					// value prop exists and onChange is not defined:
					// we need to put the current value back into the store
					// if the clear action was triggered by interacting with
					// selected-filters component
					this.handleToggle(this.state.currentValue, true, this.props);
				}
			}
		}
	}

	componentWillUnmount() {
		this.props.removeComponent(this.props.componentId);
	}

	static parseValue = (value, props) => {
		if (Array.isArray(value)) {
			if (typeof value[0] === 'string') {
				return props.data.filter(item => value.includes(item.value));
			}
			return value;
		}
		return props.data.filter(item => item.value === value);
	};

	static defaultQuery = (value, props) => {
		let query = null;
		if (value && value.length) {
			query = {
				bool: {
					boost: 1.0,
					minimum_should_match: 1,
					should: value.map(item => ({
						term: {
							[props.dataField]: item.value,
						},
					})),
				},
			};
		}

		if (query && props.nestedField) {
			return {
				query: {
					nested: {
						path: props.nestedField,
						query,
					},
				},
			};
		}
		return query;
	};

	handleToggle = (value, isDefaultValue = false, props = this.props, hasMounted = true) => {
		const { currentValue } = this.state;
		const toggleValue = value;
		let finalValue = [];

		if (isDefaultValue) {
			finalValue = ToggleButton.parseValue(toggleValue, props);
		} else if (this.props.multiSelect) {
			finalValue = currentValue.some(item => item.value === toggleValue.value)
				? currentValue.filter(item => item.value !== toggleValue.value)
				: currentValue.concat(toggleValue);
		} else {
			finalValue = currentValue.some(item => item.value === toggleValue.value)
				? []
				: [toggleValue];
		}

		this.setValue(finalValue, props, hasMounted);
	};

	setReact(props) {
		if (props.react) {
			props.watchComponent(props.componentId, props.react);
		}
	}

	setValue = (value, props = this.props, hasMounted = true) => {
		// ignore state updates when component is locked
		if (props.beforeValueChange && this.locked) {
			return;
		}

		this.locked = true;
		const performUpdate = () => {
			const handleUpdates = () => {
				this.updateQuery(value, props);
				this.locked = false;
				if (props.onValueChange) props.onValueChange(value);
			};

			if (hasMounted) {
				this.setState(
					{
						currentValue: value,
					},
					handleUpdates,
				);
			} else {
				handleUpdates();
			}
		};
		checkValueChange(
			props.componentId,
			props.multiSelect ? value : value[0],
			props.beforeValueChange,
			performUpdate,
		);
	};

	updateQuery = (value, props) => {
		let filterValue = value;
		if (!props.multiSelect) {
			filterValue = value[0] ? value[0].value : null;
		}
		const { customQuery } = props;

		let query = ToggleButton.defaultQuery(value, props);
		let customQueryOptions;
		if (customQuery) {
			({ query } = customQuery(value, props) || {});
			customQueryOptions = getOptionsFromQuery(customQuery(value, props));
		}

		props.setQueryOptions(props.componentId, customQueryOptions);
		props.updateQuery({
			componentId: props.componentId,
			query,
			value: filterValue, // sets a string in URL not array
			label: props.filterLabel,
			showFilter: props.showFilter,
			URLParams: props.URLParams,
			componentType: componentTypes.toggleButton,
		});
	};

	handleClick = (item) => {
		const { value, onChange } = this.props;
		if (value === undefined) {
			this.handleToggle(item);
		} else if (onChange) onChange(item);
	};

	render() {
		return (
			<Container
				style={this.props.style}
				className={`${toggleButtons} ${this.props.className || ''}`}
			>
				{this.props.title && (
					<Title className={getClassName(this.props.innerClass, 'title') || null}>
						{this.props.title}
					</Title>
				)}
				{this.props.data.map((item) => {
					const isSelected = this.state.currentValue.some(
						value => value.value === item.value,
					);
					return (
						<Button
							className={`${getClassName(this.props.innerClass, 'button')} ${
								isSelected ? 'active' : ''
							}`}
							onClick={() => this.handleClick(item)}
							key={item.value}
							primary={isSelected}
							large
						>
							{item.label}
						</Button>
					);
				})}
			</Container>
		);
	}
}

ToggleButton.propTypes = {
	addComponent: types.funcRequired,
	removeComponent: types.funcRequired,
	setQueryListener: types.funcRequired,
	updateQuery: types.funcRequired,
	watchComponent: types.funcRequired,
	selectedValue: types.selectedValue,
	setQueryOptions: types.funcRequired,
	setComponentProps: types.funcRequired,
	updateComponentProps: types.funcRequired,
	// component props
	className: types.string,
	componentId: types.stringRequired,
	data: types.data,
	dataField: types.stringRequired,
	defaultValue: types.stringOrArray,
	value: types.stringOrArray,
	filterLabel: types.string,
	nestedField: types.string,
	innerClass: types.style,
	multiSelect: types.bool,
	onValueChange: types.func,
	onChange: types.func,
	onQueryChange: types.func,
	react: types.react,
	showFilter: types.bool,
	style: types.style,
	title: types.title,
	URLParams: types.bool,
};

ToggleButton.defaultProps = {
	className: null,
	multiSelect: true,
	showFilter: true,
	style: {},
	URLParams: false,
};

const mapStateToProps = (state, props) => ({
	selectedValue:
		(state.selectedValues[props.componentId]
			&& state.selectedValues[props.componentId].value)
		|| null,
});

const mapDispatchtoProps = dispatch => ({
	setComponentProps: (component, options) => dispatch(setComponentProps(component, options)),
	updateComponentProps: (component, options) =>
		dispatch(updateComponentProps(component, options)),
	addComponent: component => dispatch(addComponent(component)),
	removeComponent: component => dispatch(removeComponent(component)),
	updateQuery: updateQueryObject => dispatch(updateQuery(updateQueryObject)),
	watchComponent: (component, react) => dispatch(watchComponent(component, react)),
	setQueryListener: (component, onQueryChange, beforeQueryChange) =>
		dispatch(setQueryListener(component, onQueryChange, beforeQueryChange)),
	setQueryOptions: (component, props) => dispatch(setQueryOptions(component, props)),
});

const ConnectedComponent = connect(
	mapStateToProps,
	mapDispatchtoProps,
)(props => <ToggleButton ref={props.myForwardedRef} {...props} />);

// eslint-disable-next-line
const ForwardRefComponent = React.forwardRef((props, ref) => (
	<ConnectedComponent {...props} myForwardedRef={ref} />
));
hoistNonReactStatics(ForwardRefComponent, ToggleButton);

ForwardRefComponent.name = 'ToggleButton';
export default ForwardRefComponent;
<commit_msg>feat(web): add keyboard accessibility in ToggleButton (#1038)<commit_after>import React, { Component } from 'react';

import {
	addComponent,
	removeComponent,
	watchComponent,
	updateQuery,
	setQueryListener,
	setQueryOptions,
	setComponentProps,
	updateComponentProps,
} from '@appbaseio/reactivecore/lib/actions';
import hoistNonReactStatics from 'hoist-non-react-statics';
import {
	isEqual,
	checkValueChange,
	checkPropChange,
	checkSomePropChange,
	getClassName,
	getOptionsFromQuery,
	handleA11yAction,
} from '@appbaseio/reactivecore/lib/utils/helper';
import types from '@appbaseio/reactivecore/lib/utils/types';
import { componentTypes } from '@appbaseio/reactivecore/lib/utils/constants';

import Title from '../../styles/Title';
import Container from '../../styles/Container';
import Button, { toggleButtons } from '../../styles/Button';
import { connect, getValidPropsKeys } from '../../utils';

class ToggleButton extends Component {
	constructor(props) {
		super(props);

		const value = props.selectedValue || props.value || props.defaultValue || [];
		const currentValue = ToggleButton.parseValue(value, props);

		this.state = {
			currentValue,
		};
		this.locked = false;

		props.addComponent(props.componentId);
		props.setComponentProps(props.componentId, {
			...props,
			componentType: componentTypes.toggleButton,
		});
		props.setQueryListener(props.componentId, props.onQueryChange, null);
		this.setReact(props);
		const hasMounted = false;

		if (currentValue.length) {
			this.handleToggle(currentValue, true, props, hasMounted);
		}
	}

	componentDidUpdate(prevProps) {
		checkSomePropChange(this.props, prevProps, getValidPropsKeys(this.props), () => {
			this.props.updateComponentProps(this.props.componentId, this.props);
		});
		checkPropChange(this.props.react, prevProps.react, () => {
			this.setReact(this.props);
		});

		checkSomePropChange(this.props, prevProps, ['dataField', 'nestedField'], () => {
			this.updateQuery(this.state.currentValue, this.props);
		});

		if (!isEqual(this.props.value, prevProps.value)) {
			this.handleToggle(this.props.value, true, this.props);
		} else if (this.props.multiSelect) {
			// for multiselect selectedValue will be an array
			if (
				!isEqual(this.state.currentValue, this.props.selectedValue)
				&& !isEqual(prevProps.selectedValue, this.props.selectedValue)
			) {
				const { value, onChange } = this.props;
				if (value === undefined) {
					this.handleToggle(this.props.selectedValue || [], true, this.props);
				} else if (onChange) {
					// value prop exists
					onChange(this.props.selectedValue || '');
				} else {
					// value prop exists and onChange is not defined:
					// we need to put the current value back into the store
					// if the clear action was triggered by interacting with
					// selected-filters component
					this.handleToggle(this.state.currentValue, true, this.props);
				}
			}
		} else {
			// else selectedValue will be a string
			const currentValue = this.state.currentValue[0]
				? this.state.currentValue[0].value
				: null;
			if (
				!isEqual(currentValue, this.props.selectedValue)
				&& !isEqual(prevProps.selectedValue, this.props.selectedValue)
			) {
				const { value, onChange } = this.props;
				if (value === undefined) {
					this.handleToggle(this.props.selectedValue || [], true, this.props);
				} else if (onChange) {
					// value prop exists
					onChange(this.props.selectedValue || '');
				} else {
					// value prop exists and onChange is not defined:
					// we need to put the current value back into the store
					// if the clear action was triggered by interacting with
					// selected-filters component
					this.handleToggle(this.state.currentValue, true, this.props);
				}
			}
		}
	}

	componentWillUnmount() {
		this.props.removeComponent(this.props.componentId);
	}

	static parseValue = (value, props) => {
		if (Array.isArray(value)) {
			if (typeof value[0] === 'string') {
				return props.data.filter(item => value.includes(item.value));
			}
			return value;
		}
		return props.data.filter(item => item.value === value);
	};

	static defaultQuery = (value, props) => {
		let query = null;
		if (value && value.length) {
			query = {
				bool: {
					boost: 1.0,
					minimum_should_match: 1,
					should: value.map(item => ({
						term: {
							[props.dataField]: item.value,
						},
					})),
				},
			};
		}

		if (query && props.nestedField) {
			return {
				query: {
					nested: {
						path: props.nestedField,
						query,
					},
				},
			};
		}
		return query;
	};

	handleToggle = (value, isDefaultValue = false, props = this.props, hasMounted = true) => {
		const { currentValue } = this.state;
		const toggleValue = value;
		let finalValue = [];

		if (isDefaultValue) {
			finalValue = ToggleButton.parseValue(toggleValue, props);
		} else if (this.props.multiSelect) {
			finalValue = currentValue.some(item => item.value === toggleValue.value)
				? currentValue.filter(item => item.value !== toggleValue.value)
				: currentValue.concat(toggleValue);
		} else {
			finalValue = currentValue.some(item => item.value === toggleValue.value)
				? []
				: [toggleValue];
		}

		this.setValue(finalValue, props, hasMounted);
	};

	setReact(props) {
		if (props.react) {
			props.watchComponent(props.componentId, props.react);
		}
	}

	setValue = (value, props = this.props, hasMounted = true) => {
		// ignore state updates when component is locked
		if (props.beforeValueChange && this.locked) {
			return;
		}

		this.locked = true;
		const performUpdate = () => {
			const handleUpdates = () => {
				this.updateQuery(value, props);
				this.locked = false;
				if (props.onValueChange) props.onValueChange(value);
			};

			if (hasMounted) {
				this.setState(
					{
						currentValue: value,
					},
					handleUpdates,
				);
			} else {
				handleUpdates();
			}
		};
		checkValueChange(
			props.componentId,
			props.multiSelect ? value : value[0],
			props.beforeValueChange,
			performUpdate,
		);
	};

	updateQuery = (value, props) => {
		let filterValue = value;
		if (!props.multiSelect) {
			filterValue = value[0] ? value[0].value : null;
		}
		const { customQuery } = props;

		let query = ToggleButton.defaultQuery(value, props);
		let customQueryOptions;
		if (customQuery) {
			({ query } = customQuery(value, props) || {});
			customQueryOptions = getOptionsFromQuery(customQuery(value, props));
		}

		props.setQueryOptions(props.componentId, customQueryOptions);
		props.updateQuery({
			componentId: props.componentId,
			query,
			value: filterValue, // sets a string in URL not array
			label: props.filterLabel,
			showFilter: props.showFilter,
			URLParams: props.URLParams,
			componentType: componentTypes.toggleButton,
		});
	};

	handleClick = (item) => {
		const { value, onChange } = this.props;
		if (value === undefined) {
			this.handleToggle(item);
		} else if (onChange) onChange(item);
	};

	render() {
		return (
			<Container
				style={this.props.style}
				className={`${toggleButtons} ${this.props.className || ''}`}
			>
				{this.props.title && (
					<Title className={getClassName(this.props.innerClass, 'title') || null}>
						{this.props.title}
					</Title>
				)}
				{this.props.data.map((item) => {
					const isSelected = this.state.currentValue.some(
						value => value.value === item.value,
					);
					return (
						<Button
							className={`${getClassName(this.props.innerClass, 'button')} ${
								isSelected ? 'active' : ''
							}`}
							onClick={() => this.handleClick(item)}
							onKeyPress={e => handleA11yAction(e, () => this.handleClick(item))}
							key={item.value}
							tabIndex="0"
							primary={isSelected}
							large
						>
							{item.label}
						</Button>
					);
				})}
			</Container>
		);
	}
}

ToggleButton.propTypes = {
	addComponent: types.funcRequired,
	removeComponent: types.funcRequired,
	setQueryListener: types.funcRequired,
	updateQuery: types.funcRequired,
	watchComponent: types.funcRequired,
	selectedValue: types.selectedValue,
	setQueryOptions: types.funcRequired,
	setComponentProps: types.funcRequired,
	updateComponentProps: types.funcRequired,
	// component props
	className: types.string,
	componentId: types.stringRequired,
	data: types.data,
	dataField: types.stringRequired,
	defaultValue: types.stringOrArray,
	value: types.stringOrArray,
	filterLabel: types.string,
	nestedField: types.string,
	innerClass: types.style,
	multiSelect: types.bool,
	onValueChange: types.func,
	onChange: types.func,
	onQueryChange: types.func,
	react: types.react,
	showFilter: types.bool,
	style: types.style,
	title: types.title,
	URLParams: types.bool,
};

ToggleButton.defaultProps = {
	className: null,
	multiSelect: true,
	showFilter: true,
	style: {},
	URLParams: false,
};

const mapStateToProps = (state, props) => ({
	selectedValue:
		(state.selectedValues[props.componentId]
			&& state.selectedValues[props.componentId].value)
		|| null,
});

const mapDispatchtoProps = dispatch => ({
	setComponentProps: (component, options) => dispatch(setComponentProps(component, options)),
	updateComponentProps: (component, options) =>
		dispatch(updateComponentProps(component, options)),
	addComponent: component => dispatch(addComponent(component)),
	removeComponent: component => dispatch(removeComponent(component)),
	updateQuery: updateQueryObject => dispatch(updateQuery(updateQueryObject)),
	watchComponent: (component, react) => dispatch(watchComponent(component, react)),
	setQueryListener: (component, onQueryChange, beforeQueryChange) =>
		dispatch(setQueryListener(component, onQueryChange, beforeQueryChange)),
	setQueryOptions: (component, props) => dispatch(setQueryOptions(component, props)),
});

const ConnectedComponent = connect(
	mapStateToProps,
	mapDispatchtoProps,
)(props => <ToggleButton ref={props.myForwardedRef} {...props} />);

// eslint-disable-next-line
const ForwardRefComponent = React.forwardRef((props, ref) => (
	<ConnectedComponent {...props} myForwardedRef={ref} />
));
hoistNonReactStatics(ForwardRefComponent, ToggleButton);

ForwardRefComponent.name = 'ToggleButton';
export default ForwardRefComponent;
<|endoftext|>


<commit_before>// Helper functions for fields defined by the field.html template

"use strict";

var $ = require('jquery'),
    _ = require('lodash'),
    format = require('util').format,
    moment = require('moment');

var DATETIME_FORMAT = exports.DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
var DATE_FORMAT = exports.DATE_FORMAT = "YYYY-MM-DD";

var multiChoiceDisplayTemplate = _.template('

' +
                                            '<% _.each(rows, function(row) { ' +
                                            '%>
<%= row %>
' +
                                            '<% }); %>' +
                                            '

');

var getField = exports.getField = function ($fields, name) {
    return $fields.filter(format('[data-field="%s"]', name));
};
var getSerializableField = exports.getSerializableField = function ($fields, name) {
    // takes a jQuery collection of edit fields and returns the
    // actual input or select field that will be serialized
    var $subfields = getField($fields, name),
        selector = '[name="' + name + '"]';
    return $subfields.find(selector).add($subfields.filter(selector));
};

var excludeButtons = exports.excludeButtons = function (selector) {
    return $(selector).filter(":not(.btn)");
};

var getDateFieldFormat = exports.getDateFieldFormat = function ($elem) {
    return $elem.closest('[data-type]').is('[data-type="date"]') ? DATE_FORMAT : DATETIME_FORMAT;
};

exports.applyDateToDatepicker = function($elem, value) {
    var format = getDateFieldFormat($elem);
    var date = moment(value, format);
    if (date && date.isValid()) {
        $elem.datepicker('update', date.toDate());
    } else {
        $elem.val('');
    }
};

var getTimestampFromDatepicker = exports.getTimestampFromDatepicker = function($elem) {
    // The datepicker always has a value, so we must check the
    // text box to which the date picker is attached for a non-empty
    // value before fetching the value from the datepicker
    if ($elem.val()) {
        var format = getDateFieldFormat($elem);
        return moment($elem.datepicker("getDate")).format(format);
    } else {
        return '';
    }
};

exports.formToDictionary = function ($form, $editFields, $displayFields) {
    $displayFields = $displayFields || $();

    var isTypeaheadHiddenField = function(name) {
        return getSerializableField($editFields, name).is('[data-typeahead-hidden]');
    };
    var getDisplayValue = function(type, name) {
        var $displayField;
        if (isTypeaheadHiddenField(name)) {
            return $form.find('[data-typeahead-restore="' + name + '"]').val();
        }
        $displayField = getField($displayFields, name);
        if ($displayField.is('[data-value]')) {
            if (type === 'bool') {
                return $displayField.attr('data-value') === "True";
            }
            return $displayField.attr('data-value');
        }
        return undefined;
    };

    var result = {};

    // initialize results with an empty list for all multiselects
    // so that a value is sent every time for all multiselects
    $form.find('select[name][multiple]').each(function(i, elem) {
        result[elem.name] = [];
    });

    _.each($form.serializeArray(), function(item) {
        var type = getField($editFields, item.name).attr('data-type'),
            displayValue = getDisplayValue(type, item.name),
            $field = getSerializableField($editFields, item.name);

        if (type === 'multichoice') {
            // always serialize and send multichoice values
            result[item.name].push(item.value);
            return;
        }

        if (item.value === displayValue) {
            return;  // Don't serialize unchanged values
        }

        if (type === 'bool') {
            if ($field.prop('type') === 'radio') {
                result[item.name] = (item.value === 'True');
            }
            // Note that checkboxes are handled below so we catch unchecked
            // ones which serializeArray ignores
        } else if (item.value === '' && (type === 'int' || type === 'float' ||
                                         type === 'date' || type === 'datetime')) {
            // convert empty numeric fields to null
            result[item.name] = null;
        } else if (type === 'date' || type === 'datetime') {
            result[item.name] = getTimestampFromDatepicker($field);
        } else if (item.value === '' && isTypeaheadHiddenField(item.name)) {
            // convert empty foreign key id strings to null
            result[item.name] = null;
        } else {
            result[item.name] = item.value;
        }
    });
    $form.find('[name][type="checkbox"]').not('[disabled]').each(function(i, elem) {
        if (elem.checked !== getDisplayValue('bool', elem.name)) {
            result[elem.name] = elem.checked;
        }
    });
    return result;
};

exports.renderMultiChoices = function($container) {
    $container.each(function (__, el) {
        var $el = $(el),
            value = JSON.parse($(el).attr('data-value'));
        $(el).html(multiChoiceDisplayTemplate({rows: value}));
    });
};
<commit_msg>Only PUT multichoice field value if it has changed<commit_after>// Helper functions for fields defined by the field.html template

"use strict";

var $ = require('jquery'),
    _ = require('lodash'),
    format = require('util').format,
    moment = require('moment');

var DATETIME_FORMAT = exports.DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
var DATE_FORMAT = exports.DATE_FORMAT = "YYYY-MM-DD";

var multiChoiceDisplayTemplate = _.template('

' +
                                            '<% _.each(rows, function(row) { ' +
                                            '%>
<%= row %>
' +
                                            '<% }); %>' +
                                            '

');

var getField = exports.getField = function ($fields, name) {
    return $fields.filter(format('[data-field="%s"]', name));
};
var getSerializableField = exports.getSerializableField = function ($fields, name) {
    // takes a jQuery collection of edit fields and returns the
    // actual input or select field that will be serialized
    var $subfields = getField($fields, name),
        selector = '[name="' + name + '"]';
    return $subfields.find(selector).add($subfields.filter(selector));
};

var excludeButtons = exports.excludeButtons = function (selector) {
    return $(selector).filter(":not(.btn)");
};

var getDateFieldFormat = exports.getDateFieldFormat = function ($elem) {
    return $elem.closest('[data-type]').is('[data-type="date"]') ? DATE_FORMAT : DATETIME_FORMAT;
};

exports.applyDateToDatepicker = function($elem, value) {
    var format = getDateFieldFormat($elem);
    var date = moment(value, format);
    if (date && date.isValid()) {
        $elem.datepicker('update', date.toDate());
    } else {
        $elem.val('');
    }
};

var getTimestampFromDatepicker = exports.getTimestampFromDatepicker = function($elem) {
    // The datepicker always has a value, so we must check the
    // text box to which the date picker is attached for a non-empty
    // value before fetching the value from the datepicker
    if ($elem.val()) {
        var format = getDateFieldFormat($elem);
        return moment($elem.datepicker("getDate")).format(format);
    } else {
        return '';
    }
};

exports.formToDictionary = function ($form, $editFields, $displayFields) {
    $displayFields = $displayFields || $();

    var isTypeaheadHiddenField = function(name) {
        return getSerializableField($editFields, name).is('[data-typeahead-hidden]');
    };
    var getDisplayValue = function(type, name) {
        var $displayField;
        if (isTypeaheadHiddenField(name)) {
            return $form.find('[data-typeahead-restore="' + name + '"]').val();
        }
        $displayField = getField($displayFields, name);
        if ($displayField.is('[data-value]')) {
            if (type === 'bool') {
                return $displayField.attr('data-value') === "True";
            }
            return $displayField.attr('data-value');
        }
        return undefined;
    };

    var result = {};

    _.each($form.serializeArray(), function(item) {
        var type = getField($editFields, item.name).attr('data-type'),
            displayValue = getDisplayValue(type, item.name),
            $field = getSerializableField($editFields, item.name);

        if (type === 'multichoice') {
            // If n choices are selected the serialized array has n items.
            // It's simpler to skip them here and process them below.
            return;
        }

        if (item.value === displayValue) {
            return;  // Don't serialize unchanged values
        }

        if (type === 'bool') {
            if ($field.prop('type') === 'radio') {
                result[item.name] = (item.value === 'True');
            }
            // Note that checkboxes are handled below so we catch unchecked
            // ones which serializeArray ignores
        } else if (item.value === '' && (type === 'int' || type === 'float' ||
                                         type === 'date' || type === 'datetime')) {
            // convert empty numeric fields to null
            result[item.name] = null;
        } else if (type === 'date' || type === 'datetime') {
            result[item.name] = getTimestampFromDatepicker($field);
        } else if (item.value === '' && isTypeaheadHiddenField(item.name)) {
            // convert empty foreign key id strings to null
            result[item.name] = null;
        } else {
            result[item.name] = item.value;
        }
    });

    $form.find('[name][type="checkbox"]').not('[disabled]').each(function(i, elem) {
        if (elem.checked !== getDisplayValue('bool', elem.name)) {
            result[elem.name] = elem.checked;
        }
    });

    $form
        .find('select[name][multiple]')
        .each(function(i, elem) {
            // Compare current multichoice value with display value
            var name = elem.name,
                value = $(elem).val() || [],
                display = getDisplayValue('multichoice', name);
            display = (display === "null") ? [] : JSON.parse(display);
            if (! _.isEqual(value.sort(), display.sort())) {
                // Value of multichoice field has changed
                result[name] = value;
            }
        });

    return result;
};

exports.renderMultiChoices = function($container) {
    $container.each(function (__, el) {
        var $el = $(el),
            value = JSON.parse($(el).attr('data-value'));
        $(el).html(multiChoiceDisplayTemplate({rows: value}));
    });
};
<|endoftext|>


<commit_before>'use strict';

var zkNotifyMgr = function(zkNotifySrv) {
  this.setNotifyMsg = function(msg) {
    zkNotifySrv.setNotifyMsg(msg);
  };
  this.getNotifyMsg = function() {
    return zkNotifySrv.getNotifyMsg();
  };
};
zkNotifyMgr.$inject = ['$rootScope', '$timeout', 'zkNotifySrv'];

angular.module('zkNotify', [])
  .service('zkNotifySrv', [function zkNotifySrv() {
    this.message = null;

    this.setNotifyMsg = function(msg) {
      this.message = msg;
    };

    this.getNotifyMsg = function() {
      return this.message;
    };
  }])
  .directive('zkNotify', ['$controller', function ($controller) {
    return {
      templateUrl: 'angular-zk-notify.html',
      restrict: 'EA',
      scope: {},
      link: function postLink(scope, element, attrs) {
        var notifyMgr = $controller(zkNotifyMgr);
        scope.message = notifyMgr.getNotifyMsg();
      }
    };
  }]);
<commit_msg>Remove useless inject<commit_after>'use strict';

var zkNotifyMgr = function(zkNotifySrv) {
  this.setNotifyMsg = function(msg) {
    zkNotifySrv.setNotifyMsg(msg);
  };
  this.getNotifyMsg = function() {
    return zkNotifySrv.getNotifyMsg();
  };
};
zkNotifyMgr.$inject = ['zkNotifySrv'];

angular.module('zkNotify', [])
  .service('zkNotifySrv', [function zkNotifySrv() {
    this.message = null;

    this.setNotifyMsg = function(msg) {
      this.message = msg;
    };

    this.getNotifyMsg = function() {
      return this.message;
    };
  }])
  .directive('zkNotify', ['$controller', function ($controller) {
    return {
      templateUrl: 'angular-zk-notify.html',
      restrict: 'EA',
      scope: {},
      link: function postLink(scope, element, attrs) {
        var notifyMgr = $controller(zkNotifyMgr);
        scope.message = notifyMgr.getNotifyMsg();
      }
    };
  }]);
<|endoftext|>


<commit_before>var dispatcher = require('../../lib/dispatcher');
var api = require('../../lib/api');

module.exports = {
  componentDidMount: function() {
    // Handle remix calls from Android wrapper
    window.createRemix = () => {
      var uri = `/users/${this.state.params.user}/projects/${this.state.params.project}/remixes`;

      // Duplicate project via API call
      api({
        method: 'post',
        uri: uri
      }, (err, data) => {
        if (err) {
          window.Platform.trackEvent('Remix', 'Remix Error', err);
          return console.error('Error remixing project', err);
        }

        var projectID = data.project.id;
        var projectTitle = data.project.title;

        // Get author's username
        api({
          method: 'GET',
          uri: `/users/${this.state.params.user}/projects/${this.state.params.project}`
        }, (err, moreData) => {
          if (err) {
            window.Platform.trackEvent('Remix', 'Remix Error', err);
            return console.error('Error remixing project', err);
          }

          window.Platform.trackEvent('Remix', 'Remix', projectId);

          if (window.Platform) {
            window.Platform.setView(
              `/users/${this.state.user.id}/projects/${projectID}`,
              JSON.stringify({
                isFreshRemix: true,
                title: projectTitle,
                originalAuthor: moreData.project.author.username
              })
            );
          }
        });
      });
    };

    if (this.android && this.state.routeData.isFreshRemix) {
      // Notify user that THIS IS A REEEEEEMIXXXXX
      dispatcher.fire('modal-confirm:show', {
        config: {
          header: 'Project Remix',
          body: `This is your copy of ${this.state.routeData.title}. You can add or change anything. The original will stay the same. Have fun!`,
          attribution: this.state.routeData.originalAuthor,
          icon: 'tinker.png',
          buttonText: 'OK, got it!'
        }
      });

      // Prepend "Remix of..." to project name

      var remixTitle = this.state.routeData.title;

      if (!remixTitle.match(/^Remix of/)) {
        remixTitle = 'Remix of ' + remixTitle;
      }

      api({
        method: 'PATCH',
        uri: `/users/${this.state.user.id}/projects/${this.state.params.project}`,
        json: {
          title: remixTitle
        }
      }, function (err, body) {
        if (err) {
          window.Platform.trackEvent('Remix', 'Remix Rename Error', projectId);
          console.error('Could not update project settings.');
        }
      });
    }
  }
};
<commit_msg>fix variable<commit_after>var dispatcher = require('../../lib/dispatcher');
var api = require('../../lib/api');

module.exports = {
  componentDidMount: function() {
    // Handle remix calls from Android wrapper
    window.createRemix = () => {
      var uri = `/users/${this.state.params.user}/projects/${this.state.params.project}/remixes`;

      // Duplicate project via API call
      api({
        method: 'post',
        uri: uri
      }, (err, data) => {
        if (err) {
          window.Platform.trackEvent('Remix', 'Remix Error', err);
          return console.error('Error remixing project', err);
        }

        var projectID = data.project.id;
        var projectTitle = data.project.title;

        // Get author's username
        api({
          method: 'GET',
          uri: `/users/${this.state.params.user}/projects/${this.state.params.project}`
        }, (err, moreData) => {
          if (err) {
            window.Platform.trackEvent('Remix', 'Remix Error', err);
            return console.error('Error remixing project', err);
          }

          window.Platform.trackEvent('Remix', 'Remix', projectID);

          if (window.Platform) {
            window.Platform.setView(
              `/users/${this.state.user.id}/projects/${projectID}`,
              JSON.stringify({
                isFreshRemix: true,
                title: projectTitle,
                originalAuthor: moreData.project.author.username
              })
            );
          }
        });
      });
    };

    if (this.android && this.state.routeData.isFreshRemix) {
      // Notify user that THIS IS A REEEEEEMIXXXXX
      dispatcher.fire('modal-confirm:show', {
        config: {
          header: 'Project Remix',
          body: `This is your copy of ${this.state.routeData.title}. You can add or change anything. The original will stay the same. Have fun!`,
          attribution: this.state.routeData.originalAuthor,
          icon: 'tinker.png',
          buttonText: 'OK, got it!'
        }
      });

      // Prepend "Remix of..." to project name

      var remixTitle = this.state.routeData.title;

      if (!remixTitle.match(/^Remix of/)) {
        remixTitle = 'Remix of ' + remixTitle;
      }

      api({
        method: 'PATCH',
        uri: `/users/${this.state.user.id}/projects/${this.state.params.project}`,
        json: {
          title: remixTitle
        }
      }, function (err, body) {
        if (err) {
          window.Platform.trackEvent('Remix', 'Remix Rename Error', projectID);
          console.error('Could not update project settings.');
        }
      });
    }
  }
};
<|endoftext|>


<commit_before>// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

// Check to see if the dev flag is set.
var dev;
process.argv.slice(2).forEach(function(arg) {
    if (arg === "--dev") dev = true;
});

// Parse the version from the package.json file
var version = require("../package.json").version.split('.');
var major = version[0];
var minor = version[1];
var patch = version[2];

// If the dev flag is set, bump the minor version.  This allows people to mix
// and match dev/stable in Python.
if (dev) {
    minor = String(parseInt(minor) + 1);
}

// Generate the version file contents.
var contents =
    "# DO NOT EDIT!  NPM AUTOMATICALLY WRITES THIS FILE!\nversion_info = (" +
    major + ", " +
    minor + ", " +
    patch +
    (dev ? ", 'dev'" : "") +
    ")\n__version__ = '.'.join(map(str, version_info))\n";

// Write the contents to the file.
var fs = require("fs");
fs.writeFile("ipywidgets/_version.py", contents, function(err) {
    if(err) {
        console.error("Could not write the Python version file", err);
        process.exit(1);
    } else {
        console.log("Python version " + major + "." + minor + "." + patch + (dev ? "dev" : "") + " written");
    }
});
<commit_msg>Update version copy script<commit_after>// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

// Check to see if the dev flag is set.
var dev;
process.argv.slice(2).forEach(function(arg) {
    if (arg === "--dev") dev = true;
});

// Parse the version from the package.json file
var version = require("../package.json").version.split('.');
var major = version[0];
var minor = version[1];
var patch = version[2];

// If the dev flag is set, bump the minor version.  This allows people to mix
// and match dev/stable in Python.
if (dev) {
    minor = String(parseInt(minor) + 1);
}

// Generate the version file contents.
var contents =
    "# DO NOT EDIT!  NPM AUTOMATICALLY WRITES THIS FILE!\nversion_info = (" +
    major + ", " +
    minor + ", " +
    patch +
    (dev ? ", 'dev'" : "") +
    ")\n__version__ = '.'.join(map(str, version_info))\n";

// Write the contents to the file.
var fs = require("fs");
fs.writeFile("_version.py", contents, function(err) {
    if(err) {
        console.error("Could not write the Python version file", err);
        process.exit(1);
    } else {
        console.log("Python version " + major + "." + minor + "." + patch + (dev ? "dev" : "") + " written");
    }
});
<|endoftext|>


<commit_before>const DrawCard = require('../../../drawcard.js');

class FickleBannerman extends DrawCard {
    setupCardAbilities() {
        this.forcedReaction({
            when: {
                afterChallenge: ({challenge}) => challenge.loser === this.controller && challenge.challengeType === 'power'
            },
            handler: () => {
                this.game.promptWithMenu(this.controller, this, {
                    activePrompt: {
                        menuTitle: 'Discard a gold from ' + this.name + '?',
                        buttons: [
                            { text: 'Yes', method: 'discardGold' },
                            { text: 'No', method: 'loseControl' }
                        ]
                    },
                    source: this
                });
            }
        });
    }

    discardGold() {
        this.removeToken('gold', 1);
        this.game.addMessage('{0} is forced to discard a gold from {1}', this.controller, this);

        return true;
    }

    loseControl() {
        var otherPlayer = this.game.getOtherPlayer(this.controller);
        this.game.takeControl(otherPlayer, this);
        this.game.addMessage('{0} takes control of {1}', otherPlayer, this);

        return true;
    }
}

FickleBannerman.code = '06007';

module.exports = FickleBannerman;
<commit_msg>Do not prompt when Fickle Bannerman has no gold (#1359)<commit_after>const DrawCard = require('../../../drawcard.js');

class FickleBannerman extends DrawCard {
    setupCardAbilities() {
        this.forcedReaction({
            when: {
                afterChallenge: event => event.challenge.loser === this.controller && event.challenge.challengeType === 'power'
            },
            handler: () => {
                if(!this.hasToken('gold')) {
                    this.loseControl();
                    return;
                }

                this.game.promptWithMenu(this.controller, this, {
                    activePrompt: {
                        menuTitle: 'Discard a gold from ' + this.name + '?',
                        buttons: [
                            { text: 'Yes', method: 'discardGold' },
                            { text: 'No', method: 'loseControl' }
                        ]
                    },
                    source: this
                });
            }
        });
    }

    discardGold() {
        this.removeToken('gold', 1);
        this.game.addMessage('{0} is forced to discard a gold from {1}', this.controller, this);

        return true;
    }

    loseControl() {
        var otherPlayer = this.game.getOtherPlayer(this.controller);
        this.game.takeControl(otherPlayer, this);
        this.game.addMessage('{0} takes control of {1}', otherPlayer, this);

        return true;
    }
}

FickleBannerman.code = '06007';

module.exports = FickleBannerman;
<|endoftext|>


<commit_before>// @flow

import JitsiMeetJS from '../lib-jitsi-meet';
import { Platform } from '../react';

const { browser } = JitsiMeetJS.util;

const DEFAULT_OPTIMAL_BROWSERS = [
    'chrome',
    'electron',
    'firefox',
    'nwjs'
];

const DEFAULT_UNSUPPORTED_BROWSERS = [];

const browserNameToCheck = {
    chrome: browser.isChrome.bind(browser),
    edge: browser.isEdge.bind(browser),
    electron: browser.isElectron.bind(browser),
    firefox: browser.isFirefox.bind(browser),
    nwjs: browser.isNWJS.bind(browser),
    opera: browser.isOpera.bind(browser),
    safari: browser.isSafari.bind(browser)
};

declare var interfaceConfig: Object;

/**
 * Returns whether or not jitsi is optimized and targeted for the  provided
 * browser name.
 *
 * @param {string} browserName - The name of the browser to check.
 * @returns {boolean}
 */
export function isBrowsersOptimal(browserName: string) {
    return (interfaceConfig.OPTIMAL_BROWSERS || DEFAULT_OPTIMAL_BROWSERS)
        .includes(browserName);
}

/**
 * Returns whether or not the current browser or the list of passed in browsers
 * is considered suboptimal. Suboptimal means it is a supported browser but has
 * not been explicitly listed as being optimal, possibly due to functionality
 * issues.
 *
 * @param {Array<string>} [browsers] - A list of browser names to check. Will
 * default to a whitelist.
 * @returns {boolean}
 */
export function isSuboptimalBrowser() {
    const optimalBrowsers
        = interfaceConfig.OPTIMAL_BROWSERS || DEFAULT_OPTIMAL_BROWSERS;

    return !_isCurrentBrowserInList(optimalBrowsers) && isSupportedBrowser();
}

/**
 * Returns whether or not the current browser should allow the app to display.
 * A supported browser is assumed to be able to support WebRtc.
 *
 * @returns {boolean}
 */
export function isSupportedBrowser() {
    if (navigator.product === 'ReactNative') {
        return false;
    }

    const isBlacklistedBrowser = _isCurrentBrowserInList(
        interfaceConfig.UNSUPPORTED_BROWSERS || DEFAULT_UNSUPPORTED_BROWSERS
    );

    if (isBlacklistedBrowser) {
        return false;
    }

    // We are intentionally allow mobile browsers because:
    // - the WelcomePage is mobile ready;
    // - if the URL points to a conference then deep-linking will take
    //   care of it.
    return Platform.OS === 'android'
        || Platform.OS === 'ios'
        || JitsiMeetJS.isWebRtcSupported();
}

/**
 * Runs various browser checks to know if the current browser is found within
 * the list.
 *
 * @param {Array<string>} list - Browser names to check. The names should be
 * keys in {@link browserNameToCheck}.
 * @private
 * @returns {boolean}
 */
function _isCurrentBrowserInList(list) {
    return Boolean(list.find(browserName => {
        const checkFunction = browserNameToCheck[browserName];

        return checkFunction ? checkFunction.call(browser) : false;
    }));
}
<commit_msg>fix(suboptimal): ignore mobile browsers in whitelists/blacklists<commit_after>// @flow

import JitsiMeetJS from '../lib-jitsi-meet';
import { Platform } from '../react';

const { browser } = JitsiMeetJS.util;

const DEFAULT_OPTIMAL_BROWSERS = [
    'chrome',
    'electron',
    'firefox',
    'nwjs'
];

const DEFAULT_UNSUPPORTED_BROWSERS = [];

const browserNameToCheck = {
    chrome: browser.isChrome.bind(browser),
    edge: browser.isEdge.bind(browser),
    electron: browser.isElectron.bind(browser),
    firefox: browser.isFirefox.bind(browser),
    nwjs: browser.isNWJS.bind(browser),
    opera: browser.isOpera.bind(browser),
    safari: browser.isSafari.bind(browser)
};

declare var interfaceConfig: Object;

/**
 * Returns whether or not jitsi is optimized and targeted for the  provided
 * browser name.
 *
 * @param {string} browserName - The name of the browser to check.
 * @returns {boolean}
 */
export function isBrowsersOptimal(browserName: string) {
    return (interfaceConfig.OPTIMAL_BROWSERS || DEFAULT_OPTIMAL_BROWSERS)
        .includes(browserName);
}

/**
 * Returns whether or not the current browser or the list of passed in browsers
 * is considered suboptimal. Suboptimal means it is a supported browser but has
 * not been explicitly listed as being optimal, possibly due to functionality
 * issues.
 *
 * @param {Array<string>} [browsers] - A list of browser names to check. Will
 * default to a whitelist.
 * @returns {boolean}
 */
export function isSuboptimalBrowser() {
    const optimalBrowsers
        = interfaceConfig.OPTIMAL_BROWSERS || DEFAULT_OPTIMAL_BROWSERS;

    return !_isCurrentBrowserInList(optimalBrowsers) && isSupportedBrowser();
}

/**
 * Returns whether or not the current browser should allow the app to display.
 * A supported browser is assumed to be able to support WebRtc.
 *
 * @returns {boolean}
 */
export function isSupportedBrowser() {
    if (navigator.product === 'ReactNative') {
        return false;
    }

    // Blacklists apply to desktop browsers only right now.
    if (!_isMobileBrowser() && _isCurrentBrowserInList(
        interfaceConfig.UNSUPPORTED_BROWSERS || DEFAULT_UNSUPPORTED_BROWSERS
    )) {
        return false;
    }

    // We are intentionally allow mobile browsers because:
    // - the WelcomePage is mobile ready;
    // - if the URL points to a conference then deep-linking will take
    //   care of it.
    return _isMobileBrowser() || JitsiMeetJS.isWebRtcSupported();
}

/**
 * Runs various browser checks to know if the current browser is found within
 * the list.
 *
 * @param {Array<string>} list - Browser names to check. The names should be
 * keys in {@link browserNameToCheck}.
 * @private
 * @returns {boolean}
 */
function _isCurrentBrowserInList(list) {
    return Boolean(list.find(browserName => {
        const checkFunction = browserNameToCheck[browserName];

        return checkFunction ? checkFunction.call(browser) : false;
    }));
}

/**
 * Returns whether or not the current environment is a mobile device.
 *
 * @private
 * @returns {boolean}
 */
function _isMobileBrowser() {
    return Platform.OS === 'android' || Platform.OS === 'ios';
}
<|endoftext|>