<commit_before>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $ */

/**
 * CSSPreprocessorDocument manages a single LESS or SASS source document
 *
 * __HIGHLIGHTING__
 *
 * CSSPreprocessorDocument supports highlighting all DOMNode corresponding to the rule at
 * the cursor position in the editor.
 *
 */
define(function CSSCSSPreprocessorDocumentModule(require, exports, module) {
    "use strict";

    var _               = require("thirdparty/lodash"),
        CSSUtils        = require("language/CSSUtils"),
        EditorManager   = require("editor/EditorManager"),
        HighlightAgent  = require("LiveDevelopment/Agents/HighlightAgent"),
        Inspector       = require("LiveDevelopment/Inspector/Inspector");

    /**
     * @constructor
     * @param {!Document} doc The source document from Brackets
     * @param {?Editor} editor The editor for this document. This is not used here since
     *                  we always need to get the active editor for a preprocessor document
     *                  and not the one passed in `editor`.
     */
    var CSSPreprocessorDocument = function CSSPreprocessorDocument(doc, editor) {
        this.doc = doc;

        this.onCursorActivity = this.onCursorActivity.bind(this);

        // Add a ref to the doc since we're listening for change events
        this.doc.addRef();
        this.onActiveEditorChange = this.onActiveEditorChange.bind(this);
        $(EditorManager).on("activeEditorChange", this.onActiveEditorChange);
        this.onActiveEditorChange(null, EditorManager.getActiveEditor(), null);
    };

    /** Close the document */
    CSSPreprocessorDocument.prototype.close = function close() {
        $(this.doc).off(".CSSPreprocessorDocument");
        $(EditorManager).off("activeEditorChange", this.onActiveEditorChange);
        this.doc.releaseRef();
        this.detachFromEditor();
    };

    CSSPreprocessorDocument.prototype.attachToEditor = function (editor) {
        this.editor = editor;

        if (this.editor) {
            $(this.editor).on("cursorActivity.CSSPreprocessorDocument", this.onCursorActivity);
            this.updateHighlight();
        }
    };

    CSSPreprocessorDocument.prototype.detachFromEditor = function () {
        if (this.editor) {
            HighlightAgent.hide();
            $(this.editor).off(".CSSPreprocessorDocument");
            this.editor = null;
        }
    };

    CSSPreprocessorDocument.prototype.updateHighlight = function () {
        if (Inspector.config.highlight && this.editor) {
            var editor = this.editor,
                codeMirror = editor._codeMirror,
                selectors = [];
            _.each(this.editor.getSelections(), function (sel) {
                var selector = CSSUtils.findSelectorAtDocumentPos(editor, (sel.reversed ? sel.end : sel.start));
                if (selector) {
                    selectors.push(selector);
                }
            });
            if (selectors.length) {
                HighlightAgent.rule(selectors.join(","));
            } else {
                HighlightAgent.hide();
            }
        }
    };

    /** Event Handlers *******************************************************/

    /** Triggered on cursor activity of the editor */
    CSSPreprocessorDocument.prototype.onCursorActivity = function onCursorActivity(event, editor) {
        this.updateHighlight();
    };

    /** Triggered when the active editor changes */
    CSSPreprocessorDocument.prototype.onActiveEditorChange = function (event, newActive, oldActive) {
        this.detachFromEditor();

        if (newActive && newActive.document === this.doc) {
            this.attachToEditor(newActive);
        }
    };

    // Export the class
    module.exports = CSSPreprocessorDocument;
});
<commit_msg>Remove the extra CSS prefix.<commit_after>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $ */

/**
 * CSSPreprocessorDocument manages a single LESS or SASS source document
 *
 * __HIGHLIGHTING__
 *
 * CSSPreprocessorDocument supports highlighting all DOMNode corresponding to the rule at
 * the cursor position in the editor.
 *
 */
define(function CSSPreprocessorDocumentModule(require, exports, module) {
    "use strict";

    var _               = require("thirdparty/lodash"),
        CSSUtils        = require("language/CSSUtils"),
        EditorManager   = require("editor/EditorManager"),
        HighlightAgent  = require("LiveDevelopment/Agents/HighlightAgent"),
        Inspector       = require("LiveDevelopment/Inspector/Inspector");

    /**
     * @constructor
     * @param {!Document} doc The source document from Brackets
     * @param {?Editor} editor The editor for this document. This is not used here since
     *                  we always need to get the active editor for a preprocessor document
     *                  and not the one passed in `editor`.
     */
    var CSSPreprocessorDocument = function CSSPreprocessorDocument(doc, editor) {
        this.doc = doc;

        this.onCursorActivity = this.onCursorActivity.bind(this);

        // Add a ref to the doc since we're listening for change events
        this.doc.addRef();
        this.onActiveEditorChange = this.onActiveEditorChange.bind(this);
        $(EditorManager).on("activeEditorChange", this.onActiveEditorChange);
        this.onActiveEditorChange(null, EditorManager.getActiveEditor(), null);
    };

    /** Close the document */
    CSSPreprocessorDocument.prototype.close = function close() {
        $(this.doc).off(".CSSPreprocessorDocument");
        $(EditorManager).off("activeEditorChange", this.onActiveEditorChange);
        this.doc.releaseRef();
        this.detachFromEditor();
    };

    CSSPreprocessorDocument.prototype.attachToEditor = function (editor) {
        this.editor = editor;

        if (this.editor) {
            $(this.editor).on("cursorActivity.CSSPreprocessorDocument", this.onCursorActivity);
            this.updateHighlight();
        }
    };

    CSSPreprocessorDocument.prototype.detachFromEditor = function () {
        if (this.editor) {
            HighlightAgent.hide();
            $(this.editor).off(".CSSPreprocessorDocument");
            this.editor = null;
        }
    };

    CSSPreprocessorDocument.prototype.updateHighlight = function () {
        if (Inspector.config.highlight && this.editor) {
            var editor = this.editor,
                codeMirror = editor._codeMirror,
                selectors = [];
            _.each(this.editor.getSelections(), function (sel) {
                var selector = CSSUtils.findSelectorAtDocumentPos(editor, (sel.reversed ? sel.end : sel.start));
                if (selector) {
                    selectors.push(selector);
                }
            });
            if (selectors.length) {
                HighlightAgent.rule(selectors.join(","));
            } else {
                HighlightAgent.hide();
            }
        }
    };

    /** Event Handlers *******************************************************/

    /** Triggered on cursor activity of the editor */
    CSSPreprocessorDocument.prototype.onCursorActivity = function onCursorActivity(event, editor) {
        this.updateHighlight();
    };

    /** Triggered when the active editor changes */
    CSSPreprocessorDocument.prototype.onActiveEditorChange = function (event, newActive, oldActive) {
        this.detachFromEditor();

        if (newActive && newActive.document === this.doc) {
            this.attachToEditor(newActive);
        }
    };

    // Export the class
    module.exports = CSSPreprocessorDocument;
});
<|endoftext|>


<commit_before>$(document).ready(function(){
    var nofinv = 1;
    $('#id_invoicenumber').addClass('invoice'+' '+nofinv);
    $('#id_invoicestatus').addClass('invoiceadditional'+' '+nofinv);

    $('#trbutton').on('click', function(){
	$('#trbutton').fadeOut('slow', function(){
	    $('#trform').fadeIn('slow');
	    $('#ptrack').html('Please fill in all the fields.');
	});
    });

    $('#id_reminderdate').datepicker();
    $('.paid:first').datepicker();

    var $name = $('#vclerk').attr('value');
    $('#id_clerk').val($name);

    $('#id_clerk').attr('readonly', true);
    $('#id_actiondate').attr('readonly', true);

    $('#id_market').on('change', function(){
	$('#id_ccode option').each(function(){
	    $(this).show();
	});

	var market = $('#id_market').val();
	if (market == 'IT') {
	    var marketArray = ['', '11', '13', '15', '16'];
	} else if (market == 'DE') {
	    var marketArray = ['', '13', '52', '55', '60', '64', '66'];
	} else if (market == 'PT' || market == 'PL' || market == 'NL' || market == 'NO') {
	    var marketArray = ['', '10'];
	} else if (market == 'BE') {
	    var marketArray = ['', '12', '14', '18', '31'];
	} else if (market == 'SE') {
	    var marketArray = ['', '16'];
	} else if (market == 'FI') {
	    var marketArray = ['', '11'];
	} else if (market == 'DK') {
	    var marketArray = ['', '12'];
	} else {
	    return false;
	}

	$('#id_ccode option').each(function(){
	    var ccodeOpt = $(this).val();
	    if ($.inArray(ccodeOpt, marketArray) > -1){
		;
	    } else {
		$(this).hide();
	    }
	});

    });

    $(document).on('change', '.status', function() {
	var status = $(this).val();
	var nextform = $(this).parent().parent().parent().next();

	if (nextform.is(':hidden')){
	    ;
	} else {
	    nextform.hide();
	    if (nextform.find('.paid').is(':hidden')) {nextform.find('.paid').show()};
	    if (nextform.find('.reject').is(':hidden')) {nextform.find('.reject').show()};
	    nextform.find('label').show();
	}

	if (status == 'RJ'){
	    $('#id_paidon').hide();
	    $('#rejpaidform').find('label:last').hide();
	    $('#rejpaidform').fadeIn('slow');
	} else if (status == 'PD') {
	    $('#id_rejectreason').hide();
	    $('#rejpaidform').find('label:first').hide();
	    $('#rejpaidform').fadeIn('slow');
	};
    });
});
<commit_msg>Depending on the choice, some elements get visible.<commit_after>$(document).ready(function(){
    var nofinv = 1;
    $('#id_invoicenumber').addClass('invoice'+' '+nofinv);
    $('#id_invoicestatus').addClass('invoiceadditional'+' '+nofinv);

    $('#trbutton').on('click', function(){
	$('#trbutton').fadeOut('slow', function(){
	    $('#trform').fadeIn('slow');
	    $('#ptrack').html('Please fill in all the fields.');
	});
    });

    $('#id_reminderdate').datepicker();
    $('.paid:first').datepicker();

    var $name = $('#vclerk').attr('value');
    $('#id_clerk').val($name);

    $('#id_clerk').attr('readonly', true);
    $('#id_actiondate').attr('readonly', true);

    $('#id_market').on('change', function(){
	$('#id_ccode option').each(function(){
	    $(this).show();
	});

	var market = $('#id_market').val();
	if (market == 'IT') {
	    var marketArray = ['', '11', '13', '15', '16'];
	} else if (market == 'DE') {
	    var marketArray = ['', '13', '52', '55', '60', '64', '66'];
	} else if (market == 'PT' || market == 'PL' || market == 'NL' || market == 'NO') {
	    var marketArray = ['', '10'];
	} else if (market == 'BE') {
	    var marketArray = ['', '12', '14', '18', '31'];
	} else if (market == 'SE') {
	    var marketArray = ['', '16'];
	} else if (market == 'FI') {
	    var marketArray = ['', '11'];
	} else if (market == 'DK') {
	    var marketArray = ['', '12'];
	} else {
	    return false;
	}

	$('#id_ccode option').each(function(){
	    var ccodeOpt = $(this).val();
	    if ($.inArray(ccodeOpt, marketArray) > -1){
		;
	    } else {
		$(this).hide();
	    }
	});

    });

    $(document).on('change', '.status', function() {
	var status = $(this).val();
	var nextform = $(this).parent().parent().parent().next();

	if (nextform.is(':hidden')){
	    ;
	} else {
	    nextform.hide();
	    if (nextform.find('.paid').is(':hidden')) {nextform.find('.paid').show()};
	    if (nextform.find('.reject').is(':hidden')) {nextform.find('.reject').show()};
	    nextform.find('label').show();
	}

	if (status == 'RJ'){
	    nextform.find('.paid').hide();
	    nextform.find('label:last').hide();
	    nextform.fadeIn('slow');
	} else if (status == 'PD') {
	    nextform.find('.reject').hide();
	    nextform.find('label:first').hide();
	    nextform.fadeIn('slow');
	};
    });
});
<|endoftext|>


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

