<commit_before>(function () {

    angular.module("references").factory("DocumentsServiceFactory", DocumentsServiceFactory);

    DocumentsServiceFactory.$inject = [
        '$rootScope',
        'Notification',
        'researchEntityService',
        'ModalService'
    ];

    function DocumentsServiceFactory($rootScope, Notification, researchEntityService, ModalService) {
        return {create: function (researchEntity) {
                var service = {};

                service.unverifyDocument = unverifyDocument;
                service.deleteDraft = deleteDraft;
                service.deleteDrafts = deleteDrafts;
                service.verifyDraft = verifyDraft;
                service.verifyDrafts = verifyDrafts;
                service.openEditPopup = openEditPopup;
                service.verifyDocument = verifyDocument;
                service.discardDocument = discardDocument;
                service.verifyDocuments = verifyDocuments;
                service.discardDocuments = discardDocuments;
                service.copyDocument = copyDocument;
                service.copyDocuments = copyDocuments;

                return service;

                function deleteDrafts(drafts) {
                    var draftIds = _.map(drafts, 'id');
                    researchEntityService
                            .deleteDrafts(researchEntity, draftIds)
                            .then(function (results) {
                                Notification.success(results.length + " draft(s) deleted");
                                $rootScope.$broadcast("draft.deleted", results);
                            })
                            .catch(function (err) {
                                Notification.warning("An error happened");
                            });
                }

                function deleteDraft(draft) {
                    researchEntityService
                            .deleteDraft(researchEntity, draft.id)
                            .then(function (d) {
                                Notification.success("Draft deleted");
                                $rootScope.$broadcast("draft.deleted", d);
                            })
                            .catch(function () {
                                Notification.warning("Failed to delete draft");
                            });
                }

                function verifyDrafts(drafts) {
                    var draftIds = _.map(drafts, 'id');
                    researchEntityService
                            .verifyDrafts(researchEntity, draftIds)
                            .then(function (allDocs) {
                                var partitions = _.partition(allDocs, 'draft');
                                var drafts = partitions[0];
                                var documents = partitions[1];
                                if (documents.length)
                                    Notification.success(documents.length + " draft(s) verified");
                                if (drafts.length)
                                    Notification.warning(drafts.length + " is/are not valid and cannot be verified");
                                $rootScope.$broadcast("draft.verified", documents);
                            })
                            .catch(function (err) {
                                $rootScope.$broadcast("draft.verified", []);
                                Notification.warning("An error happened");
                            });
                }

                function verifyDraft(draft) {
                    return researchEntityService.verifyDraft(researchEntity, draft)
                            .then(function (document) {
                                if (document.draft) {
                                    Notification.warning("Draft is not valid and cannot be verified");
                                }
                                else {
                                    Notification.success("Draft verified");
                                    $rootScope.$broadcast("draft.verified", document);
                                }
                            })
                            .catch(function () {
                                Notification.warning("Failed to verify draft");
                            });

                }

                function unverifyDocument(document) {
                    ModalService
                            .multipleChoiceConfirm('Unverifying', 'Do you want to unverify the document?', ['Create New Version', 'Unverify'])
                            .then(function (buttonIndex) {

                                researchEntityService.unverify(researchEntity, document)
                                        .then(function (draft) {
                                            delete draft.id;
                                            $rootScope.$broadcast('draft.unverified', {});
                                            switch (buttonIndex) {
                                                case 0:
                                                    researchEntityService
                                                            .copyDocument(researchEntity, draft)
                                                            .then(function (draft) {
                                                                $rootScope.$broadcast("draft.created", draft);
                                                                return draft;
                                                            })
                                                            .then(openEditPopup);
                                                    break;
                                                case 1:
                                                    Notification.success("Document succesfully unverified");
                                                    break;
                                            }

                                        })
                                        .catch(function () {
                                            Notification.warning("Failed to unverify document");
                                        });

                            })
                            .catch(function () {
                            });
                }

                function copyDocument(document) {
                    researchEntityService
                            .copyDocument(researchEntity, document)
                            .then(function (draft) {
                                Notification.success('Document copied');
                                $rootScope.$broadcast("draft.created", draft);
                                document.addTag('copied');
                                openEditPopup(document);
                            });
                }

                function copyDocuments(documents) {
                    var notCopiedCocuments = documents.filter(function (d) {
                        return !d.tags.includes('copied');
                    });
                    if (notCopiedCocuments.length === 0) {
                        Notification.success("No documents to copy");
                        return;
                    }
                    researchEntityService
                            .copyDocuments(researchEntity, notCopiedCocuments)
                            .then(function (drafts) {
                                Notification.success(drafts.length + " draft(s) created");
                                documents.forEach(function (d) {
                                    d.addTag('copied');
                                });
                                $rootScope.$broadcast("draft.created", drafts);
                            })
                            .catch(function (err) {
                                Notification.warning("An error happened");
                            });
                }

                function verifyDocument(document) {
                    //sTODO move to a service
                    researchEntityService
                            .verifyDocument(researchEntity, document.id)
                            .then(function () {
                                Notification.success('Document verified');
                                $rootScope.$broadcast("notification.accepted", document);
                            })
                            .catch(function () {
                                Notification.warning('Failed to verify document');
                            });
                }

                function verifyDocuments(documents) {
                    var documentIds = _.map(documents, 'id');
                    researchEntityService
                            .verifyDocuments(researchEntity, documentIds)
                            .then(function (allDocs) {
                                Notification.success(allDocs.length + " document(s) verified");
                                $rootScope.$broadcast("notification.accepted", documents);
                            })
                            .catch(function (err) {
                                Notification.warning("An error happened");
                            });
                }

                function discardDocument(document) {
                    researchEntityService
                            .discardDocument(researchEntity, document.id)
                            .then(function () {
                                Notification.success('Document discarded');
                                $rootScope.$broadcast("notification.discarded", document);
                            })
                            .catch(function () {
                                Notification.warning('Failed to discard document');
                            });
                }

                function discardDocuments(documents) {
                    var documentIds = _.map(documents, 'id');
                    researchEntityService
                            .discardDocuments(researchEntity, documentIds)
                            .then(function (results) {
                                var resultPartitioned = _.partition(results, function (o) {
                                    return o;
                                });
                                Notification.success(resultPartitioned[0].length + " document(s) discarded");
                                $rootScope.$broadcast("notification.discarded", documents);
                            })
                            .catch(function (err) {
                                Notification.warning("An error happened");
                            });
                }

                function openEditPopup(draft) {
                    ModalService
                            .openScientillaDocumentForm(draft.clone(), researchEntity);
                }
            }
        };
    }
}());
<commit_msg>Fixed bug on document copy action.<commit_after>(function () {

    angular.module("references").factory("DocumentsServiceFactory", DocumentsServiceFactory);

    DocumentsServiceFactory.$inject = [
        '$rootScope',
        'Notification',
        'researchEntityService',
        'ModalService'
    ];

    function DocumentsServiceFactory($rootScope, Notification, researchEntityService, ModalService) {
        return {create: function (researchEntity) {
                var service = {};

                service.unverifyDocument = unverifyDocument;
                service.deleteDraft = deleteDraft;
                service.deleteDrafts = deleteDrafts;
                service.verifyDraft = verifyDraft;
                service.verifyDrafts = verifyDrafts;
                service.openEditPopup = openEditPopup;
                service.verifyDocument = verifyDocument;
                service.discardDocument = discardDocument;
                service.verifyDocuments = verifyDocuments;
                service.discardDocuments = discardDocuments;
                service.copyDocument = copyDocument;
                service.copyDocuments = copyDocuments;

                return service;

                function deleteDrafts(drafts) {
                    var draftIds = _.map(drafts, 'id');
                    researchEntityService
                            .deleteDrafts(researchEntity, draftIds)
                            .then(function (results) {
                                Notification.success(results.length + " draft(s) deleted");
                                $rootScope.$broadcast("draft.deleted", results);
                            })
                            .catch(function (err) {
                                Notification.warning("An error happened");
                            });
                }

                function deleteDraft(draft) {
                    researchEntityService
                            .deleteDraft(researchEntity, draft.id)
                            .then(function (d) {
                                Notification.success("Draft deleted");
                                $rootScope.$broadcast("draft.deleted", d);
                            })
                            .catch(function () {
                                Notification.warning("Failed to delete draft");
                            });
                }

                function verifyDrafts(drafts) {
                    var draftIds = _.map(drafts, 'id');
                    researchEntityService
                            .verifyDrafts(researchEntity, draftIds)
                            .then(function (allDocs) {
                                var partitions = _.partition(allDocs, 'draft');
                                var drafts = partitions[0];
                                var documents = partitions[1];
                                if (documents.length)
                                    Notification.success(documents.length + " draft(s) verified");
                                if (drafts.length)
                                    Notification.warning(drafts.length + " is/are not valid and cannot be verified");
                                $rootScope.$broadcast("draft.verified", documents);
                            })
                            .catch(function (err) {
                                $rootScope.$broadcast("draft.verified", []);
                                Notification.warning("An error happened");
                            });
                }

                function verifyDraft(draft) {
                    return researchEntityService.verifyDraft(researchEntity, draft)
                            .then(function (document) {
                                if (document.draft) {
                                    Notification.warning("Draft is not valid and cannot be verified");
                                }
                                else {
                                    Notification.success("Draft verified");
                                    $rootScope.$broadcast("draft.verified", document);
                                }
                            })
                            .catch(function () {
                                Notification.warning("Failed to verify draft");
                            });

                }

                function unverifyDocument(document) {
                    ModalService
                            .multipleChoiceConfirm('Unverifying', 'Do you want to unverify the document?', ['Create New Version', 'Unverify'])
                            .then(function (buttonIndex) {

                                researchEntityService.unverify(researchEntity, document)
                                        .then(function (draft) {
                                            delete draft.id;
                                            $rootScope.$broadcast('draft.unverified', {});
                                            switch (buttonIndex) {
                                                case 0:
                                                    researchEntityService
                                                            .copyDocument(researchEntity, draft)
                                                            .then(function (draft) {
                                                                $rootScope.$broadcast("draft.created", draft);
                                                                return draft;
                                                            })
                                                            .then(openEditPopup);
                                                    break;
                                                case 1:
                                                    Notification.success("Document succesfully unverified");
                                                    break;
                                            }

                                        })
                                        .catch(function () {
                                            Notification.warning("Failed to unverify document");
                                        });

                            })
                            .catch(function () {
                            });
                }

                function copyDocument(document) {
                    researchEntityService
                            .copyDocument(researchEntity, document)
                            .then(function (draft) {
                                Notification.success('Document copied');
                                $rootScope.$broadcast("draft.created", draft);
                                document.addTag('copied');
                                openEditPopup(draft);
                            });
                }

                function copyDocuments(documents) {
                    var notCopiedCocuments = documents.filter(function (d) {
                        return !d.tags.includes('copied');
                    });
                    if (notCopiedCocuments.length === 0) {
                        Notification.success("No documents to copy");
                        return;
                    }
                    researchEntityService
                            .copyDocuments(researchEntity, notCopiedCocuments)
                            .then(function (drafts) {
                                Notification.success(drafts.length + " draft(s) created");
                                documents.forEach(function (d) {
                                    d.addTag('copied');
                                });
                                $rootScope.$broadcast("draft.created", drafts);
                            })
                            .catch(function (err) {
                                Notification.warning("An error happened");
                            });
                }

                function verifyDocument(document) {
                    //sTODO move to a service
                    researchEntityService
                            .verifyDocument(researchEntity, document.id)
                            .then(function () {
                                Notification.success('Document verified');
                                $rootScope.$broadcast("notification.accepted", document);
                            })
                            .catch(function () {
                                Notification.warning('Failed to verify document');
                            });
                }

                function verifyDocuments(documents) {
                    var documentIds = _.map(documents, 'id');
                    researchEntityService
                            .verifyDocuments(researchEntity, documentIds)
                            .then(function (allDocs) {
                                Notification.success(allDocs.length + " document(s) verified");
                                $rootScope.$broadcast("notification.accepted", documents);
                            })
                            .catch(function (err) {
                                Notification.warning("An error happened");
                            });
                }

                function discardDocument(document) {
                    researchEntityService
                            .discardDocument(researchEntity, document.id)
                            .then(function () {
                                Notification.success('Document discarded');
                                $rootScope.$broadcast("notification.discarded", document);
                            })
                            .catch(function () {
                                Notification.warning('Failed to discard document');
                            });
                }

                function discardDocuments(documents) {
                    var documentIds = _.map(documents, 'id');
                    researchEntityService
                            .discardDocuments(researchEntity, documentIds)
                            .then(function (results) {
                                var resultPartitioned = _.partition(results, function (o) {
                                    return o;
                                });
                                Notification.success(resultPartitioned[0].length + " document(s) discarded");
                                $rootScope.$broadcast("notification.discarded", documents);
                            })
                            .catch(function (err) {
                                Notification.warning("An error happened");
                            });
                }

                function openEditPopup(draft) {
                    ModalService
                            .openScientillaDocumentForm(draft.clone(), researchEntity);
                }
            }
        };
    }
}());
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');

