<commit_before>'use strict';

// Declare app level module which depends on filters, and services
angular.module('app', ['ngRoute', 'infinite-scroll'])

.config(function($routeProvider, $locationProvider) {

  $routeProvider
    .when('/', {
      templateUrl: 'views/index.html',
      controller : 'SearchResultsCtrl'
    })
    .when('/search/:query', {
      redirectTo: function(routeParams) {
        console.log(routeParams);
        return '/search?scope=everything&q=' + routeParams.query;
      }
    })
    .when('/search', {
      templateUrl: 'views/index.html',
      controller : 'SearchResultsCtrl'
    })
    .when('/show/:id', {
      templateUrl: 'views/show.html',
      controller : 'RecordCtrl'
    })
    .otherwise({
      redirectTo: '/'
    });

  // use the HTML5 History API
  $locationProvider.html5Mode(true);

})

.service('ScopesService', [function () {

  this.items = [
    {
      id: 'everything',
      label: 'Everything',
      viafSearchField: 'local.names',
    },
    {
      id: 'persons',
      label: 'Persons',
      viafSearchField: 'local.personalNames',
    },
    {
      id: 'corporations',
      label: 'Corporations',
      viafSearchField: 'local.corporateNames',
    },
    {
      id: 'conferences',
      label: 'Conferences',
      viafSearchField: 'local.names',
    },
  ];

  this.get = () => {
    return this.items;
  }

  this.find = (value) => {
    if (!value) {
      return this.items[0];
    }
    for (let idx = 0; idx < this.items.length; idx++) {
      if (this.items[idx].id == value) {
        return this.items[idx]
      }
    }
    return this.items[0];
  }

}])

.service('ApiService', ['$rootScope', '$http', '$q', function($rootScope, $http, $q) {

  var that = this;

  this.busy = false;
  this.canceler = null;

  this.searchBibsys = function(query, queryScope, startPos) {

    // TODO: queryScope

    if (that.canceler) {
      console.log('Aborting');
      that.canceler.resolve();
    }

    that.canceler = $q.defer();

    $rootScope.$broadcast('requestStart');
    $rootScope.$broadcast('searchStart', query);
    $http({
      method: 'GET',
      url: 'api.php?q=' + query + '&start=' + startPos + '&scope=' + queryScope,
      timeout: that.canceler
    })
    .then(function(response) {
      console.log(response.status)
      if (response.status != 200) {
        console.log('Search failed')
        $rootScope.$broadcast('searchFailed', response.data);
      } else {
        $rootScope.$broadcast('searchResults', response.data);
      }
      $rootScope.$broadcast('requestFinish');
     })
    .catch(function() {
      $rootScope.$broadcast('searchFailed');
      $rootScope.$broadcast('requestFinish');
     });
  };

}])

.controller('SearchResultsCtrl', ['$location', '$scope', '$routeParams', 'ApiService', 'ScopesService', function($location, $scope, $routeParams, ApiService, ScopesService) {
  console.log($routeParams);

  var nextRecordPosition = 1;
  $scope.busy = false;
  $scope.records = [];
  $scope.numberOfRecords = -1;
  $scope.query = $routeParams.q;
  $scope.queryEncoded = encodeURIComponent($routeParams.q);
  $scope.queryScope = $routeParams.scope;

  const selectedScope = ScopesService.find($location.search().scope);
  $scope.viafSearchField = selectedScope.viafSearchField;

  $scope.$on('requestStart', function() {
    $scope.busy = true;
  });

  $scope.$on('requestFinish', function() {
    $scope.busy = false;
  });

  $scope.$on('searchResults', function(e, results) {
    $scope.records = $scope.records.concat(results.records);
    $scope.numberOfRecords = results.numberOfRecords;
    nextRecordPosition = results.nextRecordPosition;
    console.log('Got ', results.records.length, ' recs in this batch, total: ', $scope.records.length, 'recs');
  });

  $scope.$on('searchFailed', function(e, results) {
    console.log('Search failed');
    $scope.records = [];
    $scope.error = 'Search failed';
  });

  $scope.moreRecords = function () {
    if ($scope.busy || ! nextRecordPosition || $scope.error) {
      return;
    }
    if ($scope.query) {
      console.log('Get more records from ', nextRecordPosition, 'scope: ', $scope.queryScope);
      $scope.error = null;
      ApiService.searchBibsys($scope.query, $scope.queryScope, nextRecordPosition);
    }
  };

}])

.controller('SearchFormCtrl', ['$scope', '$routeParams', '$location', 'ScopesService', function($scope, $routeParams, $location, ScopesService) {

  $scope.busy = false;
  $scope.query = $routeParams.query;
  $scope.query = '';
  $scope.scopes = ScopesService.get();

  $scope.selectedScope = ScopesService.find($location.search().scope);

  $scope.submit = function() {
    $location
      .path('/search')
      .search('scope', $scope.selectedScope.id)
      .search('q', $scope.query);
  };

  $scope.$on('searchStart', function(e, query) {
    console.log('Search start', query);
    $scope.query = query;
  });

  $scope.$on('requestStart', function() {
    $scope.busy = true;
  });

  $scope.$on('requestFinish', function() {
    $scope.busy = false;
  });

}])

.directive('viafLink', function () {
  return {
    // We limit this directive to attributes only
    restrict : 'A',

    // We will replace the original element code
    replace : true,

    // We must supply at least one element in the code to replace the div
    template : ' Checking	',

    link: function(scope, element, attrs) {
      scope.$watch('viaf', function(viaf) {
        console.log('VIAF changed');
        if (viaf !== undefined) {
          if (viaf && viaf.id) {
            element.html('' + viaf.id + ' : ' + viaf.mainHeadings.reduce(function(prev, cur) { return (prev ? prev + ' | ' : '') + cur.title; }, null) );
          } else {
            element.html('Record not connected to VIAF');
          }
        }
      })
    }
  }
})

.directive('wdLink', function () {
  return {
    // We limit this directive to attributes only
    restrict : 'A',

    // We will replace the original element code
    replace : true,

    // We must supply at least one element in the code to replace the div
    template : ' Checking	',

    link: function(scope, element, attrs) {
      scope.$watch('wikidata', function(wikidata) {
        console.log('wikidata changed');
        if (wikidata !== undefined) {
          if (wikidata && wikidata.id) {
            element.html('Q' + wikidata.id + ' (Reasonator)');
          } else {
            element.html('Record not connected to Wikidata');
          }
        }
      })
    }
  }
})

.service('WikidataService', ['$http', '$q', function($http, $q) {

  this.fromBibsysId = function(id) {
    var deferred = $q.defer();

    var data = { id: null };

    function queryWdq() {
    console.log('Query WDQ');
      $http({
        url: 'https://wdq.wmflabs.org/api',
        method: 'JSONP',
        params: {
          q: 'string[1015:"' + id + '"]',
          callback: 'JSON_CALLBACK'
        }
      })
      .catch(function(response) {
        deferred.reject(response.status);
      })
      .then(function(response) {

        if (response.data.items.length == 0) {
          deferred.resolve(data);
          return;
        }

        data.id = response.data.items[0];
        queryWd()
      });
    }

    function querySparql() {
    console.log('Query SPARQL');
      $http({
        url: 'api.php',
        method: 'GET',
        params: {
          sparql: id
        }
      })
      .catch(function(response) {
        deferred.reject(response.status);
      })
      .then(function(response) {

        if (response.data.items.length == 0) {
          deferred.resolve(data);
          return;
        }
        data.id = response.data.items[0].split('Q')[1];
        queryWd()
      });
    }

    function queryWd() {
      console.log('Query WD');
      $http({
        url: 'https://www.wikidata.org/w/api.php',
        method: 'JSONP',
        params: {
          action: 'wbgetentities',
          format: 'json',
          ids: 'Q' + data.id,
          props: 'info|sitelinks/urls|aliases|labels|descriptions|claims|datatype',
          callback: 'JSON_CALLBACK'
        }
      })
      .catch(function(response) {
        deferred.reject(response.status);
      })
      .then(function(response) {
        // TODO
        deferred.resolve(data);
      });
    }

    querySparql();

    return deferred.promise;
  }

}])

.controller('RecordCtrl', ['$scope', '$http', '$routeParams', '$timeout', 'WikidataService', function($scope, $http, $routeParams, $timeout, WikidataService) {

  $scope.busy = true;
  $scope.authorityBusy = true;
  $scope.biblioBusy = false;
  $scope.viafBusy = true;
  $scope.wdBusy = true;

  var tasksLeft = 3;
  var nextRecordPosition = 1;
  $scope.id = $routeParams.id;

  function taskDone() {
    tasksLeft--;
    console.log('Task done, left: ' + tasksLeft);
    if (tasksLeft <= 0) $scope.busy = false;
  }

  // Query Bibsys
  $http.get('api.php?id=' + $scope.id)
    .then(function(response) {
      $scope.record = response.data.numberOfRecords ? response.data.records[0] : false;
      $scope.authorityBusy = false;
      taskDone();
    });

  // Query VIAF
  $http.get('api.php?viaf=' + $scope.id)
    .then(function(response) {
      $scope.viaf = response.data.numberOfRecords ? response.data.records[0] : false;
      $scope.viafBusy = false;
      taskDone();
    });

  // Query Wikidata
  WikidataService.fromBibsysId($scope.id).then(function(data) {
    console.log('WD promise done');
    $scope.wdBusy = false;
    $scope.wikidata = data;
    taskDone();
  }, function() {
    $scope.wdBusy = false;
    $scope.wikidata = null;
    taskDone();
  });

  $scope.morePublications = function() {
    if (!nextRecordPosition || $scope.biblioBusy) {
      return;
    }
    console.log('Getting more bibliographic records, starting at ', nextRecordPosition);

    $scope.biblioBusy = true;
    $http.get('api.php?pub=' + $scope.id + '&start=' + nextRecordPosition)
      .then(function(response) {
        $scope.publications = $scope.publications.concat(response.data.records);
        nextRecordPosition = response.data.nextRecordPosition;
        $scope.biblioBusy = false;
       });
  };

  $scope.publications = [];

  // getBiblio();

  var startTime = (new Date()).getTime()/1000;

  function upTimer() {
    var t = (new Date()).getTime()/1000 - startTime;
    if ($scope.authorityBusy) $scope.authorityTimer = t;
    if ($scope.viafBusy) $scope.viafTimer = t;
    if ($scope.wdBusy) $scope.wdTimer = t;
    if ($scope.biblioBusy) $scope.biblioTimer = t;
    if (tasksLeft) $timeout(upTimer, 50);
  }

  $scope.authorityTimer = 0;
  $scope.biblioTimer = 0;
  $scope.viafTimer = 0;
  $scope.wdTimer = 0;
  upTimer();

}]);

angular.module('infinite-scroll').value('THROTTLE_MILLISECONDS', 250);
<commit_msg>Filter: Only unique alternative names<commit_after>'use strict';

// Declare app level module which depends on filters, and services
angular.module('app', ['ngRoute', 'infinite-scroll'])

.config(function($routeProvider, $locationProvider) {

  $routeProvider
    .when('/', {
      templateUrl: 'views/index.html',
      controller : 'SearchResultsCtrl'
    })
    .when('/search/:query', {
      redirectTo: function(routeParams) {
        console.log(routeParams);
        return '/search?scope=everything&q=' + routeParams.query;
      }
    })
    .when('/search', {
      templateUrl: 'views/index.html',
      controller : 'SearchResultsCtrl'
    })
    .when('/show/:id', {
      templateUrl: 'views/show.html',
      controller : 'RecordCtrl'
    })
    .otherwise({
      redirectTo: '/'
    });

  // use the HTML5 History API
  $locationProvider.html5Mode(true);

})