/* global glowroot, angular */

glowroot.controller('ConfigCapturePointCtrl', [
  '$scope',
  '$http',
  '$timeout',
  'confirmIfHasChanges',
  'httpErrors',
  'queryStrings',
  'conversions',
  function ($scope, $http, $timeout, confirmIfHasChanges, httpErrors, queryStrings) {
    // don't initialize page binding object since it is inherited from pointcut-list.js

    function onNewData(data) {
      $scope.config = data;
      $scope.originalConfig = angular.copy(data);

      if (data.className) {
        var methodSignature = {
          name: data.methodName,
          parameterTypes: data.methodParameterTypes,
          returnType: data.methodReturnType,
          modifiers: data.methodModifiers
        };
        var captureKind = data.captureKind;
        $scope.selectedClassName = data.className;
        $scope.selectedMethodName = data.methodName;
        $scope.selectedMethodSignature = methodSignature;
        $scope.methodSignatures = [ methodSignature ];
        $scope.captureKindTransaction = captureKind === 'transaction';
        $scope.showMetric = captureKind === 'metric' || captureKind === 'trace_entry' || captureKind === 'transaction';
        $scope.showTraceEntry = captureKind === 'trace_entry' || captureKind === 'transaction';
        $scope.showTraceEntryStackThreshold = captureKind === 'trace_entry';
        $scope.loadMethodSignatures = true;
      } else {
        $timeout(function () {
          // focus on class name
          $scope.isFocus = true;
        }, 0);
      }
    }

    onNewData($scope.capturePoint.config);

    $scope.hasChanges = function () {
      return ($scope.config.className || $scope.originalConfig.className) &&
          !angular.equals($scope.config, $scope.originalConfig);
    };
    $scope.$on('$locationChangeStart', confirmIfHasChanges($scope));

    $scope.showClassNameSpinner = 0;
    $scope.showMethodNameSpinner = 0;

    $scope.classNames = function (suggestion) {
      var postData = {
        partialClassName: suggestion,
        limit: 10
      };
      $scope.showClassNameSpinner++;
      // use 'then' method to return promise
      return $http.get('backend/config/matching-class-names?' + queryStrings.encodeObject(postData))
          .then(function (response) {
            $scope.showClassNameSpinner--;
            return response.data;
          }, function () {
            $scope.showClassNameSpinner--;
            // TODO handle error
          });
    };

    $scope.onSelectClassName = function () {
      var className = $scope.config.className;
      // check if the value has really changed (e.g. that a user didn't start altering text and
      // then changed mind and put the previous value back)
      if (className !== $scope.selectedClassName) {
        $scope.selectedClassName = className;
        $scope.selectedMethodName = '';
        $scope.methodSignatures = [];
        $scope.config.methodName = '';
      }
    };

    $scope.methodNames = function (suggestion) {
      if (!$scope.config.className) {
        return [];
      }
      if (suggestion.indexOf('*') !== -1) {
        return [ suggestion ];
      }
      var queryData = {
        className: $scope.config.className,
        partialMethodName: suggestion,
        limit: 10
      };
      $scope.showMethodNameSpinner++;
      return $http.get('backend/config/matching-method-names?' + queryStrings.encodeObject(queryData))
          .then(function (response) {
            $scope.showMethodNameSpinner--;
            return response.data;
          }, function () {
            $scope.showMethodNameSpinner--;
            // TODO handle error
          });
    };

    $scope.onSelectMethodName = function () {
      var methodName = $scope.config.methodName;

      // since matchingMethods clears the trace entry text, check here if the value has really changed
      // (e.g. that a user didn't start altering text and then changed mind and put the previous
      // value back)

      // also, this condition is needed in case where user enters a value and clicks outside of the input field
      // to set the value to something that is not available in the dropdown

      if (methodName !== $scope.selectedMethodName) {
        $scope.loadMethodSignatures = false;
        $scope.selectedMethodName = methodName;
        if (methodName.indexOf('*') !== -1) {
          $scope.methodSignatures = [
            {
              name: methodName,
              parameterTypes: [ '..' ],
              returnType: '',
              modifiers: []
            }
          ];
          $scope.selectedMethodSignature = $scope.methodSignatures[0];
        } else {
          matchingMethods(methodName);
        }
      } else if ($scope.loadMethodSignatures) {
        $scope.loadMethodSignatures = false;
        matchingMethods(methodName, true);
      }
    };

    $scope.onBlurMethodName = function () {
      if (!$scope.config.methodName) {
        // the user cleared the text input and tabbed away
        $scope.methodSignatures = [];
        $scope.selectedMethodSignature = undefined;
      }
    };

    $scope.methodSignatureText = function (methodSignature) {
      if (methodSignature.name.indexOf('*') !== -1 || methodSignature.name.indexOf('|') !== -1) {
        return 'match any signature';
      }
      if (isSignatureAll(methodSignature)) {
        return 'match any signature';
      }
      var text = '';
      var i;
      for (i = 0; i < methodSignature.modifiers.length; i++) {
        text += methodSignature.modifiers[i] + ' ';
      }
      text += methodSignature.returnType + ' ' + methodSignature.name + '(';
      for (i = 0; i < methodSignature.parameterTypes.length; i++) {
        if (i > 0) {
          text += ', ';
        }
        text += methodSignature.parameterTypes[i];
      }
      text += ')';
      return text;
    };

    $scope.save = function (deferred) {
      var postData = angular.copy($scope.config);
      var url;
      var update = $scope.config.version;
      if (update) {
        url = 'backend/config/capture-points/update';
      } else {
        url = 'backend/config/capture-points/add';
      }
      $http.post(url, postData)
          .success(function (data) {
            onNewData(data);
            $scope.page.dirty = true;
            deferred.resolve(update ? 'Saved' : 'Added');
          })
          .error(httpErrors.handler($scope, deferred));
    };

    $scope.delete = function (deferred) {
      if ($scope.config.version) {
        $http.post('backend/config/capture-points/remove', '"' + $scope.config.version + '"')
            .success(function (data) {
              $scope.$parent.removeCapturePoint($scope.capturePoint);
              $scope.page.dirty = true;
              deferred.resolve('Deleted');
            })
            .error(httpErrors.handler($scope, deferred));
      } else {
        $scope.$parent.removeCapturePoint($scope.capturePoint);
        deferred.resolve('Deleted');
      }
    };

    function matchingMethods(methodName, keepSelectedMethodSignature) {
      var queryData = {
        className: $scope.config.className,
        methodName: methodName
      };
      $scope.methodSignaturesLoading = true;
      $http.get('backend/config/method-signatures?' + queryStrings.encodeObject(queryData))
          .success(function (data) {
            $scope.methodSignaturesLoading = false;
            $scope.methodSignatures = data;
            if (data.length !== 1) {
              // this includes the case where data.length === 0, which is possible if the user enters a
              // non-matching method name and clicks outside of the input field to bypass the typeahead values
              $scope.methodSignatures.push({
                name: methodName,
                parameterTypes: [ '..' ],
                returnType: '',
                modifiers: []
              });
            }
            if (keepSelectedMethodSignature) {
              for (var i = 0; i < data.length; i++) {
                if ($scope.selectedMethodSignature.name === data[i].name &&
                    angular.equals($scope.selectedMethodSignature.parameterTypes, data[i].parameterTypes)) {
                  $scope.selectedMethodSignature = data[i];
                  break;
                }
              }
            } else if (data.length === 1) {
              $scope.selectedMethodSignature = data[0];
            } else if (!keepSelectedMethodSignature) {
              $scope.selectedMethodSignature = undefined;
            }
          })
          .error(httpErrors.handler($scope));
    }

    $scope.$watch('config.captureKind', function (value) {
      $scope.captureKindTransaction = value === 'transaction';
      $scope.showMetric = value === 'metric' || value === 'trace_entry' || value === 'transaction';
      $scope.showTraceEntry = value === 'trace_entry' || value === 'transaction';
      $scope.showTraceEntryStackThreshold = value === 'trace_entry';
      if (!$scope.showMetric) {
        $scope.config.metricName = '';
      }
      if (!$scope.showTraceEntry) {
        $scope.config.traceEntryTemplate = '';
        $scope.config.traceEntryCaptureSelfNested = false;
      }
      if (!$scope.showTraceEntryStackThreshold) {
        $scope.config.traceEntryStackThresholdMillis = '';
      }
    });

    $scope.$watch('selectedMethodSignature', function (newValue) {
      if (newValue) {
        $scope.config.methodParameterTypes = newValue.parameterTypes;
        $scope.config.methodReturnType = '';
        $scope.config.methodModifiers = [];
      } else {
        $scope.config.methodParameterTypes = [];
        $scope.config.methodReturnType = '';
        $scope.config.methodModifiers = [];
      }
    });

    function isSignatureAll(methodSignature) {
      return methodSignature.modifiers.length === 0 && methodSignature.returnType === '' &&
          methodSignature.parameterTypes.length === 1 && methodSignature.parameterTypes[0] === '..';
    }
  }
]);
<commit_msg>Update signature on blur of method name selection<commit_after>/*
 * Copyright 2012-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* global glowroot, angular */