/**@ngInject*/
function ProjectController($scope, $modal, $state, Project, project, alerts, projectQuestions) {
  this.project = project;
  this.$modal = $modal;

  $scope.project = this.project;
  $scope.alerts = alerts;
  $scope.questions = projectQuestions;

  $scope.editingProject = false;

  $scope.editProject = function(shouldEdit) {
    $scope.editingProject = shouldEdit;
  };

  $scope.updateProject = function() {
    var filteredProject = _.omit($scope.project, 'created_at', 'updated_at', 'deleted_at', 'id', 'services', 'domain',
        'url', 'state', 'state_ok', 'problem_count', 'account_number', 'resources', 'icon', 'cpu', 'hdd', 'ram',
        'status', 'users', 'order_history', 'description', 'cc', 'staff_id', 'approved', 'img', 'project_answers');

    if ($scope.project.project_answers.length > 0) {
      filteredProject.project_answers  = _.reduce($scope.project.project_answers,
          function(pas, pa){ pas.push(_.omit(pa, 'id', 'project_id', 'created_at', 'updated_at',
              'project_question')); return pas;}, []);
    }

    Project.update({id: $scope.project.id, project: filteredProject} , function() {
      $state.go('base.project', {id: $scope.project.id}, {reload: true});
    });
  };

  $scope.openProjectUsersModal = function () {

    var modalInstance = $modal.open({
      templateUrl: 'projects/users-modal.html',
      controller: 'ProjectUsersController'
    });

    modalInstance.result.then(function (selectedItems) {
    }, function () {
    });
  };
}

ProjectController.resolve = {
  /**@ngInject*/
  projectQuestions: function(DataService) {
    //todo: move this to a project questions resource.
    return DataService.getProjectQuestions().$promise;
  },
  /**@ngInject*/
  project: function(Project, $stateParams) {
    return Project.get({id: $stateParams.projectId}).$promise;
  },
  /**@ngInject*/
  alerts: function(DataService) {
    // @todo This should be switched to something that only returns alerts for the project.
    return DataService.getAlerts().$promise;
  }
};

ProjectController.prototype = {

  openAddServicesModal: function() {
    var modalInstance = this.$modal.open({
      templateUrl: 'projects/add-services-modal.html',
      controller: 'ProjectServicesController as projectServicesCtrl',
      size: 'lg',
      resolve: {
        /**@ngInject*/
        categories: function(ProductCategory) {
          return ProductCategory.query().$promise;
        },
        /**@ngInject*/
        products: function(Product) {
          return Product.query({"includes[]": ["cloud"]}).$promise;
        }
      }
    });
  },

  openAddUsersModal: function () {
    var modalInstance = this.$modal.open({
      templateUrl: 'projects/users-modal.html',
      controller: 'ProjectUsersController'
    });
  },

  getBudgetData: function() {
    var projectBudget = this.project.budget || 0;
    var usedBudget = 0;

    _.each(this.project.order_history, function(item, key, all) {
      // @todo In the future we can split this off to complete/pending
      usedBudget += item.total;

    });

    return {
      'total' : projectBudget,
      'used'    : usedBudget,
      'usedPercent' : Math.round(((usedBudget / projectBudget) * 100))
    };
  }
};

module.exports = ProjectController;
<commit_msg>Budget percent cleanup<commit_after>'use strict';

var _ = require('lodash');

/**@ngInject*/
function ProjectController($scope, $modal, $state, Project, project, alerts, projectQuestions) {
  this.project = project;
  this.$modal = $modal;

  $scope.project = this.project;
  $scope.alerts = alerts;
  $scope.questions = projectQuestions;

  $scope.editingProject = false;

  $scope.editProject = function(shouldEdit) {
    $scope.editingProject = shouldEdit;
  };

  $scope.updateProject = function() {
    var filteredProject = _.omit($scope.project, 'created_at', 'updated_at', 'deleted_at', 'id', 'services', 'domain',
        'url', 'state', 'state_ok', 'problem_count', 'account_number', 'resources', 'icon', 'cpu', 'hdd', 'ram',
        'status', 'users', 'order_history', 'description', 'cc', 'staff_id', 'approved', 'img', 'project_answers');

    if ($scope.project.project_answers.length > 0) {
      filteredProject.project_answers  = _.reduce($scope.project.project_answers,
          function(pas, pa){ pas.push(_.omit(pa, 'id', 'project_id', 'created_at', 'updated_at',
              'project_question')); return pas;}, []);
    }

    Project.update({id: $scope.project.id, project: filteredProject} , function() {
      $state.go('base.project', {id: $scope.project.id}, {reload: true});
    });
  };

  $scope.openProjectUsersModal = function () {

    var modalInstance = $modal.open({
      templateUrl: 'projects/users-modal.html',
      controller: 'ProjectUsersController'
    });

    modalInstance.result.then(function (selectedItems) {
    }, function () {
    });
  };
}

ProjectController.resolve = {
  /**@ngInject*/
  projectQuestions: function(DataService) {
    //todo: move this to a project questions resource.
    return DataService.getProjectQuestions().$promise;
  },
  /**@ngInject*/
  project: function(Project, $stateParams) {
    return Project.get({id: $stateParams.projectId}).$promise;
  },
  /**@ngInject*/
  alerts: function(DataService) {
    // @todo This should be switched to something that only returns alerts for the project.
    return DataService.getAlerts().$promise;
  }
};