.service('ScopesService', [function () {

  this.items = [
    {
      id: 'everything',
      label: 'Everything',
      viafSearchField: 'local.names',
    },
    {
      id: 'persons',
      label: 'Persons',
      viafSearchField: 'local.personalNames',
    },
    {
      id: 'corporations',
      label: 'Corporations',
      viafSearchField: 'local.corporateNames',
    },
    {
      id: 'conferences',
      label: 'Conferences',
      viafSearchField: 'local.names',
    },
  ];

  this.get = () => {
    return this.items;
  }

  this.find = (value) => {
    if (!value) {
      return this.items[0];
    }
    for (let idx = 0; idx < this.items.length; idx++) {
      if (this.items[idx].id == value) {
        return this.items[idx]
      }
    }
    return this.items[0];
  }

}])

.service('ApiService', ['$rootScope', '$http', '$q', function($rootScope, $http, $q) {

  var that = this;

  this.busy = false;
  this.canceler = null;

  this.searchBibsys = function(query, queryScope, startPos) {

    // TODO: queryScope

    if (that.canceler) {
      console.log('Aborting');
      that.canceler.resolve();
    }

    that.canceler = $q.defer();

    $rootScope.$broadcast('requestStart');
    $rootScope.$broadcast('searchStart', query);
    $http({
      method: 'GET',
      url: 'api.php?q=' + query + '&start=' + startPos + '&scope=' + queryScope,
      timeout: that.canceler
    })
    .then(function(response) {
      console.log(response.status)
      if (response.status != 200) {
        console.log('Search failed')
        $rootScope.$broadcast('searchFailed', response.data);
      } else {
        $rootScope.$broadcast('searchResults', response.data);
      }
      $rootScope.$broadcast('requestFinish');
     })
    .catch(function() {
      $rootScope.$broadcast('searchFailed');
      $rootScope.$broadcast('requestFinish');
     });
  };

}])

.controller('SearchResultsCtrl', ['$location', '$scope', '$routeParams', 'ApiService', 'ScopesService', function($location, $scope, $routeParams, ApiService, ScopesService) {
  console.log($routeParams);

  var nextRecordPosition = 1;
  $scope.busy = false;
  $scope.records = [];
  $scope.numberOfRecords = -1;
  $scope.query = $routeParams.q;
  $scope.queryEncoded = encodeURIComponent($routeParams.q);
  $scope.queryScope = $routeParams.scope;

  const selectedScope = ScopesService.find($location.search().scope);
  $scope.viafSearchField = selectedScope.viafSearchField;

  $scope.$on('requestStart', function() {
    $scope.busy = true;
  });

  $scope.$on('requestFinish', function() {
    $scope.busy = false;
  });

  $scope.$on('searchResults', function(e, results) {
    $scope.records = $scope.records.concat(results.records);
    $scope.numberOfRecords = results.numberOfRecords;
    nextRecordPosition = results.nextRecordPosition;
    console.log('Got ', results.records.length, ' recs in this batch, total: ', $scope.records.length, 'recs');
  });

  $scope.$on('searchFailed', function(e, results) {
    console.log('Search failed');
    $scope.records = [];
    $scope.error = 'Search failed';
  });

  $scope.moreRecords = function () {
    if ($scope.busy || ! nextRecordPosition || $scope.error) {
      return;
    }
    if ($scope.query) {
      console.log('Get more records from ', nextRecordPosition, 'scope: ', $scope.queryScope);
      $scope.error = null;
      ApiService.searchBibsys($scope.query, $scope.queryScope, nextRecordPosition);
    }
  };

}])

.controller('SearchFormCtrl', ['$scope', '$routeParams', '$location', 'ScopesService', function($scope, $routeParams, $location, ScopesService) {

  $scope.busy = false;
  $scope.query = $routeParams.query;
  $scope.query = '';
  $scope.scopes = ScopesService.get();

  $scope.selectedScope = ScopesService.find($location.search().scope);

  $scope.submit = function() {
    $location
      .path('/search')
      .search('scope', $scope.selectedScope.id)
      .search('q', $scope.query);
  };

  $scope.$on('searchStart', function(e, query) {
    console.log('Search start', query);
    $scope.query = query;
  });

  $scope.$on('requestStart', function() {
    $scope.busy = true;
  });

  $scope.$on('requestFinish', function() {
    $scope.busy = false;
  });

}])

.directive('viafLink', function () {
  return {
    // We limit this directive to attributes only
    restrict : 'A',

    // We will replace the original element code
    replace : true,

    // We must supply at least one element in the code to replace the div
    template : ' Checking	',

    link: function(scope, element, attrs) {
      scope.$watch('viaf', function(viaf) {
        console.log('VIAF changed');
        if (viaf !== undefined) {
          if (viaf && viaf.id) {
            element.html('' + viaf.id + ' : ' + viaf.mainHeadings.reduce(function(prev, cur) { return (prev ? prev + ' | ' : '') + cur.title; }, null) );
          } else {
            element.html('Record not connected to VIAF');
          }
        }
      })
    }
  }
})

.directive('wdLink', function () {
  return {
    // We limit this directive to attributes only
    restrict : 'A',

    // We will replace the original element code
    replace : true,

    // We must supply at least one element in the code to replace the div
    template : ' Checking	',

    link: function(scope, element, attrs) {
      scope.$watch('wikidata', function(wikidata) {
        console.log('wikidata changed');
        if (wikidata !== undefined) {
          if (wikidata && wikidata.id) {
            element.html('Q' + wikidata.id + ' (Reasonator)');
          } else {
            element.html('Record not connected to Wikidata');
          }
        }
      })
    }
  }
})

.service('WikidataService', ['$http', '$q', function($http, $q) {

  this.fromBibsysId = function(id) {
    var deferred = $q.defer();

    var data = { id: null };

    function queryWdq() {
    console.log('Query WDQ');
      $http({
        url: 'https://wdq.wmflabs.org/api',
        method: 'JSONP',
        params: {
          q: 'string[1015:"' + id + '"]',
          callback: 'JSON_CALLBACK'
        }
      })
      .catch(function(response) {
        deferred.reject(response.status);
      })
      .then(function(response) {

        if (response.data.items.length == 0) {
          deferred.resolve(data);
          return;
        }

        data.id = response.data.items[0];
        queryWd()
      });
    }

    function querySparql() {
    console.log('Query SPARQL');
      $http({
        url: 'api.php',
        method: 'GET',
        params: {
          sparql: id
        }
      })
      .catch(function(response) {
        deferred.reject(response.status);
      })
      .then(function(response) {

        if (response.data.items.length == 0) {
          deferred.resolve(data);
          return;
        }
        data.id = response.data.items[0].split('Q')[1];
        queryWd()
      });
    }

    function queryWd() {
      console.log('Query WD');
      $http({
        url: 'https://www.wikidata.org/w/api.php',
        method: 'JSONP',
        params: {
          action: 'wbgetentities',
          format: 'json',
          ids: 'Q' + data.id,
          props: 'info|sitelinks/urls|aliases|labels|descriptions|claims|datatype',
          callback: 'JSON_CALLBACK'
        }
      })
      .catch(function(response) {
        deferred.reject(response.status);
      })
      .then(function(response) {
        // TODO
        deferred.resolve(data);
      });
    }

    querySparql();

    return deferred.promise;
  }

}])

.controller('RecordCtrl', ['$scope', '$http', '$routeParams', '$timeout', 'WikidataService', function($scope, $http, $routeParams, $timeout, WikidataService) {

  $scope.busy = true;
  $scope.authorityBusy = true;
  $scope.biblioBusy = false;
  $scope.viafBusy = true;
  $scope.wdBusy = true;

  var tasksLeft = 3;
  var nextRecordPosition = 1;
  $scope.id = $routeParams.id;

  function taskDone() {
    tasksLeft--;
    console.log('Task done, left: ' + tasksLeft);
    if (tasksLeft <= 0) $scope.busy = false;
  }

  // Query Bibsys
  $http.get('api.php?id=' + $scope.id)
    .then(function(response) {
      let record = response.data.numberOfRecords ? response.data.records[0] : false;
      if (record && record.altLabels) {
        record.altLabels = record.altLabels.filter(function (value, index, self) {
          // Only unique
          return self.indexOf(value) === index;
        })
      }
      $scope.record = record;
      $scope.authorityBusy = false;
      taskDone();
    });

  // Query VIAF
  $http.get('api.php?viaf=' + $scope.id)
    .then(function(response) {
      $scope.viaf = response.data.numberOfRecords ? response.data.records[0] : false;
      $scope.viafBusy = false;
      taskDone();
    });

  // Query Wikidata
  WikidataService.fromBibsysId($scope.id).then(function(data) {
    console.log('WD promise done');
    $scope.wdBusy = false;
    $scope.wikidata = data;
    taskDone();
  }, function() {
    $scope.wdBusy = false;
    $scope.wikidata = null;
    taskDone();
  });

  $scope.morePublications = function() {
    if (!nextRecordPosition || $scope.biblioBusy) {
      return;
    }
    console.log('Getting more bibliographic records, starting at ', nextRecordPosition);

    $scope.biblioBusy = true;
    $http.get('api.php?pub=' + $scope.id + '&start=' + nextRecordPosition)
      .then(function(response) {
        $scope.publications = $scope.publications.concat(response.data.records);
        nextRecordPosition = response.data.nextRecordPosition;
        $scope.biblioBusy = false;
       });
  };

  $scope.publications = [];

  // getBiblio();

  var startTime = (new Date()).getTime()/1000;

  function upTimer() {
    var t = (new Date()).getTime()/1000 - startTime;
    if ($scope.authorityBusy) $scope.authorityTimer = t;
    if ($scope.viafBusy) $scope.viafTimer = t;
    if ($scope.wdBusy) $scope.wdTimer = t;
    if ($scope.biblioBusy) $scope.biblioTimer = t;
    if (tasksLeft) $timeout(upTimer, 50);
  }

  $scope.authorityTimer = 0;
  $scope.biblioTimer = 0;
  $scope.viafTimer = 0;
  $scope.wdTimer = 0;
  upTimer();

}]);

angular.module('infinite-scroll').value('THROTTLE_MILLISECONDS', 250);
<|endoftext|>


<commit_before>import THREE from 'three'

let GizmoMaterial = function ( parameters ) {
		THREE.MeshBasicMaterial.call( this )
		//this.depthTest = false
		//this.depthWrite = false
		this.side = THREE.DoubleSide
		//this.transparent = true
		//this.opacity = 0.8
		this.setValues( parameters )

		this.highlightColor = parameters.highlightColor !== undefined ? options.parameters : 0xFFFF00

		this.oldColor = this.color.clone()
		//this.oldOpacity = this.opacity

		this.highlight = function( highlighted ) {

			if ( highlighted ) {

				this.color.set( this.highlightColor )//.setRGB( 1, 1, 0 )
				//this.opacity = 1

			} else {

					this.color.copy( this.oldColor )
					//this.opacity = this.oldOpacity

			}

		}
}

GizmoMaterial.prototype = Object.create( THREE.MeshBasicMaterial.prototype )