glowroot.controller('ConfigCapturePointCtrl', [
  '$scope',
  '$http',
  '$timeout',
  'confirmIfHasChanges',
  'httpErrors',
  'queryStrings',
  'conversions',
  function ($scope, $http, $timeout, confirmIfHasChanges, httpErrors, queryStrings) {
    // don't initialize page binding object since it is inherited from pointcut-list.js

    function onNewData(data) {
      $scope.config = data;
      $scope.originalConfig = angular.copy(data);

      if (data.className) {
        var methodSignature = {
          name: data.methodName,
          parameterTypes: data.methodParameterTypes,
          returnType: data.methodReturnType,
          modifiers: data.methodModifiers
        };
        var captureKind = data.captureKind;
        $scope.selectedClassName = data.className;
        $scope.selectedMethodName = data.methodName;
        $scope.selectedMethodSignature = methodSignature;
        $scope.methodSignatures = [ methodSignature ];
        $scope.captureKindTransaction = captureKind === 'transaction';
        $scope.showMetric = captureKind === 'metric' || captureKind === 'trace_entry' || captureKind === 'transaction';
        $scope.showTraceEntry = captureKind === 'trace_entry' || captureKind === 'transaction';
        $scope.showTraceEntryStackThreshold = captureKind === 'trace_entry';
        $scope.loadMethodSignatures = true;
      } else {
        $timeout(function () {
          // focus on class name
          $scope.isFocus = true;
        }, 0);
      }
    }

    onNewData($scope.capturePoint.config);

    $scope.hasChanges = function () {
      return ($scope.config.className || $scope.originalConfig.className) &&
          !angular.equals($scope.config, $scope.originalConfig);
    };
    $scope.$on('$locationChangeStart', confirmIfHasChanges($scope));

    $scope.showClassNameSpinner = 0;
    $scope.showMethodNameSpinner = 0;

    $scope.classNames = function (suggestion) {
      var postData = {
        partialClassName: suggestion,
        limit: 10
      };
      $scope.showClassNameSpinner++;
      // use 'then' method to return promise
      return $http.get('backend/config/matching-class-names?' + queryStrings.encodeObject(postData))
          .then(function (response) {
            $scope.showClassNameSpinner--;
            return response.data;
          }, function () {
            $scope.showClassNameSpinner--;
            // TODO handle error
          });
    };

    $scope.onSelectClassName = function () {
      var className = $scope.config.className;
      // check if the value has really changed (e.g. that a user didn't start altering text and
      // then changed mind and put the previous value back)
      if (className !== $scope.selectedClassName) {
        $scope.selectedClassName = className;
        $scope.selectedMethodName = '';
        $scope.methodSignatures = [];
        $scope.config.methodName = '';
      }
    };

    $scope.methodNames = function (suggestion) {
      if (!$scope.config.className) {
        return [];
      }
      if (suggestion.indexOf('*') !== -1) {
        return [ suggestion ];
      }
      var queryData = {
        className: $scope.config.className,
        partialMethodName: suggestion,
        limit: 10
      };
      $scope.showMethodNameSpinner++;
      return $http.get('backend/config/matching-method-names?' + queryStrings.encodeObject(queryData))
          .then(function (response) {
            $scope.showMethodNameSpinner--;
            return response.data;
          }, function () {
            $scope.showMethodNameSpinner--;
            // TODO handle error
          });
    };

    $scope.onSelectMethodName = function () {
      var methodName = $scope.config.methodName;

      // since matchingMethods clears the trace entry text, check here if the value has really changed
      // (e.g. that a user didn't start altering text and then changed mind and put the previous
      // value back)

      // also, this condition is needed in case where user enters a value and clicks outside of the input field
      // to set the value to something that is not available in the dropdown

      if (methodName !== $scope.selectedMethodName) {
        $scope.loadMethodSignatures = false;
        $scope.selectedMethodName = methodName;
        if (methodName.indexOf('*') !== -1) {
          $scope.methodSignatures = [
            {
              name: methodName,
              parameterTypes: [ '..' ],
              returnType: '',
              modifiers: []
            }
          ];
          $scope.selectedMethodSignature = $scope.methodSignatures[0];
        } else {
          matchingMethods(methodName);
        }
      } else if ($scope.loadMethodSignatures) {
        $scope.loadMethodSignatures = false;
        matchingMethods(methodName, true);
      }
    };

    $scope.onBlurMethodName = function () {
      if ($scope.config.methodName) {
        $scope.onSelectMethodName();
      } else {
        // the user cleared the text input and tabbed away
        $scope.methodSignatures = [];
        $scope.selectedMethodSignature = undefined;
      }
    };

    $scope.methodSignatureText = function (methodSignature) {
      if (methodSignature.name.indexOf('*') !== -1 || methodSignature.name.indexOf('|') !== -1) {
        return 'match any signature';
      }
      if (isSignatureAll(methodSignature)) {
        return 'match any signature';
      }
      var text = '';
      var i;
      for (i = 0; i < methodSignature.modifiers.length; i++) {
        text += methodSignature.modifiers[i] + ' ';
      }
      text += methodSignature.returnType + ' ' + methodSignature.name + '(';
      for (i = 0; i < methodSignature.parameterTypes.length; i++) {
        if (i > 0) {
          text += ', ';
        }
        text += methodSignature.parameterTypes[i];
      }
      text += ')';
      return text;
    };

    $scope.save = function (deferred) {
      var postData = angular.copy($scope.config);
      var url;
      var update = $scope.config.version;
      if (update) {
        url = 'backend/config/capture-points/update';
      } else {
        url = 'backend/config/capture-points/add';
      }
      $http.post(url, postData)
          .success(function (data) {
            onNewData(data);
            $scope.page.dirty = true;
            deferred.resolve(update ? 'Saved' : 'Added');
          })
          .error(httpErrors.handler($scope, deferred));
    };

    $scope.delete = function (deferred) {
      if ($scope.config.version) {
        $http.post('backend/config/capture-points/remove', '"' + $scope.config.version + '"')
            .success(function (data) {
              $scope.$parent.removeCapturePoint($scope.capturePoint);
              $scope.page.dirty = true;
              deferred.resolve('Deleted');
            })
            .error(httpErrors.handler($scope, deferred));
      } else {
        $scope.$parent.removeCapturePoint($scope.capturePoint);
        deferred.resolve('Deleted');
      }
    };

    function matchingMethods(methodName, keepSelectedMethodSignature) {
      var queryData = {
        className: $scope.config.className,
        methodName: methodName
      };
      $scope.methodSignaturesLoading = true;
      $http.get('backend/config/method-signatures?' + queryStrings.encodeObject(queryData))
          .success(function (data) {
            $scope.methodSignaturesLoading = false;
            $scope.methodSignatures = data;
            if (data.length !== 1) {
              // this includes the case where data.length === 0, which is possible if the user enters a
              // non-matching method name and clicks outside of the input field to bypass the typeahead values
              $scope.methodSignatures.push({
                name: methodName,
                parameterTypes: [ '..' ],
                returnType: '',
                modifiers: []
              });
            }
            if (keepSelectedMethodSignature) {
              for (var i = 0; i < data.length; i++) {
                if ($scope.selectedMethodSignature.name === data[i].name &&
                    angular.equals($scope.selectedMethodSignature.parameterTypes, data[i].parameterTypes)) {
                  $scope.selectedMethodSignature = data[i];
                  break;
                }
              }
            } else if (data.length === 1) {
              $scope.selectedMethodSignature = data[0];
            } else if (!keepSelectedMethodSignature) {
              $scope.selectedMethodSignature = undefined;
            }
          })
          .error(httpErrors.handler($scope));
    }

    $scope.$watch('config.captureKind', function (value) {
      $scope.captureKindTransaction = value === 'transaction';
      $scope.showMetric = value === 'metric' || value === 'trace_entry' || value === 'transaction';
      $scope.showTraceEntry = value === 'trace_entry' || value === 'transaction';
      $scope.showTraceEntryStackThreshold = value === 'trace_entry';
      if (!$scope.showMetric) {
        $scope.config.metricName = '';
      }
      if (!$scope.showTraceEntry) {
        $scope.config.traceEntryTemplate = '';
        $scope.config.traceEntryCaptureSelfNested = false;
      }
      if (!$scope.showTraceEntryStackThreshold) {
        $scope.config.traceEntryStackThresholdMillis = '';
      }
    });

    $scope.$watch('selectedMethodSignature', function (newValue) {
      if (newValue) {
        $scope.config.methodParameterTypes = newValue.parameterTypes;
        $scope.config.methodReturnType = '';
        $scope.config.methodModifiers = [];
      } else {
        $scope.config.methodParameterTypes = [];
        $scope.config.methodReturnType = '';
        $scope.config.methodModifiers = [];
      }
    });

    function isSignatureAll(methodSignature) {
      return methodSignature.modifiers.length === 0 && methodSignature.returnType === '' &&
          methodSignature.parameterTypes.length === 1 && methodSignature.parameterTypes[0] === '..';
    }
  }
]);
<|endoftext|>