ProjectController.prototype = {

  openAddServicesModal: function() {
    var modalInstance = this.$modal.open({
      templateUrl: 'projects/add-services-modal.html',
      controller: 'ProjectServicesController as projectServicesCtrl',
      size: 'lg',
      resolve: {
        /**@ngInject*/
        categories: function(ProductCategory) {
          return ProductCategory.query().$promise;
        },
        /**@ngInject*/
        products: function(Product) {
          return Product.query({"includes[]": ["cloud"]}).$promise;
        }
      }
    });
  },

  openAddUsersModal: function () {
    var modalInstance = this.$modal.open({
      templateUrl: 'projects/users-modal.html',
      controller: 'ProjectUsersController'
    });
  },

  getBudgetData: function() {
    var projectBudget = this.project.budget || 0;
    var usedBudget = 0;

    _.each(this.project.order_history, function(item, key, all) {
      // @todo In the future we can split this off to complete/pending
      usedBudget += item.total;

    });

    var usedPercent = 0;
    if (projectBudget > 0) {
      usedPercent = Math.round(((usedBudget / projectBudget) * 100));
      if (usedPercent > 100) {
        usedPercent = 100;
      }
    }

    return {
      'total' : projectBudget,
      'used'    : usedBudget,
      'usedPercent' : usedPercent
    };
  }
};

module.exports = ProjectController;
<|endoftext|>


<commit_before>/**
 * `withData` higher-order component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import castArray from 'lodash/castArray';
import memize from 'memize';

/**
 * WordPress dependencies
 */
import { addFilter, addAction, removeAction, removeFilter } from '@wordpress/hooks';
import { Component } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { getModulesData, stringifyObject } from '../../util';
import getNoDataComponent from '../legacy-notifications/nodata';
import getDataErrorComponent from '../legacy-notifications/data-error';
import getSetupIncompleteComponent, { getModuleInactiveComponent } from '../legacy-notifications/setup-incomplete';
import { TYPE_MODULES } from '../data/constants';
import ctaWrapper from '../legacy-notifications/cta-wrapper';
import { CORE_USER } from '../../googlesitekit/datastore/user/constants';
import { requestWithDateRange } from '../data/utils/request-with-date-range';
const { withSelect } = Data;

const hashRequests = memize( stringifyObject );

/**
 * Provides data from the API to components. (Legacy HOC.)
 *
 * A Higher order Component that provides data functionality to Components.
 *
 * This function takes a React Component that is data dependent, resolving via the data API.
 *
 * Automatically detects data errors, displaying an error CTA Component. Components can extend the default
 * error handling to enable custom error messaging or data shapes.
 *
 * Components can provide a callback that checks if the data is "zero" - typically when an account is newly established and not yet providing data. In most cases the API returns all 0s, however some APIs may return empty strings or null.
 *
 * Components can optionally include `handleDataError` and `handleDataSuccess` function as props. `handleDataError` will be
 * called with the error message string if there is a data error and called with no string if the data is empty.
 * `handleDataSuccess` will be called when data resolves correctly.
 *
 * @since 1.0.0
 *
 * @param {WPElement} DataDependentComponent The React Component to render once we have its required data.
 * @param {Array}     selectData             An array of data objects to resolve.
 *                                                 Each object includes the following properties:
 *                                                 {string}         type       The data type. Either 'core' or 'modules'.
 *                                                 {string}         identifier The data identifier, for example a module slug.
 *                                                 {string}         datapoint  The datapoint.
 *                                                 {Object?}        data       Optional arguments to pass along.
 *                                                 {number}         priority   The data request priority, used for batching.
 *                                                 {number}         maxAge     How long to cache the data results.
 *                                                 {string | array} context    The context(s) to resolve data, eg 'Dashboard'.
 *
 * @param {WPElement} loadingComponent       A React Component to render while the data is resolving.
 * @param {Object}    layoutOptions          An object with layout options that are passed to the getNoDataComponent and getDataErrorComponent components.
 * @param {Function}  isDataZero             A callback function that is passed the resolved data and returns true
 *                                                 if the data is "zero".
 * @param {Function}  getDataError           A callback function that is passed the resolved data and returns the
 *                                                 error message.
 * @return {WPElement} Component  	Returns React.Components based on data and state.
 *                                  If has data  	  Return DataDependentComponent with data.
 *                                  has no data		  Fallback message when no data.
 *                                  in loading state  Return loadingComponent.
 *                                  has an error	  Returns error.
 *
 */