let GizmoLineMaterial = function ( parameters ) {
		THREE.LineBasicMaterial.call( this )
		//this.depthTest = false
		//this.depthWrite = false
		//this.side = THREE.DoubleSide
		//this.transparent = true
		//this.opacity = 0.8
		this.setValues( parameters )

		this.highlightColor = parameters.highlightColor !== undefined ? options.parameters : "#ffd200"
		this.linewidth = parameters.lineWidth || 1;
		this.oldColor = this.color.clone()
		//this.oldOpacity = this.opacity

		this.highlight = function( highlighted ) {

			if ( highlighted ) {

				this.color.set( this.highlightColor )//.setRGB( 1, 1, 0 )
				//this.opacity = 1

			} else {

					this.color.copy( this.oldColor )
					//this.opacity = this.oldOpacity
			}
		}
}

GizmoLineMaterial.prototype = Object.create( THREE.LineBasicMaterial.prototype )

export {GizmoMaterial,GizmoLineMaterial}
<commit_msg>- cleaned up GizmoMaterials, minor lineWidth related tweaks<commit_after>import THREE from 'three'

let GizmoMaterial = function ( parameters ) {
		THREE.MeshBasicMaterial.call( this )
		this.side = THREE.DoubleSide
		this.setValues( parameters )

		this.highlightColor = parameters.highlightColor !== undefined ? options.parameters : 0xFFFF00
		this.oldColor = this.color.clone()
		//this.oldOpacity = this.opacity

		this.highlight = function( highlighted ) {

			if ( highlighted ) {

				this.color.set( this.highlightColor )

			} else {

					this.color.copy( this.oldColor )
			}

		}
}

GizmoMaterial.prototype = Object.create( THREE.MeshBasicMaterial.prototype )

let GizmoLineMaterial = function ( parameters ) {
		THREE.LineBasicMaterial.call( this )
		this.setValues( parameters )

		this.highlightColor = parameters.highlightColor !== undefined ? options.parameters : "#ffd200"
		this.linewidth = parameters.lineWidth || parameters.linewidth || 1

		this.oldColor = this.color.clone()

		this.highlight = function( highlighted ) {

			if ( highlighted ) {

				this.color.set( this.highlightColor )

			} else {

					this.color.copy( this.oldColor )
			}
		}
}

GizmoLineMaterial.prototype = Object.create( THREE.LineBasicMaterial.prototype )

export {GizmoMaterial,GizmoLineMaterial}
<|endoftext|>


<commit_before>/**
 * GravityTester Class
 *
 * Class to handle gravity cases.
 * Gets positions, does viewport tests.
 */

(function (root, factory) {

	'use strict';

	if (typeof define === 'function' && define.amd) {
		define([
			'jquery',
			'./Constants',
			'./GravityParser',
			'./PositionCalculator'
		], factory);
	} else if (typeof exports === 'object') {
		module.exports = factory(
			require('jquery'),
			require('./Constants'),
			require('./GravityParser'),
			require('./PositionCalculator')
		);
	} else {
		root.ProtipGravityTester = factory(
			root.jQuery,
			root.ProtipConstants,
			root.ProtipGravityParser,
			root.ProtipPositionCalculator
		);
	}
}(this, function ($, C, GravityParser, PositionCalculator) {

	'use strict';

	/**
	 * GravityTester class.
	 *
	 * @param protipItem {ProtipItemClass} The ProtipItem Instance.
	 * @returns {<top>:<string>, <left>:<string>}
	 * @constructor
	 */
	var GravityTester = function(protipItem){
		return this._Construct(protipItem);
	};

	// Define the GravityTester members.
	$.extend(true, GravityTester.prototype, {

		/**
		 * Constructor.
		 *
		 * @memberOf GravityTester
		 * @param protipItemInstance {ProtipItemClass} The ProtipItem Instance.
		 * @returns {<top>:<string>, <left>:<string>}
		 * @private
		 */
		_Construct: function(protipItemInstance){
			/**
			 * ItemClass instance.
			 *
			 * @type {ProtipItemClass}
			 * @private
			 */
			this._item = protipItemInstance;

			/**
			 * Results.
			 *
			 * @type {object}
			 * @private
			 */
			this._result = undefined;

			// Set some initial values.
			this._setWindowDimensions();

			/**
			 * List of positions to check.
			 *
			 * @type {array}
			 * @private
			 */
			this._positionList = new GravityParser(this._item.data.gravity, this._item.data.position);

			// Iterate through each position and do a check.
			var i;
			for (i = 0; i < this._positionList.length; i++) {
				// We had a successful test, break the loop.
				if (this._test(this._positionList[i])){
					break;
				}
			}

			// Return the result if we had one. Return values for the default position if not.
			return this._result || new PositionCalculator(this._item);
		},

		/**
		 * Does position test based on the position given in the parameter.
		 * It'll do a position calculation and tries the protip from every side.
		 *
		 * @param position {object} Position of the element.
		 * @returns {boolean}
		 * @private
		 */
		_test: function(position){
			var result = new PositionCalculator(this._item, position.key, position);
			this._item.el.protip.css(result);
			this._setProtipDimensions();

			if (this._topOk() && this._rightOk() && this._bottomOk() && this._leftOk()) {
				result.position = position.key;
				this._result = result;
				return true;
			}
			return false;
		},

		/**
		 * Check protip overflow from the top.
		 *
		 * @returns {boolean}
		 * @private
		 */
		_topOk: function(){
			return ((this._dimensions.offset.top - this._windowDimensions.scrollTop) > 0);
		},

		/**
		 * Check protip overflow from the right.
		 *
		 * @returns {boolean}
		 * @private
		 */
		_rightOk: function(){
			return ((this._dimensions.offset.left + this._dimensions.width) < this._windowDimensions.width);
		},

		/**
		 * Check protip overflow from the bottom
		 *
		 * @returns {boolean}
		 * @private
		 */
		_bottomOk: function(){
			return (((this._dimensions.offset.top - this._windowDimensions.scrollTop) + this._dimensions.height) < this._windowDimensions.height);
		},

		/**
		 * Check protip overflow from the left
		 *
		 * @returns {boolean}
		 * @private
		 */
		_leftOk: function(){
			return (this._dimensions.offset.left > 0);
		},

		/**
		 * Gets/sets initial protip dimensions to caclulate with.
		 *
		 * @private
		 */
		_setProtipDimensions: function(){
			if (this._item.classInstance.settings.forceMinWidth) {
				this._item.el.protip.css({
					position: 'fixed',
					minWidth: 0
				});

				var minWidth = this._item.el.protip.width() + 1; // Thanks Firefox
				this._item.el.protip.css({
					position: '',
					minWidth: minWidth + 'px'
				});
			}

			this._dimensions = {
				width:  this._item.el.protip.outerWidth(),
				height: this._item.el.protip.outerHeight(),
				offset: this._item.el.protip.offset()
			};
		},

		/**
		 * Get some window dimension values
		 *
		 * @private
		 */
		_setWindowDimensions: function(){
			var w = window,
				d = document,
				e = d.documentElement,
				g = d.getElementsByTagName('body')[0],
				x = w.innerWidth || e.clientWidth || g.clientWidth,
				y = w.innerHeight|| e.clientHeight|| g.clientHeight;

			this._windowDimensions = {
				width: parseInt(x),
				height: parseInt(y),
				scrollTop : (window.pageYOffset || document.documentElement.scrollTop || document.getElementsByTagName('body')[0].scrollTop || 0)
			};
		}
	});

	return GravityTester;

}));<commit_msg>Changed min width calculation priority.<commit_after>/**
 * GravityTester Class
 *
 * Class to handle gravity cases.
 * Gets positions, does viewport tests.
 */

(function (root, factory) {

	'use strict';

	if (typeof define === 'function' && define.amd) {
		define([
			'jquery',
			'./Constants',
			'./GravityParser',
			'./PositionCalculator'
		], factory);
	} else if (typeof exports === 'object') {
		module.exports = factory(
			require('jquery'),
			require('./Constants'),
			require('./GravityParser'),
			require('./PositionCalculator')
		);
	} else {
		root.ProtipGravityTester = factory(
			root.jQuery,
			root.ProtipConstants,
			root.ProtipGravityParser,
			root.ProtipPositionCalculator
		);
	}
}(this, function ($, C, GravityParser, PositionCalculator) {

	'use strict';

	/**
	 * GravityTester class.
	 *
	 * @param protipItem {ProtipItemClass} The ProtipItem Instance.
	 * @returns {<top>:<string>, <left>:<string>}
	 * @constructor
	 */
	var GravityTester = function(protipItem){
		return this._Construct(protipItem);
	};

	// Define the GravityTester members.
	$.extend(true, GravityTester.prototype, {

		/**
		 * Constructor.
		 *
		 * @memberOf GravityTester
		 * @param protipItemInstance {ProtipItemClass} The ProtipItem Instance.
		 * @returns {<top>:<string>, <left>:<string>}
		 * @private
		 */
		_Construct: function(protipItemInstance){
			/**
			 * ItemClass instance.
			 *
			 * @type {ProtipItemClass}
			 * @private
			 */
			this._item = protipItemInstance;

			/**
			 * Results.
			 *
			 * @type {object}
			 * @private
			 */
			this._result = undefined;

			// Set some initial values.
			this._setWindowDimensions();

			/**
			 * List of positions to check.
			 *
			 * @type {array}
			 * @private
			 */
			this._positionList = new GravityParser(this._item.data.gravity, this._item.data.position);

			// Iterate through each position and do a check.
			var i;
			for (i = 0; i < this._positionList.length; i++) {
				// We had a successful test, break the loop.
				if (this._test(this._positionList[i])){
					break;
				}
			}

			// Return the result if we had one. Return values for the default position if not.
			return this._result || new PositionCalculator(this._item);
		},

		/**
		 * Does position test based on the position given in the parameter.
		 * It'll do a position calculation and tries the protip from every side.
		 *
		 * @param position {object} Position of the element.
		 * @returns {boolean}
		 * @private
		 */
		_test: function(position){
			var result = new PositionCalculator(this._item, position.key, position);
			this._setProtipMinWidth();
			this._item.el.protip.css(result);
			this._setProtipDimensions();

			if (this._topOk() && this._rightOk() && this._bottomOk() && this._leftOk()) {
				result.position = position.key;
				this._result = result;
				return true;
			}
			return false;
		},

		/**
		 * Check protip overflow from the top.
		 *
		 * @returns {boolean}
		 * @private
		 */
		_topOk: function(){
			return ((this._dimensions.offset.top - this._windowDimensions.scrollTop) > 0);
		},

		/**
		 * Check protip overflow from the right.
		 *
		 * @returns {boolean}
		 * @private
		 */
		_rightOk: function(){
			return ((this._dimensions.offset.left + this._dimensions.width) < this._windowDimensions.width);
		},

		/**
		 * Check protip overflow from the bottom
		 *
		 * @returns {boolean}
		 * @private
		 */
		_bottomOk: function(){
			return (((this._dimensions.offset.top - this._windowDimensions.scrollTop) + this._dimensions.height) < this._windowDimensions.height);
		},

		/**
		 * Check protip overflow from the left
		 *
		 * @returns {boolean}
		 * @private
		 */
		_leftOk: function(){
			return (this._dimensions.offset.left > 0);
		},

		/**
		 * Sets the min width of the tooltip.
		 *
		 * @private
		 */
		_setProtipMinWidth: function() {
			if (this._item.classInstance.settings.forceMinWidth) {
				this._item.el.protip.css({
					position: 'fixed',
					left: 0,
					top: 0,
					minWidth: 0
				});

				var minWidth = this._item.el.protip.width() + 1; // Thanks Firefox
				this._item.el.protip.css({
					position: '',
					left: '',
					top: '',
					minWidth: minWidth + 'px'
				});
			}
		},

		/**
		 * Gets/sets initial protip dimensions to caclulate with.
		 *
		 * @private
		 */
		_setProtipDimensions: function(){
			this._dimensions = {
				width:  this._item.el.protip.outerWidth(),
				height: this._item.el.protip.outerHeight(),
				offset: this._item.el.protip.offset()
			};
		},

		/**
		 * Get some window dimension values
		 *
		 * @private
		 */
		_setWindowDimensions: function(){
			var w = window,
				d = document,
				e = d.documentElement,
				g = d.getElementsByTagName('body')[0],
				x = w.innerWidth || e.clientWidth || g.clientWidth,
				y = w.innerHeight|| e.clientHeight|| g.clientHeight;

			this._windowDimensions = {
				width: parseInt(x),
				height: parseInt(y),
				scrollTop : (window.pageYOffset || document.documentElement.scrollTop || document.getElementsByTagName('body')[0].scrollTop || 0)
			};
		}
	});

	return GravityTester;

}));<|endoftext|>