<commit_before>var pow = Math.pow,
    floor = Math.floor,
    abs = Math.abs,
    log = Math.log;

function round(n, precision) {
    var prec = Math.pow(10, precision);
    return Math.round(n * prec) / prec;
}

function abbreviateNumber(n) {
    var base = floor(log(abs(n)) / log(1000));
    var suffix = 'kMB' [base - 1];
    return suffix ? round(n / pow(1000, base), 2) + suffix : '' + n;
}

$(document).ready(function() {

    $('#live-values span').each(function() {
        var $span = $(this);
        $span.text(abbreviateNumber($span.attr('data-isk')));
    });

    // More JSON
    $.getJSON('/market/history/34/', function(data) {

        // Default data
        var invTypeID = 34;
        var invTypeName = "Tritanium";
        var mapRegionIDs = [10000002, 10000043, 10000032, 10000030];
        var mapRegions = {
            "10000032": "Sinq Laison",
            "10000002": "The Forge",
            "10000043": "Domain",
            "10000030": "Heimatar"
        };

        // Parse data
        var length = data.length;
        var prices = [];
        var namesParsed = $.parseJSON(mapRegions);

        // Only proceed if there is any data
        if (length !== 0) {

            var groupingUnits = [
                ['week', [1]],
                ['month', [1, 2, 3, 4, 6]]
            ];

            var counter = 0;

            $.each(data, function(key, val) {
                prices[counter] = {
                    type: 'line',
                    name: mapRegions[key],
                    data: val,
                    dataGrouping: {
                        units: groupingUnits
                    },
                    tooltip: {
                        valueDecimals: 2
                    }
                };
                counter++;
            });

            // Create the chart
            window.chart = new Highcharts.StockChart({
                chart: {
                    renderTo: 'history',
                    height: 300,
                    buttonOptions: {
                        enabled: false
                    }
                },
                scrollbar: {
                    enabled: false
                },
                rangeSelector: {
                    enabled: false
                },
                navigator: {
                    enabled: false
                },
                title: {
                    text: "Tritanium"
                },
                yAxis: {
                    title: {
                        text: 'Price'
                    },
                    lineWidth: 2
                },
                tooltip: {
                    valueDecimals: 2
                },
                series: prices
            });
        }
    });

    // Generate params string for value list
    var params = "";
    $.each(types, function(index, value) {
        if (index === 0) {
            params += "?type=" + value;
        } else {
            params += "&type=" + value;
        }
    });

    var url = '/stats/' + region + '/' + params;

    // Define stat loader
    var stat_loader = function load_stats(data) {
        $.each(data, function(key, val) {

            // Iterate over typestats or insert EMDR stats
            if (key == "typestats") {
                // Iterate over types
                $.each(val, function(type_key, type_val) {
                    // Iterate over type values
                    $.each(type_val, function(type_val_key, type_val_val) {
                        // Set values
                        console.log('Updating #' + type_val_key + '_' + type_key);
                        var element = $('#' + type_val_key + '_' + type_key);
                        var old_val = parseFloat(element.attr('data-isk'));
                        var new_val = type_val_val.toFixed(2);

                        // Pulse values on change
                        if (old_val > new_val) {
                            // If new value is smaller, pulse red, remove style left over from the pulse afterwards
                            element.text(abbreviateNumber(new_val));
                            element.attr('data-isk', new_val);
                            element.pulse({
                                color: '#FF0000'
                            }, {
                                duration: 400
                            }, function() {
                                element.removeAttr("style");
                            });
                        }
                        if (old_val < new_val) {
                            // If new value is higher, pulse green, remove style left over from the pulse afterwards
                            element.text(abbreviateNumber(new_val));
                            element.attr('data-isk', new_val);
                            element.pulse({
                                color: '#50C878'
                            }, {
                                duration: 400
                            }, function() {
                                element.removeAttr("style");
                            });
                        } else {
                            // If it's just the same number don't do anything
                        }

                        // Set font color depending on value on bid fields
                        if (/move/i.test(type_val_key)) {
                            if (new_val > 0) {
                                // >0 => green and +
                                element.removeClass();
                                element.addClass('green');

                                if (element.text().indexOf("+") < 0) {
                                    element.text('+' + element.text());
                                }
                            } else if (new_val < 0) {
                                // <0 => red
                                element.removeClass();
                                element.addClass('red');
                            } else {
                                // = 0 => white
                                element.removeClass();
                            }
                        }
                    });
                });

            } else {
                $('#' + key).text(String(val).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"));
            }

        });

        // Schdeule next reload
        reload();
    };

    // Load initial dataset
    $.getJSON(url, stat_loader);

    function reload() {
        setTimeout(function() {
            $.getJSON(url, stat_loader);
        }, 5000);
    }
});<commit_msg>JS fix<commit_after>var pow = Math.pow,
    floor = Math.floor,
    abs = Math.abs,
    log = Math.log;

function round(n, precision) {
    var prec = Math.pow(10, precision);
    return Math.round(n * prec) / prec;
}

function abbreviateNumber(n) {
    var base = floor(log(abs(n)) / log(1000));
    var suffix = 'kMB' [base - 1];
    return suffix ? round(n / pow(1000, base), 2) + suffix : '' + n;
}

$(document).ready(function() {

    $('#live-values span').each(function() {
        var $span = $(this);
        $span.text(abbreviateNumber($span.attr('data-isk')));
    });

    // More JSON
    $.getJSON('/market/history/34/', function(data) {

        // Default data
        var invTypeID = 34;
        var invTypeName = "Tritanium";
        var mapRegionIDs = [10000002, 10000043, 10000032, 10000030];
        var mapRegions = {
            "10000032": "Sinq Laison",
            "10000002": "The Forge",
            "10000043": "Domain",
            "10000030": "Heimatar"
        };

        // Parse data
        var length = data.length;
        var prices = [];
        var namesParsed = $.parseJSON(mapRegions);

        // Only proceed if there is any data
        if (length !== 0) {

            var groupingUnits = [
                ['week', [1]],
                ['month', [1, 2, 3, 4, 6]]
            ];

            var counter = 0;

            $.each(data, function(key, val) {
                prices[counter] = {
                    type: 'line',
                    name: mapRegions[key],
                    data: val,
                    dataGrouping: {
                        units: groupingUnits
                    },
                    tooltip: {
                        valueDecimals: 2
                    }
                };
                counter++;
            });

            // Create the chart
            window.chart = new Highcharts.StockChart({
                chart: {
                    renderTo: 'history',
                    height: 300,
                    buttonOptions: {
                        enabled: false
                    }
                },
                scrollbar: {
                    enabled: false
                },
                rangeSelector: {
                    enabled: false
                },
                navigator: {
                    enabled: false
                },
                title: {
                    text: "Tritanium"
                },
                yAxis: {
                    title: {
                        text: 'Price'
                    },
                    lineWidth: 2
                },
                tooltip: {
                    valueDecimals: 2
                },
                series: prices
            });
        }
    });

    // Generate params string for value list
    var params = "";
    $.each(types, function(index, value) {
        if (index === 0) {
            params += "?type=" + value;
        } else {
            params += "&type=" + value;
        }
    });

    var url = '/stats/' + region + '/' + params;

    // Define stat loader
    var stat_loader = function load_stats(data) {
        $.each(data, function(key, val) {

            // Iterate over typestats or insert EMDR stats
            if (key == "typestats") {
                // Iterate over types
                $.each(val, function(type_key, type_val) {
                    // Iterate over type values
                    $.each(type_val, function(type_val_key, type_val_val) {
                        // Set values
                        var element = $('#' + type_val_key + '_' + type_key);
                        var old_val = parseFloat(element.attr('data-isk'));
                        var new_val = type_val_val.toFixed(2);

                        // Pulse values on change
                        if (old_val > new_val) {
                            // If new value is smaller, pulse red, remove style left over from the pulse afterwards
                            element.text(abbreviateNumber(new_val));
                            element.attr('data-isk', new_val);
                            element.pulse({
                                color: '#FF0000'
                            }, {
                                duration: 400
                            }, function() {
                                element.removeAttr("style");
                            });
                        }
                        if (old_val < new_val) {
                            // If new value is higher, pulse green, remove style left over from the pulse afterwards
                            element.text(abbreviateNumber(new_val));
                            element.attr('data-isk', new_val);
                            element.pulse({
                                color: '#50C878'
                            }, {
                                duration: 400
                            }, function() {
                                element.removeAttr("style");
                            });
                        } else {
                            // If it's just the same number don't do anything
                        }

                        // Set font color depending on value on bid fields
                        if (/move/i.test(type_val_key)) {
                            if (new_val > 0) {
                                // >0 => green and +
                                element.removeClass();
                                element.addClass('green');

                                if (element.text().indexOf("+") < 0) {
                                    element.text('+' + element.text());
                                }
                            } else if (new_val < 0) {
                                // <0 => red
                                element.removeClass();
                                element.addClass('red');
                            } else {
                                // = 0 => white
                                element.removeClass();
                            }
                        }
                    });
                });

            } else {
                $('#' + key).text(String(val).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"));
            }

        });

        // Schdeule next reload
        reload();
    };

    // Load initial dataset
    $.getJSON(url, stat_loader);

    function reload() {
        setTimeout(function() {
            $.getJSON(url, stat_loader);
        }, 5000);
    }
});<|endoftext|>


<commit_before>'use strict';

const promiseRetry        = require('promise-retry');
const zlib                = require('zlib');
const del                 = require('del');
const nectar              = require('nectar');
const extrakt             = require('extrakt');
const pify                = require('pify');
const mkdirp              = pify(require('mkdirp'));
const path                = require('path');
const fs                  = require('fs');
const pathExists          = require('path-exists');
const filesizeParser      = require('filesize-parser');
const defaults            = require('defa');
const _                   = require('lodash');
const EventEmitter        = require('events').EventEmitter;
const Promise             = require('bluebird');
const CacheableOperation  = require('./CacheableOperation');
const CachedResult        = require('./CachedResult');
const getRedundantResults = require('./util/getRedundantResults');
const StoringResult       = require('./StoringResult');
const RestoredFromCache   = require('./RestoredFromCache');
const SavedToCache        = require('./SavedToCache');
const Index               = require('./Index');

/**
 * @event cleanup Fired when the cache exceeds the maximum allowed size and redundant results are removed from the index
 * @event query Fired when the cache index is queried
 * @event saved Fired when a result was saved to the cache
 */
class Cache extends EventEmitter {

    constructor(options) {
        super();
        options = _.isObject(options) ? options : {};
        defaults(options, {
            lazyLoad:         false,
            workingDirectory: process.cwd()
        }, options => {
            return {
                workspace: path.join(options.workingDirectory, '.johnny'),
                maxSize:   '512mb'
            };
        }, options => {
            return {
                maxSizeBytes: () => filesizeParser(options.maxSize),
                dataStore:    () => path.join(options.workspace, '.index')
            };
        });
        this.lazyLoad         = options.lazyLoad;
        this.workspace        = options.workspace;
        this.workingDirectory = options.workingDirectory;
        this.maxSize          = options.maxSizeBytes;
        this.index            = new Index({filename: options.dataStore});
        this.ready            = this.lazyLoad ? null : this.sync();
    }

    /**
     * Makes sure the cache size does not exceed the maximum allowed size, removing the most irrelevant rows from the DB
     * @param {Number} [freeUp = 0] The number of bytes to additionally clear up
     * @returns {Promise}
     */
    maintainMaxSize(freeUp) {
        if (!freeUp) freeUp = 0;
        return this.getAllDocs()
            .then(docs => _.map(docs, doc => CachedResult.fromDocument(null, doc)))
            .then(cachedResults => {
                let totalCacheSize = _.sumBy(cachedResults, cachedResult => cachedResult.fileSize) + freeUp;
                let remove         = getRedundantResults(cachedResults, this.maxSize, totalCacheSize);
                if (remove.length)
                    this.emit('cleanup', {current: totalCacheSize, allowed: this.maxSize, removing: remove,});
                return remove.length ? this.removeResults(remove) : Promise.resolve();
            });
    }

    /**
     * @param {CachedResult[]} cachedResults
     */
    removeResults(cachedResults) {
        return this.index.Result.destroy({where: {id: {in: _.map(cachedResults, 'id')}}});
    }

    /**
     * Returns a promise for the preparation of the cache
     * @returns {Promise}
     */
    awaitReady() {
        if (this.ready === null) this.ready = this.sync();
        return this.ready;
    }

    /**
     * Run some update / refresh operations (usually performed on instantiation)
     * @returns {Promise}
     */
    sync() {
        // Make sure the workspace directory exists
        return mkdirp(this.workspace)

        // Synchronize the index database
            .then(() => this.index.sync())

            // Remove expired rows from the index
            .then(() => this.untrackExpired())

            // Remove irrelevant rows from the index if the max size is exceeded
            .then(() => this.maintainMaxSize())

            // Delete all cache files that are not represented in the index
            .then(() => this.purgeUntracked());
    }

    /**
     * Remove expired row from the index
     * @returns {Promise}
     */
    untrackExpired() {
        return this.index.Result.destroy({where: {expires: {ne: -1, lte: Date.now()}}});
    }

    /**
     * Gets all documents from the index
     * @returns {Promise}
     */
    getAllDocs() {
        return this.index.Result.findAll();
    }

    /**
     * Remove files for which there is no related row in the index
     * @returns {Promise}
     */
    purgeUntracked() {
        return this.getAllDocs()
            .then(docs => [path.join(this.workspace, '*.*')].concat(_.map(docs, doc => {
                return '!' + this.getStorageLocation(CachedResult.fromDocument(null, doc));
            })))
            .then(globs => del(globs, {force: true}));
    }

    /**
     * Prevent hash collisions
     * @param {CachedResult} cachedResult
     * @returns {Promise}
     */
    incrementFilename(cachedResult) {
        let updatedFilename;
        let baseName  = path.basename(cachedResult.fileName).substring(0, path.basename(cachedResult.fileName).lastIndexOf('.'));
        let ext       = path.extname(cachedResult.fileName);
        let increment = 0;
        return promiseRetry((retry) => {
            updatedFilename = baseName + (increment ? ('-' + increment) : '') + ext;
            let fileName    = this.getStorageLocation(updatedFilename);
            return pathExists(fileName).then(exists => {
                if (exists) {
                    increment++;
                    retry(new Error('file exists'));
                }
            });
        }, {retries: 100, minTimeout: 0}).then(() => {
            cachedResult.fileName = updatedFilename;
            return cachedResult;
        });
    }

    /**
     * @param {CachedResult|SavedToCache|RestoredFromCache|string} obj
     * @return {string}
     */
    getStorageLocation(obj) {
        if (obj instanceof SavedToCache || obj instanceof RestoredFromCache)
            obj = obj.cachedResult;
        if (obj instanceof CachedResult)
            obj = obj.fileName;
        return path.join(this.workspace, obj);
    }

    getWorkingDirectory() {
        return this.workingDirectory;
    }

    /**
     * Creates a new CachedResult object for saving
     * @param cacheableOperation
     * @returns {Promise.<CachedResult>}
     */
    prepareResult(cacheableOperation) {
        const cachedResult = CachedResult.createNew(cacheableOperation);
        return cachedResult.setHashes(cacheableOperation)
            .then(cachedResult => this.incrementFilename(cachedResult));
    }

    /**
     * Save the result of the given operation to cache
     * @param {CacheableOperation} cacheableOperation
     * @returns {Promise.<SavedToCache>}
     */
    storeResult(cacheableOperation) {
        let cachedResult;
        let startSave = Date.now();
        return this.prepareResult(cacheableOperation)
            .then(result => {
                cachedResult = result;
                if (cacheableOperation.onStore) cacheableOperation.onStore(cacheableOperation);
                let writeStream = fs.createWriteStream(this.getStorageLocation(cachedResult));
                if (cacheableOperation.compress) writeStream = zlib.createGzip().pipe(writeStream);
                return nectar(cacheableOperation.output, writeStream, {cwd: cacheableOperation.workingDirectory});
            })
            .then(() => this.assignFileSize(cachedResult))
            .then(() => this.index.Result.create(cachedResult.getDocument()))
            .then(inserted => cachedResult.id = inserted.id)
            .then(() => this.sync())
            .then(() => {
                this.emit('saved', {cacheableOperation, cachedResult});
                return new SavedToCache(cacheableOperation.runtime, Date.now() - startSave, cachedResult);
            });
    }

    /**
     * Run the cacheable operation and save the result to the cache
     * @param {CacheableOperation} cacheableOperation
     * @return {Promise.<StoringResult|SavedToCache>} An instance of either StoringResult or SavedToCache,
     * depending on the value of options.awaitStore passed to Cache.doCached
     */
    run(cacheableOperation) {
        return this.awaitReady()
            .then(() => cacheableOperation.run())
            .then(() => new StoringResult(this.storeResult(cacheableOperation), cacheableOperation))
            .then(storingResult => cacheableOperation.awaitStore ? storingResult.savedToCache : storingResult);
    }

    /**
     * Restores the given cached result
     * @param cachedResult
     * @returns {Promise.<RestoredFromCache>}
     */
    restore(cachedResult) {
        let start = Date.now();
        return extrakt(this.getStorageLocation(cachedResult), cachedResult.workingDirectory)
            .then(() => new RestoredFromCache(cachedResult, Date.now() - start));
    }

    /**
     * Generates the query for retreiving the appropriate cached result document
     * @param {CacheableOperation} cacheableOperation
     * @returns {Promise.<object>}
     */
    getQuery(cacheableOperation) {
        return Promise.all([
            cacheableOperation.getFileHash(),
            cacheableOperation.getOutputHash()
        ])
            .then(hashes => {
                return {
                    fileHash:   hashes[0],
                    outputHash: hashes[1],
                    action:     cacheableOperation.action,
                    $or:        [{expires: -1}, {expires: {$gt: Date.now()}}]
                };
            });
    }

    /**
     * @param {CacheableOperation} cacheableOperation
     * @returns {Promise.<object|null>}
     */
    getCachedResult(cacheableOperation) {
        let jobs = [this.getQuery(cacheableOperation), this.awaitReady()];
        return Promise.all(jobs)
            .then(values => {
                this.emit('query', {query: values[0]});
                return this.index.Result.findOne({where: values[0]});
            })
            .then(doc => doc ? CachedResult.fromDocument(cacheableOperation, doc) : null);
    }

    /**
     * Check the file size of the stored file related to the cached result and assign it to the object
     * @param {CachedResult} cachedResult
     * @returns {Promise}
     */
    assignFileSize(cachedResult) {
        return pify(fs.stat)(this.getStorageLocation(cachedResult))
            .then(stat => {
                cachedResult.fileSize = stat.size;
            }, () => null);
    }

    /**
     * Checks whether the cached result has a corresponding file
     * @param {CachedResult} cachedResult
     * @returns {Promise.<CachedResult|null>}
     */
    ensureExists(cachedResult) {
        return pify(fs.access)(this.getStorageLocation(cachedResult))
            .then(() => cachedResult, () => null);
    }

    /**
     * @param {function} run
     * @param {object} options
     * @returns {Promise.<StoringResult|SavedToCache|RestoredFromCache>}
     */
    doCached(run, options) {
        let cacheableOperation = new CacheableOperation(run, defaults(options, {
            workingDirectory: this.workingDirectory
        }));
        return this.getCachedResult(cacheableOperation)
            .then(cachedResult => cachedResult !== null ? this.ensureExists(cachedResult) : null)
            .then(cachedResult => {
                if (cachedResult !== null) {
                    // TODO remove options-level event handlers in favor of eventEmitter-level listeners
                    if (options.onRestore) options.onRestore(cacheableOperation, cachedResult);
                    return this.restore(cachedResult);
                } else {
                    // TODO remove options-level event handlers in favor of eventEmitter-level listeners
                    if (options.onRun) options.onRun(cacheableOperation);
                    return this.run(cacheableOperation);
                }
            });
    }
}

module.exports                   = Cache;
module.exports.StoringResult     = StoringResult;
module.exports.SavedToCache      = SavedToCache;
module.exports.RestoredFromCache = RestoredFromCache;<commit_msg>add “sync” event<commit_after>'use strict';

const promiseRetry        = require('promise-retry');
const zlib                = require('zlib');
const del                 = require('del');
const nectar              = require('nectar');
const extrakt             = require('extrakt');
const pify                = require('pify');
const mkdirp              = pify(require('mkdirp'));
const path                = require('path');
const fs                  = require('fs');
const pathExists          = require('path-exists');
const filesizeParser      = require('filesize-parser');
const defaults            = require('defa');
const _                   = require('lodash');
const EventEmitter        = require('events').EventEmitter;
const Promise             = require('bluebird');
const CacheableOperation  = require('./CacheableOperation');
const CachedResult        = require('./CachedResult');
const getRedundantResults = require('./util/getRedundantResults');
const StoringResult       = require('./StoringResult');
const RestoredFromCache   = require('./RestoredFromCache');
const SavedToCache        = require('./SavedToCache');
const Index               = require('./Index');

/**
 * @event sync Fired when the index is refreshed
 * @event cleanup Fired when the cache exceeds the maximum allowed size and redundant results are removed from the index
 * @event query Fired when the cache index is queried
 * @event saved Fired when a result was saved to the cache
 */
class Cache extends EventEmitter {

    constructor(options) {
        super();
        options = _.isObject(options) ? options : {};
        defaults(options, {
            lazyLoad:         false,
            workingDirectory: process.cwd()
        }, options => {
            return {
                workspace: path.join(options.workingDirectory, '.johnny'),
                maxSize:   '512mb'
            };
        }, options => {
            return {
                maxSizeBytes: () => filesizeParser(options.maxSize),
                dataStore:    () => path.join(options.workspace, '.index')
            };
        });
        this.lazyLoad         = options.lazyLoad;
        this.workspace        = options.workspace;
        this.workingDirectory = options.workingDirectory;
        this.maxSize          = options.maxSizeBytes;
        this.index            = new Index({filename: options.dataStore});
        this.ready            = this.lazyLoad ? null : this.sync();
    }

    /**
     * Makes sure the cache size does not exceed the maximum allowed size, removing the most irrelevant rows from the DB
     * @param {Number} [freeUp = 0] The number of bytes to additionally clear up
     * @returns {Promise}
     */
    maintainMaxSize(freeUp) {
        if (!freeUp) freeUp = 0;
        return this.getAllDocs()
            .then(docs => _.map(docs, doc => CachedResult.fromDocument(null, doc)))
            .then(cachedResults => {
                let totalCacheSize = _.sumBy(cachedResults, cachedResult => cachedResult.fileSize) + freeUp;
                let remove         = getRedundantResults(cachedResults, this.maxSize, totalCacheSize);
                if (remove.length)
                    this.emit('cleanup', {current: totalCacheSize, allowed: this.maxSize, removing: remove,});
                return remove.length ? this.removeResults(remove) : Promise.resolve();
            });
    }

    /**
     * @param {CachedResult[]} cachedResults
     */
    removeResults(cachedResults) {
        return this.index.Result.destroy({where: {id: {in: _.map(cachedResults, 'id')}}});
    }

    /**
     * Returns a promise for the preparation of the cache
     * @returns {Promise}
     */
    awaitReady() {
        if (this.ready === null) this.ready = this.sync();
        return this.ready;
    }

    /**
     * Run some update / refresh operations (usually performed on instantiation)
     * @returns {Promise}
     */
    sync() {
        this.emit('sync');

        // Make sure the workspace directory exists
        return mkdirp(this.workspace)

        // Synchronize the index database
            .then(() => this.index.sync())

            // Remove expired rows from the index
            .then(() => this.untrackExpired())

            // Remove irrelevant rows from the index if the max size is exceeded
            .then(() => this.maintainMaxSize())

            // Delete all cache files that are not represented in the index
            .then(() => this.purgeUntracked());
    }

    /**
     * Remove expired row from the index
     * @returns {Promise}
     */
    untrackExpired() {
        return this.index.Result.destroy({where: {expires: {ne: -1, lte: Date.now()}}});
    }

    /**
     * Gets all documents from the index
     * @returns {Promise}
     */
    getAllDocs() {
        return this.index.Result.findAll();
    }

    /**
     * Remove files for which there is no related row in the index
     * @returns {Promise}
     */
    purgeUntracked() {
        return this.getAllDocs()
            .then(docs => [path.join(this.workspace, '*.*')].concat(_.map(docs, doc => {
                return '!' + this.getStorageLocation(CachedResult.fromDocument(null, doc));
            })))
            .then(globs => del(globs, {force: true}));
    }

    /**
     * Prevent hash collisions
     * @param {CachedResult} cachedResult
     * @returns {Promise}
     */
    incrementFilename(cachedResult) {
        let updatedFilename;
        let baseName  = path.basename(cachedResult.fileName).substring(0, path.basename(cachedResult.fileName).lastIndexOf('.'));
        let ext       = path.extname(cachedResult.fileName);
        let increment = 0;
        return promiseRetry((retry) => {
            updatedFilename = baseName + (increment ? ('-' + increment) : '') + ext;
            let fileName    = this.getStorageLocation(updatedFilename);
            return pathExists(fileName).then(exists => {
                if (exists) {
                    increment++;
                    retry(new Error('file exists'));
                }
            });
        }, {retries: 100, minTimeout: 0}).then(() => {
            cachedResult.fileName = updatedFilename;
            return cachedResult;
        });
    }

    /**
     * @param {CachedResult|SavedToCache|RestoredFromCache|string} obj
     * @return {string}
     */
    getStorageLocation(obj) {
        if (obj instanceof SavedToCache || obj instanceof RestoredFromCache)
            obj = obj.cachedResult;
        if (obj instanceof CachedResult)
            obj = obj.fileName;
        return path.join(this.workspace, obj);
    }

    getWorkingDirectory() {
        return this.workingDirectory;
    }

    /**
     * Creates a new CachedResult object for saving
     * @param cacheableOperation
     * @returns {Promise.<CachedResult>}
     */
    prepareResult(cacheableOperation) {
        const cachedResult = CachedResult.createNew(cacheableOperation);
        return cachedResult.setHashes(cacheableOperation)
            .then(cachedResult => this.incrementFilename(cachedResult));
    }

    /**
     * Save the result of the given operation to cache
     * @param {CacheableOperation} cacheableOperation
     * @returns {Promise.<SavedToCache>}
     */
    storeResult(cacheableOperation) {
        let cachedResult;
        let startSave = Date.now();
        return this.prepareResult(cacheableOperation)
            .then(result => {
                cachedResult = result;
                if (cacheableOperation.onStore) cacheableOperation.onStore(cacheableOperation);
                let writeStream = fs.createWriteStream(this.getStorageLocation(cachedResult));
                if (cacheableOperation.compress) writeStream = zlib.createGzip().pipe(writeStream);
                return nectar(cacheableOperation.output, writeStream, {cwd: cacheableOperation.workingDirectory});
            })
            .then(() => this.assignFileSize(cachedResult))
            .then(() => this.index.Result.create(cachedResult.getDocument()))
            .then(inserted => cachedResult.id = inserted.id)
            .then(() => this.sync())
            .then(() => {
                this.emit('saved', {cacheableOperation, cachedResult});
                return new SavedToCache(cacheableOperation.runtime, Date.now() - startSave, cachedResult);
            });
    }

    /**
     * Run the cacheable operation and save the result to the cache
     * @param {CacheableOperation} cacheableOperation
     * @return {Promise.<StoringResult|SavedToCache>} An instance of either StoringResult or SavedToCache,
     * depending on the value of options.awaitStore passed to Cache.doCached
     */
    run(cacheableOperation) {
        return this.awaitReady()
            .then(() => cacheableOperation.run())
            .then(() => new StoringResult(this.storeResult(cacheableOperation), cacheableOperation))
            .then(storingResult => cacheableOperation.awaitStore ? storingResult.savedToCache : storingResult);
    }

    /**
     * Restores the given cached result
     * @param cachedResult
     * @returns {Promise.<RestoredFromCache>}
     */
    restore(cachedResult) {
        let start = Date.now();
        return extrakt(this.getStorageLocation(cachedResult), cachedResult.workingDirectory)
            .then(() => new RestoredFromCache(cachedResult, Date.now() - start));
    }

    /**
     * Generates the query for retreiving the appropriate cached result document
     * @param {CacheableOperation} cacheableOperation
     * @returns {Promise.<object>}
     */
    getQuery(cacheableOperation) {
        return Promise.all([
            cacheableOperation.getFileHash(),
            cacheableOperation.getOutputHash()
        ])
            .then(hashes => {
                return {
                    fileHash:   hashes[0],
                    outputHash: hashes[1],
                    action:     cacheableOperation.action,
                    $or:        [{expires: -1}, {expires: {$gt: Date.now()}}]
                };
            });
    }

    /**
     * @param {CacheableOperation} cacheableOperation
     * @returns {Promise.<object|null>}
     */
    getCachedResult(cacheableOperation) {
        let jobs = [this.getQuery(cacheableOperation), this.awaitReady()];
        return Promise.all(jobs)
            .then(values => {
                this.emit('query', {query: values[0]});
                return this.index.Result.findOne({where: values[0]});
            })
            .then(doc => doc ? CachedResult.fromDocument(cacheableOperation, doc) : null);
    }

    /**
     * Check the file size of the stored file related to the cached result and assign it to the object
     * @param {CachedResult} cachedResult
     * @returns {Promise}
     */
    assignFileSize(cachedResult) {
        return pify(fs.stat)(this.getStorageLocation(cachedResult))
            .then(stat => {
                cachedResult.fileSize = stat.size;
            }, () => null);
    }

    /**
     * Checks whether the cached result has a corresponding file
     * @param {CachedResult} cachedResult
     * @returns {Promise.<CachedResult|null>}
     */
    ensureExists(cachedResult) {
        return pify(fs.access)(this.getStorageLocation(cachedResult))
            .then(() => cachedResult, () => null);
    }

    /**
     * @param {function} run
     * @param {object} options
     * @returns {Promise.<StoringResult|SavedToCache|RestoredFromCache>}
     */
    doCached(run, options) {
        let cacheableOperation = new CacheableOperation(run, defaults(options, {
            workingDirectory: this.workingDirectory
        }));
        return this.getCachedResult(cacheableOperation)
            .then(cachedResult => cachedResult !== null ? this.ensureExists(cachedResult) : null)
            .then(cachedResult => {
                if (cachedResult !== null) {
                    // TODO remove options-level event handlers in favor of eventEmitter-level listeners
                    if (options.onRestore) options.onRestore(cacheableOperation, cachedResult);
                    return this.restore(cachedResult);
                } else {
                    // TODO remove options-level event handlers in favor of eventEmitter-level listeners
                    if (options.onRun) options.onRun(cacheableOperation);
                    return this.run(cacheableOperation);
                }
            });
    }
}

module.exports                   = Cache;
module.exports.StoringResult     = StoringResult;
module.exports.SavedToCache      = SavedToCache;
module.exports.RestoredFromCache = RestoredFromCache;<|endoftext|>


<commit_before>(function() {
    gf.Sprite = gf.Entity.extend({
        //initializes a new entity with the start position (pos) and the settings defined in Tiled
        init: function(pos, settings) {
            /****************************************************************************
             * Properties that are defined in the `settings` object,
             * these can be specified in the properties of the object layer
             * in Tiled, and overriden on a per-object basis
             ****************************************************************************/
            //size of the sprite (each frame)
            this.size = new THREE.Vector2(0, 0);

            //number of frames accross x / y (calculated, not set)
            this.numFrames = new THREE.Vector2(0, 0);

            //offset in the image to the first frame (in pixels)
            this.offset = new THREE.Vector2(0, 0);

            //opacity of the sprite
            this.opacity = 1.0;

            //raw THREE.Texture instance loaded by the gf.loader
            this.texture = null;

            //defined sprite animations
            this.anim = {};

            //currently active animation
            this.currentAnim = null;

            //currently active frame's active time
            this.currentFrameTime = 0;

            //animations paused
            this.animPaused = false;

            //use screen coords instead of world coords
            this.useScreenCoords = false;

            /****************************************************************************
             * Call base constructor
             ****************************************************************************/
            this._super(pos, settings);

            this._baseHitSize = this.hitSize.clone();
            this._baseScaledHitSize = this.scaledHitSize.clone();
            this._baseHitOffset = this.hitOffset.clone();

            // create a default animation sequence with all sprites
            this.addAnimation('default', { frames: null, duration: 1000 });
            // set as default
            this.setActiveAnimation('default');
        },
        //add a new animation
        addAnimation: function(name, settings) {
            settings = settings || {};

            if(settings instanceof Array)
                settings = { frames: settings };

            this.anim[name] = {
                name: name,
                frames: [],
                idx: 0,
                length: 0,
                frameTime: 0,
                loop: !!settings.loop,
                hitSize: settings.hitSize,
                hitOffset: settings.hitOffset
            };

            if(!settings.frames) {
                settings.frames = [];
                // create a default animation with all sprites in the spritesheet
                for(var f = 0, fl = this.numFrames.x * this.numFrames.y; f < fl; ++f) {
                    settings.frames[f] = f;
                }
            }

            // compute and add the offset of each frame
            for(var i = 0, il = settings.frames.length; i < il; ++i) {
                this.anim[name].frames[i] = new THREE.Vector2(this.size.x * (settings.frames[i] % this.numFrames.x), this.size.y * Math.floor(settings.frames[i] / this.numFrames.x));
            }
            this.anim[name].length = this.anim[name].frames.length;
            this.anim[name].frameTime = (settings.duration || 0) / this.anim[name].frames.length;

            return this;
        },
        setActiveAnimation: function(name, resetAnim, cb) {
            if(typeof resetAnim == 'function') {
                cb = resetAnim;
                resetAnim = null;
            }

            if(!this.anim[name]) {
                this.currentAnim = name;
            } else {
                if(this.currentAnim && this.currentAnim._cb && !this.currentAnim._cbCalled) {
                    this.currentAnim._cbCalled = true;
                    this.currentAnim._cb(true);
                }

                this.currentAnim = this.anim[name];
                this.currentAnim._done = false;
                this.currentAnim._cbCalled = false;
                this.currentAnim._cb = cb;
                if(resetAnim) this.currentAnim.idx = 0;
                this._setOffset();

                //if we are changing hitbox, save current settings and update
                if(this.currentAnim.hitSize) {
                    if(this.currentAnim.hitSize instanceof THREE.Vector2) {
                        this.hitSize.copy(this.currentAnim.hitSize);
                        this.scaledHitSize.copy(this.currentAnim.hitSize).multiplyScalar(this.scale);
                    }
                    else if(this.currentAnim.hitSize instanceof Array) {
                        this.hitSize.set(this.currentAnim.hitSize[0], this.currentAnim.hitSize[1]);
                        this.scaledHitSize.set(this.currentAnim.hitSize[0], this.currentAnim.hitSize[1]).multiplyScalar(this.scale);
                    }
                }
                else {
                    this.hitSize.copy(this._baseHitSize);
                    this.scaledHitSize.copy(this._baseScaledHitSize);
                }

                if(this.currentAnim.hitOffset) {
                    if(this.currentAnim.hitOffset instanceof THREE.Vector2)
                        this.hitOffset.copy(this.currentAnim.hitOffset);
                    else if(this.currentAnim.hitOffset instanceof Array)
                        this.hitOffset.set(this.currentAnim.hitOffset[0], this.currentAnim.hitOffset[1]);
                }
                else {
                    this.hitOffset.copy(this._baseHitOffset);
                }

                this._hitboxMesh.scale.set(this.scaledHitSize.x, this.scaledHitSize.y, 1);
                this.setPosition(this._mesh.position);
            }

            this.currentFrameTime = 0;

            return this;
        },
        isActiveAnimation: function(name) {
            return this.currentAnim.name == name;
        },
        update: function() {
            this._super();

            if(!this.currentAnim || this.currentAnim.frameTime === 0 || this.currentAnim.length <= 1) return;

            this.currentFrameTime += gf.game._delta * 1000;

            //update animation if necessary
            if (this.isVisible && !this.animPaused && !this.currentAnim._done && (this.currentFrameTime > this.currentAnim.frameTime)) {
                while(this.currentFrameTime > this.currentAnim.frameTime) {
                    this.currentFrameTime -= this.currentAnim.frameTime;
                    this.currentAnim.idx++;

                    if(this.currentAnim.idx == this.currentAnim.length) {
                        if(this.currentAnim.loop) {
                            this.currentAnim.idx = 0;
                        }
                        else {
                            this.currentAnim._done = true;
                            if(this.currentAnim._cb && !this.currentAnim._cbCalled) {
                                this.currentAnim._cbCalled = true;
                                this.currentAnim._cb();
                            }
                            return;
                        }
                    }

                    this._setOffset();
                }
            }

            return this;
        },
        _setOffset: function() {
            //for some reason when inversed, the tiles need to start at 1 instead of 0
            var add = this.currentAnim.inverse ? 1 : 0,
                frame = this.currentAnim.frames[this.currentAnim.idx];

            this._material.uvOffset.set(
                frame.x / this.texture.image.width,
                1 - (this.size.y / this.texture.image.height) - (frame.y / this.texture.image.height)
            );
        },
        //override Entity mesh with a sprite instead
        _createMesh: function() {
            if(this._mesh) return;

            if(this.filtered) {
                this.texture.magFilter = THREE.LinearFilter;
                this.texture.minFilter = THREE.LinearMipMapLinearFilter;
            } else {
                this.texture.magFilter = THREE.NearestFilter;
                this.texture.minFilter = THREE.NearestMipMapNearestFilter;
            }

            this.numFrames.set(this.texture.image.width / this.size.x, this.texture.image.height / this.size.y);

            this._material = new THREE.SpriteMaterial({
                color: 0xffffff,
                opacity: 1,
                map: this.texture,

                blending: THREE.NormalBlending,
                alignment: THREE.SpriteAlignment.center,
                useScreenCoordinates: this.useScreenCoords,
                //depthTest: false, //default: !useScreenCoordinates
                sizeAttenuation: true, //default: !useScreenCoordinates
                //scaledByViewport: true //default: !sizeAttenuation
            });

            this._material.uvScale.set(
                1 / this.numFrames.x,
                1 / this.numFrames.y
            );
            this._material.uvOffset.set(0, 0);

            this._mesh = new THREE.Sprite(this._material);

            var scale = (this.size.x / this.texture.image.width); //scale of a frame
            scale *= (this.texture.image.width / gf.game._renderer.domElement.height); //relationship to viewport
            scale *= this.scale; //user defined scale
            this._mesh.scale.set(scale, scale, 1);
        }
    });
})();<commit_msg>use texture specified in Tiled<commit_after>(function() {
    gf.Sprite = gf.Entity.extend({
        //initializes a new entity with the start position (pos) and the settings defined in Tiled
        init: function(pos, settings) {
            /****************************************************************************
             * Properties that are defined in the `settings` object,
             * these can be specified in the properties of the object layer
             * in Tiled, and overriden on a per-object basis
             ****************************************************************************/
            //size of the sprite (each frame)
            this.size = new THREE.Vector2(0, 0);

            //number of frames accross x / y (calculated, not set)
            this.numFrames = new THREE.Vector2(0, 0);

            //offset in the image to the first frame (in pixels)
            this.offset = new THREE.Vector2(0, 0);

            //opacity of the sprite
            this.opacity = 1.0;

            //raw THREE.Texture instance loaded by the gf.loader
            this.texture = null;

            //defined sprite animations
            this.anim = {};

            //currently active animation
            this.currentAnim = null;

            //currently active frame's active time
            this.currentFrameTime = 0;

            //animations paused
            this.animPaused = false;

            //use screen coords instead of world coords
            this.useScreenCoords = false;

            if(typeof settings.texture == 'string') {
                if(gf.resources[settings.texture])
                    settings.texture = gf.resources[settings.texture].data;
                else
                    throw 'Unable to find texture ' + settings.texture + '; resource not loaded.';
            }

            /****************************************************************************
             * Call base constructor
             ****************************************************************************/
            this._super(pos, settings);

            this._baseHitSize = this.hitSize.clone();
            this._baseScaledHitSize = this.scaledHitSize.clone();
            this._baseHitOffset = this.hitOffset.clone();

            // create a default animation sequence with all sprites
            this.addAnimation('default', { frames: null, duration: 1000 });
            // set as default
            this.setActiveAnimation('default');
        },
        //add a new animation
        addAnimation: function(name, settings) {
            settings = settings || {};

            if(settings instanceof Array)
                settings = { frames: settings };

            this.anim[name] = {
                name: name,
                frames: [],
                idx: 0,
                length: 0,
                frameTime: 0,
                loop: !!settings.loop,
                hitSize: settings.hitSize,
                hitOffset: settings.hitOffset
            };

            if(!settings.frames) {
                settings.frames = [];
                // create a default animation with all sprites in the spritesheet
                for(var f = 0, fl = this.numFrames.x * this.numFrames.y; f < fl; ++f) {
                    settings.frames[f] = f;
                }
            }

            // compute and add the offset of each frame
            for(var i = 0, il = settings.frames.length; i < il; ++i) {
                this.anim[name].frames[i] = new THREE.Vector2(this.size.x * (settings.frames[i] % this.numFrames.x), this.size.y * Math.floor(settings.frames[i] / this.numFrames.x));
            }
            this.anim[name].length = this.anim[name].frames.length;
            this.anim[name].frameTime = (settings.duration || 0) / this.anim[name].frames.length;

            return this;
        },
        setActiveAnimation: function(name, resetAnim, cb) {
            if(typeof resetAnim == 'function') {
                cb = resetAnim;
                resetAnim = null;
            }

            if(!this.anim[name]) {
                this.currentAnim = name;
            } else {
                if(this.currentAnim && this.currentAnim._cb && !this.currentAnim._cbCalled) {
                    this.currentAnim._cbCalled = true;
                    this.currentAnim._cb(true);
                }

                this.currentAnim = this.anim[name];
                this.currentAnim._done = false;
                this.currentAnim._cbCalled = false;
                this.currentAnim._cb = cb;
                if(resetAnim) this.currentAnim.idx = 0;
                this._setOffset();

                //if we are changing hitbox, save current settings and update
                if(this.currentAnim.hitSize) {
                    if(this.currentAnim.hitSize instanceof THREE.Vector2) {
                        this.hitSize.copy(this.currentAnim.hitSize);
                        this.scaledHitSize.copy(this.currentAnim.hitSize).multiplyScalar(this.scale);
                    }
                    else if(this.currentAnim.hitSize instanceof Array) {
                        this.hitSize.set(this.currentAnim.hitSize[0], this.currentAnim.hitSize[1]);
                        this.scaledHitSize.set(this.currentAnim.hitSize[0], this.currentAnim.hitSize[1]).multiplyScalar(this.scale);
                    }
                }
                else {
                    this.hitSize.copy(this._baseHitSize);
                    this.scaledHitSize.copy(this._baseScaledHitSize);
                }

                if(this.currentAnim.hitOffset) {
                    if(this.currentAnim.hitOffset instanceof THREE.Vector2)
                        this.hitOffset.copy(this.currentAnim.hitOffset);
                    else if(this.currentAnim.hitOffset instanceof Array)
                        this.hitOffset.set(this.currentAnim.hitOffset[0], this.currentAnim.hitOffset[1]);
                }
                else {
                    this.hitOffset.copy(this._baseHitOffset);
                }

                this._hitboxMesh.scale.set(this.scaledHitSize.x, this.scaledHitSize.y, 1);
                this.setPosition(this._mesh.position);
            }

            this.currentFrameTime = 0;

            return this;
        },
        isActiveAnimation: function(name) {
            return this.currentAnim.name == name;
        },
        update: function() {
            this._super();

            if(!this.currentAnim || this.currentAnim.frameTime === 0 || this.currentAnim.length <= 1) return;

            this.currentFrameTime += gf.game._delta * 1000;

            //update animation if necessary
            if (this.isVisible && !this.animPaused && !this.currentAnim._done && (this.currentFrameTime > this.currentAnim.frameTime)) {
                while(this.currentFrameTime > this.currentAnim.frameTime) {
                    this.currentFrameTime -= this.currentAnim.frameTime;
                    this.currentAnim.idx++;

                    if(this.currentAnim.idx == this.currentAnim.length) {
                        if(this.currentAnim.loop) {
                            this.currentAnim.idx = 0;
                        }
                        else {
                            this.currentAnim._done = true;
                            if(this.currentAnim._cb && !this.currentAnim._cbCalled) {
                                this.currentAnim._cbCalled = true;
                                this.currentAnim._cb();
                            }
                            return;
                        }
                    }

                    this._setOffset();
                }
            }

            return this;
        },
        _setOffset: function() {
            //for some reason when inversed, the tiles need to start at 1 instead of 0
            var add = this.currentAnim.inverse ? 1 : 0,
                frame = this.currentAnim.frames[this.currentAnim.idx];

            this._material.uvOffset.set(
                frame.x / this.texture.image.width,
                1 - (this.size.y / this.texture.image.height) - (frame.y / this.texture.image.height)
            );
        },
        //override Entity mesh with a sprite instead
        _createMesh: function() {
            if(this._mesh) return;

            if(this.filtered) {
                this.texture.magFilter = THREE.LinearFilter;
                this.texture.minFilter = THREE.LinearMipMapLinearFilter;
            } else {
                this.texture.magFilter = THREE.NearestFilter;
                this.texture.minFilter = THREE.NearestMipMapNearestFilter;
            }

            this.numFrames.set(this.texture.image.width / this.size.x, this.texture.image.height / this.size.y);

            this._material = new THREE.SpriteMaterial({
                color: 0xffffff,
                opacity: 1,
                map: this.texture,

                blending: THREE.NormalBlending,
                alignment: THREE.SpriteAlignment.center,
                useScreenCoordinates: this.useScreenCoords,
                //depthTest: false, //default: !useScreenCoordinates
                sizeAttenuation: true, //default: !useScreenCoordinates
                //scaledByViewport: true //default: !sizeAttenuation
            });

            this._material.uvScale.set(
                1 / this.numFrames.x,
                1 / this.numFrames.y
            );
            this._material.uvOffset.set(0, 0);

            this._mesh = new THREE.Sprite(this._material);

            var scale = (this.size.x / this.texture.image.width); //scale of a frame
            scale *= (this.texture.image.width / gf.game._renderer.domElement.height); //relationship to viewport
            scale *= this.scale; //user defined scale
            this._mesh.scale.set(scale, scale, 1);
        }
    });
})();<|endoftext|>