const withData = (
	DataDependentComponent,
	selectData,
	loadingComponent = null,

	layoutOptions = {
		inGrid: false,
		fullWidth: false,
		createGrid: false,
	},

	// The default isDataZero handler always returns false, Components must define when data is zero.
	// `isDataZero` is passed `returnedData`and `datapoint`.
	isDataZero = () => {
		return false;
	},

	// The default getDataError handler detects data.error and extracts the message from data.error.message or data.error.errors[0].message.
	getDataError = ( data ) => {
		if ( data && data.error ) {
			if ( data.error.message ) {
				return data.error.message;
			}
			if ( data.error.errors && data.error.errors[ 0 ] && data.error.errors[ 0 ].message ) {
				return data.error.errors[ 0 ].message;
			}
			return __( 'Unidentified error', 'google-site-kit' );
		}

		if ( data && data.errors && data.errors[ 0 ] && data.errors[ 0 ].message ) {
			return data.errors[ 0 ].message;
		}

		if ( data && data.error_data ) {
			const errors = Object.values( data.error_data );

			// Catch RateLimitExceeded specifically.
			if ( errors[ 0 ] && 'RateLimitExceeded' === errors[ 0 ].reason ) {
				return __( 'Too many requests have been sent within a given time span. Please reload this page again in a few seconds', 'google-site-kit' );
			}
		}

		if ( data && data.errors ) {
			const errors = Object.values( data.errors );
			if ( errors[ 0 ] && errors[ 0 ][ 0 ] ) {
				return errors[ 0 ][ 0 ];
			}
		}

		// If error is the root of the response, ensure all expected parts are
		// present, just to "be sure" that it is an error. All above error
		// handlers are legacy and are likely never hit, but let's keep them
		// because nobody will ever know.
		if ( data.code && data.message && data.data && data.data.status ) {
			return data.message;
		}

		// No error.
		return false;
	}
) => {
	/**
	 * Map of data requests by context.
	 *
	 * @since n.e.x.t
	 *
	 * @type {Object.<string, Object[]>}
	 */
	const dataRequestsByContext = selectData.reduce(
		( acc, dataRequest ) => {
			castArray( dataRequest.context ).forEach( ( context ) => {
				acc[ context ] = acc[ context ] || [];
				acc[ context ].push( dataRequest );
			} );
			return acc;
		},
		{}
	);
	// ...and returns another component...
	class NewComponent extends Component {
		constructor( props ) {
			super( props );

			this.state = {
				data: false,
				zeroData: false,
				errorMessage: false,
				moduleRequiringSetup: '',
			};

			this.handleModuleDataReset = this.handleModuleDataReset.bind( this );
			this.handleReturnedData = this.handleReturnedData.bind( this );
			this.addDataRequests = this.addDataRequests.bind( this );
			this.removeDataRequests = this.removeDataRequests.bind( this );
		}

		componentDidMount() {
			addAction(
				'googlesitekit.moduleDataReset',
				'googlesitekit.moduleDataResetHandler',
				this.handleModuleDataReset
			);

			this.addDataRequests();
		}

		componentWillUnmount() {
			removeAction(
				'googlesitekit.moduleDataReset',
				'googlesitekit.moduleDataResetHandler',
				this.handleModuleDataReset
			);

			this.removeDataRequests();
		}

		handleModuleDataReset() {
			// When the global dateRange changes, it will trigger the googlesitekit.moduleDataReset action.
			// When this happens, we need to re-hook the requests for the default date range to be applied correctly.
			this.removeDataRequests();
			this.addDataRequests();

			this.setState( {
				data: false,
				errorMessage: false,
				zeroData: false,
			} );
		}

		addDataRequests() {
			const { dateRange } = this.props;

			Object.entries( dataRequestsByContext ).forEach(
				( [ context, dataRequests ] ) => {
					addFilter(
						`googlesitekit.module${ context }DataRequest`,
						`googlesitekit.withData.${ hashRequests( dataRequests ) }`,
						( contextRequests ) => {
							const modulesData = getModulesData();
							const requestsToAdd = [];
							for ( const dataRequest of dataRequests ) {
								const { type, identifier } = dataRequest || {};
								// If a dataRequest's module requires setup, set it in the state.
								// This will cause the setup incomplete component to be rendered in all cases.
								if ( TYPE_MODULES === type && ! modulesData[ identifier ]?.setupComplete ) {
									this.setState( {
										moduleRequiringSetup: identifier,
										moduleRequiringActivation: ! modulesData[ identifier ]?.active,
									} );
									continue;
								}

								// Apply default date range if not set.
								const request = requestWithDateRange( dataRequest, dateRange );
								request.callback = ( returnedData ) => {
									this.handleReturnedData( returnedData, dataRequest );
								};
								requestsToAdd.push( request );
							}
							return contextRequests.concat( requestsToAdd );
						}
					);
				}
			);
		}

		removeDataRequests() {
			Object.entries( dataRequestsByContext ).forEach(
				( [ context, dataRequests ] ) => {
					removeFilter(
						`googlesitekit.module${ context }DataRequest`,
						`googlesitekit.withData.${ hashRequests( dataRequests ) }`,
					);
				}
			);
		}

		/**
		 * Handles a single datapoint returned from the data API.
		 *
		 * Each resolved data point is passed thru this handler to detect errors and zero data conditions, and
		 * to trigger `handleDataError` and `handleDataSuccess` helpers.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} returnedData The data returned from the API.
		 * @param {Object} requestData  The data object for the request.
		 */
		handleReturnedData( returnedData, requestData ) {
			// If available, `handleDataError` will be called for errors (with a string) and empty data.
			const {
				handleDataError,
				handleDataSuccess,
			} = this.props;
			const { datapoint, identifier, toState } = requestData;

			// Check to see if the returned data is an error. If so, getDataError will return a string.
			const errorMessage = getDataError( returnedData );
			if ( errorMessage ) {
				// Set an error state on the Component.
				this.setState( {
					errorMessage,
					errorObj: returnedData,
					module: identifier,
				} );

				// If the Component included a `handleDataError` helper, pass it the error message.
				if ( handleDataError ) {
					handleDataError( errorMessage, returnedData );
				}
			} else if ( isDataZero( returnedData, datapoint, requestData ) ) { // No data error, next check for zero data.
				// If we have a `handleDataError` call it without any parameters (indicating empty data).
				if ( handleDataError ) {
					handleDataError( errorMessage, returnedData );
				}

				// Set a zeroData state on the Component.
				this.setState( { zeroData: true } );
			} else if ( handleDataSuccess ) {
				// Success! `handleDataSuccess` will be called (ie. not error or zero).
				handleDataSuccess();
			}

			// Resolve the returned data by setting state on the Component.
			this.setState( {
				requestDataToState: toState,
				data: returnedData,
				datapoint,
				module: identifier,
			} );
		}

		render() {
			const {
				data,
				datapoint,
				module,
				zeroData,
				errorMessage,
				errorObj,
				requestDataToState,
				moduleRequiringSetup,
				moduleRequiringActivation,
			} = this.state;
			const { ActivateModuleComponent } = this.props;

			if ( moduleRequiringActivation ) {
				if ( ActivateModuleComponent ) {
					return ctaWrapper( <ActivateModuleComponent />, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
				}
				return getModuleInactiveComponent( moduleRequiringSetup, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			if ( moduleRequiringSetup ) {
				return getSetupIncompleteComponent( moduleRequiringSetup, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			// Render the loading component until we have data.
			if ( ! data ) {
				return loadingComponent;
			}

			// If we have an error, display the DataErrorComponent.
			if ( errorMessage ) {
				return ( 'string' !== typeof errorMessage ) ? errorMessage : getDataErrorComponent( module, errorMessage, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid, errorObj );
			}

			// If we have zeroData, display the NoDataComponent.
			if ( zeroData ) {
				const moduleName = getModulesData()[ module ]?.name || __( 'Site Kit', 'google-site-kit' );

				return getNoDataComponent( moduleName, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			// Render the Component when we have data, passing the datapoint.
			return (
				<DataDependentComponent
					data={ data }
					datapoint={ datapoint }
					requestDataToState={ requestDataToState }
					{ ...this.props }
				/>
			);
		}
	}

	const displayName = DataDependentComponent.displayName || DataDependentComponent.name || 'AnonymousComponent';
	NewComponent.displayName = `withData(${ displayName })`;

	return withSelect( ( select ) => {
		return {
			dateRange: select( CORE_USER ).getDateRange(),
		};
	} )( NewComponent );
};

export default withData;
<commit_msg>Remove unused ActivateModuleComponent prop.<commit_after>/**
 * `withData` higher-order component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import castArray from 'lodash/castArray';
import memize from 'memize';

/**
 * WordPress dependencies
 */
import { addFilter, addAction, removeAction, removeFilter } from '@wordpress/hooks';
import { Component } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { getModulesData, stringifyObject } from '../../util';
import getNoDataComponent from '../legacy-notifications/nodata';
import getDataErrorComponent from '../legacy-notifications/data-error';
import getSetupIncompleteComponent, { getModuleInactiveComponent } from '../legacy-notifications/setup-incomplete';
import { TYPE_MODULES } from '../data/constants';
import { CORE_USER } from '../../googlesitekit/datastore/user/constants';
import { requestWithDateRange } from '../data/utils/request-with-date-range';
const { withSelect } = Data;

const hashRequests = memize( stringifyObject );

/**
 * Provides data from the API to components. (Legacy HOC.)
 *
 * A Higher order Component that provides data functionality to Components.
 *
 * This function takes a React Component that is data dependent, resolving via the data API.
 *
 * Automatically detects data errors, displaying an error CTA Component. Components can extend the default
 * error handling to enable custom error messaging or data shapes.
 *
 * Components can provide a callback that checks if the data is "zero" - typically when an account is newly established and not yet providing data. In most cases the API returns all 0s, however some APIs may return empty strings or null.
 *
 * Components can optionally include `handleDataError` and `handleDataSuccess` function as props. `handleDataError` will be
 * called with the error message string if there is a data error and called with no string if the data is empty.
 * `handleDataSuccess` will be called when data resolves correctly.
 *
 * @since 1.0.0
 *
 * @param {WPElement} DataDependentComponent The React Component to render once we have its required data.
 * @param {Array}     selectData             An array of data objects to resolve.
 *                                                 Each object includes the following properties:
 *                                                 {string}         type       The data type. Either 'core' or 'modules'.
 *                                                 {string}         identifier The data identifier, for example a module slug.
 *                                                 {string}         datapoint  The datapoint.
 *                                                 {Object?}        data       Optional arguments to pass along.
 *                                                 {number}         priority   The data request priority, used for batching.
 *                                                 {number}         maxAge     How long to cache the data results.
 *                                                 {string | array} context    The context(s) to resolve data, eg 'Dashboard'.
 *
 * @param {WPElement} loadingComponent       A React Component to render while the data is resolving.
 * @param {Object}    layoutOptions          An object with layout options that are passed to the getNoDataComponent and getDataErrorComponent components.
 * @param {Function}  isDataZero             A callback function that is passed the resolved data and returns true
 *                                                 if the data is "zero".
 * @param {Function}  getDataError           A callback function that is passed the resolved data and returns the
 *                                                 error message.
 * @return {WPElement} Component  	Returns React.Components based on data and state.
 *                                  If has data  	  Return DataDependentComponent with data.
 *                                  has no data		  Fallback message when no data.
 *                                  in loading state  Return loadingComponent.
 *                                  has an error	  Returns error.
 *
 */
const withData = (
	DataDependentComponent,
	selectData,
	loadingComponent = null,

	layoutOptions = {
		inGrid: false,
		fullWidth: false,
		createGrid: false,
	},

	// The default isDataZero handler always returns false, Components must define when data is zero.
	// `isDataZero` is passed `returnedData`and `datapoint`.
	isDataZero = () => {
		return false;
	},

	// The default getDataError handler detects data.error and extracts the message from data.error.message or data.error.errors[0].message.
	getDataError = ( data ) => {
		if ( data && data.error ) {
			if ( data.error.message ) {
				return data.error.message;
			}
			if ( data.error.errors && data.error.errors[ 0 ] && data.error.errors[ 0 ].message ) {
				return data.error.errors[ 0 ].message;
			}
			return __( 'Unidentified error', 'google-site-kit' );
		}

		if ( data && data.errors && data.errors[ 0 ] && data.errors[ 0 ].message ) {
			return data.errors[ 0 ].message;
		}

		if ( data && data.error_data ) {
			const errors = Object.values( data.error_data );

			// Catch RateLimitExceeded specifically.
			if ( errors[ 0 ] && 'RateLimitExceeded' === errors[ 0 ].reason ) {
				return __( 'Too many requests have been sent within a given time span. Please reload this page again in a few seconds', 'google-site-kit' );
			}
		}

		if ( data && data.errors ) {
			const errors = Object.values( data.errors );
			if ( errors[ 0 ] && errors[ 0 ][ 0 ] ) {
				return errors[ 0 ][ 0 ];
			}
		}

		// If error is the root of the response, ensure all expected parts are
		// present, just to "be sure" that it is an error. All above error
		// handlers are legacy and are likely never hit, but let's keep them
		// because nobody will ever know.
		if ( data.code && data.message && data.data && data.data.status ) {
			return data.message;
		}

		// No error.
		return false;
	}
) => {
	/**
	 * Map of data requests by context.
	 *
	 * @since n.e.x.t
	 *
	 * @type {Object.<string, Object[]>}
	 */
	const dataRequestsByContext = selectData.reduce(
		( acc, dataRequest ) => {
			castArray( dataRequest.context ).forEach( ( context ) => {
				acc[ context ] = acc[ context ] || [];
				acc[ context ].push( dataRequest );
			} );
			return acc;
		},
		{}
	);
	// ...and returns another component...
	class NewComponent extends Component {
		constructor( props ) {
			super( props );

			this.state = {
				data: false,
				zeroData: false,
				errorMessage: false,
				moduleRequiringSetup: '',
			};

			this.handleModuleDataReset = this.handleModuleDataReset.bind( this );
			this.handleReturnedData = this.handleReturnedData.bind( this );
			this.addDataRequests = this.addDataRequests.bind( this );
			this.removeDataRequests = this.removeDataRequests.bind( this );
		}

		componentDidMount() {
			addAction(
				'googlesitekit.moduleDataReset',
				'googlesitekit.moduleDataResetHandler',
				this.handleModuleDataReset
			);

			this.addDataRequests();
		}

		componentWillUnmount() {
			removeAction(
				'googlesitekit.moduleDataReset',
				'googlesitekit.moduleDataResetHandler',
				this.handleModuleDataReset
			);

			this.removeDataRequests();
		}

		handleModuleDataReset() {
			// When the global dateRange changes, it will trigger the googlesitekit.moduleDataReset action.
			// When this happens, we need to re-hook the requests for the default date range to be applied correctly.
			this.removeDataRequests();
			this.addDataRequests();

			this.setState( {
				data: false,
				errorMessage: false,
				zeroData: false,
			} );
		}

		addDataRequests() {
			const { dateRange } = this.props;

			Object.entries( dataRequestsByContext ).forEach(
				( [ context, dataRequests ] ) => {
					addFilter(
						`googlesitekit.module${ context }DataRequest`,
						`googlesitekit.withData.${ hashRequests( dataRequests ) }`,
						( contextRequests ) => {
							const modulesData = getModulesData();
							const requestsToAdd = [];
							for ( const dataRequest of dataRequests ) {
								const { type, identifier } = dataRequest || {};
								// If a dataRequest's module requires setup, set it in the state.
								// This will cause the setup incomplete component to be rendered in all cases.
								if ( TYPE_MODULES === type && ! modulesData[ identifier ]?.setupComplete ) {
									this.setState( {
										moduleRequiringSetup: identifier,
										moduleRequiringActivation: ! modulesData[ identifier ]?.active,
									} );
									continue;
								}

								// Apply default date range if not set.
								const request = requestWithDateRange( dataRequest, dateRange );
								request.callback = ( returnedData ) => {
									this.handleReturnedData( returnedData, dataRequest );
								};
								requestsToAdd.push( request );
							}
							return contextRequests.concat( requestsToAdd );
						}
					);
				}
			);
		}

		removeDataRequests() {
			Object.entries( dataRequestsByContext ).forEach(
				( [ context, dataRequests ] ) => {
					removeFilter(
						`googlesitekit.module${ context }DataRequest`,
						`googlesitekit.withData.${ hashRequests( dataRequests ) }`,
					);
				}
			);
		}

		/**
		 * Handles a single datapoint returned from the data API.
		 *
		 * Each resolved data point is passed thru this handler to detect errors and zero data conditions, and
		 * to trigger `handleDataError` and `handleDataSuccess` helpers.
		 *
		 * @since 1.0.0
		 *
		 * @param {Object} returnedData The data returned from the API.
		 * @param {Object} requestData  The data object for the request.
		 */
		handleReturnedData( returnedData, requestData ) {
			// If available, `handleDataError` will be called for errors (with a string) and empty data.
			const {
				handleDataError,
				handleDataSuccess,
			} = this.props;
			const { datapoint, identifier, toState } = requestData;

			// Check to see if the returned data is an error. If so, getDataError will return a string.
			const errorMessage = getDataError( returnedData );
			if ( errorMessage ) {
				// Set an error state on the Component.
				this.setState( {
					errorMessage,
					errorObj: returnedData,
					module: identifier,
				} );

				// If the Component included a `handleDataError` helper, pass it the error message.
				if ( handleDataError ) {
					handleDataError( errorMessage, returnedData );
				}
			} else if ( isDataZero( returnedData, datapoint, requestData ) ) { // No data error, next check for zero data.
				// If we have a `handleDataError` call it without any parameters (indicating empty data).
				if ( handleDataError ) {
					handleDataError( errorMessage, returnedData );
				}

				// Set a zeroData state on the Component.
				this.setState( { zeroData: true } );
			} else if ( handleDataSuccess ) {
				// Success! `handleDataSuccess` will be called (ie. not error or zero).
				handleDataSuccess();
			}

			// Resolve the returned data by setting state on the Component.
			this.setState( {
				requestDataToState: toState,
				data: returnedData,
				datapoint,
				module: identifier,
			} );
		}

		render() {
			const {
				data,
				datapoint,
				module,
				zeroData,
				errorMessage,
				errorObj,
				requestDataToState,
				moduleRequiringSetup,
				moduleRequiringActivation,
			} = this.state;

			if ( moduleRequiringActivation ) {
				return getModuleInactiveComponent( moduleRequiringSetup, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			if ( moduleRequiringSetup ) {
				return getSetupIncompleteComponent( moduleRequiringSetup, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			// Render the loading component until we have data.
			if ( ! data ) {
				return loadingComponent;
			}

			// If we have an error, display the DataErrorComponent.
			if ( errorMessage ) {
				return ( 'string' !== typeof errorMessage ) ? errorMessage : getDataErrorComponent( module, errorMessage, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid, errorObj );
			}

			// If we have zeroData, display the NoDataComponent.
			if ( zeroData ) {
				const moduleName = getModulesData()[ module ]?.name || __( 'Site Kit', 'google-site-kit' );

				return getNoDataComponent( moduleName, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			// Render the Component when we have data, passing the datapoint.
			return (
				<DataDependentComponent
					data={ data }
					datapoint={ datapoint }
					requestDataToState={ requestDataToState }
					{ ...this.props }
				/>
			);
		}
	}

	const displayName = DataDependentComponent.displayName || DataDependentComponent.name || 'AnonymousComponent';
	NewComponent.displayName = `withData(${ displayName })`;

	return withSelect( ( select ) => {
		return {
			dateRange: select( CORE_USER ).getDateRange(),
		};
	} )( NewComponent );
};

export default withData;
<|endoftext|>


<commit_before>var logger = require("jitsi-meet-logger").getLogger(__filename);
var AuthUtil = require("./modules/util/AuthUtil");
var JitsiConnection = require("./JitsiConnection");
var JitsiMediaDevices = require("./JitsiMediaDevices");
var JitsiConferenceEvents = require("./JitsiConferenceEvents");
var JitsiConnectionEvents = require("./JitsiConnectionEvents");
var JitsiMediaDevicesEvents = require('./JitsiMediaDevicesEvents');
var JitsiConnectionErrors = require("./JitsiConnectionErrors");
var JitsiConferenceErrors = require("./JitsiConferenceErrors");
var JitsiTrackEvents = require("./JitsiTrackEvents");
var JitsiTrackErrors = require("./JitsiTrackErrors");
var JitsiTrackError = require("./JitsiTrackError");
var JitsiRecorderErrors = require("./JitsiRecorderErrors");
var Logger = require("jitsi-meet-logger");
var MediaType = require("./service/RTC/MediaType");
var RTC = require("./modules/RTC/RTC");
var RTCUIHelper = require("./modules/RTC/RTCUIHelper");
var Statistics = require("./modules/statistics/statistics");
var Resolutions = require("./service/RTC/Resolutions");
var ScriptUtil = require("./modules/util/ScriptUtil");
var GlobalOnErrorHandler = require("./modules/util/GlobalOnErrorHandler");
var RTCBrowserType = require("./modules/RTC/RTCBrowserType");

// The amount of time to wait until firing
// JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN event
var USER_MEDIA_PERMISSION_PROMPT_TIMEOUT = 500;

function getLowerResolution(resolution) {
    if(!Resolutions[resolution])
        return null;
    var order = Resolutions[resolution].order;
    var res = null;
    var resName = null;
    for(var i in Resolutions) {
        var tmp = Resolutions[i];
        if (!res || (res.order < tmp.order && tmp.order < order)) {
            resName = i;
            res = tmp;
        }
    }
    return resName;
}

/**
 * Namespace for the interface of Jitsi Meet Library.
 */
var LibJitsiMeet = {

    version: '{#COMMIT_HASH#}',

    JitsiConnection: JitsiConnection,
    events: {
        conference: JitsiConferenceEvents,
        connection: JitsiConnectionEvents,
        track: JitsiTrackEvents,
        mediaDevices: JitsiMediaDevicesEvents
    },
    errors: {
        conference: JitsiConferenceErrors,
        connection: JitsiConnectionErrors,
        recorder: JitsiRecorderErrors,
        track: JitsiTrackErrors
    },
    errorTypes: {
        JitsiTrackError: JitsiTrackError
    },
    logLevels: Logger.levels,
    mediaDevices: JitsiMediaDevices,
    analytics: null,
    init: function (options) {
        var logObject, attr;
        Statistics.init(options);
        this.analytics = Statistics.analytics;

        if (options.enableWindowOnErrorHandler) {
            GlobalOnErrorHandler.addHandler(
                this.getGlobalOnErrorHandler.bind(this));
        }

        // Log deployment-specific information, if available.
        if (window.jitsiRegionInfo
            && Object.keys(window.jitsiRegionInfo).length > 0) {
            logObject = {};
            for (attr in window.jitsiRegionInfo) {
                if (window.jitsiRegionInfo.hasOwnProperty(attr)) {
                    logObject[attr] = window.jitsiRegionInfo[attr];
                }
            }

            logObject.id = "deployment_info";
            Statistics.sendLog(JSON.stringify(logObject));
        }

        if(this.version) {
            logObject = {
                id: "component_version",
                component: "lib-jitsi-meet",
                version: this.version
            }
            Statistics.sendLog(JSON.stringify(logObject));
        }

        return RTC.init(options || {});
    },
    /**
     * Returns whether the desktop sharing is enabled or not.
     * @returns {boolean}
     */
    isDesktopSharingEnabled: function () {
        return RTC.isDesktopSharingEnabled();
    },
    setLogLevel: function (level) {
        Logger.setLogLevel(level);
    },
    /**
     * Creates the media tracks and returns them trough the callback.
     * @param options Object with properties / settings specifying the tracks which should be created.
     * should be created or some additional configurations about resolution for example.
     * @param {Array} options.devices the devices that will be requested
     * @param {string} options.resolution resolution constraints
     * @param {bool} options.dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
     * type: "audio" or "video", videoType: "camera" or "desktop"}
     * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
     * @param {string} options.cameraDeviceId
     * @param {string} options.micDeviceId
     * @param {object} options.desktopSharingExtensionExternalInstallation -
     * enables external installation process for desktop sharing extension if
     * the inline installation is not posible. The following properties should
     * be provided:
     * @param {intiger} interval - the interval (in ms) for
     * checking whether the desktop sharing extension is installed or not
     * @param {Function} checkAgain - returns boolean. While checkAgain()==true
     * createLocalTracks will wait and check on every "interval" ms for the
     * extension. If the desktop extension is not install and checkAgain()==true
     * createLocalTracks will finish with rejected Promise.
     * @param {Function} listener - The listener will be called to notify the
     * user of lib-jitsi-meet that createLocalTracks is starting external
     * extension installation process.
     * NOTE: If the inline installation process is not possible and external
     * installation is enabled the listener property will be called to notify
     * the start of external installation process. After that createLocalTracks
     * will start to check for the extension on every interval ms until the
     * plugin is installed or until checkAgain return false. If the extension
     * is found createLocalTracks will try to get the desktop sharing track and
     * will finish the execution. If checkAgain returns false, createLocalTracks
     * will finish the execution with rejected Promise.
     *
     * @param {boolean} (firePermissionPromptIsShownEvent) - if event
     *      JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN should be fired
     * @returns {Promise.<{Array.<JitsiTrack>}, JitsiConferenceError>}
     *     A promise that returns an array of created JitsiTracks if resolved,
     *     or a JitsiConferenceError if rejected.
     */
    createLocalTracks: function (options, firePermissionPromptIsShownEvent) {
        var promiseFulfilled = false;

        if (firePermissionPromptIsShownEvent === true) {
            window.setTimeout(function () {
                if (!promiseFulfilled) {
                    JitsiMediaDevices.emitEvent(
                        JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
                        RTCBrowserType.getBrowserName());
                }
            }, USER_MEDIA_PERMISSION_PROMPT_TIMEOUT);
        }

        if(!window.connectionTimes)
            window.connectionTimes = {};
        window.connectionTimes["obtainPermissions.start"] =
            window.performance.now();

        return RTC.obtainAudioAndVideoPermissions(options || {})
            .then(function(tracks) {
                promiseFulfilled = true;

                window.connectionTimes["obtainPermissions.end"] =
                    window.performance.now();

                if(!RTC.options.disableAudioLevels)
                    for(var i = 0; i < tracks.length; i++) {
                        var track = tracks[i];
                        var mStream = track.getOriginalStream();
                        if(track.getType() === MediaType.AUDIO){
                            Statistics.startLocalStats(mStream,
                                track.setAudioLevel.bind(track));
                            track.addEventListener(
                                JitsiTrackEvents.LOCAL_TRACK_STOPPED,
                                function(){
                                    Statistics.stopLocalStats(mStream);
                                });
                        }
                    }

                return tracks;
            }).catch(function (error) {
                promiseFulfilled = true;

                if(error.name === JitsiTrackErrors.UNSUPPORTED_RESOLUTION) {
                    var oldResolution = options.resolution || '360',
                        newResolution = getLowerResolution(oldResolution);

                    if (newResolution !== null) {
                        options.resolution = newResolution;

                        logger.debug("Retry createLocalTracks with resolution",
                            newResolution);

                        return LibJitsiMeet.createLocalTracks(options);
                    }
                }

                if (JitsiTrackErrors.CHROME_EXTENSION_USER_CANCELED ===
                        error.name) {
                    // User cancelled action is not really an error, so only
                    // log it as an event to avoid having conference classified
                    // as partially failed
                    var logObject = {
                        id: "chrome_extension_user_canceled",
                        message: error.message
                    };
                    Statistics.sendLog(JSON.stringify(logObject));
                } else {
                    // Report gUM failed to the stats
                    Statistics.sendGetUserMediaFailed(error);
                }

                window.connectionTimes["obtainPermissions.end"] =
                    window.performance.now();

                return Promise.reject(error);
            }.bind(this));
    },
    /**
     * Checks if its possible to enumerate available cameras/micropones.
     * @returns {Promise<boolean>} a Promise which will be resolved only once
     * the WebRTC stack is ready, either with true if the device listing is
     * available available or with false otherwise.
     * @deprecated use JitsiMeetJS.mediaDevices.isDeviceListAvailable instead
     */
    isDeviceListAvailable: function () {
        logger.warn('This method is deprecated, use ' +
            'JitsiMeetJS.mediaDevices.isDeviceListAvailable instead');
        return this.mediaDevices.isDeviceListAvailable();
    },
    /**
     * Returns true if changing the input (camera / microphone) or output
     * (audio) device is supported and false if not.
     * @params {string} [deviceType] - type of device to change. Default is
     *      undefined or 'input', 'output' - for audio output device change.
     * @returns {boolean} true if available, false otherwise.
     * @deprecated use JitsiMeetJS.mediaDevices.isDeviceChangeAvailable instead
     */
    isDeviceChangeAvailable: function (deviceType) {
        logger.warn('This method is deprecated, use ' +
            'JitsiMeetJS.mediaDevices.isDeviceChangeAvailable instead');
        return this.mediaDevices.isDeviceChangeAvailable(deviceType);
    },
    /**
     * Executes callback with list of media devices connected.
     * @param {function} callback
     * @deprecated use JitsiMeetJS.mediaDevices.enumerateDevices instead
     */
    enumerateDevices: function (callback) {
        logger.warn('This method is deprecated, use ' +
            'JitsiMeetJS.mediaDevices.enumerateDevices instead');
        this.mediaDevices.enumerateDevices(callback);
    },
    /**
     * @returns function that can be used to be attached to window.onerror and
     * if options.enableWindowOnErrorHandler is enabled returns
     * the function used by the lib.
     * (function(message, source, lineno, colno, error)).
     */
    getGlobalOnErrorHandler: function (message, source, lineno, colno, error) {
        logger.error(
            'UnhandledError: ' + message,
            'Script: ' + source,
            'Line: ' + lineno,
            'Column: ' + colno,
            'StackTrace: ', error);
        Statistics.reportGlobalError(error);
    },

    /**
     * Represents a hub/namespace for utility functionality which may be of
     * interest to LibJitsiMeet clients.
     */
    util: {
        ScriptUtil: ScriptUtil,
        RTCUIHelper: RTCUIHelper,
        AuthUtil: AuthUtil
    }
};

//Setups the promise object.
window.Promise = window.Promise || require("es6-promise").Promise;

module.exports = LibJitsiMeet;
<commit_msg>Adds some analytics events for getusermedia success or fail.<commit_after>var logger = require("jitsi-meet-logger").getLogger(__filename);
var AuthUtil = require("./modules/util/AuthUtil");
var JitsiConnection = require("./JitsiConnection");
var JitsiMediaDevices = require("./JitsiMediaDevices");
var JitsiConferenceEvents = require("./JitsiConferenceEvents");
var JitsiConnectionEvents = require("./JitsiConnectionEvents");
var JitsiMediaDevicesEvents = require('./JitsiMediaDevicesEvents');
var JitsiConnectionErrors = require("./JitsiConnectionErrors");
var JitsiConferenceErrors = require("./JitsiConferenceErrors");
var JitsiTrackEvents = require("./JitsiTrackEvents");
var JitsiTrackErrors = require("./JitsiTrackErrors");
var JitsiTrackError = require("./JitsiTrackError");
var JitsiRecorderErrors = require("./JitsiRecorderErrors");
var Logger = require("jitsi-meet-logger");
var MediaType = require("./service/RTC/MediaType");
var RTC = require("./modules/RTC/RTC");
var RTCUIHelper = require("./modules/RTC/RTCUIHelper");
var Statistics = require("./modules/statistics/statistics");
var Resolutions = require("./service/RTC/Resolutions");
var ScriptUtil = require("./modules/util/ScriptUtil");
var GlobalOnErrorHandler = require("./modules/util/GlobalOnErrorHandler");
var RTCBrowserType = require("./modules/RTC/RTCBrowserType");

// The amount of time to wait until firing
// JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN event
var USER_MEDIA_PERMISSION_PROMPT_TIMEOUT = 500;

function getLowerResolution(resolution) {
    if(!Resolutions[resolution])
        return null;
    var order = Resolutions[resolution].order;
    var res = null;
    var resName = null;
    for(var i in Resolutions) {
        var tmp = Resolutions[i];
        if (!res || (res.order < tmp.order && tmp.order < order)) {
            resName = i;
            res = tmp;
        }
    }
    return resName;
}

/**
 * Checks the available devices in options and concatenate the data to the
 * name, which will be used as analytics event name. Adds resolution for the
 * devices.
 * @param name name of event
 * @param options gum options
 * @returns {*}
 */
function addDeviceTypeToAnalyticsEvent(name, options) {
    if (options.devices.indexOf("audio") !== -1) {
        name += ".audio";
    }
    if (options.devices.indexOf("desktop") !== -1) {
        name += ".desktop";
    }
    if (options.devices.indexOf("video") !== -1) {
        // we have video add resolution
        name += ".video." + options.resolution;
    }

    return name;
}

/**
 * Namespace for the interface of Jitsi Meet Library.
 */
var LibJitsiMeet = {

    version: '{#COMMIT_HASH#}',

    JitsiConnection: JitsiConnection,
    events: {
        conference: JitsiConferenceEvents,
        connection: JitsiConnectionEvents,
        track: JitsiTrackEvents,
        mediaDevices: JitsiMediaDevicesEvents
    },
    errors: {
        conference: JitsiConferenceErrors,
        connection: JitsiConnectionErrors,
        recorder: JitsiRecorderErrors,
        track: JitsiTrackErrors
    },
    errorTypes: {
        JitsiTrackError: JitsiTrackError
    },
    logLevels: Logger.levels,
    mediaDevices: JitsiMediaDevices,
    analytics: null,
    init: function (options) {
        var logObject, attr;
        Statistics.init(options);
        this.analytics = Statistics.analytics;

        if (options.enableWindowOnErrorHandler) {
            GlobalOnErrorHandler.addHandler(
                this.getGlobalOnErrorHandler.bind(this));
        }

        // Log deployment-specific information, if available.
        if (window.jitsiRegionInfo
            && Object.keys(window.jitsiRegionInfo).length > 0) {
            logObject = {};
            for (attr in window.jitsiRegionInfo) {
                if (window.jitsiRegionInfo.hasOwnProperty(attr)) {
                    logObject[attr] = window.jitsiRegionInfo[attr];
                }
            }

            logObject.id = "deployment_info";
            Statistics.sendLog(JSON.stringify(logObject));
        }

        if(this.version) {
            logObject = {
                id: "component_version",
                component: "lib-jitsi-meet",
                version: this.version
            }
            Statistics.sendLog(JSON.stringify(logObject));
        }

        return RTC.init(options || {});
    },
    /**
     * Returns whether the desktop sharing is enabled or not.
     * @returns {boolean}
     */
    isDesktopSharingEnabled: function () {
        return RTC.isDesktopSharingEnabled();
    },
    setLogLevel: function (level) {
        Logger.setLogLevel(level);
    },
    /**
     * Creates the media tracks and returns them trough the callback.
     * @param options Object with properties / settings specifying the tracks which should be created.
     * should be created or some additional configurations about resolution for example.
     * @param {Array} options.devices the devices that will be requested
     * @param {string} options.resolution resolution constraints
     * @param {bool} options.dontCreateJitsiTrack if <tt>true</tt> objects with the following structure {stream: the Media Stream,
     * type: "audio" or "video", videoType: "camera" or "desktop"}
     * will be returned trough the Promise, otherwise JitsiTrack objects will be returned.
     * @param {string} options.cameraDeviceId
     * @param {string} options.micDeviceId
     * @param {object} options.desktopSharingExtensionExternalInstallation -
     * enables external installation process for desktop sharing extension if
     * the inline installation is not posible. The following properties should
     * be provided:
     * @param {intiger} interval - the interval (in ms) for
     * checking whether the desktop sharing extension is installed or not
     * @param {Function} checkAgain - returns boolean. While checkAgain()==true
     * createLocalTracks will wait and check on every "interval" ms for the
     * extension. If the desktop extension is not install and checkAgain()==true
     * createLocalTracks will finish with rejected Promise.
     * @param {Function} listener - The listener will be called to notify the
     * user of lib-jitsi-meet that createLocalTracks is starting external
     * extension installation process.
     * NOTE: If the inline installation process is not possible and external
     * installation is enabled the listener property will be called to notify
     * the start of external installation process. After that createLocalTracks
     * will start to check for the extension on every interval ms until the
     * plugin is installed or until checkAgain return false. If the extension
     * is found createLocalTracks will try to get the desktop sharing track and
     * will finish the execution. If checkAgain returns false, createLocalTracks
     * will finish the execution with rejected Promise.
     *
     * @param {boolean} (firePermissionPromptIsShownEvent) - if event
     *      JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN should be fired
     * @returns {Promise.<{Array.<JitsiTrack>}, JitsiConferenceError>}
     *     A promise that returns an array of created JitsiTracks if resolved,
     *     or a JitsiConferenceError if rejected.
     */
    createLocalTracks: function (options, firePermissionPromptIsShownEvent) {
        var promiseFulfilled = false;

        if (firePermissionPromptIsShownEvent === true) {
            window.setTimeout(function () {
                if (!promiseFulfilled) {
                    JitsiMediaDevices.emitEvent(
                        JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
                        RTCBrowserType.getBrowserName());
                }
            }, USER_MEDIA_PERMISSION_PROMPT_TIMEOUT);
        }

        if(!window.connectionTimes)
            window.connectionTimes = {};
        window.connectionTimes["obtainPermissions.start"] =
            window.performance.now();

        return RTC.obtainAudioAndVideoPermissions(options || {})
            .then(function(tracks) {
                promiseFulfilled = true;

                window.connectionTimes["obtainPermissions.end"] =
                    window.performance.now();

                Statistics.analytics.sendEvent(addDeviceTypeToAnalyticsEvent(
                    "getUserMedia.success", options), options);

                if(!RTC.options.disableAudioLevels)
                    for(var i = 0; i < tracks.length; i++) {
                        var track = tracks[i];
                        var mStream = track.getOriginalStream();
                        if(track.getType() === MediaType.AUDIO){
                            Statistics.startLocalStats(mStream,
                                track.setAudioLevel.bind(track));
                            track.addEventListener(
                                JitsiTrackEvents.LOCAL_TRACK_STOPPED,
                                function(){
                                    Statistics.stopLocalStats(mStream);
                                });
                        }
                    }

                return tracks;
            }).catch(function (error) {
                promiseFulfilled = true;

                if(error.name === JitsiTrackErrors.UNSUPPORTED_RESOLUTION) {
                    var oldResolution = options.resolution || '360',
                        newResolution = getLowerResolution(oldResolution);

                    if (newResolution !== null) {
                        options.resolution = newResolution;

                        logger.debug("Retry createLocalTracks with resolution",
                            newResolution);

                        Statistics.analytics.sendEvent(
                            "getUserMedia.fail.resolution." + oldResolution);

                        return LibJitsiMeet.createLocalTracks(options);
                    }
                }

                if (JitsiTrackErrors.CHROME_EXTENSION_USER_CANCELED ===
                        error.name) {
                    // User cancelled action is not really an error, so only
                    // log it as an event to avoid having conference classified
                    // as partially failed
                    var logObject = {
                        id: "chrome_extension_user_canceled",
                        message: error.message
                    };
                    Statistics.sendLog(JSON.stringify(logObject));
                    Statistics.analytics.sendEvent(
                        "getUserMedia.userCancel.extensionInstall");
                } else {
                    // Report gUM failed to the stats
                    Statistics.sendGetUserMediaFailed(error);
                }

                window.connectionTimes["obtainPermissions.end"] =
                    window.performance.now();

                Statistics.analytics.sendEvent(
                    addDeviceTypeToAnalyticsEvent(
                        "getUserMedia.failed", options) + '.' + error.name,
                    options);

                return Promise.reject(error);
            }.bind(this));
    },
    /**
     * Checks if its possible to enumerate available cameras/micropones.
     * @returns {Promise<boolean>} a Promise which will be resolved only once
     * the WebRTC stack is ready, either with true if the device listing is
     * available available or with false otherwise.
     * @deprecated use JitsiMeetJS.mediaDevices.isDeviceListAvailable instead
     */
    isDeviceListAvailable: function () {
        logger.warn('This method is deprecated, use ' +
            'JitsiMeetJS.mediaDevices.isDeviceListAvailable instead');
        return this.mediaDevices.isDeviceListAvailable();
    },
    /**
     * Returns true if changing the input (camera / microphone) or output
     * (audio) device is supported and false if not.
     * @params {string} [deviceType] - type of device to change. Default is
     *      undefined or 'input', 'output' - for audio output device change.
     * @returns {boolean} true if available, false otherwise.
     * @deprecated use JitsiMeetJS.mediaDevices.isDeviceChangeAvailable instead
     */
    isDeviceChangeAvailable: function (deviceType) {
        logger.warn('This method is deprecated, use ' +
            'JitsiMeetJS.mediaDevices.isDeviceChangeAvailable instead');
        return this.mediaDevices.isDeviceChangeAvailable(deviceType);
    },
    /**
     * Executes callback with list of media devices connected.
     * @param {function} callback
     * @deprecated use JitsiMeetJS.mediaDevices.enumerateDevices instead
     */
    enumerateDevices: function (callback) {
        logger.warn('This method is deprecated, use ' +
            'JitsiMeetJS.mediaDevices.enumerateDevices instead');
        this.mediaDevices.enumerateDevices(callback);
    },
    /**
     * @returns function that can be used to be attached to window.onerror and
     * if options.enableWindowOnErrorHandler is enabled returns
     * the function used by the lib.
     * (function(message, source, lineno, colno, error)).
     */
    getGlobalOnErrorHandler: function (message, source, lineno, colno, error) {
        logger.error(
            'UnhandledError: ' + message,
            'Script: ' + source,
            'Line: ' + lineno,
            'Column: ' + colno,
            'StackTrace: ', error);
        Statistics.reportGlobalError(error);
    },

    /**
     * Represents a hub/namespace for utility functionality which may be of
     * interest to LibJitsiMeet clients.
     */
    util: {
        ScriptUtil: ScriptUtil,
        RTCUIHelper: RTCUIHelper,
        AuthUtil: AuthUtil
    }
};

//Setups the promise object.
window.Promise = window.Promise || require("es6-promise").Promise;

module.exports = LibJitsiMeet;
<|endoftext|>


<commit_before>import {Buttons} from 'widget/buttons'
import {Layout} from 'widget/layout'
import {MapAreaLayout} from 'app/home/map/mapAreaLayout'
import {SceneSelectionType} from './opticalMosaicRecipe'
import {VisualizationSelector} from 'app/home/map/imageLayerSource/visualizationSelector'
import {compose} from 'compose'
import {msg} from 'translate'
import {selectFrom} from 'stateUtils'
import {visualizations} from './visualizations'
import {withMapAreaContext} from 'app/home/map/mapAreaContext'
import PropTypes from 'prop-types'
import React from 'react'

const defaultLayerConfig = {
    panSharpen: false
}

class _OpticalMosaicImageLayer extends React.Component {
    render() {
        const {layer, map} = this.props
        return (
            <React.Fragment>
                <MapAreaLayout
                    layer={this.hasScenes() ? layer : null}
                    form={this.renderImageLayerForm()}
                    map={map}
                />
            </React.Fragment>
        )
    }

    renderImageLayerForm() {
        return (
            <Layout>
                {this.renderBandSelection()}
                {this.canPanSharpen()
                    ? this.renderPanSharpen()
                    : null}
            </Layout>
        )
    }

    renderPanSharpen() {
        const {layerConfig: {panSharpen}} = this.props
        return (
            <Buttons
                label={'Pan sharpen'}
                selected={panSharpen}
                onChange={enabled => this.togglePanSharpen(enabled)}
                options={[
                    {value: true, label: 'Yes'},
                    {value: false, label: 'No'}
                ]}/>
        )
    }

    renderBandSelection() {
        const {recipe, source, layerConfig = {}} = this.props
        const visParamsToOption = visParams => ({
            value: visParams.bands.join(','),
            label: visParams.bands.map(band => msg(['bands', band])).join(', '),
            visParams
        })
        const bandCombinationOptions = {
            label: msg('process.mosaic.bands.combinations'),
            options: visualizations[this.reflectance()].map(visParamsToOption)
        }
        const indexOptions = {
            label: msg('process.mosaic.bands.indexes'),
            options: visualizations.indexes.map(visParamsToOption)
        }
        const metadataOptions = {
            label: msg('process.mosaic.bands.metadata'),
            options: visualizations.metadata.map(visParamsToOption)
        }
        const options = this.median()
            ? [bandCombinationOptions, indexOptions]
            : [bandCombinationOptions, indexOptions, metadataOptions]
        return (
            <VisualizationSelector
                source={source}
                recipe={recipe}
                presetOptions={options}
                selectedVisParams={layerConfig.visParams}
            />
        )
    }

    componentDidMount() {
        this.selectVisualization(visualizations[this.reflectance()][0])
    }

    hasScenes() {
        const {recipe} = this.props
        const type = selectFrom(recipe, 'model.sceneSelectionOptions.type')
        const scenes = selectFrom(recipe, 'model.scenes') || {}
        return type !== SceneSelectionType.SELECT || Object.values(scenes)
            .find(scenes => scenes.length)
    }

    reflectance() {
        const {recipe} = this.props
        const corrections = selectFrom(recipe, 'model.compositeOptions.corrections')
        return corrections.includes('SR') ? 'SR' : 'TOA'
    }

    median() {
        const {recipe} = this.props
        const compositeOptions = selectFrom(recipe, 'model.compositeOptions')
        return compositeOptions.compose === 'MEDIAN'
    }

    canPanSharpen() {
        const {recipe, layerConfig: {visParams}} = this.props
        const sources = selectFrom(recipe, 'model.sources')
        return sources.LANDSAT
        && this.reflectance() === 'TOA'
        && visParams
        && ['red,green,blue', 'nir,red,green'].includes(visParams.bands.join(','))
    }

    togglePanSharpen(enabled) {
        const {layerConfig: {visParams}, mapAreaContext: {updateLayerConfig}} = this.props
        updateLayerConfig({visParams, panSharpen: enabled && this.canPanSharpen()})
    }

    selectVisualization(visParams) {
        const {layerConfig: {panSharpen}, mapAreaContext: {updateLayerConfig}} = this.props
        updateLayerConfig({visParams, panSharpen})
    }
}

export const OpticalMosaicImageLayer = compose(
    _OpticalMosaicImageLayer,
    withMapAreaContext()
)

OpticalMosaicImageLayer.defaultProps = {
    layerConfig: defaultLayerConfig
}

OpticalMosaicImageLayer.propTypes = {
    recipe: PropTypes.object.isRequired,
    source: PropTypes.object.isRequired,
    layer: PropTypes.object,
    layerConfig: PropTypes.object,
    map: PropTypes.object
}
<commit_msg>Prevented overriding of previous optical mosaic visParams.<commit_after>import {Buttons} from 'widget/buttons'
import {Layout} from 'widget/layout'
import {MapAreaLayout} from 'app/home/map/mapAreaLayout'
import {SceneSelectionType} from './opticalMosaicRecipe'
import {VisualizationSelector} from 'app/home/map/imageLayerSource/visualizationSelector'
import {compose} from 'compose'
import {msg} from 'translate'
import {selectFrom} from 'stateUtils'
import {visualizations} from './visualizations'
import {withMapAreaContext} from 'app/home/map/mapAreaContext'
import PropTypes from 'prop-types'
import React from 'react'

const defaultLayerConfig = {
    panSharpen: false
}

class _OpticalMosaicImageLayer extends React.Component {
    render() {
        const {layer, map} = this.props
        return (
            <React.Fragment>
                <MapAreaLayout
                    layer={this.hasScenes() ? layer : null}
                    form={this.renderImageLayerForm()}
                    map={map}
                />
            </React.Fragment>
        )
    }

    renderImageLayerForm() {
        return (
            <Layout>
                {this.renderBandSelection()}
                {this.canPanSharpen()
                    ? this.renderPanSharpen()
                    : null}
            </Layout>
        )
    }

    renderPanSharpen() {
        const {layerConfig: {panSharpen}} = this.props
        return (
            <Buttons
                label={'Pan sharpen'}
                selected={panSharpen}
                onChange={enabled => this.togglePanSharpen(enabled)}
                options={[
                    {value: true, label: 'Yes'},
                    {value: false, label: 'No'}
                ]}/>
        )
    }

    renderBandSelection() {
        const {recipe, source, layerConfig = {}} = this.props
        const visParamsToOption = visParams => ({
            value: visParams.bands.join(','),
            label: visParams.bands.map(band => msg(['bands', band])).join(', '),
            visParams
        })
        const bandCombinationOptions = {
            label: msg('process.mosaic.bands.combinations'),
            options: visualizations[this.reflectance()].map(visParamsToOption)
        }
        const indexOptions = {
            label: msg('process.mosaic.bands.indexes'),
            options: visualizations.indexes.map(visParamsToOption)
        }
        const metadataOptions = {
            label: msg('process.mosaic.bands.metadata'),
            options: visualizations.metadata.map(visParamsToOption)
        }
        const options = this.median()
            ? [bandCombinationOptions, indexOptions]
            : [bandCombinationOptions, indexOptions, metadataOptions]
        return (
            <VisualizationSelector
                source={source}
                recipe={recipe}
                presetOptions={options}
                selectedVisParams={layerConfig.visParams}
            />
        )
    }

    componentDidMount() {
        const {layerConfig: {visParams}} = this.props
        if (!visParams) {
            this.selectVisualization(visualizations[this.reflectance()][0])
        }
    }

    hasScenes() {
        const {recipe} = this.props
        const type = selectFrom(recipe, 'model.sceneSelectionOptions.type')
        const scenes = selectFrom(recipe, 'model.scenes') || {}
        return type !== SceneSelectionType.SELECT || Object.values(scenes)
            .find(scenes => scenes.length)
    }

    reflectance() {
        const {recipe} = this.props
        const corrections = selectFrom(recipe, 'model.compositeOptions.corrections')
        return corrections.includes('SR') ? 'SR' : 'TOA'
    }

    median() {
        const {recipe} = this.props
        const compositeOptions = selectFrom(recipe, 'model.compositeOptions')
        return compositeOptions.compose === 'MEDIAN'
    }

    canPanSharpen() {
        const {recipe, layerConfig: {visParams}} = this.props
        const sources = selectFrom(recipe, 'model.sources')
        return sources.LANDSAT
        && this.reflectance() === 'TOA'
        && visParams
        && ['red,green,blue', 'nir,red,green'].includes(visParams.bands.join(','))
    }

    togglePanSharpen(enabled) {
        const {layerConfig: {visParams}, mapAreaContext: {updateLayerConfig}} = this.props
        updateLayerConfig({visParams, panSharpen: enabled && this.canPanSharpen()})
    }

    selectVisualization(visParams) {
        const {layerConfig: {panSharpen}, mapAreaContext: {updateLayerConfig}} = this.props
        updateLayerConfig({visParams, panSharpen})
    }
}

export const OpticalMosaicImageLayer = compose(
    _OpticalMosaicImageLayer,
    withMapAreaContext()
)

OpticalMosaicImageLayer.defaultProps = {
    layerConfig: defaultLayerConfig
}

OpticalMosaicImageLayer.propTypes = {
    recipe: PropTypes.object.isRequired,
    source: PropTypes.object.isRequired,
    layer: PropTypes.object,
    layerConfig: PropTypes.object,
    map: PropTypes.object
}
<|endoftext|>