<commit_before>var m = require('mithril');
var classSet = require('chessground').util.classSet;
var util = require('../util');
var inviteFormCtrl = require('./inviteForm').ctrl;
var partial = require('chessground').util.partial;

function memberActivity(onIdle) {
  var timeout;
  var schedule = function() {
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(onIdle, 100);
  };
  schedule();
  return schedule;
}

module.exports = {
  ctrl: function(initDict, myId, ownerId, send, setTab, startTour, notif) {

    var dict = m.prop(initDict);
    var confing = m.prop(null); // which user is being configured by us
    var active = {}; // recently active contributors
    var online = {}; // userId -> bool
    var spectatorIds = [];

    var owner = function() {
      return dict()[ownerId];
    };

    var isOwner = function() {
      return myId === ownerId;
    };

    var myMember = function() {
      return myId ? dict()[myId] : null;
    };

    var canContribute = function() {
      return (myMember() || {}).role === 'w';
    };

    var inviteForm = inviteFormCtrl(send, dict, setTab);

    var setActive = function(id) {
      if (active[id]) active[id]();
      else active[id] = memberActivity(function() {
        delete(active[id]);
        m.redraw();
      });
      m.redraw();
    };

    var updateOnline = function() {
      online = {};
      var members = dict();
      spectatorIds.forEach(function(id) {
        if (members[id]) online[id] = true;
      });
      m.redraw();
    }

    return {
      dict: dict,
      confing: confing,
      myId: myId,
      inviteForm: inviteForm,
      update: function(members) {
        if (isOwner()) confing(Object.keys(members).filter(function(uid) {
          return !dict()[uid];
        })[0]);
        var wasViewer = myMember() && !canContribute();
        var wasContrib = myMember() && canContribute();
        dict(members);
        if (wasViewer && canContribute()) {
          if (lichess.once('study-tour')) startTour();
          notif.set({
            text: 'You are now a contributor',
            duration: 3000
          });
        } else if (wasContrib && !canContribute()) notif.set({
          text: 'You are now a spectator',
          duration: 3000
        });
        updateOnline();
      },
      setActive: setActive,
      isActive: function(id) {
        return !!active[id];
      },
      owner: owner,
      myMember: myMember,
      isOwner: isOwner,
      canContribute: canContribute,
      setRole: function(id, role) {
        setActive(id);
        send("setRole", {
          userId: id,
          role: role
        });
        confing(null);
      },
      kick: function(id) {
        send("kick", id);
        confing(null);
      },
      leave: function() {
        send("leave");
      },
      ordered: function() {
        var d = dict();
        return Object.keys(d).map(function(id) {
          return d[id];
        }).sort(function(a, b) {
          if (a.role === 'r' && b.role === 'w') return 1;
          if (a.role === 'w' && b.role === 'r') return -1;
          return a.addedAt > b.addedAt;
        });
      },
      size: function() {
        return Object.keys(dict()).length;
      },
      setSpectators: function(usernames) {
        this.inviteForm.setSpectators(usernames);
        spectatorIds = usernames.map(util.titleNameToId);
        updateOnline();
      },
      isOnline: function(userId) {
        return online[userId];
      },
      titleNameToId: util.titleNameToId,
      hasOnlineContributor: function() {
        var members = dict();
        for (var i in members)
          if (online[i] && members[i].role === 'w') return true;
        return false;
      }
    };
  },
  view: function(ctrl) {

    var isOwner = ctrl.members.isOwner();

    var username = function(member) {
      var u = member.user;
      return m('span.user_link.ulpt', {
        'data-href': '/@/' + u.name
      }, (u.title ? u.title + ' ' : '') + u.name);
    };

    var statusIcon = function(member) {
      var contrib = member.role === 'w';
      return m('span', {
        class: classSet({
          contrib: contrib,
          active: ctrl.members.isActive(member.user.id),
          online: ctrl.members.isOnline(member.user.id),
          status: true
        }),
        title: contrib ? 'Contributor' : 'Viewer',
      }, m('i', {
        'data-icon': contrib ? '' : 'v'
      }));
    };

    var configButton = function(ctrl, member) {
      if (isOwner && member.user.id !== ctrl.members.myId)
        return m('i.action.config', {
          'data-icon': '%',
          key: 'config-' + member.user.id,
          config: util.bindOnce('click', function() {
            ctrl.members.confing(ctrl.members.confing() === member.user.id ? null : member.user.id);
          })
        });
      if (!isOwner && member.user.id === ctrl.members.myId)
        return m('span.action.leave', {
          'data-icon': 'F',
          key: 'leave',
          title: 'Leave the study',
          config: util.bindOnce('click', ctrl.members.leave)
        });
    };

    var memberConfig = function(member) {
      return m('div.config', {
        key: member.user.id + '-config',
        config: function(el, isUpdate) {
          if (!isUpdate) $(el).parent('.members').scrollTo(el, 200);
        }
      }, [
        (function(id) {
          return m('div.role', [
            m('div.switch', [
              m('input', {
                id: id,
                class: 'cmn-toggle cmn-toggle-round',
                type: 'checkbox',
                checked: member.role === 'w',
                onchange: function(e) {
                  ctrl.members.setRole(member.user.id, e.target.checked ? 'w' : 'r');
                }
              }),
              m('label', {
                'for': id
              })
            ]),
            m('label', {
              'for': id
            }, 'Contributor')
          ]);
        })('member-role'),
        m('div.kick', m('a.button.text[data-icon=L]', {
          onclick: partial(ctrl.members.kick, member.user.id)
        }, 'Kick from this study'))
      ]);
    };

    return [
      m('div', {
        key: 'members',
        class: 'list members',
        config: function() {
          lichess.pubsub.emit('content_loaded')();
        }
      }, [
        ctrl.members.ordered().map(function(member) {
          var confing = ctrl.members.confing() === member.user.id;
          return [
            m('div', {
              key: member.user.id,
              class: 'elem member' + (confing ? ' editing' : '')
            }, [
              m('div.left', [
                statusIcon(member),
                username(member)
              ]),
              configButton(ctrl, member)
            ]),
            confing ? memberConfig(member) : null
          ];
        }),
        isOwner ? m('div', {
            key: 'invite-someone',
            class: 'elem member add',
            config: util.bindOnce('click', ctrl.members.inviteForm.toggle)
          },
          m('div.left', [
            m('span.status', m('i[data-icon=O]')),
            m('span.add_text', 'Add contributors')
          ])
        ) : null
      ])
    ];
  }
};
<commit_msg>update study UI<commit_after>var m = require('mithril');
var classSet = require('chessground').util.classSet;
var util = require('../util');
var inviteFormCtrl = require('./inviteForm').ctrl;
var partial = require('chessground').util.partial;

function memberActivity(onIdle) {
  var timeout;
  var schedule = function() {
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(onIdle, 100);
  };
  schedule();
  return schedule;
}

module.exports = {
  ctrl: function(initDict, myId, ownerId, send, setTab, startTour, notif) {

    var dict = m.prop(initDict);
    var confing = m.prop(null); // which user is being configured by us
    var active = {}; // recently active contributors
    var online = {}; // userId -> bool
    var spectatorIds = [];
    var max = 30;

    var owner = function() {
      return dict()[ownerId];
    };

    var isOwner = function() {
      return myId === ownerId;
    };

    var myMember = function() {
      return myId ? dict()[myId] : null;
    };

    var canContribute = function() {
      return (myMember() || {}).role === 'w';
    };

    var inviteForm = inviteFormCtrl(send, dict, setTab);

    var setActive = function(id) {
      if (active[id]) active[id]();
      else active[id] = memberActivity(function() {
        delete(active[id]);
        m.redraw();
      });
      m.redraw();
    };

    var updateOnline = function() {
      online = {};
      var members = dict();
      spectatorIds.forEach(function(id) {
        if (members[id]) online[id] = true;
      });
      m.redraw();
    }

    return {
      dict: dict,
      confing: confing,
      myId: myId,
      inviteForm: inviteForm,
      update: function(members) {
        if (isOwner()) confing(Object.keys(members).filter(function(uid) {
          return !dict()[uid];
        })[0]);
        var wasViewer = myMember() && !canContribute();
        var wasContrib = myMember() && canContribute();
        dict(members);
        if (wasViewer && canContribute()) {
          if (lichess.once('study-tour')) startTour();
          notif.set({
            text: 'You are now a contributor',
            duration: 3000
          });
        } else if (wasContrib && !canContribute()) notif.set({
          text: 'You are now a spectator',
          duration: 3000
        });
        updateOnline();
      },
      setActive: setActive,
      isActive: function(id) {
        return !!active[id];
      },
      owner: owner,
      myMember: myMember,
      isOwner: isOwner,
      canContribute: canContribute,
      max: max,
      setRole: function(id, role) {
        setActive(id);
        send("setRole", {
          userId: id,
          role: role
        });
        confing(null);
      },
      kick: function(id) {
        send("kick", id);
        confing(null);
      },
      leave: function() {
        send("leave");
      },
      ordered: function() {
        var d = dict();
        return Object.keys(d).map(function(id) {
          return d[id];
        }).sort(function(a, b) {
          if (a.role === 'r' && b.role === 'w') return 1;
          if (a.role === 'w' && b.role === 'r') return -1;
          return a.addedAt > b.addedAt;
        });
      },
      size: function() {
        return Object.keys(dict()).length;
      },
      setSpectators: function(usernames) {
        this.inviteForm.setSpectators(usernames);
        spectatorIds = usernames.map(util.titleNameToId);
        updateOnline();
      },
      isOnline: function(userId) {
        return online[userId];
      },
      titleNameToId: util.titleNameToId,
      hasOnlineContributor: function() {
        var members = dict();
        for (var i in members)
          if (online[i] && members[i].role === 'w') return true;
        return false;
      }
    };
  },
  view: function(ctrl) {

    var isOwner = ctrl.members.isOwner();

    var username = function(member) {
      var u = member.user;
      return m('span.user_link.ulpt', {
        'data-href': '/@/' + u.name
      }, (u.title ? u.title + ' ' : '') + u.name);
    };

    var statusIcon = function(member) {
      var contrib = member.role === 'w';
      return m('span', {
        class: classSet({
          contrib: contrib,
          active: ctrl.members.isActive(member.user.id),
          online: ctrl.members.isOnline(member.user.id),
          status: true
        }),
        title: contrib ? 'Contributor' : 'Viewer',
      }, m('i', {
        'data-icon': contrib ? '' : 'v'
      }));
    };

    var configButton = function(ctrl, member) {
      if (isOwner && member.user.id !== ctrl.members.myId)
        return m('i.action.config', {
          'data-icon': '%',
          key: 'config-' + member.user.id,
          config: util.bindOnce('click', function() {
            ctrl.members.confing(ctrl.members.confing() === member.user.id ? null : member.user.id);
          })
        });
      if (!isOwner && member.user.id === ctrl.members.myId)
        return m('span.action.leave', {
          'data-icon': 'F',
          key: 'leave',
          title: 'Leave the study',
          config: util.bindOnce('click', ctrl.members.leave)
        });
    };

    var memberConfig = function(member) {
      return m('div.config', {
        key: member.user.id + '-config',
        config: function(el, isUpdate) {
          if (!isUpdate) $(el).parent('.members').scrollTo(el, 200);
        }
      }, [
        (function(id) {
          return m('div.role', [
            m('div.switch', [
              m('input', {
                id: id,
                class: 'cmn-toggle cmn-toggle-round',
                type: 'checkbox',
                checked: member.role === 'w',
                onchange: function(e) {
                  ctrl.members.setRole(member.user.id, e.target.checked ? 'w' : 'r');
                }
              }),
              m('label', {
                'for': id
              })
            ]),
            m('label', {
              'for': id
            }, 'Contributor')
          ]);
        })('member-role'),
        m('div.kick', m('a.button.text[data-icon=L]', {
          onclick: partial(ctrl.members.kick, member.user.id)
        }, 'Kick from this study'))
      ]);
    };

    var ordered = ctrl.members.ordered();

    return [
      m('div', {
        key: 'members',
        class: 'list members',
        config: function() {
          lichess.pubsub.emit('content_loaded')();
        }
      }, [
        ordered.map(function(member) {
          var confing = ctrl.members.confing() === member.user.id;
          return [
            m('div', {
              key: member.user.id,
              class: 'elem member' + (confing ? ' editing' : '')
            }, [
              m('div.left', [
                statusIcon(member),
                username(member)
              ]),
              configButton(ctrl, member)
            ]),
            confing ? memberConfig(member) : null
          ];
        }),
        (isOwner && ordered.length < ctrl.members.max) ? m('div', {
            key: 'invite-someone',
            class: 'elem member add',
            config: util.bindOnce('click', ctrl.members.inviteForm.toggle)
          },
          m('div.left', [
            m('span.status', m('i[data-icon=O]')),
            m('span.add_text', 'Add contributors')
          ])
        ) : null
      ])
    ];
  }
};
<|endoftext|>


<commit_before>import Conditions from '../../../../../resources/conditions.js';
import NetRegexes from '../../../../../resources/netregexes.js';
import { Responses } from '../../../../../resources/responses.js';
import ZoneId from '../../../../../resources/zone_id.js';

// TODO: knockback direction from big hand after giant lasers (Palm Of Temperance 58B4/58B6/?/?)
// TODO: do the formless markers come out in hate order? if so, be smart about swap vs buster call.
// TODO: for left/right reach during Blade Of Flame, call out Left + #1 alarm for #1.
// TODO: classical sculpture healer stacks are id 0106 headmarkers, but happen earlier too :C
// TODO: knockback from lion

// Each tether ID corresponds to a primal:
// 008C -- Shiva
// 008D -- Titan
// 008E -- Leviathan
// 008F -- Ifrit
// 0090 -- Ramuh
// 0091 -- Garuda
// We can collect + store these for later use on Stock/Release.
const shivaTetherId = '008C';
const titanTetherId = '008D';
const tetherIds = ['008E', '008F', '0090', '0091'];

const getTetherString = (tethers, output) => {
  // All tethers in E12S are double tethers, plus an optional junction (not in the tether list).
  if (!tethers || tethers.length !== 2)
    return;

  const sorted = tethers.sort();

  const comboStr = sorted[0] + sorted[1];
  if (comboStr in primalOutputStrings)
    return output[comboStr]();

  return output.combined({
    safespot1: output[sorted[0]](),
    safespot2: output[sorted[1]](),
  });
};

// TODO: also on the pre-statue cast, call south for any levi mechanics, west for any ifrit.
const primalOutputStrings = {
  // Tethers.
  '008E': {
    en: 'Middle',
  },
  '008F': {
    en: 'Sides',
  },
  '0090': {
    en: 'Out',
  },
  '0091': {
    en: 'Intercards',
  },
  // Tether combos.
  '008E008F': {
    en: 'Under + Sides',
  },
  '008E0090': {
    en: 'North/South + Out',
  },
  '008E0091': {
    en: 'Under + Intercards',
  },
  // Text output.
  'combined': {
    en: '${safespot1} + ${safespot2}',
  },
  'stock': {
    en: 'Stock: ${text}',
  },
  'junctionSuffix': {
    en: '${text} (${junction})',
  },
  // Junctions.
  'spread': {
    // Shiva spread.
    en: 'spread',
  },
  'stacks': {
    // Titan healer stacks.
    en: 'stacks',
  },
  'stack': {
    // Obliterate whole group laser stack.
    // This is deliberately "stack" singular (vs Titan "stacks").
    en: 'group stack',
  },
};

export default {
  zoneId: ZoneId.EdensPromiseEternitySavage,
  timelineFile: 'e12s.txt',
  triggers: [
    {
      id: 'E12S Promise Rapturous Reach Left',
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: '58AD', capture: false }),
      response: Responses.goLeft('info'),
    },
    {
      id: 'E12S Promise Rapturous Reach Right',
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: '58AE', capture: false }),
      response: Responses.goRight('info'),
    },
    {
      id: 'E12S Promise Obliteration',
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: '58A8', capture: false }),
      condition: Conditions.caresAboutAOE(),
      response: Responses.aoe(),
    },
    {
      id: 'E12S Promise Formless Judgement',
      netRegex: NetRegexes.headMarker({ id: '01A2', capture: false }),
      condition: (data) => data.role === 'tank',
      suppressSeconds: 1,
      alertText: (data, _, output) => {
        return output.busterAndSwap();
      },
      outputStrings: {
        busterAndSwap: {
          en: 'Tank Buster + Swap',
        },
      },
    },
    {
      id: 'E12S Promise Formless Judgement Warning',
      netRegex: NetRegexes.headMarker({ id: '01A2' }),
      condition: (data, matches) => {
        if (data.role === 'tank')
          return false;
        // ew sorry
        return Conditions.caresAboutPhysical()(data, matches);
      },
      suppressSeconds: 1,
      alertText: (data, _, output) => output.text(),
      outputStrings: {
        text: {
          en: 'Tank Busters',
        },
      },
    },
    {
      id: 'E12S Promise Junction Shiva',
      netRegex: NetRegexes.tether({ id: shivaTetherId, capture: false }),
      // Call out what the mechanic will be so that folks have time to move.
      preRun: (data) => {
        data.junctionSuffix = 'spread';
        data.junctionCount = data.junctionCount || 0;
        data.junctionCount++;
      },
      durationSeconds: (data) => data.junctionCount === 2 ? 4 : 13,
      alertText: (data, _, output) => {
        // The 2nd and 3rd junctions are different mechanics.
        if (data.junctionCount === 2)
          return output.diamondDust();
        return output.junctionWithCast();
      },
      outputStrings: {
        // Use parentheses to try to connote that this is a tell for the future, e.g. wolex.
        junctionWithCast: {
          en: 'Spread',
        },
        // TODO: maybe this should be a timeline trigger instead, since it needs more mit.
        diamondDust: {
          en: 'Big AOE, Get Middle',
        },
      },
    },
    {
      id: 'E12S Promise Junction Titan',
      netRegex: NetRegexes.tether({ id: titanTetherId, capture: false }),
      preRun: (data) => {
        data.junctionSuffix = 'stacks';
        data.junctionCount = data.junctionCount || 0;
        data.junctionCount++;
      },
      durationSeconds: (data) => data.junctionCount === 3 ? 4 : 13,
      alertText: (data, _, output) => {
        // The 2nd and 3rd junctions are different mechanics.
        if (data.junctionCount === 3)
          return output.earthenFury();
        return output.junctionWithCast();
      },
      outputStrings: {
        junctionWithCast: {
          en: 'Healer Stacks',
        },
        earthenFury: {
          en: 'Big AOE, Bombs Soon',
        },
      },
    },
    {
      id: 'E12S Promise Tether Collect',
      netRegex: NetRegexes.tether({ id: tetherIds }),
      run: (data, matches) => {
        data.tethers = data.tethers || [];
        data.tethers.push(matches.id);
      },
    },
    {
      id: 'E12S Promise Stock',
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: '5892', capture: false }),
      infoText: (data, _, output) => {
        data.stockedTethers = data.tethers;
        delete data.tethers;

        const text = getTetherString(data.stockedTethers, output);
        if (!text)
          return;
        return output.stock({ text: text });
      },
      outputStrings: primalOutputStrings,
    },
    {
      id: 'E12S Promise Cast Release',
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: ['4E43', '5893'] }),
      alertText: (data, matches, output) => {
        // At the end of the fight, there is a stock -> cast -> release,
        // which means that we need to grab the original tethers during the first stock.
        const isRelease = matches.id === '5893';

        // On the 2nd cast.
        if (!isRelease) {
          data.castCount = data.castCount || 0;
          data.castCount++;
          if (data.castCount === 2)
            data.junctionSuffix = 'stack';
        }

        const text = getTetherString(isRelease ? data.stockedTethers : data.tethers, output);
        if (!text)
          return;
        if (!data.junctionSuffix)
          return text;
        return output.junctionSuffix({
          text: text,
          junction: output[data.junctionSuffix](),
        });
      },
      run: (data) => {
        delete data.tethers;
        delete data.stockedTethers;
        delete data.junctionSuffix;
      },
      outputStrings: primalOutputStrings,
    },
    {
      id: 'E12S Promise Tether Cleanup',
      netRegex: NetRegexes.startsUsing({ id: ['4E43', '5892', '5893'], capture: false }),
      delaySeconds: 10,
      run: (data) => delete data.tethers,
    },
    {
      id: 'E12S Promise Plunging Ice',
      // This has a 9 second cast. :eyes:
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: '589D', capture: false }),
      delaySeconds: 3.1, // just for safety
      response: Responses.knockback('alert'),
    },
    {
      id: 'E12S Promise Titan Orange Square',
      netRegex: NetRegexes.headMarker({ id: '0182' }),
      condition: Conditions.targetIsYou(),
      infoText: (data, _, output) => output.text(),
      outputStrings: {
        text: {
          en: 'Orange Stack',
        },
      },
    },
    {
      id: 'E12S Promise Titan Yellow Pyramid',
      netRegex: NetRegexes.headMarker({ id: '0181' }),
      condition: Conditions.targetIsYou(),
      alertText: (data, _, output) => output.text(),
      outputStrings: {
        text: {
          en: 'Yellow Spread',
        },
      },
    },
    {
      // TODO: the 2nd and third set of markers have two blues that can be on anybody.
      // TODO: therefore you need a priority system, and it'd be nice to call out your partner.
      id: 'E12S Promise Titan Blue',
      netRegex: NetRegexes.headMarker({ id: '0183' }),
      condition: Conditions.targetIsYou(),
      alarmText: (data, _, output) => output.text(),
      outputStrings: {
        text: {
          en: 'Blue!',
        },
      },
    },
    {
      id: 'E12S Promise Blade Of Flame Markers',
      netRegex: NetRegexes.headMarker({ id: ['0159', '015[A-F]', '01560'] }),
      condition: Conditions.targetIsYou(),
      durationSeconds: 15,
      alertText: (data, matches, output) => {
        // We could arguably tell you which giant you're tethered to by finding their position?
        // And then saying something like "North" but that's probably more confusing than helpful.
        // Unclear which one gets triangles or squares too, so don't say that.

        // ids are sequential: #1 square, #2 square, #3 square, #4 square, #1 triangle etc
        // Convert id values into 1-4 to use for output.
        const decimalBase = parseInt('0159', 16);
        const decimalId = (parseInt(matches.id, 16) - decimalBase) % 4 + 1;
        return output[decimalId.toString()]();
      },
      outputStrings: {
        '1': {
          en: '1',
        },
        '2': {
          en: '2',
        },
        '3': {
          en: '3',
        },
        '4': {
          en: '4',
        },
      },
    },
    {
      // We could arguably tell people where their lion is, but this is probably plenty.
      id: 'E12S Promise Small Lion Tether',
      netRegex: NetRegexes.tether({ source: 'Beastly Sculpture', id: '0011' }),
      condition: Conditions.targetIsYou(),
      // Don't collide with reach left/right call.
      delaySeconds: 0.5,
      alertText: (data, _, output) => output.text(),
      outputStrings: {
        text: {
          en: 'Lion Tether on YOU',
        },
      },
    },
  ],
};
<commit_msg>raidboss: e12s disable randomized headmarker lines<commit_after>import Conditions from '../../../../../resources/conditions.js';
import NetRegexes from '../../../../../resources/netregexes.js';
import { Responses } from '../../../../../resources/responses.js';
import ZoneId from '../../../../../resources/zone_id.js';

// Handle randomized headmarkers:
// TODO: tankbuster markers
// TODO: do the formless markers come out in hate order? if so, be smart about swap vs buster call.
// TODO: electric slide markers
// TODO: titan headmarkers

// TODO: knockback direction from big hand after giant lasers (Palm Of Temperance 58B4/58B6/?/?)
// TODO: for left/right reach during Blade Of Flame, call out Left + #1 alarm for #1.
// TODO: classical sculpture healer stacks are id 0106 headmarkers, but happen earlier too :C
// TODO: knockback from lion

// Each tether ID corresponds to a primal:
// 008C -- Shiva
// 008D -- Titan
// 008E -- Leviathan
// 008F -- Ifrit
// 0090 -- Ramuh
// 0091 -- Garuda
// We can collect + store these for later use on Stock/Release.
const shivaTetherId = '008C';
const titanTetherId = '008D';
const tetherIds = ['008E', '008F', '0090', '0091'];

const getTetherString = (tethers, output) => {
  // All tethers in E12S are double tethers, plus an optional junction (not in the tether list).
  if (!tethers || tethers.length !== 2)
    return;

  const sorted = tethers.sort();

  const comboStr = sorted[0] + sorted[1];
  if (comboStr in primalOutputStrings)
    return output[comboStr]();

  return output.combined({
    safespot1: output[sorted[0]](),
    safespot2: output[sorted[1]](),
  });
};

// TODO: also on the pre-statue cast, call south for any levi mechanics, west for any ifrit.
const primalOutputStrings = {
  // Tethers.
  '008E': {
    en: 'Middle',
  },
  '008F': {
    en: 'Sides',
  },
  '0090': {
    en: 'Out',
  },
  '0091': {
    en: 'Intercards',
  },
  // Tether combos.
  '008E008F': {
    en: 'Under + Sides',
  },
  '008E0090': {
    en: 'North/South + Out',
  },
  '008E0091': {
    en: 'Under + Intercards',
  },
  // Text output.
  'combined': {
    en: '${safespot1} + ${safespot2}',
  },
  'stock': {
    en: 'Stock: ${text}',
  },
  'junctionSuffix': {
    en: '${text} (${junction})',
  },
  // Junctions.
  'spread': {
    // Shiva spread.
    en: 'spread',
  },
  'stacks': {
    // Titan healer stacks.
    en: 'stacks',
  },
  'stack': {
    // Obliterate whole group laser stack.
    // This is deliberately "stack" singular (vs Titan "stacks").
    en: 'group stack',
  },
};

export default {
  zoneId: ZoneId.EdensPromiseEternitySavage,
  timelineFile: 'e12s.txt',
  triggers: [
    {
      id: 'E12S Promise Rapturous Reach Left',
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: '58AD', capture: false }),
      response: Responses.goLeft('info'),
    },
    {
      id: 'E12S Promise Rapturous Reach Right',
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: '58AE', capture: false }),
      response: Responses.goRight('info'),
    },
    {
      id: 'E12S Promise Obliteration',
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: '58A8', capture: false }),
      condition: Conditions.caresAboutAOE(),
      response: Responses.aoe(),
    },
    {
      id: 'E12S Promise Junction Shiva',
      netRegex: NetRegexes.tether({ id: shivaTetherId, capture: false }),
      // Call out what the mechanic will be so that folks have time to move.
      preRun: (data) => {
        data.junctionSuffix = 'spread';
        data.junctionCount = data.junctionCount || 0;
        data.junctionCount++;
      },
      durationSeconds: (data) => data.junctionCount === 2 ? 4 : 13,
      alertText: (data, _, output) => {
        // The 2nd and 3rd junctions are different mechanics.
        if (data.junctionCount === 2)
          return output.diamondDust();
        return output.junctionWithCast();
      },
      outputStrings: {
        // Use parentheses to try to connote that this is a tell for the future, e.g. wolex.
        junctionWithCast: {
          en: 'Spread',
        },
        // TODO: maybe this should be a timeline trigger instead, since it needs more mit.
        diamondDust: {
          en: 'Big AOE, Get Middle',
        },
      },
    },
    {
      id: 'E12S Promise Junction Titan',
      netRegex: NetRegexes.tether({ id: titanTetherId, capture: false }),
      preRun: (data) => {
        data.junctionSuffix = 'stacks';
        data.junctionCount = data.junctionCount || 0;
        data.junctionCount++;
      },
      durationSeconds: (data) => data.junctionCount === 3 ? 4 : 13,
      alertText: (data, _, output) => {
        // The 2nd and 3rd junctions are different mechanics.
        if (data.junctionCount === 3)
          return output.earthenFury();
        return output.junctionWithCast();
      },
      outputStrings: {
        junctionWithCast: {
          en: 'Healer Stacks',
        },
        earthenFury: {
          en: 'Big AOE, Bombs Soon',
        },
      },
    },
    {
      id: 'E12S Promise Tether Collect',
      netRegex: NetRegexes.tether({ id: tetherIds }),
      run: (data, matches) => {
        data.tethers = data.tethers || [];
        data.tethers.push(matches.id);
      },
    },
    {
      id: 'E12S Promise Stock',
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: '5892', capture: false }),
      infoText: (data, _, output) => {
        data.stockedTethers = data.tethers;
        delete data.tethers;

        const text = getTetherString(data.stockedTethers, output);
        if (!text)
          return;
        return output.stock({ text: text });
      },
      outputStrings: primalOutputStrings,
    },
    {
      id: 'E12S Promise Cast Release',
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: ['4E43', '5893'] }),
      alertText: (data, matches, output) => {
        // At the end of the fight, there is a stock -> cast -> release,
        // which means that we need to grab the original tethers during the first stock.
        const isRelease = matches.id === '5893';

        // On the 2nd cast.
        if (!isRelease) {
          data.castCount = data.castCount || 0;
          data.castCount++;
          if (data.castCount === 2)
            data.junctionSuffix = 'stack';
        }

        const text = getTetherString(isRelease ? data.stockedTethers : data.tethers, output);
        if (!text)
          return;
        if (!data.junctionSuffix)
          return text;
        return output.junctionSuffix({
          text: text,
          junction: output[data.junctionSuffix](),
        });
      },
      run: (data) => {
        delete data.tethers;
        delete data.stockedTethers;
        delete data.junctionSuffix;
      },
      outputStrings: primalOutputStrings,
    },
    {
      id: 'E12S Promise Tether Cleanup',
      netRegex: NetRegexes.startsUsing({ id: ['4E43', '5892', '5893'], capture: false }),
      delaySeconds: 10,
      run: (data) => delete data.tethers,
    },
    {
      id: 'E12S Promise Plunging Ice',
      // This has a 9 second cast. :eyes:
      netRegex: NetRegexes.startsUsing({ source: 'Eden\'s Promise', id: '589D', capture: false }),
      delaySeconds: 3.1, // just for safety
      response: Responses.knockback('alert'),
    },
    {
      // We could arguably tell people where their lion is, but this is probably plenty.
      id: 'E12S Promise Small Lion Tether',
      netRegex: NetRegexes.tether({ source: 'Beastly Sculpture', id: '0011' }),
      condition: Conditions.targetIsYou(),
      // Don't collide with reach left/right call.
      delaySeconds: 0.5,
      alertText: (data, _, output) => output.text(),
      outputStrings: {
        text: {
          en: 'Lion Tether on YOU',
        },
      },
    },
  ],
};
<|endoftext|>


<commit_before>'use strict';

// Triggers for all occasions and zones.
[{
  zoneRegex: /.*/,
  triggers: [
    {
      id: 'General Provoke',
      regex: /:(\y{Name}):1D6D:Provoke:/,
      regexDe: /:(\y{Name}):1D6D:Herausforderung:/,
      regexFr: /:(\y{Name}):1D6D:Provocation:/,
      condition: function(data) {
        return data.role == 'tank';
      },
      infoText: function(data, matches) {
        return {
          en: 'Provoke: ' + data.ShortName(matches[1]),
          de: 'Herausforderung: ' + data.ShortName(matches[1]),
          fr: 'Provocation: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Shirk',
      regex: /:(\y{Name}):1D71:Shirk:/,
      regexDe: /:(\y{Name}):1D71:Geteiltes Leid:/,
      regexFr: /:(\y{Name}):1D71:Dérobade:/,
      regexJa: /:(\y{Name}):1D71:シャーク:/,
      condition: function(data) {
        return data.role == 'tank';
      },
      infoText: function(data, matches) {
        return {
          en: 'Shirk: ' + data.ShortName(matches[1]),
          de: 'Geteiltes Leid: ' + data.ShortName(matches[1]),
          fr: 'Dérobade: ' + data.ShortName(matches[1]),
          ja: 'シャーク: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Holmgang',
      regex: /:(\y{Name}):2B:Holmgang:/,
      regexJa: /:(\y{Name}):2B:ホルムギャング:/,
      condition: function(data) {
        return data.role == 'tank';
      },
      infoText: function(data, matches) {
        return 'Holmgang: ' + data.ShortName(matches[1]);
      },
    },
    {
      id: 'General Hallowed',
      regex: /:(\y{Name}):1E:Hallowed Ground:/,
      regexDe: /:(\y{Name}):1E:Heiliger Boden:/,
      regexFr: /:(\y{Name}):1E:Invincible:/,
      regexJa: /:(\y{Name}):1E:インビンシブル:/,
      condition: function(data) {
        return data.role == 'tank';
      },
      infoText: function(data, matches) {
        return {
          en: 'Hallowed: ' + data.ShortName(matches[1]),
          de: 'Heiliger Boden: ' + data.ShortName(matches[1]),
          fr: 'Invincible: ' + data.ShortName(matches[1]),
          ja: 'インビンシブル: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Superbolide',
      regex: /:(\y{Name}):3F18:Superbolide:/,
      regexDe: /:(\y{Name}):3F18:Meteoritenfall:/,
      regexFr: /:(\y{Name}):3F18:Bolide:/,
      regexJa: /:(\y{Name}):3F18:ボーライド:/,
      condition: function(data) {
        return data.role == 'tank';
      },
      infoText: function(data, matches) {
        return {
          en: 'Bolide: ' + data.ShortName(matches[1]),
          de: 'Meteoritenfall: ' + data.ShortName(matches[1]),
          fr: 'Bolide: ' + data.ShortName(matches[1]),
          ja: 'ボーライド: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Living',
      regex: /:(\y{Name}):E36:Living Dead:/,
      regexDe: /:(\y{Name}):E36:Totenerweckung:/,
      regexFr: /:(\y{Name}):E36:Mort-Vivant:/,
      regexJa: /:(\y{Name}):E36:リビングデッド:/,
      condition: function(data) {
        return data.role == 'tank';
      },
      infoText: function(data, matches) {
        return {
          en: 'Living: ' + data.ShortName(matches[1]),
          de: 'Totenerweckung: ' + data.ShortName(matches[1]),
          fr: 'Mort-vivant: ' + data.ShortName(matches[1]),
          ja: 'リビングデッド: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Walking',
      regex: /1A:\y{ObjectId}:(\y{Name}) gains the effect of Walking Dead/,
      regexDe: /1A:\y{ObjectId}:(\y{Name}) gains the effect of Erweckter/,
      regexFr: /1A:\y{ObjectId}:(\y{Name}) gains the effect of Marcheur Des Limbes/,
      condition: function(data) {
        return data.role == 'tank';
      },
      infoText: function(data, matches) {
        return {
          en: 'Walking: ' + data.ShortName(matches[1]),
          de: 'Erweckter: ' + data.ShortName(matches[1]),
          fr: 'Marcheur Des Limbes: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Ready check',
      regex: /:(?:(\y{Name}) has initiated|You have commenced) a ready check\./,
      regexDe: /:(?:(\y{Name}) hat|Du hast) eine Bereitschaftsanfrage gestellt\./,
      regexFr: /:Un appel de préparation a été lancé par (\y{Name})\./,
      sound: '../../resources/sounds/Overwatch/D.Va_-_Game_on.ogg',
      soundVolume: 0.6,
    },
  ],
}];
<commit_msg>raidboss: show tank invicibility triggers to healers<commit_after>'use strict';

// Triggers for all occasions and zones.
[{
  zoneRegex: /.*/,
  triggers: [
    {
      id: 'General Provoke',
      regex: /:(\y{Name}):1D6D:Provoke:/,
      regexDe: /:(\y{Name}):1D6D:Herausforderung:/,
      regexFr: /:(\y{Name}):1D6D:Provocation:/,
      condition: function(data) {
        return data.role == 'tank';
      },
      infoText: function(data, matches) {
        return {
          en: 'Provoke: ' + data.ShortName(matches[1]),
          de: 'Herausforderung: ' + data.ShortName(matches[1]),
          fr: 'Provocation: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Shirk',
      regex: /:(\y{Name}):1D71:Shirk:/,
      regexDe: /:(\y{Name}):1D71:Geteiltes Leid:/,
      regexFr: /:(\y{Name}):1D71:Dérobade:/,
      regexJa: /:(\y{Name}):1D71:シャーク:/,
      condition: function(data) {
        return data.role == 'tank';
      },
      infoText: function(data, matches) {
        return {
          en: 'Shirk: ' + data.ShortName(matches[1]),
          de: 'Geteiltes Leid: ' + data.ShortName(matches[1]),
          fr: 'Dérobade: ' + data.ShortName(matches[1]),
          ja: 'シャーク: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Holmgang',
      regex: /:(\y{Name}):2B:Holmgang:/,
      regexJa: /:(\y{Name}):2B:ホルムギャング:/,
      condition: function(data) {
        return data.role == 'tank' || data.role == 'healer';
      },
      infoText: function(data, matches) {
        return 'Holmgang: ' + data.ShortName(matches[1]);
      },
    },
    {
      id: 'General Hallowed',
      regex: /:(\y{Name}):1E:Hallowed Ground:/,
      regexDe: /:(\y{Name}):1E:Heiliger Boden:/,
      regexFr: /:(\y{Name}):1E:Invincible:/,
      regexJa: /:(\y{Name}):1E:インビンシブル:/,
      condition: function(data) {
        return data.role == 'tank' || data.role == 'healer';
      },
      infoText: function(data, matches) {
        return {
          en: 'Hallowed: ' + data.ShortName(matches[1]),
          de: 'Heiliger Boden: ' + data.ShortName(matches[1]),
          fr: 'Invincible: ' + data.ShortName(matches[1]),
          ja: 'インビンシブル: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Superbolide',
      regex: /:(\y{Name}):3F18:Superbolide:/,
      regexDe: /:(\y{Name}):3F18:Meteoritenfall:/,
      regexFr: /:(\y{Name}):3F18:Bolide:/,
      regexJa: /:(\y{Name}):3F18:ボーライド:/,
      condition: function(data) {
        return data.role == 'tank' || data.role == 'healer';
      },
      infoText: function(data, matches) {
        return {
          en: 'Bolide: ' + data.ShortName(matches[1]),
          de: 'Meteoritenfall: ' + data.ShortName(matches[1]),
          fr: 'Bolide: ' + data.ShortName(matches[1]),
          ja: 'ボーライド: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Living',
      regex: /:(\y{Name}):E36:Living Dead:/,
      regexDe: /:(\y{Name}):E36:Totenerweckung:/,
      regexFr: /:(\y{Name}):E36:Mort-Vivant:/,
      regexJa: /:(\y{Name}):E36:リビングデッド:/,
      condition: function(data) {
        return data.role == 'tank' || data.role == 'healer';
      },
      infoText: function(data, matches) {
        return {
          en: 'Living: ' + data.ShortName(matches[1]),
          de: 'Totenerweckung: ' + data.ShortName(matches[1]),
          fr: 'Mort-vivant: ' + data.ShortName(matches[1]),
          ja: 'リビングデッド: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Walking',
      regex: /1A:\y{ObjectId}:(\y{Name}) gains the effect of Walking Dead/,
      regexDe: /1A:\y{ObjectId}:(\y{Name}) gains the effect of Erweckter/,
      regexFr: /1A:\y{ObjectId}:(\y{Name}) gains the effect of Marcheur Des Limbes/,
      condition: function(data) {
        return data.role == 'tank' | data.role == 'healer';
      },
      infoText: function(data, matches) {
        return {
          en: 'Walking: ' + data.ShortName(matches[1]),
          de: 'Erweckter: ' + data.ShortName(matches[1]),
          fr: 'Marcheur Des Limbes: ' + data.ShortName(matches[1]),
        };
      },
    },
    {
      id: 'General Ready check',
      regex: /:(?:(\y{Name}) has initiated|You have commenced) a ready check\./,
      regexDe: /:(?:(\y{Name}) hat|Du hast) eine Bereitschaftsanfrage gestellt\./,
      regexFr: /:Un appel de préparation a été lancé par (\y{Name})\./,
      sound: '../../resources/sounds/Overwatch/D.Va_-_Game_on.ogg',
      soundVolume: 0.6,
    },
  ],
}];
<|endoftext|>


<commit_before>import React, { useState } from 'react'
import { useQuery } from '@apollo/react-hooks'
import gql from 'graphql-tag'
import 'url-search-params-polyfill';

import Browse from '../components/Browse'

const GET_LIBRARIES = gql`
  {
    allLibraries {
      id
      name
    }
  }
`
const GET_PROFILE = gql`
  {
    profile {
      id
      username
      email
    }
  }
`
const GET_PHOTOS = gql`
  query Photos($filters: String) {
    allPhotos(multiFilter: $filters) {
      edges {
        node {
          id
          location
        }
      }
    }
  }
`

const BrowseContainer = props => {
  const [expanded, setExpanded] = useState(true)

  const params = new URLSearchParams(window.location.search)
  const mode = params.get('mode')
    ? params.get('mode').toUpperCase()
    : 'TIMELINE'

  const {
    loading: librariesLoading,
    error: librariesError,
    data: librariesData,
  } = useQuery(GET_LIBRARIES)

  const {
    loading: profileLoading,
    error: profileError,
    data: profileData,
  } = useQuery(GET_PROFILE)

  let photoSections = []
  let photos = []

  const filtersStr = props.selectedFilters.map(filter => filter.id).join(',')

  const {
    loading: photosLoading,
    error: photosError,
    data: photosData,
  } = useQuery(GET_PHOTOS, {
    variables: {
      filters: filtersStr,
    },
  })

  if (photosData) {
    photos = photosData.allPhotos.edges.map(photo => ({
      id: photo.node.id,
      thumbnail: `/thumbnails/256x256_cover_q50/${photo.node.id}/`,
      location: photo.node.location
        ? [photo.node.location.split(',')[0], photo.node.location.split(',')[1]]
        : null,
    }))
  }

  let section = {
    id: 12345,
    title: null,
    segments: [
      {
        numPhotos: photos.length,
        photos: photos,
      },
    ],
  }

  photoSections.push(section)

  let anyLoading = profileLoading || librariesLoading || photosLoading
  let anyError = profileError ? profileError : (librariesError ? librariesError : photosError)

  return (
    <Browse
      profile={profileData ? profileData.profile : null }
      libraries={librariesData ? librariesData.allLibraries : null}
      selectedFilters={props.selectedFilters}
      mode={mode}
      loading={anyLoading}
      error={anyError}
      photoSections={photoSections}
      onFilterToggle={props.onFilterToggle}
      onClearFilters={props.onClearFilters}
      expanded={expanded}
      onExpandCollapse={() => setExpanded(!expanded)}
    />
  )
}

export default BrowseContainer
<commit_msg>Update BrowseContainer.js<commit_after>import React, { useState } from 'react'
import { useQuery } from '@apollo/react-hooks'
import gql from 'graphql-tag'
import 'url-search-params-polyfill';

import Browse from '../components/Browse'

const GET_LIBRARIES = gql`
  {
    allLibraries {
      id
      name
    }
  }
`
const GET_PROFILE = gql`
  {
    profile {
      id
      username
      email
    }
  }
`
const GET_PHOTOS = gql`
  query Photos($filters: String) {
    allPhotos(multiFilter: $filters) {
      edges {
        node {
          id
          location
        }
      }
    }
  }
`

const GET_SETTINGS = gql`
query {
  Library{
    color
    location
    style
  }
}
`

const BrowseContainer = props => {
  const [expanded, setExpanded] = useState(true)

  const params = new URLSearchParams(window.location.search)
  const mode = params.get('mode')
    ? params.get('mode').toUpperCase()
    : 'TIMELINE'

  const {
    loading: librariesLoading,
    error: librariesError,
    data: librariesData,
  } = useQuery(GET_LIBRARIES)

  const {
    loading: profileLoading,
    error: profileError,
    data: profileData,
  } = useQuery(GET_PROFILE)

  let photoSections = []
  let photos = []

  const filtersStr = props.selectedFilters.map(filter => filter.id).join(',')

  const {
    loading: photosLoading,
    error: photosError,
    data: photosData,
  } = useQuery(GET_PHOTOS, {
    variables: {
      filters: filtersStr,
    },
  })

  if (photosData) {
    photos = photosData.allPhotos.edges.map(photo => ({
      id: photo.node.id,
      thumbnail: `/thumbnails/256x256_cover_q50/${photo.node.id}/`,
      location: photo.node.location
        ? [photo.node.location.split(',')[0], photo.node.location.split(',')[1]]
        : null,
    }))
  }

  let section = {
    id: 12345,
    title: null,
    segments: [
      {
        numPhotos: photos.length,
        photos: photos,
      },
    ],
  }

  photoSections.push(section)

  let anyLoading = profileLoading || librariesLoading || photosLoading
  let anyError = profileError ? profileError : (librariesError ? librariesError : photosError)

  return (
    <Browse
      profile={profileData ? profileData.profile : null }
      libraries={librariesData ? librariesData.allLibraries : null}
      selectedFilters={props.selectedFilters}
      mode={mode}
      loading={anyLoading}
      error={anyError}
      photoSections={photoSections}
      onFilterToggle={props.onFilterToggle}
      onClearFilters={props.onClearFilters}
      expanded={expanded}
      onExpandCollapse={() => setExpanded(!expanded)}
    />
  )
}

export default BrowseContainer
<|endoftext|>


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

import NodeStore from "../../stores/NodeStore";

class NodeActivation extends Component {
  constructor() {
    super();

    this.state = {
      activation: {},
    };
  }

  componentDidMount() {
    NodeStore.getActivation(this.props.match.params.devEUI, (nodeActivation) => {
      this.setState({
        activation: nodeActivation.deviceActivation,
      });
    });
  }

  render() {
    if (this.state.activation.devAddr === undefined) {
      return(

              The node has not been activated yet or device has been inactive for a long time.

      );
    } else {
      return(

            <form onSubmit={this.handleSubmit}>
              <fieldset disabled={true}>

                  Device address
                  <input className="form-control" id="devAddr" type="text" value={this.state.activation.devAddr || ''} />

                  Network session encryption key
                  <input className="form-control" id="nwkSEncKey" type="text" value={this.state.activation.nwkSEncKey || ''} />

                  Serving network session integrity key
                  <input className="form-control" id="sNwkSIntKey" type="text" value={this.state.activation.sNwkSIntKey || ''} />

                  Forwarding network session integrity key
                  <input className="form-control" id="fNwkSIntKey" type="text" value={this.state.activation.fNwkSIntKey || ''} />

                  Application session key
                  <input className="form-control" id="appSKey" type="text" value={this.state.activation.appSKey || ''} />

                  Uplink frame-counter
                  <input className="form-control" id="fCntUp" type="number" value={this.state.activation.fCntUp || 0} />

                  Downlink frame-counter
                  <input className="form-control" id="fCntDown" type="number" required value={this.state.activation.fCntDown || 0} />

                  Disable frame-counter validation

                      <input type="checkbox" name="skipFCntCheck" id="skipFCntCheck" checked={!!this.state.activation.skipFCntCheck} /> Disable frame-counter validation

                    Note that disabling the frame-counter validation will compromise security as it enables people to perform replay-attacks.
                    This setting can only be set for ABP devices.

              </fieldset>
            </form>

      );
    }
  }
}

export default NodeActivation;<commit_msg>Remove skipFCntCheck from form.<commit_after>import React, { Component } from "react";

import NodeStore from "../../stores/NodeStore";

class NodeActivation extends Component {
  constructor() {
    super();

    this.state = {
      activation: {},
    };
  }

  componentDidMount() {
    NodeStore.getActivation(this.props.match.params.devEUI, (nodeActivation) => {
      this.setState({
        activation: nodeActivation.deviceActivation,
      });
    });
  }

  render() {
    if (this.state.activation.devAddr === undefined) {
      return(

              The node has not been activated yet or device has been inactive for a long time.

      );
    } else {
      return(

            <form onSubmit={this.handleSubmit}>
              <fieldset disabled={true}>

                  Device address
                  <input className="form-control" id="devAddr" type="text" value={this.state.activation.devAddr || ''} />

                  Network session encryption key
                  <input className="form-control" id="nwkSEncKey" type="text" value={this.state.activation.nwkSEncKey || ''} />

                  Serving network session integrity key
                  <input className="form-control" id="sNwkSIntKey" type="text" value={this.state.activation.sNwkSIntKey || ''} />

                  Forwarding network session integrity key
                  <input className="form-control" id="fNwkSIntKey" type="text" value={this.state.activation.fNwkSIntKey || ''} />

                  Application session key
                  <input className="form-control" id="appSKey" type="text" value={this.state.activation.appSKey || ''} />

                  Uplink frame-counter
                  <input className="form-control" id="fCntUp" type="number" value={this.state.activation.fCntUp || 0} />

                  Downlink frame-counter
                  <input className="form-control" id="fCntDown" type="number" required value={this.state.activation.fCntDown || 0} />

              </fieldset>
            </form>

      );
    }
  }
}

export default NodeActivation;
<|endoftext|>


<commit_before>
environment = 'node';

var Sugar = require('../../../release/sugar-edge');

// Test suite
require('../../javascripts/setup.js');
require('../../javascripts/date_helper.js');
require('../../javascripts/object_helper.js');

// Tests
require('../sugar/object.js');
require('../sugar/string.js');
require('../sugar/array.js');
require('../sugar/number.js');
require('../sugar/date.js');
require('../sugar/regexp.js');
require('../sugar/function.js');
require('../sugar/es5.js');
require('../sugar/equals.js');
require('../sugar/date_zh_cn.js')
require('../sugar/date_zh_tw.js')
require('../sugar/date_ko.js')
require('../sugar/date_ru.js')
require('../sugar/date_es.js')
require('../sugar/date_pt.js')
require('../sugar/date_fr.js')
require('../sugar/date_it.js')
require('../sugar/date_de.js')
require('../sugar/date_ja.js')
require('../sugar/date_sv.js')
require('../sugar/date_range.js');
require('../sugar/inflections.js');
require('../sugar/language.js');

syncTestsFinished();
<commit_msg>test.js runs from release/<commit_after>
environment = 'node';

var Sugar = require('../../../release/sugar-full.development');

// Test suite
require('../../javascripts/setup.js');
require('../../javascripts/date_helper.js');
require('../../javascripts/object_helper.js');

// Tests
require('../sugar/object.js');
require('../sugar/string.js');
require('../sugar/array.js');
require('../sugar/number.js');
require('../sugar/date.js');
require('../sugar/regexp.js');
require('../sugar/function.js');
require('../sugar/es5.js');
require('../sugar/equals.js');
require('../sugar/date_zh_cn.js')
require('../sugar/date_zh_tw.js')
require('../sugar/date_ko.js')
require('../sugar/date_ru.js')
require('../sugar/date_es.js')
require('../sugar/date_pt.js')
require('../sugar/date_fr.js')
require('../sugar/date_it.js')
require('../sugar/date_de.js')
require('../sugar/date_ja.js')
require('../sugar/date_sv.js')
require('../sugar/date_range.js');
require('../sugar/inflections.js');
require('../sugar/language.js');

syncTestsFinished();
<|endoftext|>


<commit_before>const url = require('url');

let base = 'http://mlb.mlb.com/lookup/json/named.';
let endpoints = {
	search: 'search_player_all.bam',
	roster: 'roster_all.bam',
	stats: {
		hitting: 'sport_hitting_tm.bam',
		pitching: 'sport_pitching_tm.bam'
	}
};

function buildRequest(type, options) {
	let uri = base;
	let currentYear = new Date().getFullYear();

	switch (type) {
		case 'search':
			if (options.active === false) {
				uri += `${endpoints.search}?sport_code='mlb'&name_part='${options.query}%25'&active_sw='N'`;
			} else {
				uri += `${endpoints.search}?sport_code='mlb'&name_part='${options.query || options}%25'&active_sw='Y'`;
			}
			break;
		case 'roster':
			if (options.full) {
				uri += `${endpoints.roster}?team_id='${options.key || options}'`
			} else {
				uri += `${endpoints.roster}?team_id='${options.key || options}'&roster_all.col_in=name_display_first_last&roster_all.col_in=player_id`
			}
			break;
		case 'statsHitting':
			if (options.year) {
				uri += `${endpoints.stats.hitting}?player_id=${options.id || options}&sport_hitting_tm.season=${options.year}&game_type='R'&league_list_id='mlb'`;
			} else {
				uri += `${endpoints.stats.hitting}?player_id=${options.id || options}&game_type='R'&league_list_id='mlb'`;
			}
			break;
		case 'statsPitching':
			if (options.year) {
				uri += `${endpoints.stats.pitching}?player_id=${options.id || options}&sport_pitching_tm.season=${options.year}&game_type='R'&league_list_id='mlb'`;
			} else {
				uri += `${endpoints.stats.pitching}?player_id=${options.id || options}&game_type='R'&league_list_id='mlb'`;
			}
			break;
		default:
			return new Error('Invalid request type.');
			break;
	}

	return url.parse(uri);

}

module.exports = buildRequest;
<commit_msg>remove currentYear<commit_after>const url = require('url');

let base = 'http://mlb.mlb.com/lookup/json/named.';
let endpoints = {
	search: 'search_player_all.bam',
	roster: 'roster_all.bam',
	stats: {
		hitting: 'sport_hitting_tm.bam',
		pitching: 'sport_pitching_tm.bam'
	}
};

function buildRequest(type, options) {
	let uri = base;

	switch (type) {
		case 'search':
			if (options.active === false) {
				uri += `${endpoints.search}?sport_code='mlb'&name_part='${options.query}%25'&active_sw='N'`;
			} else {
				uri += `${endpoints.search}?sport_code='mlb'&name_part='${options.query || options}%25'&active_sw='Y'`;
			}
			break;
		case 'roster':
			if (options.full) {
				uri += `${endpoints.roster}?team_id='${options.key || options}'`
			} else {
				uri += `${endpoints.roster}?team_id='${options.key || options}'&roster_all.col_in=name_display_first_last&roster_all.col_in=player_id`
			}
			break;
		case 'statsHitting':
			if (options.year) {
				uri += `${endpoints.stats.hitting}?player_id=${options.id || options}&sport_hitting_tm.season=${options.year}&game_type='R'&league_list_id='mlb'`;
			} else {
				uri += `${endpoints.stats.hitting}?player_id=${options.id || options}&game_type='R'&league_list_id='mlb'`;
			}
			break;
		case 'statsPitching':
			if (options.year) {
				uri += `${endpoints.stats.pitching}?player_id=${options.id || options}&sport_pitching_tm.season=${options.year}&game_type='R'&league_list_id='mlb'`;
			} else {
				uri += `${endpoints.stats.pitching}?player_id=${options.id || options}&game_type='R'&league_list_id='mlb'`;
			}
			break;
		default:
			return new Error('Invalid request type.');
			break;
	}

	return url.parse(uri);

}

module.exports = buildRequest;
<|endoftext|>