<commit_before>define( ['dojo/_base/declare',
         'dojo/_base/array',
         'JBrowse/View/Track/Wiggle',
         'JBrowse/Util'
        ],
        function( declare, array, Wiggle, Util ) {

// feature class for the features we make for the calculated coverage
// values
var CoverageFeature = Util.fastDeclare(
    {
        get: function(f) { return this[f]; },
        tags: function() { return [ 'start', 'end', 'score' ]; },
        score: 0,
        constructor: function( args ) {
            this.start = args.start;
            this.end = args.end;
            this.score = args.score;
        }
    });

return declare( Wiggle,
{

    constructor: function() {
        // force conf variables that are meaningless for this kind of track, and maybe harmful
        delete this.config.bicolor_pivot;
        delete this.config.scale;
        delete this.config.align;
    },

    _defaultConfig: function() {
        return Util.deepUpdate(
            dojo.clone( this.inherited(arguments) ),
            {
                min_score: 0,
                max_score: 100
            }
        );
    },

    getGlobalStats: function() {
        return {};
    },

    getFeatures: function( query, featureCallback, finishCallback, errorCallback ) {
        var thisB = this;
        var leftBase  = query.start;
        var rightBase = query.end;
        var scale = query.scale; // px/bp
        var widthBp = rightBase-leftBase;
        var widthPx = widthBp * ( query.scale || 1/query.basesPerSpan);

        var binWidth = Math.ceil( query.basesPerSpan ); // in bp

        var coverageBins = {};
        var bpToBin = function( bp ) {
            return Math.floor( (bp-leftBase) / binWidth );
        };

        thisB.store.getFeatures(
            query,
            function( feature ) {
                var startBin = bpToBin( feature.get('start') );
                var endBin   = bpToBin( feature.get('end')-1 );
                for( var i = startBin; i <= endBin; i++ ) {
                    if ( coverageBins[i] ) {
                        coverageBins[i]['matchCoverage']++;
                    }
                    else {
                        coverageBins[i] = {};
                        coverageBins[i]['matchCoverage'] = 1;
                    }
                }
                // Calculate SNP coverage
                if( binWidth == 1 ) {
                    var mdTag = feature.get('MD');
                    if( mdTag ) {
                        var SNPs = thisB._mdToMismatches(feature, mdTag);
                        // loops through mismatches and updates coverage variables accordingly.
                        for (var i = 0; i<SNPs.length; i++) {
                            var pos = bpToBin( feature.get('start') + SNPs[i].start );
                            // Note: we reduce matchCoverage so the sum is the total coverage
                            coverageBins[pos]['matchCoverage']--;
                            coverageBins[pos][SNPs[i].bases] = ( coverageBins[pos][SNPs[i].bases] || 0 ) + 1;
                        }
                    }
                }
            },
            function () {
                var makeFeatures = function() {
                    // make fake features from the coverage
                    for( var i = 0; i < Math.ceil( widthBp/binWidth ); i++ ) {
                        var bpOffset = leftBase+binWidth*i;
                        featureCallback( new CoverageFeature({
                            start: bpOffset,
                            end:   bpOffset+binWidth,
                            score: coverageBins[i] || {'matchCoverage': 0}
                         }));
                    }
                    finishCallback();
                };

                // if we are zoomed to base level, try to fetch the
                // reference sequence for this region and record each
                // of the bases in the coverage bins
                if( binWidth == 1 ) {
                    var sequence;
                    thisB.browser.getStore( 'refseqs', function( refSeqStore ) {
                        if( refSeqStore ) {
                            refSeqStore.getFeatures( query,
                                                     function(f) {
                                                         sequence = f.get('seq');
                                                     },
                                                     function() {
                                                         if( sequence ) {
                                                             for( var base = leftBase; base <= rightBase; base++ ) {
                                                                 var bin = bpToBin( base );
                                                                 coverageBins[bin]['refBase'] = sequence[bin];
                                                             }
                                                         }
                                                         makeFeatures();
                                                     },
                                                     makeFeatures
                                                   );
                        } else {
                            makeFeatures();
                        }
                    });
                } else {
                    makeFeatures();
                }
            }
        );
    },

    /*
     * Draw a set of features on the canvas.
     * @private
     */
    _drawFeatures: function( scale, leftBase, rightBase, block, canvas, features, featureRects, dataScale ) {
        var context = canvas.getContext('2d');
        var canvasHeight = canvas.height;
        var toY = dojo.hitch( this, function( val ) {
           return canvasHeight * ( 1-dataScale.normalize.call(this, val) );
        });
        var originY = toY( dataScale.origin );

        // a canvas element below the histogram that will contain indicators of likely SNPs
        var snpCanvas = dojo.create('canvas',
                                    {height: parseInt(block.parentNode.style.height, 10) - canvas.height,
                                     width: canvas.width,
                                     style: { cursor: 'default'},
                                     innerHTML: 'Your web browser cannot display this type of track.',
                                     className: 'SNP-indicator-track'
                                 }, block);
        // widen the canvas and offset it.
        snpCanvas.width += snpCanvas.height;
        snpCanvas.style.position = 'relative';
        snpCanvas.style.left = -snpCanvas.height*0.5 + 'px';
        var snpContext = snpCanvas.getContext('2d');

        var barColor  = {'matchCoverage':'#999', 'A':'#00BF00', 'T':'red', 'C':'#4747ff', 'G':'#d5bb04'}; // base colors from "main.css"
        var negColor  = this.config.style.neg_color;
        var clipColor = this.config.style.clip_marker_color;
        var bgColor   = this.config.style.bg_color;
        var disableClipMarkers = this.config.disable_clip_markers;

        var drawRectangle = function(ID, yPos, height, fRect) {
            // draw the background color if we are configured to do so
            if( bgColor && yPos >= 0 ) {
                context.fillStyle = bgColor;
                context.fillRect( fRect.l, 0, fRect.w, canvasHeight );
            }

            if( yPos <= canvasHeight ) { // if the rectangle is visible at all
                context.fillStyle = barColor[ID] || 'black';
                if( yPos <= originY ) {
                    // bar goes upward
                    context.fillRect( fRect.l, yPos, fRect.w, height);
                    if( !disableClipMarkers && yPos < 0 ) { // draw clip marker if necessary
                        context.fillStyle = clipColor || negColor;
                        context.fillRect( fRect.l, 0, fRect.w, 2 );
                    }
                }
                else {
                    // bar goes downward
                    context.fillRect( fRect.l, originY, fRect.w, height );
                    if( !disableClipMarkers && yPos >= canvasHeight ) { // draw clip marker if necessary
                        context.fillStyle = clipColor || barColor[ID];
                        context.fillRect( fRect.l, canvasHeight-3, fRect.w, 2 );
                    }
                }
            }
        };

        dojo.forEach( features, function(f,i) {
            var fRect = featureRects[i];
            var score = f.get('score');
            var totalHeight = 0;
            for (var counts in score) {
                if (score.hasOwnProperty(counts) && counts != 'refBase') {
                    totalHeight += score[counts];
                }
            }
            // draw indicators of SNPs if base coverage is greater than 50% of total coverage
            for (var ID in score) {
                if (score.hasOwnProperty(ID) && ID != 'matchCoverage' && ID != 'refBase' && score[ID] > 0.5*totalHeight) {
                    snpContext.beginPath();
                    snpContext.arc( fRect.l + 0.5*(fRect.w+snpCanvas.height),
                                    0.40*snpCanvas.height,
                                    0.20*snpCanvas.height,
                                    1.75 * Math.PI,
                                    1.25 * Math.PI,
                                    false);
                    snpContext.lineTo(fRect.l + 0.5*(fRect.w+snpCanvas.height), 0);
                    snpContext.closePath();
                    snpContext.fillStyle = barColor[ID] || 'black';
                    snpContext.fill();
                    snpContext.lineWidth = 1;
                    snpContext.strokeStyle = 'black';
                    snpContext.stroke();
                }
            }
            // Note: 'matchCoverage' is done first to ensure the grey part of the graph is on top
            drawRectangle('matchCoverage', toY(totalHeight), originY-toY( score['matchCoverage'] )+1, fRect);
            totalHeight -= score['matchCoverage'];

            for (var counts in score) {
                if (score.hasOwnProperty(counts) && counts != 'matchCoverage') {
                    drawRectangle( counts, toY(totalHeight), originY-toY( score[counts] )+1, fRect);
                    totalHeight -= score[counts];
                }
            }

        }, this );
    },

    /**
     * parse a SAM MD tag to find mismatching bases of the template versus the reference
     * @returns {Array[Object]} array of mismatches and their positions
     */
    _mdToMismatches: function( feature, mdstring ) {
        var mismatchRecords = [];
        var curr = { start: 0, bases: '' };
        var seq = feature.get('seq');
        var nextRecord = function() {
              mismatchRecords.push( curr );
              curr = { start: curr.start + curr.bases.length, bases: ''};
        };
        array.forEach( mdstring.match(/(\d+|\^[a-z]+|[a-z])/ig), function( token ) {
          if( token.match(/^\d/) ) { // matching bases
              curr.start += parseInt( token );
          }
          else if( token.match(/^\^/) ) { // insertion in the template
              var i = token.length-1;
              while( i-- ) {
                  curr.bases = '*';
                  nextRecord();
              }
          }
          else if( token.match(/^[a-z]/i) ) { // mismatch
              curr.bases = seq.substr( curr.start, token.length );
              nextRecord();
          }
        });
        return mismatchRecords;
    },

    /*
     * The following method is required to override the equivalent method in "WiggleBase.js"
     * It displays more complete data.
     */
    _showPixelValue: function( scoreDisplay, score ) {
        if( score && typeof score['matchCoverage'] == 'number') {
            var scoreSummary = '
';
            if( score['matchCoverage'] ){
                scoreSummary +=
                      "
Ref"
                    + (score['refBase'] ? ' ('+score['refBase']+')': '')
                    + "	"
                    + score['matchCoverage']
                    + '
';
            }
            for (var ID in score) {
                if( score.hasOwnProperty(ID) && ID != 'matchCoverage' && ID != 'refBase' ) {
                    scoreSummary += '
'+ID + '	' +score[ID] +'
';
                }
            }
            scoreDisplay.innerHTML = scoreSummary+'
';
            return true;
        } else {
            return false;
        }
    }
});
});<commit_msg>include "Total" in BaseCoverage score display<commit_after>define( ['dojo/_base/declare',
         'dojo/_base/array',
         'JBrowse/View/Track/Wiggle',
         'JBrowse/Util'
        ],
        function( declare, array, Wiggle, Util ) {

var dojof = Util.dojof;

// feature class for the features we make for the calculated coverage
// values
var CoverageFeature = Util.fastDeclare(
    {
        get: function(f) { return this[f]; },
        tags: function() { return [ 'start', 'end', 'score' ]; },
        score: 0,
        constructor: function( args ) {
            this.start = args.start;
            this.end = args.end;
            this.score = args.score;
        }
    });

return declare( Wiggle,
{

    constructor: function() {
        // force conf variables that are meaningless for this kind of track, and maybe harmful
        delete this.config.bicolor_pivot;
        delete this.config.scale;
        delete this.config.align;
    },

    _defaultConfig: function() {
        return Util.deepUpdate(
            dojo.clone( this.inherited(arguments) ),
            {
                min_score: 0,
                max_score: 100
            }
        );
    },

    getGlobalStats: function() {
        return {};
    },

    getFeatures: function( query, featureCallback, finishCallback, errorCallback ) {
        var thisB = this;
        var leftBase  = query.start;
        var rightBase = query.end;
        var scale = query.scale; // px/bp
        var widthBp = rightBase-leftBase;
        var widthPx = widthBp * ( query.scale || 1/query.basesPerSpan);

        var binWidth = Math.ceil( query.basesPerSpan ); // in bp

        var coverageBins = {};
        var bpToBin = function( bp ) {
            return Math.floor( (bp-leftBase) / binWidth );
        };

        thisB.store.getFeatures(
            query,
            function( feature ) {
                var startBin = bpToBin( feature.get('start') );
                var endBin   = bpToBin( feature.get('end')-1 );
                for( var i = startBin; i <= endBin; i++ ) {
                    if ( coverageBins[i] ) {
                        coverageBins[i]['matchCoverage']++;
                    }
                    else {
                        coverageBins[i] = {};
                        coverageBins[i]['matchCoverage'] = 1;
                    }
                }
                // Calculate SNP coverage
                if( binWidth == 1 ) {
                    var mdTag = feature.get('MD');
                    if( mdTag ) {
                        var SNPs = thisB._mdToMismatches(feature, mdTag);
                        // loops through mismatches and updates coverage variables accordingly.
                        for (var i = 0; i<SNPs.length; i++) {
                            var pos = bpToBin( feature.get('start') + SNPs[i].start );
                            // Note: we reduce matchCoverage so the sum is the total coverage
                            coverageBins[pos]['matchCoverage']--;
                            var base = SNPs[i].bases;
                            coverageBins[pos][base] = ( coverageBins[pos][base] || 0 ) + 1;
                        }
                    }
                }
            },
            function () {
                var makeFeatures = function() {
                    // make fake features from the coverage
                    for( var i = 0; i < Math.ceil( widthBp/binWidth ); i++ ) {
                        var bpOffset = leftBase+binWidth*i;
                        featureCallback( new CoverageFeature({
                            start: bpOffset,
                            end:   bpOffset+binWidth,
                            score: coverageBins[i] || {'matchCoverage': 0}
                         }));
                    }
                    finishCallback();
                };

                // if we are zoomed to base level, try to fetch the
                // reference sequence for this region and record each
                // of the bases in the coverage bins
                if( binWidth == 1 ) {
                    var sequence;
                    thisB.browser.getStore( 'refseqs', function( refSeqStore ) {
                        if( refSeqStore ) {
                            refSeqStore.getFeatures( query,
                                                     function(f) {
                                                         sequence = f.get('seq');
                                                     },
                                                     function() {
                                                         if( sequence ) {
                                                             for( var base = leftBase; base <= rightBase; base++ ) {
                                                                 var bin = bpToBin( base );
                                                                 coverageBins[bin]['refBase'] = sequence[bin];
                                                             }
                                                         }
                                                         makeFeatures();
                                                     },
                                                     makeFeatures
                                                   );
                        } else {
                            makeFeatures();
                        }
                    });
                } else {
                    makeFeatures();
                }
            }
        );
    },

    /*
     * Draw a set of features on the canvas.
     * @private
     */
    _drawFeatures: function( scale, leftBase, rightBase, block, canvas, features, featureRects, dataScale ) {
        var context = canvas.getContext('2d');
        var canvasHeight = canvas.height;
        var toY = dojo.hitch( this, function( val ) {
           return canvasHeight * ( 1-dataScale.normalize.call(this, val) );
        });
        var originY = toY( dataScale.origin );

        // a canvas element below the histogram that will contain indicators of likely SNPs
        var snpCanvas = dojo.create('canvas',
                                    {height: parseInt(block.parentNode.style.height, 10) - canvas.height,
                                     width: canvas.width,
                                     style: { cursor: 'default'},
                                     innerHTML: 'Your web browser cannot display this type of track.',
                                     className: 'SNP-indicator-track'
                                 }, block);
        // widen the canvas and offset it.
        snpCanvas.width += snpCanvas.height;
        snpCanvas.style.position = 'relative';
        snpCanvas.style.left = -snpCanvas.height*0.5 + 'px';
        var snpContext = snpCanvas.getContext('2d');

        var barColor  = {'matchCoverage':'#999', 'A':'#00BF00', 'T':'red', 'C':'#4747ff', 'G':'#d5bb04'}; // base colors from "main.css"
        var negColor  = this.config.style.neg_color;
        var clipColor = this.config.style.clip_marker_color;
        var bgColor   = this.config.style.bg_color;
        var disableClipMarkers = this.config.disable_clip_markers;

        var drawRectangle = function(ID, yPos, height, fRect) {
            // draw the background color if we are configured to do so
            if( bgColor && yPos >= 0 ) {
                context.fillStyle = bgColor;
                context.fillRect( fRect.l, 0, fRect.w, canvasHeight );
            }

            if( yPos <= canvasHeight ) { // if the rectangle is visible at all
                context.fillStyle = barColor[ID] || 'black';
                if( yPos <= originY ) {
                    // bar goes upward
                    context.fillRect( fRect.l, yPos, fRect.w, height);
                    if( !disableClipMarkers && yPos < 0 ) { // draw clip marker if necessary
                        context.fillStyle = clipColor || negColor;
                        context.fillRect( fRect.l, 0, fRect.w, 2 );
                    }
                }
                else {
                    // bar goes downward
                    context.fillRect( fRect.l, originY, fRect.w, height );
                    if( !disableClipMarkers && yPos >= canvasHeight ) { // draw clip marker if necessary
                        context.fillStyle = clipColor || barColor[ID];
                        context.fillRect( fRect.l, canvasHeight-3, fRect.w, 2 );
                    }
                }
            }
        };

        dojo.forEach( features, function(f,i) {
            var fRect = featureRects[i];
            var score = f.get('score');
            var totalHeight = 0;
            for (var counts in score) {
                if (score.hasOwnProperty(counts) && counts != 'refBase') {
                    totalHeight += score[counts];
                }
            }
            // draw indicators of SNPs if base coverage is greater than 50% of total coverage
            for (var ID in score) {
                if (score.hasOwnProperty(ID) && ID != 'matchCoverage' && ID != 'refBase' && score[ID] > 0.5*totalHeight) {
                    snpContext.beginPath();
                    snpContext.arc( fRect.l + 0.5*(fRect.w+snpCanvas.height),
                                    0.40*snpCanvas.height,
                                    0.20*snpCanvas.height,
                                    1.75 * Math.PI,
                                    1.25 * Math.PI,
                                    false);
                    snpContext.lineTo(fRect.l + 0.5*(fRect.w+snpCanvas.height), 0);
                    snpContext.closePath();
                    snpContext.fillStyle = barColor[ID] || 'black';
                    snpContext.fill();
                    snpContext.lineWidth = 1;
                    snpContext.strokeStyle = 'black';
                    snpContext.stroke();
                }
            }
            // Note: 'matchCoverage' is done first to ensure the grey part of the graph is on top
            drawRectangle('matchCoverage', toY(totalHeight), originY-toY( score['matchCoverage'] )+1, fRect);
            totalHeight -= score['matchCoverage'];

            for (var counts in score) {
                if (score.hasOwnProperty(counts) && counts != 'matchCoverage') {
                    drawRectangle( counts, toY(totalHeight), originY-toY( score[counts] )+1, fRect);
                    totalHeight -= score[counts];
                }
            }

        }, this );
    },

    /**
     * parse a SAM MD tag to find mismatching bases of the template versus the reference
     * @returns {Array[Object]} array of mismatches and their positions
     */
    _mdToMismatches: function( feature, mdstring ) {
        var mismatchRecords = [];
        var curr = { start: 0, bases: '' };
        var seq = feature.get('seq');
        var nextRecord = function() {
              mismatchRecords.push( curr );
              curr = { start: curr.start + curr.bases.length, bases: ''};
        };
        array.forEach( mdstring.match(/(\d+|\^[a-z]+|[a-z])/ig), function( token ) {
          if( token.match(/^\d/) ) { // matching bases
              curr.start += parseInt( token );
          }
          else if( token.match(/^\^/) ) { // insertion in the template
              var i = token.length-1;
              while( i-- ) {
                  curr.bases = '*';
                  nextRecord();
              }
          }
          else if( token.match(/^[a-z]/i) ) { // mismatch
              curr.bases = seq.substr( curr.start, token.length );
              nextRecord();
          }
        });
        return mismatchRecords;
    },

    /*
     * The following method is required to override the equivalent method in "WiggleBase.js"
     * It displays more complete data.
     */
    _showPixelValue: function( scoreDisplay, score ) {
        if( score && typeof score['matchCoverage'] == 'number') {
            var snps = dojo.clone( score );
            delete snps.refBase;
            delete snps.matchCoverage;

            var total = (score['matchCoverage'] || 0)
                + dojof.values( snps ).reduce(function(a,b){return a+b;}, 0);

            var scoreSummary = '
';
            scoreSummary +=
                  '
'
                + (score['refBase'] ? score['refBase'] +'*' : 'Ref')
                + "	"
                + score['matchCoverage'] || 0
                + '
';
            for (var ID in snps) {
                scoreSummary += '
'+ID + '	' +snps[ID] +'
';
            }
            scoreSummary += '
Total	'+total+'
';
            scoreDisplay.innerHTML = scoreSummary+'
';
            return true;
        } else {
            return false;
        }
    }
});
});<|endoftext|>


<commit_before>import angular from 'angular';
import _ from 'lodash';
import {QueryCtrl} from 'app/plugins/sdk';
import MQEQuery from './query_builder';

export class MQEQueryCtrl extends QueryCtrl {

  constructor($scope, $injector, $q, uiSegmentSrv, templateSrv)  {
    super($scope, $injector);

    this.scope = $scope;
    this.$q = $q;
    this.uiSegmentSrv = uiSegmentSrv;
    this.templateSrv = templateSrv;

    var target_defaults = {
      rawQuery: "",
      metrics: [{metric: ""}],
      apps: [],
      hosts: []
    };
    _.defaults(this.target, target_defaults);

    this.appSegments = _.map(this.target.apps, this.uiSegmentSrv.newSegment);
    this.hostSegments = _.map(this.target.hosts, this.uiSegmentSrv.newSegment);
    this.removeSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove --'});
    this.fixSegments(this.appSegments);
    this.fixSegments(this.hostSegments);

    // bs-typeahead can't work with async code so we need to
    // store metrics first.
    this.availableMetrics = [];
    this.updateMetrics();
    // Pass this to getMetrics() function, because it's called from bs-typeahead
    // without proper context.
    this.getMetrics = _.bind(this.getMetrics, this);
  }

  invokeMQEQuery(query) {
    return this.datasource._mqe_query(query).then(result => {
      return result.body;
    });
  }

  exploreMetrics(query) {
    return this.datasource._mqe_explore(query);
  }

  updateMetrics() {
    var self = this;
    this.exploreMetrics('metrics').then(metrics => {
      self.availableMetrics = metrics;
    });
  }

  // Event handlers

  onChangeInternal() {
    this.panelCtrl.refresh(); // Asks the panel to refresh data.
  }

  toggleEditorMode() {
    this.target.rawQuery = !this.target.rawQuery;
  }

  getCollapsedText() {
    return this.target.rawQuery;
  }

  appSegmentChanged(segment, index) {
    if (segment.type === 'plus-button') {
      segment.type = undefined;
    }
    this.target.apps = _.map(_.filter(this.appSegments, segment => {
      return (segment.type !== 'plus-button' &&
              segment.value !== this.removeSegment.value);
    }), 'value');
    this.appSegments = _.map(this.target.apps, this.uiSegmentSrv.newSegment);
    this.appSegments.push(this.uiSegmentSrv.newPlusButton());
    this.onChangeInternal();
  }

  hostSegmentChanged(segment, index) {
    if (segment.type === 'plus-button') {
      segment.type = undefined;
    }
    this.target.hosts = _.map(_.filter(this.hostSegments, segment => {
      return (segment.type !== 'plus-button' &&
              segment.value !== this.removeSegment.value);
    }), 'value');
    this.hostSegments = _.map(this.target.hosts, this.uiSegmentSrv.newSegment);
    this.hostSegments.push(this.uiSegmentSrv.newPlusButton());
    this.onChangeInternal();
  }

  addMetric() {
    this.target.metrics.push({metric: ""});
    this.onChangeInternal();
  }

  removeMetric(index) {
    this.target.metrics.splice(index, 1);
    this.onChangeInternal();
  }

  ///////////////////////
  // Query suggestions //
  ///////////////////////

  getMetrics() {
    // Don't touch original metric list
    var metrics = _.clone(this.availableMetrics);
    for (let variable of this.templateSrv.variables) {
      metrics.unshift('$' + variable.name);
    }
    return metrics;
  }

  describeMetric(metric) {
    var describeQuery = MQEQuery.getColumns(metric);
    return this.invokeMQEQuery(describeQuery);
  }

  getApps() {
    return this.exploreMetrics('apps').then(apps => {
      let segments = this.transformToSegments(apps, true);
      segments.splice(0, 0, angular.copy(this.removeSegment));
      return segments;
    });
  }

  getHosts() {
    return this.exploreMetrics('hosts').then(hosts => {
      let segments = this.transformToSegments(hosts, true);
      segments.splice(0, 0, angular.copy(this.removeSegment));
      return segments;
    });
  }

  ///////////////////////

  transformToSegments(results, addTemplateVars) {
    var segments = _.map(_.flatten(results), value => {
      return this.uiSegmentSrv.newSegment({
        value: value.toString(),
        expandable: false
      });
    });

    if (addTemplateVars) {
      for (let variable of this.templateSrv.variables) {
        segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '$' + variable.name, expandable: true }));
      }
    }
    return segments;
  }

  fixSegments(segments) {
    var count = segments.length;
    var lastSegment = segments[Math.max(count-1, 0)];

    if (!lastSegment || lastSegment.type !== 'plus-button') {
      segments.push(this.uiSegmentSrv.newPlusButton());
    }
  }

}

MQEQueryCtrl.templateUrl = 'partials/query.editor.html';
<commit_msg>Update panel when metric selected from dropdown.<commit_after>import angular from 'angular';
import _ from 'lodash';
import {QueryCtrl} from 'app/plugins/sdk';
import MQEQuery from './query_builder';

export class MQEQueryCtrl extends QueryCtrl {

  constructor($scope, $injector, $q, uiSegmentSrv, templateSrv)  {
    super($scope, $injector);

    this.scope = $scope;
    this.$q = $q;
    this.uiSegmentSrv = uiSegmentSrv;
    this.templateSrv = templateSrv;

    var target_defaults = {
      rawQuery: "",
      metrics: [{metric: ""}],
      apps: [],
      hosts: []
    };
    _.defaults(this.target, target_defaults);

    this.appSegments = _.map(this.target.apps, this.uiSegmentSrv.newSegment);
    this.hostSegments = _.map(this.target.hosts, this.uiSegmentSrv.newSegment);
    this.removeSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove --'});
    this.fixSegments(this.appSegments);
    this.fixSegments(this.hostSegments);

    // bs-typeahead can't work with async code so we need to
    // store metrics first.
    this.availableMetrics = [];
    this.updateMetrics();
    // Pass this to getMetrics() function, because it's called from bs-typeahead
    // without proper context.
    this.getMetrics = _.bind(this.getMetrics, this);

    // Update panel when metric selected from dropdown
    $scope.$on('typeahead-updated', () => {
      this.onChangeInternal();
    });
  }

  invokeMQEQuery(query) {
    return this.datasource._mqe_query(query).then(result => {
      return result.body;
    });
  }

  exploreMetrics(query) {
    return this.datasource._mqe_explore(query);
  }

  updateMetrics() {
    var self = this;
    this.exploreMetrics('metrics').then(metrics => {
      self.availableMetrics = metrics;
    });
  }

  // Event handlers

  onChangeInternal() {
    this.panelCtrl.refresh(); // Asks the panel to refresh data.
  }

  toggleEditorMode() {
    this.target.rawQuery = !this.target.rawQuery;
  }

  getCollapsedText() {
    return this.target.rawQuery;
  }

  appSegmentChanged(segment, index) {
    if (segment.type === 'plus-button') {
      segment.type = undefined;
    }
    this.target.apps = _.map(_.filter(this.appSegments, segment => {
      return (segment.type !== 'plus-button' &&
              segment.value !== this.removeSegment.value);
    }), 'value');
    this.appSegments = _.map(this.target.apps, this.uiSegmentSrv.newSegment);
    this.appSegments.push(this.uiSegmentSrv.newPlusButton());
    this.onChangeInternal();
  }

  hostSegmentChanged(segment, index) {
    if (segment.type === 'plus-button') {
      segment.type = undefined;
    }
    this.target.hosts = _.map(_.filter(this.hostSegments, segment => {
      return (segment.type !== 'plus-button' &&
              segment.value !== this.removeSegment.value);
    }), 'value');
    this.hostSegments = _.map(this.target.hosts, this.uiSegmentSrv.newSegment);
    this.hostSegments.push(this.uiSegmentSrv.newPlusButton());
    this.onChangeInternal();
  }

  addMetric() {
    this.target.metrics.push({metric: ""});
    this.onChangeInternal();
  }

  removeMetric(index) {
    this.target.metrics.splice(index, 1);
    this.onChangeInternal();
  }

  ///////////////////////
  // Query suggestions //
  ///////////////////////

  getMetrics() {
    // Don't touch original metric list
    var metrics = _.clone(this.availableMetrics);
    for (let variable of this.templateSrv.variables) {
      metrics.unshift('$' + variable.name);
    }
    return metrics;
  }

  describeMetric(metric) {
    var describeQuery = MQEQuery.getColumns(metric);
    return this.invokeMQEQuery(describeQuery);
  }

  getApps() {
    return this.exploreMetrics('apps').then(apps => {
      let segments = this.transformToSegments(apps, true);
      segments.splice(0, 0, angular.copy(this.removeSegment));
      return segments;
    });
  }

  getHosts() {
    return this.exploreMetrics('hosts').then(hosts => {
      let segments = this.transformToSegments(hosts, true);
      segments.splice(0, 0, angular.copy(this.removeSegment));
      return segments;
    });
  }

  ///////////////////////

  transformToSegments(results, addTemplateVars) {
    var segments = _.map(_.flatten(results), value => {
      return this.uiSegmentSrv.newSegment({
        value: value.toString(),
        expandable: false
      });
    });

    if (addTemplateVars) {
      for (let variable of this.templateSrv.variables) {
        segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '$' + variable.name, expandable: true }));
      }
    }
    return segments;
  }

  fixSegments(segments) {
    var count = segments.length;
    var lastSegment = segments[Math.max(count-1, 0)];

    if (!lastSegment || lastSegment.type !== 'plus-button') {
      segments.push(this.uiSegmentSrv.newPlusButton());
    }
  }

}

MQEQueryCtrl.templateUrl = 'partials/query.editor.html';
<|endoftext|>


<commit_before>
<commit_msg>Fix deleted search styles<commit_after>import color from '../../util/colors'
import marginOffset from '../../util/marginOffset'
import style from '../style'
import ScreenSizes from '../../util/ScreenSizes'

const MARGIN_SIZE = 10
const SEARCH_BAR_MARGIN_TOP_IOS = 35

export const SEARCH_BAR_MARGIN = marginOffset(SEARCH_BAR_MARGIN_TOP_IOS)
export const ROW_HEIGHT = ScreenSizes.isSmall() ? 50 : 60
export const SEARCH_BAR_HEIGHT = ScreenSizes.isSmall() ? 44 : 48
export const MAP_HEIGHT = ScreenSizes.isSmall() ? 220 : 275

const styles = {
  expandHeader: {
    container: {
      height: 40,
      backgroundColor: 'white',
      borderWidth: 1,
      borderColor: color.gray5,
      justifyContent: 'center',
      alignItems: 'center',
      borderRadius: 5
    },
    topBorderCurve: {
      borderTopLeftRadius: 8,
      borderTopRightRadius: 8,
      borderBottomRightRadius: 0,
      borderBottomLeftRadius: 0
    },
    noBorderCurve: {
      borderRadius: 0
    }
  },
  listItem: {
    container: {
      flexDirection: 'row',
      paddingLeft: 10,
      paddingTop: ScreenSizes.isSmall() ? 4 : 8,
      paddingBottom: ScreenSizes.isSmall() ? 4 : 8,
      borderBottomWidth: 1,
      borderBottomColor: color.offWhite,
      backgroundColor: color.white,
      height: ROW_HEIGHT
    },
    image: {
      width: ScreenSizes.isSmall() ? 42 : 44,
      height: ScreenSizes.isSmall() ? 42 : 44,
      borderRadius: 5,
      borderWidth: 1,
      borderColor: color.offWhite
    },
    verticalStack: {
      flex: 1,
      flexDirection: 'column',
      paddingLeft: 10,
      paddingRight: 10
    },
    title: {
      fontSize: ScreenSizes.isSmall() ? 16 : 18,
      marginTop: 2
    },
    shortDisplay: {
      fontSize: 14,
      color: color.gray3,
      marginBottom: 6
    },
    selectedBusiness: {
      marginBottom: 10
    }
  },
  searchTab: {
    list: {
      position: 'absolute',
      top: 35 + SEARCH_BAR_HEIGHT,
      left: MARGIN_SIZE,
      right: MARGIN_SIZE,
      bottom: 0,
      borderRadius: 5,
      ...style.dropshadow
    },
    searchBar: {
      height: SEARCH_BAR_HEIGHT,
      marginLeft: MARGIN_SIZE,
      marginRight: MARGIN_SIZE,
      marginTop: SEARCH_BAR_MARGIN,
      backgroundColor: color.white,
      flexDirection: 'row',
      ...style.dropshadow
    },
  }
}

export default styles
<|endoftext|>


<commit_before>
/*
  Jasmine extra matchers 0.2.2

  Copyright (c) 2014-2016 Tomasz Jakub Rup

  https://github.com/tomi77/jasmine-extra-matchers

  Released under the MIT license
 */
var _;

_ = require('lodash');

beforeEach(function() {
  jasmine.addMatchers({
    toBeInstanceOf: function() {
      return {
        compare: function(actual, expected) {
          return {
            pass: actual instanceof expected
          };
        }
      };
    },
    toBeInfinity: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual === Infinity
          };
        }
      };
    },
    hasOwnProperty: function() {
      return {
        compare: function(obj, key) {
          return {
            pass: obj != null ? obj.hasOwnProperty(key) : void 0
          };
        }
      };
    },
    toBeEven: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual % 2 === 0
          };
        }
      };
    },
    toBeOdd: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual % 2 !== 0
          };
        }
      };
    },
    toBeNumeric: function() {
      return {
        compare: function(actual) {
          return {
            pass: !isNaN(parseFloat(actual)) && isFinite(actual)
          };
        }
      };
    },
    toBeInteger: function() {
      return {
        compare: function(actual) {
          return {
            pass: !isNaN(parseFloat(actual)) && isFinite(actual) && actual % 1 === 0
          };
        }
      };
    },
    toBeFloat: function() {
      return {
        compare: function(actual) {
          return {
            pass: !isNaN(parseFloat(actual)) && isFinite(actual) && actual % 1 !== 0
          };
        }
      };
    },
    toBePositive: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual > 0
          };
        }
      };
    },
    toBeNegative: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual < 0
          };
        }
      };
    },
    toBeTrue: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual === true
          };
        }
      };
    },
    toBeFalse: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual === false
          };
        }
      };
    },
    toExists: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual != null
          };
        }
      };
    },
    toBeTypeOf: function() {
      return {
        compare: function(actual, expected) {
          return {
            pass: expected === typeof actual
          };
        }
      };
    },
    toBeFunction: function() {
      return {
        compare: function(actual) {
          return {
            pass: _.isFunction(actual)
          };
        }
      };
    },
    toBeObject: function() {
      return {
        compare: function(actual) {
          return {
            pass: _.isObject(actual)
          };
        }
      };
    },
    toBeArray: function() {
      return {
        compare: function(actual) {
          return {
            pass: _.isArray(actual)
          };
        }
      };
    },
    toBeString: function() {
      return {
        compare: function(actual) {
          return {
            pass: _.isString(actual)
          };
        }
      };
    },
    toBeFinite: function() {
      return {
        compare: function(actual) {
          return {
            pass: _.isFinite(actual)
          };
        }
      };
    },
    toBeBoolean: function() {
      return {
        compare: function(actual) {
          return {
            pass: _.isBoolean(actual)
          };
        }
      };
    }
  });
});
<commit_msg>build<commit_after>
/*
  Jasmine extra matchers 0.2.2

  Copyright (c) 2014-2016 Tomasz Jakub Rup

  https://github.com/tomi77/jasmine-extra-matchers

  Released under the MIT license
 */
beforeEach(function() {
  jasmine.addMatchers({
    toBeInstanceOf: function() {
      return {
        compare: function(actual, expected) {
          return {
            pass: actual instanceof expected
          };
        }
      };
    },
    toBeInfinity: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual === Infinity
          };
        }
      };
    },
    hasOwnProperty: function() {
      return {
        compare: function(obj, key) {
          return {
            pass: obj != null ? obj.hasOwnProperty(key) : void 0
          };
        }
      };
    },
    toBeEven: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual % 2 === 0
          };
        }
      };
    },
    toBeOdd: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual % 2 !== 0
          };
        }
      };
    },
    toBeNumeric: function() {
      return {
        compare: function(actual) {
          return {
            pass: !isNaN(parseFloat(actual)) && isFinite(actual)
          };
        }
      };
    },
    toBeInteger: function() {
      return {
        compare: function(actual) {
          return {
            pass: !isNaN(parseFloat(actual)) && isFinite(actual) && actual % 1 === 0
          };
        }
      };
    },
    toBeFloat: function() {
      return {
        compare: function(actual) {
          return {
            pass: !isNaN(parseFloat(actual)) && isFinite(actual) && actual % 1 !== 0
          };
        }
      };
    },
    toBePositive: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual > 0
          };
        }
      };
    },
    toBeNegative: function() {
      return {
        compare: function(actual) {
          return {
            pass: actual < 0
          };
        }
      };
    }
  });
});
<|endoftext|>


<commit_before>/**
 * @namespace hs.geolocation
 * @memberOf hs
 */
define(['angular', 'ol'],

    function(angular, ol) {
        angular.module('hs.geolocation', ['hs.map'])
            .directive('hs.geolocation.directive', ['hs.map.service', 'hs.geolocation.service', 'Core', function(OlMap, Geolocation, Core) {
                return {
                    templateUrl: hsl_path + 'components/geolocation/partials/geolocation.html',
                    link: function link(scope, element, attrs) {
                        element.appendTo($(".ol-overlaycontainer-stopevent"));
                        $('.locate .blocate').click(function() {
                            $('.locate').toggleClass('ol-collapsed');
                            Geolocation.geolocation.setTracking(true);
                            Geolocation.toggleFeatures(!$('.locate').hasClass('ol-collapsed'));
                        });
                        if (Core.panel_side == 'left') {
                            $('.locate').css({
                                right: '.5em'
                            });
                        }
                        if (Core.panel_side == 'right') {
                            $('.locate').css({
                                right: 'auto',
                                left: '.2em'
                            });
                        }
                    },
                    replace: true
                };
            }])

        .service('hs.geolocation.service', ['hs.map.service', '$rootScope', '$log',
            function(OlMap, $rootScope, $log) {
                var me = {
                    following: false,
                    geolocation: null,
                    toggleFeatures: function(visible) {
                        if (visible) {
                            featuresOverlay.addFeature(accuracyFeature);
                            featuresOverlay.addFeature(positionFeature);
                        } else {
                            featuresOverlay.removeFeature(accuracyFeature);
                            featuresOverlay.removeFeature(positionFeature);

                        }
                    }
                };

                me.geolocation = new ol.Geolocation({
                    projection: OlMap.map.getView().getProjection()
                });

                me.changed_handler = function() {
                    if (!me.geolocation.getTracking()) return;

                    me.accuracy = me.geolocation.getAccuracy() ? me.geolocation.getAccuracy() + ' [m]' : '';
                    me.altitude = me.geolocation.getAltitude() ? me.geolocation.getAltitude() + ' [m]' : '-';
                    me.altitudeAccuracy = me.geolocation.getAltitudeAccuracy() ? '+/- ' + me.geolocation.getAltitudeAccuracy() + ' [m]' : '';
                    me.heading = me.geolocation.getHeading() ? me.geolocation.getHeading() : null;
                    me.speed = me.geolocation.getSpeed() ? me.geolocation.getSpeed() + ' [m/s]' : '-';
                    if (me.geolocation.getPosition()) {
                        var p = me.geolocation.getPosition();
                        $log.info(p);
                        if (!positionFeature.getGeometry())
                            positionFeature.setGeometry(new ol.geom.Point(p));
                        else
                            positionFeature.getGeometry().setCoordinates(p);
                        if (me.following)
                            OlMap.map.getView().setCenter(p);
                    }
                    if (me.heading) OlMap.map.getView().setRotation(me.heading);
                    $rootScope.$broadcast('geolocation.updated');
                }

                me.geolocation.on('change', me.changed_handler);

                // handle geolocation error.
                me.geolocation.on('error', function(error) {
                    var info = document.getElementById('info');
                    info.innerHTML = error.message;
                    info.style.display = '';
                });
                //var track = new ol.dom.Input(document.getElementById('track'));
                //track.bindTo('checked', geolocation, 'tracking');

                me.style = new ol.style.Style({
                    image: new ol.style.Circle({
                        fill: new ol.style.Fill({
                            color: [242, 121, 0, 0.7]
                        }),
                        stroke: new ol.style.Stroke({
                            color: [0xbb, 0x33, 0x33, 0.7]
                        }),
                        radius: 5
                    }),
                    fill: new ol.style.Fill({
                        color: [0xbb, 0xbb, 0xbb, 0.2]
                    }),
                    stroke: new ol.style.Stroke({
                        color: [0x66, 0x66, 0x00, 0.8]
                    })
                });

                var accuracyFeature = new ol.Feature();
                accuracyFeature.bindTo('geometry', me.geolocation, 'accuracyGeometry');

                var positionFeature = new ol.Feature();

                accuracyFeature.setStyle(me.style);
                positionFeature.setStyle(me.style);

                var featuresOverlay = new ol.FeatureOverlay({
                    map: OlMap.map,
                    features: []
                });

                return me;
            }
        ]).controller('hs.geolocation.controller', ['$scope', 'hs.geolocation.service', function($scope, service) {
            $scope.speed = null;
            $scope.alt = null;
            $scope.altitudeAccuracy = null;

            $scope.getGeolocationProvider = function() {
                return service.geolocation;
            }

            $scope.gpsActive = function(set_to) {
                if (arguments.length == 0)
                    return service.geolocation.getTracking();
                else
                    service.geolocation.setTracking(set_to);
            }

            $scope.following = function(set_to) {
                if (arguments.length == 0)
                    return service.following;
                else {
                    service.following = set_to;
                    service.changed_handler();
                }
            }

            $scope.setFeatureStyle = function(style) {
                return service.style = style;
            }

            $scope.$on('geolocation.updated', function(event) {
                $scope.speed = service.speed;
                $scope.alt = service.altitude;
                $scope.altitudeAccuracy = service.altitudeAccuracy;
                if (!$scope.$$phase) $scope.$digest();
            });
            $scope.$emit('scope_loaded', "Geolocation");
        }]);
    })
<commit_msg>fixed geoloaction for new OL version<commit_after>/**
 * @namespace hs.geolocation
 * @memberOf hs
 */
define(['angular', 'ol'],

    function(angular, ol) {
        angular.module('hs.geolocation', ['hs.map'])
            .directive('hs.geolocation.directive', ['hs.map.service', 'hs.geolocation.service', 'Core', function(OlMap, Geolocation, Core) {
                return {
                    templateUrl: hsl_path + 'components/geolocation/partials/geolocation.html',
                    link: function link(scope, element, attrs) {
                        element.appendTo($(".ol-overlaycontainer-stopevent"));
                        $('.locate .blocate').click(function() {
                            $('.locate').toggleClass('ol-collapsed');
                            Geolocation.geolocation.setTracking(true);
                            Geolocation.toggleFeatures(!$('.locate').hasClass('ol-collapsed'));
                        });
                        if (Core.panel_side == 'left') {
                            $('.locate').css({
                                right: '.5em'
                            });
                        }
                        if (Core.panel_side == 'right') {
                            $('.locate').css({
                                right: 'auto',
                                left: '.2em'
                            });
                        }
                    },
                    replace: true
                };
            }])

        .service('hs.geolocation.service', ['hs.map.service', '$rootScope', '$log',
            function(OlMap, $rootScope, $log) {
                var me = {
                    following: false,
                    geolocation: null,
                    toggleFeatures: function(visible) {
                        var src = me.position_layer.getSource();
                        if (visible) {
                            OlMap.map.addLayer(me.position_layer);
                            src.addFeature(accuracyFeature);
                            src.addFeature(positionFeature);
                        } else {
                            src.removeFeature(accuracyFeature);
                            src.removeFeature(positionFeature);
                            OlMap.map.removeLayer(me.position_layer);
                        }
                    }
                };

                me.geolocation = new ol.Geolocation({
                    projection: OlMap.map.getView().getProjection()
                });

                me.changed_handler = function() {
                    if (!me.geolocation.getTracking()) return;

                    me.accuracy = me.geolocation.getAccuracy() ? me.geolocation.getAccuracy() + ' [m]' : '';
                    me.altitude = me.geolocation.getAltitude() ? me.geolocation.getAltitude() + ' [m]' : '-';
                    me.altitudeAccuracy = me.geolocation.getAltitudeAccuracy() ? '+/- ' + me.geolocation.getAltitudeAccuracy() + ' [m]' : '';
                    me.heading = me.geolocation.getHeading() ? me.geolocation.getHeading() : null;
                    me.speed = me.geolocation.getSpeed() ? me.geolocation.getSpeed() + ' [m/s]' : '-';
                    if (me.geolocation.getPosition()) {
                        var p = me.geolocation.getPosition();
                        $log.info(p);
                        if (!positionFeature.getGeometry())
                            positionFeature.setGeometry(new ol.geom.Point(p));
                        else
                            positionFeature.getGeometry().setCoordinates(p);
                        if (me.following)
                            OlMap.map.getView().setCenter(p);
                    }
                    if (me.heading) OlMap.map.getView().setRotation(me.heading);
                    $rootScope.$broadcast('geolocation.updated');
                }

                me.geolocation.on('change', me.changed_handler);

                // handle geolocation error.
                me.geolocation.on('error', function(error) {
                    var info = document.getElementById('info');
                    info.innerHTML = error.message;
                    info.style.display = '';
                });
                //var track = new ol.dom.Input(document.getElementById('track'));
                //track.bindTo('checked', geolocation, 'tracking');

                me.style = new ol.style.Style({
                    image: new ol.style.Circle({
                        fill: new ol.style.Fill({
                            color: [242, 121, 0, 0.7]
                        }),
                        stroke: new ol.style.Stroke({
                            color: [0xbb, 0x33, 0x33, 0.7]
                        }),
                        radius: 5
                    }),
                    fill: new ol.style.Fill({
                        color: [0xbb, 0xbb, 0xbb, 0.2]
                    }),
                    stroke: new ol.style.Stroke({
                        color: [0x66, 0x66, 0x00, 0.8]
                    })
                });

                var accuracyFeature = new ol.Feature();
                me.geolocation.on('change:accuracyGeometry', function(){accuracyFeature.set('geometry', me.geolocation.accuracyGeometry);});

                //accuracyFeature.bindTo('geometry', me.geolocation, 'accuracyGeometry');

                var positionFeature = new ol.Feature();

                accuracyFeature.setStyle(me.style);
                positionFeature.setStyle(me.style);

                me.position_layer = new ol.layer.Vector({
                    title: "Position",
                    show_in_manager: false,
                    source: new ol.source.Vector()
                });

                return me;
            }
        ]).controller('hs.geolocation.controller', ['$scope', 'hs.geolocation.service', 'hs.map.service', function($scope, service, OlMap) {
            $scope.speed = null;
            $scope.alt = null;
            $scope.altitudeAccuracy = null;

            $scope.getGeolocationProvider = function() {
                return service.geolocation;
            }

            $scope.gpsActive = function(set_to) {
                if (arguments.length == 0)
                    return service.geolocation.getTracking();
                else {
                    service.geolocation.setTracking(set_to);
                }
            }

            $scope.following = function(set_to) {
                if (arguments.length == 0)
                    return service.following;
                else {
                    service.following = set_to;
                    service.changed_handler();
                }
            }

            $scope.setFeatureStyle = function(style) {
                return service.style = style;
            }

            $scope.$on('geolocation.updated', function(event) {
                $scope.speed = service.speed;
                $scope.alt = service.altitude;
                $scope.altitudeAccuracy = service.altitudeAccuracy;
                if (!$scope.$$phase) $scope.$digest();
            });
            $scope.$emit('scope_loaded', "Geolocation");
        }]);
    })
<|endoftext|>


<commit_before>import VectorLayer from 'ol/layer/Vector';
import {Draw} from 'ol/interaction';
import {Fill, Stroke, Style} from 'ol/style';
import {GeometryType, LineString, Polygon} from 'ol/geom';
import {Vector} from 'ol/source';
import {getArea, getDistance} from 'ol/sphere';
import {transform} from 'ol/proj';

export default [
  '$rootScope',
  'hs.map.service',
  'hs.utils.service',
  function ($rootScope, OlMap, utils) {
    const me = this;

    let map;

    OlMap.loaded().then((m) => {
      map = m;
    });

    this.draw;

    this.measureVector = new VectorLayer({
      source: new Vector(),
      style: new Style({
        fill: new Fill({
          color: 'rgba(255, 255, 255, 0.2)',
        }),
        stroke: new Stroke({
          color: '#ffcc33',
          width: 2,
        }),
      }),
    });

    this.data = {};

    this.data.measurements = [];

    this.data.multipleShapeMode = false;

    this.sketch = {};

    this.currentMeasurement;

    /**
     * @memberof hs.measure.service
     * @function switchMultipleMode
     * @public
     * @param {Boolean} mode Optional parameter if multiple shape mode should be enabled
     * @description Enable/disable multiple shape mode for measuring (switch without parameter)
     */
    this.switchMultipleMode = function (mode) {
      if (angular.isDefined(mode)) {
        me.data.multipleShapeMode = mode;
      } else {
        me.data.multipleShapeMode = !me.data.multipleShapeMode;
      }
    };

    /**
     * @memberof hs.measure.service
     * @function changeMeasureParams
     * @public
     * @param {String} type Geometry type of measurement ('area' for polygon, 'line' for linestring)
     * @description Change geometry type of measurement without deleting of old ones
     */
    this.changeMeasureParams = function (type) {
      map.removeInteraction(me.draw);
      me.sketch = null;
      addInteraction(type);
    };

    /**
     * @memberof hs.measure.service
     * @function clearMeasurement
     * @public
     * @description Clear all measurements and restart measuring
     */
    this.clearMeasurement = function () {
      me.draw.setActive(false);
      me.data.measurements.length = 0;
      me.measureVector.getSource().clear();
      me.sketch = null;
      me.draw.setActive(true);
    };

    /**
     * @memberof hs.measure.service
     * @function activateMeasuring
     * @public
     * @param {Boolean} mode Optional parameter, Geometry type of measurement ('area' for polygon, 'line' for linestring) Line is default
     * @description Start measuring interaction in app
     */
    this.activateMeasuring = function (type) {
      map.addLayer(me.measureVector);
      map.getViewport().addEventListener('mousemove', mouseMoveHandler);
      map.getViewport().addEventListener('touchmove', mouseMoveHandler);
      map.getViewport().addEventListener('touchend', mouseMoveHandler);

      addInteraction(type);
    };

    /**
     * @memberof hs.measure.service
     * @function deactivateMeasuring
     * @public
     * @description Stop measuring interaction in app
     */
    this.deactivateMeasuring = function () {
      OlMap.loaded().then((m) => {
        map.getViewport().removeEventListener('mousemove', mouseMoveHandler);
        map.getViewport().removeEventListener('touchmove', mouseMoveHandler);
        map.getViewport().removeEventListener('touchend', mouseMoveHandler);

        map.removeInteraction(me.draw);
        map.removeLayer(me.measureVector);
      });
    };

    /**
     * @memberof hs.measure.service
     * @function mouseMoveHandler
     * @private
     * @param {Object} evt Callback param for mouse move event
     * @description Callback for mouse and touch move event, compute live measurement results
     */
    const mouseMoveHandler = function (evt) {
      if (me.sketch) {
        let output;

        for (let i = 0; i < me.sketch.length; i++) {
          const geom = me.sketch[i].getGeometry();
          if (utils.instOf(geom, Polygon)) {
            output = addMultiple(formatArea(geom), output);
          } else if (utils.instOf(geom, LineString)) {
            output = addMultiple(formatLength(geom), output);
          }
        }

        me.data.measurements[me.currentMeasurement] = output;
        if (me.data.measurements[me.currentMeasurement]) {
          me.data.measurements[me.currentMeasurement].geom = me.sketch;
        }
        if (!$rootScope.$$phase) {
          $rootScope.$digest();
        }
      }
    };

    /**
     * @memberof hs.measure.service
     * @function addMultiple
     * @private
     * @param {object} val1 Output of new object
     * @param {object} val2 Old value
     * @description Add two measure results for multiple shape mode to display joined result
     */
    const addMultiple = function (val1, val2) {
      if (val2 == undefined) {
        return val1;
      }
      let unit = val1.unit;
      const type = val1.type;
      if (val1.unit == val2.unit) {
        var value = Math.round((val1.size + val2.size) * 100) / 100;
        if (unit == 'm' && type == 'length' && value > 1000) {
          value = Math.round((value / 1000) * 100) / 100;
          unit = 'km';
        } else if (unit == 'm' && type == 'area' && value > 10000) {
          value = Math.round((value / 1000000) * 100) / 100;
          unit = 'km';
        }
      } else {
        const arr = [val1, val2];
        for (let i = 0; i < arr.length; i++) {
          if (arr[i].unit == 'm') {
            type == 'length' ? (arr[i].size /= 1000) : (arr[i].size /= 1000000);
          }
        }
        var value = Math.round((arr[0].size + arr[1].size) * 100) / 100;
        unit = 'km';
      }
      const output = {
        size: value,
        type: type,
        unit: unit,
      };
      return output;
    };

    /**
     * @memberof hs.measure.service
     * @function addInteraction
     * @private
     * @param {Boolean} type Geometry type
     * @description Initialize draw interaction on Ol.map and event handlers for handling start and end of drawing
     */
    function addInteraction(type) {
      const drawType = type == 'area' ? 'Polygon' : 'LineString';
      me.draw = new Draw({
        source: me.measureVector.getSource(),
        type: /** @type {GeometryType} */ (drawType),
        dragVertexDelay: 150,
      });
      map.addInteraction(me.draw);

      me.draw.on('drawstart', (evt) => {
        $rootScope.$broadcast('measure.drawStart');
        if (me.data.multipleShapeMode) {
          if (!angular.isArray(me.sketch)) {
            me.sketch = [];
            me.data.measurements.push({
              size: 0,
              unit: '',
            });
          }
          me.sketch.push(evt.feature);
        } else {
          me.sketch = [evt.feature];
          me.data.measurements.push({
            size: 0,
            unit: '',
          });
        }
        me.currentMeasurement = me.data.measurements.length - 1;
      });

      me.draw.on('drawend', (evt) => {
        $rootScope.$broadcast('measure.drawEnd');
      });
    }

    /**
     * @memberof hs.measure.service
     * @function formatLength
     * @private
     * @param {ol.geom.LineString} line
     * @return {object} numeric length of line with used units
     * @description Compute and format line length with correct units (m/km)
     */
    const formatLength = function (line) {
      let length = 0;
      const coordinates = line.getCoordinates();
      const sourceProj = map.getView().getProjection();

      for (let i = 0, ii = coordinates.length - 1; i < ii; ++i) {
        const c1 = transform(coordinates[i], sourceProj, 'EPSG:4326');
        const c2 = transform(coordinates[i + 1], sourceProj, 'EPSG:4326');
        length += getDistance(c1, c2);
      }

      const output = {
        size: length,
        type: 'length',
        unit: 'm',
      };

      if (length > 100) {
        output.size = Math.round((length / 1000) * 100) / 100;
        output.unit = 'km';
      } else {
        output.size = Math.round(length * 100) / 100;
        output.unit = 'm';
      }
      return output;
    };

    /**
     * @memberof hs.measure.service
     * @function formatArea
     * @private
     * @param {ol.geom.Polygon} polygon
     * @return {object} area of polygon with used units
     * @description Compute and format polygon area with correct units (m2/km2)
     */
    const formatArea = function (polygon) {
      const sourceProj = map.getView().getProjection();
      const area = Math.abs(getArea(polygon));
      const output = {
        size: area,
        type: 'area',
        unit: 'm',
      };
      if (area > 10000) {
        output.size = Math.round((area / 1000000) * 100) / 100;
        output.unit = 'km';
      } else {
        output.size = Math.round(area * 100) / 100;
        output.unit = 'm';
      }
      return output;
    };
  },
];
<commit_msg>Small fix m -> map<commit_after>import VectorLayer from 'ol/layer/Vector';
import {Draw} from 'ol/interaction';
import {Fill, Stroke, Style} from 'ol/style';
import {GeometryType, LineString, Polygon} from 'ol/geom';
import {Vector} from 'ol/source';
import {getArea, getDistance} from 'ol/sphere';
import {transform} from 'ol/proj';

export default [
  '$rootScope',
  'hs.map.service',
  'hs.utils.service',
  function ($rootScope, OlMap, utils) {
    const me = this;

    let map;

    OlMap.loaded().then((m) => {
      map = m;
    });

    this.draw;

    this.measureVector = new VectorLayer({
      source: new Vector(),
      style: new Style({
        fill: new Fill({
          color: 'rgba(255, 255, 255, 0.2)',
        }),
        stroke: new Stroke({
          color: '#ffcc33',
          width: 2,
        }),
      }),
    });

    this.data = {};

    this.data.measurements = [];

    this.data.multipleShapeMode = false;

    this.sketch = {};

    this.currentMeasurement;

    /**
     * @memberof hs.measure.service
     * @function switchMultipleMode
     * @public
     * @param {Boolean} mode Optional parameter if multiple shape mode should be enabled
     * @description Enable/disable multiple shape mode for measuring (switch without parameter)
     */
    this.switchMultipleMode = function (mode) {
      if (angular.isDefined(mode)) {
        me.data.multipleShapeMode = mode;
      } else {
        me.data.multipleShapeMode = !me.data.multipleShapeMode;
      }
    };

    /**
     * @memberof hs.measure.service
     * @function changeMeasureParams
     * @public
     * @param {String} type Geometry type of measurement ('area' for polygon, 'line' for linestring)
     * @description Change geometry type of measurement without deleting of old ones
     */
    this.changeMeasureParams = function (type) {
      map.removeInteraction(me.draw);
      me.sketch = null;
      addInteraction(type);
    };

    /**
     * @memberof hs.measure.service
     * @function clearMeasurement
     * @public
     * @description Clear all measurements and restart measuring
     */
    this.clearMeasurement = function () {
      me.draw.setActive(false);
      me.data.measurements.length = 0;
      me.measureVector.getSource().clear();
      me.sketch = null;
      me.draw.setActive(true);
    };

    /**
     * @memberof hs.measure.service
     * @function activateMeasuring
     * @public
     * @param {Boolean} mode Optional parameter, Geometry type of measurement ('area' for polygon, 'line' for linestring) Line is default
     * @description Start measuring interaction in app
     */
    this.activateMeasuring = function (type) {
      map.addLayer(me.measureVector);
      map.getViewport().addEventListener('mousemove', mouseMoveHandler);
      map.getViewport().addEventListener('touchmove', mouseMoveHandler);
      map.getViewport().addEventListener('touchend', mouseMoveHandler);

      addInteraction(type);
    };

    /**
     * @memberof hs.measure.service
     * @function deactivateMeasuring
     * @public
     * @description Stop measuring interaction in app
     */
    this.deactivateMeasuring = function () {
      OlMap.loaded().then((map) => {
        map.getViewport().removeEventListener('mousemove', mouseMoveHandler);
        map.getViewport().removeEventListener('touchmove', mouseMoveHandler);
        map.getViewport().removeEventListener('touchend', mouseMoveHandler);

        map.removeInteraction(me.draw);
        map.removeLayer(me.measureVector);
      });
    };

    /**
     * @memberof hs.measure.service
     * @function mouseMoveHandler
     * @private
     * @param {Object} evt Callback param for mouse move event
     * @description Callback for mouse and touch move event, compute live measurement results
     */
    const mouseMoveHandler = function (evt) {
      if (me.sketch) {
        let output;

        for (let i = 0; i < me.sketch.length; i++) {
          const geom = me.sketch[i].getGeometry();
          if (utils.instOf(geom, Polygon)) {
            output = addMultiple(formatArea(geom), output);
          } else if (utils.instOf(geom, LineString)) {
            output = addMultiple(formatLength(geom), output);
          }
        }

        me.data.measurements[me.currentMeasurement] = output;
        if (me.data.measurements[me.currentMeasurement]) {
          me.data.measurements[me.currentMeasurement].geom = me.sketch;
        }
        if (!$rootScope.$$phase) {
          $rootScope.$digest();
        }
      }
    };

    /**
     * @memberof hs.measure.service
     * @function addMultiple
     * @private
     * @param {object} val1 Output of new object
     * @param {object} val2 Old value
     * @description Add two measure results for multiple shape mode to display joined result
     */
    const addMultiple = function (val1, val2) {
      if (val2 == undefined) {
        return val1;
      }
      let unit = val1.unit;
      const type = val1.type;
      if (val1.unit == val2.unit) {
        var value = Math.round((val1.size + val2.size) * 100) / 100;
        if (unit == 'm' && type == 'length' && value > 1000) {
          value = Math.round((value / 1000) * 100) / 100;
          unit = 'km';
        } else if (unit == 'm' && type == 'area' && value > 10000) {
          value = Math.round((value / 1000000) * 100) / 100;
          unit = 'km';
        }
      } else {
        const arr = [val1, val2];
        for (let i = 0; i < arr.length; i++) {
          if (arr[i].unit == 'm') {
            type == 'length' ? (arr[i].size /= 1000) : (arr[i].size /= 1000000);
          }
        }
        var value = Math.round((arr[0].size + arr[1].size) * 100) / 100;
        unit = 'km';
      }
      const output = {
        size: value,
        type: type,
        unit: unit,
      };
      return output;
    };

    /**
     * @memberof hs.measure.service
     * @function addInteraction
     * @private
     * @param {Boolean} type Geometry type
     * @description Initialize draw interaction on Ol.map and event handlers for handling start and end of drawing
     */
    function addInteraction(type) {
      const drawType = type == 'area' ? 'Polygon' : 'LineString';
      me.draw = new Draw({
        source: me.measureVector.getSource(),
        type: /** @type {GeometryType} */ (drawType),
        dragVertexDelay: 150,
      });
      map.addInteraction(me.draw);

      me.draw.on('drawstart', (evt) => {
        $rootScope.$broadcast('measure.drawStart');
        if (me.data.multipleShapeMode) {
          if (!angular.isArray(me.sketch)) {
            me.sketch = [];
            me.data.measurements.push({
              size: 0,
              unit: '',
            });
          }
          me.sketch.push(evt.feature);
        } else {
          me.sketch = [evt.feature];
          me.data.measurements.push({
            size: 0,
            unit: '',
          });
        }
        me.currentMeasurement = me.data.measurements.length - 1;
      });

      me.draw.on('drawend', (evt) => {
        $rootScope.$broadcast('measure.drawEnd');
      });
    }

    /**
     * @memberof hs.measure.service
     * @function formatLength
     * @private
     * @param {ol.geom.LineString} line
     * @return {object} numeric length of line with used units
     * @description Compute and format line length with correct units (m/km)
     */
    const formatLength = function (line) {
      let length = 0;
      const coordinates = line.getCoordinates();
      const sourceProj = map.getView().getProjection();

      for (let i = 0, ii = coordinates.length - 1; i < ii; ++i) {
        const c1 = transform(coordinates[i], sourceProj, 'EPSG:4326');
        const c2 = transform(coordinates[i + 1], sourceProj, 'EPSG:4326');
        length += getDistance(c1, c2);
      }

      const output = {
        size: length,
        type: 'length',
        unit: 'm',
      };

      if (length > 100) {
        output.size = Math.round((length / 1000) * 100) / 100;
        output.unit = 'km';
      } else {
        output.size = Math.round(length * 100) / 100;
        output.unit = 'm';
      }
      return output;
    };

    /**
     * @memberof hs.measure.service
     * @function formatArea
     * @private
     * @param {ol.geom.Polygon} polygon
     * @return {object} area of polygon with used units
     * @description Compute and format polygon area with correct units (m2/km2)
     */
    const formatArea = function (polygon) {
      const sourceProj = map.getView().getProjection();
      const area = Math.abs(getArea(polygon));
      const output = {
        size: area,
        type: 'area',
        unit: 'm',
      };
      if (area > 10000) {
        output.size = Math.round((area / 1000000) * 100) / 100;
        output.unit = 'km';
      } else {
        output.size = Math.round(area * 100) / 100;
        output.unit = 'm';
      }
      return output;
    };
  },
];
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2019 Julian Knight (Totally Information)
 *
 * 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.
 **/

// Node for Node-Red that lists all files from a given start folder
// with an optionally specified file extension.
//var fs = require('fs')
var path = require('path')
var readdirp = require('readdirp')

// Module name must match this nodes html file
var moduleName = 'fs-file-lister'

module.exports = function(RED) {
    'use strict'

    // The main node definition - most things happen in here
    function nodeGo(config) {
        // Create a RED node
        RED.nodes.createNode(this, config)

        // copy "this" object in case we need it in context of callbacks of other functions.
        var node = this

        // Store local copies of the node configuration (as defined in the .html)
        node.topic    = config.topic
        node.name     = config.name
        node.start    = config.start
        node.pattern  = config.pattern
        node.hidden   = config.hidden ? config.hidden : true /** @since v1.0.2 */
        node.path     = config.path
        node.single   = config.single
        node.depth    = config.depth
        node.stat     = config.stat

        // Make sure the parameters are strings
        if ( (typeof node.start !== 'string') || (typeof node.pattern !== 'string') ) {
            node.error('Either Folder or Ext is not a string, cannot process. Folder: ' + node.start + ', Pattern: ' + node.ext)
            return
        }

        // respond to inputs....
        /** @since v1.0.1, amended ready for Node-RED v1 */
        node.on('input', function(msg, send, done) {

            // If this is pre-1.0, 'send' will be undefined, so fallback to node.send
            send = send || node.send
            // If this is pre-1.0, 'done' will be undefined, so fallback to dummy function
            done = done || function(){}

            // override config if passed suitable payload
            if ( (typeof msg.payload === 'object') && ('start' in msg.payload) ) {
                if ( validFolderName(msg.payload.start) ) {
                    node.start = msg.payload.start
                }
            }
            if ( (typeof msg.payload === 'object') && ('pattern' in msg.payload) ) {
                if ( (typeof msg.payload.pattern === 'string') && (msg.payload.pattern.length < 1024) ) {
                    node.pattern = msg.payload.pattern
                }
            }

            var clonedMsg = RED.util.cloneMessage(msg);
            // Keep the original topic and payload
            clonedMsg.topic = msg.topic
            clonedMsg.origPayload = msg.payload
            // Output the nodes config
            clonedMsg.config = {
                'start': node.start,
                'pattern': node.pattern,
                'hidden': node.hidden, /** @since v1.0.2 */
                'path': node.path,
                'single': node.single,
                'depth': node.depth,
                'stat': node.stat
            }

            // Keep count of files found
            var totalFiles = 0

            var options = {}
            options.fileFilter = node.pattern
            options.entryType = 'files'
            if ( node.depth > -1 ) {
                options.depth = Number(node.depth)
            }
            if ( node.stat ) options.alwaysStat = true
            // Show hidden files/folders? (NB: doesn't help with Windows hidden files/folders)
            if ( node.hidden === false ) {
                options.fileFilter = ['!.*']
                options.directoryFilter = ['!.*']
            }

            var arrayOut = []

            // Recursively read the folder using the stream API
            // @ts-ignore
            readdirp(path.join(node.start), options)
                // Called if a found entry cannot be accessed (e.g. permissions)
                .on('warn', (err) => {
                    node.warn('File could not be accessed', err)
                })
                // Fatal error
                .on('error', (err) => {
                    node.error('Error processing folder listing', err)
                    // Use done for NR 1.0+
                    done(err)
                })
                // called for each entry found
                .on('data', (entry) => {
                    var file = entry.name
                    // Do we want just the file name output or the full path?
                    if ( node.path ) {
                        file = entry.fullPath
                    }
                    // Do we want to include file stats?
                    if ( node.stat ) {
                        file = {name: file}
                        try {
                            file.stat = {
                                size: entry.stats.size,
                                created: entry.stats.ctime,
                                changed: entry.stats.mtime,
                                accessed: entry.stats.atime,
                                uid: entry.stats.uid,
                                gid: entry.stats.gid,
                                mode: '0x' + entry.stats.mode.toString(16)
                            }
                        } catch (err) {
                            file.stat = {'error': 'Cannot stat file', 'err': err}
                        }
                    }
                    // If only single output
                    if ( node.single ) {
                        // accumulate the output array (send later on end)
                        arrayOut.push(file)
                    } else {
                        // or send each file as separate msg
                        node.status({fill:'green',shape:'dot',text:'# files ' + ++totalFiles})
                        send({
                            topic: clonedMsg.topic,
                            origPayload: clonedMsg.origPayload,
                            config: clonedMsg.config,
                            payload: file,
                            // debug: entry
                        })
                    }
                })
                // called when all entries have been output
                .on('end', () => {
                    // Send a single msg if arrayOut contains anything
                    if ( arrayOut.length > 0 ) {
                        node.status({fill:'blue',shape:'dot',text:'# files ' + arrayOut.length})
                        clonedMsg.payload = arrayOut
                        send(clonedMsg)
                    }
                    // Finished processing input msg (NR 1+)
                    done()
                })

        }) // --- End of node input fn --- //

        // Tidy up if we need to
            //node.on("close", function() {
            // Called when the node is shutdown - eg on redeploy.
            // Allows ports to be closed, connections dropped etc.
            // eg: node.client.disconnect();
        //});

    } // --- End of fnFileLister --- //

    // Check if a folder name is safe
    function validFolderName(folder) {
        var isValid = true
        if ( typeof folder !== 'string' ) {
            isValid = false
        } else {
            if ( folder.length > 1023 ) {
                isValid = false
            }
        }
        return isValid
    } // --- End of validFolderName function --- //

    // Register the node by name. This must be called before overriding any of the
    // Node functions.
    RED.nodes.registerType(moduleName, nodeGo)
} // ---- End of Mobule ---- //
<commit_msg>Fix hidden flag processing & stat output<commit_after>/**
 * Copyright (c) 2019 Julian Knight (Totally Information)
 *
 * 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.
 **/

// Node for Node-Red that lists all files from a given start folder
// with an optionally specified file extension.
//var fs = require('fs')
var path = require('path')
var readdirp = require('readdirp')

// Module name must match this nodes html file
var moduleName = 'fs-file-lister'

module.exports = function(RED) {
    'use strict'

    // The main node definition - most things happen in here
    function nodeGo(config) {
        // Create a RED node
        RED.nodes.createNode(this, config)

        // copy "this" object in case we need it in context of callbacks of other functions.
        var node = this

        // Store local copies of the node configuration (as defined in the .html)
        node.topic    = config.topic
        node.name     = config.name
        node.start    = config.start
        node.pattern  = config.pattern
        node.lstype   = config.lstype || 'files'              /** @since v1.0.2 */
        node.hidden   = config.hidden !== undefined ? config.hidden : true  /** @since v1.0.2 */
        node.path     = config.path
        node.single   = config.single
        node.depth    = config.depth
        node.stat     = config.stat

        // Make sure the parameters are strings
        if ( (typeof node.start !== 'string') || (typeof node.pattern !== 'string') ) {
            node.error('Either Folder or Ext is not a string, cannot process. Folder: ' + node.start + ', Pattern: ' + node.ext)
            return
        }

        // respond to inputs....
        /** @since v1.0.1, amended ready for Node-RED v1 */
        node.on('input', function(msg, send, done) {

            // If this is pre-1.0, 'send' will be undefined, so fallback to node.send
            send = send || node.send
            // If this is pre-1.0, 'done' will be undefined, so fallback to dummy function
            done = done || function(){}

            // override config if passed suitable payload
            if ( (typeof msg.payload === 'object') && ('start' in msg.payload) ) {
                if ( validFolderName(msg.payload.start) ) {
                    node.start = msg.payload.start
                }
            }
            if ( (typeof msg.payload === 'object') && ('pattern' in msg.payload) ) {
                if ( (typeof msg.payload.pattern === 'string') && (msg.payload.pattern.length < 1024) ) {
                    node.pattern = msg.payload.pattern
                }
            }

            var clonedMsg = RED.util.cloneMessage(msg);
            // Keep the original topic and payload
            clonedMsg.topic = msg.topic
            clonedMsg.origPayload = msg.payload
            // Output the nodes config
            clonedMsg.config = {
                'start': node.start,
                'pattern': node.pattern,
                'type': node.type, /** @since v1.0.2 */
                'hidden': node.hidden, /** @since v1.0.2 */
                'path': node.path,
                'single': node.single,
                'depth': node.depth,
                'stat': node.stat
            }

            // Keep count of files found
            var totalFiles = 0

            var options = {}
            options.fileFilter = node.pattern
            options.directoryFilter = node.pattern
            options.type = node.lstype
            if ( node.depth > -1 ) {
                options.depth = Number(node.depth)
            }
            if ( node.stat ) options.alwaysStat = true
            /** Show hidden files/folders? Unless explicitly asked for, readdirp will ignore them
             * NB: doesn't help with Windows hidden files/folders
             **/
            if ( node.hidden === true ) {
                // No need for this if supplied patter starts with a dot
                if (node.pattern.charAt(0) !== '.') {
                    options.fileFilter = [node.pattern, `.${node.pattern}`]
                    options.directoryFilter = [node.pattern, `.${node.pattern}`]
                }
            }

            var arrayOut = []

            // Recursively read the folder using the stream API
            // @ts-ignore
            readdirp(path.join(node.start), options)
                // Called if a found entry cannot be accessed (e.g. permissions)
                .on('warn', (err) => {
                    node.warn('File could not be accessed', err)
                })
                // Fatal error
                .on('error', (err) => {
                    node.error('Error processing folder listing', err)
                    // Use done for NR 1.0+
                    done(err)
                })
                // called for each entry found
                .on('data', (entry) => {
                    var file = entry.basename
                    console.log(file)

                    // Do we want just the file name output or the full path?
                    if ( node.path ) {
                        file = entry.fullPath
                    }
                    // Do we want to include file stats?
                    if ( node.stat ) {
                        file = {name: file}
                        try {
                            /** NB: some stats props are BigInt which cannot be serialised (e.g. break debug node)
                             *  so convert to Number but that may truncate the value.
                             * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
                             **/
                            file.stat = {
                                size: Number(entry.stats.size), //in bytes
                                created: entry.stats.birthtime, // @since v1.1.0 ctime no longer used - see Node.js docs
                                changed: entry.stats.mtime,
                                accessed: entry.stats.atime,
                                statusChanged: entry.stats.ctime, // @since v1.1.0
                                uid: Number(entry.stats.uid),
                                gid: Number(entry.stats.gid),
                                mode: '0x' + entry.stats.mode.toString(16)
                            }
                        } catch (err) {
                            file.stat = {'error': 'Cannot stat file', 'err': err}
                        }
                    }
                    // If only single output
                    if ( node.single ) {
                        // accumulate the output array (send later on end)
                        arrayOut.push(file)
                    } else {
                        // or send each file as separate msg
                        node.status({fill:'green',shape:'dot',text:'# files ' + ++totalFiles})
                        send({
                            topic: clonedMsg.topic,
                            origPayload: clonedMsg.origPayload,
                            config: clonedMsg.config,
                            payload: file,
                            // debug: entry
                        })
                    }
                })
                // called when all entries have been output
                .on('end', () => {
                    // Send a single msg if arrayOut contains anything
                    if ( node.single ) {
                        //if ( arrayOut.length > 0 ) {
                        node.status({fill:'blue',shape:'dot',text:'# files ' + arrayOut.length})
                        clonedMsg.payload = arrayOut
                        send(clonedMsg)
                    } else {
                        // Empty folder so output a warning
                        if ( totalFiles === 0 ) {
                            node.status({fill:'yellow',shape:'dot',text:'Empty Folder'})
                            clonedMsg.payload = 'Empty Folder'
                            send(clonedMsg)
                        }
                    }
                    // Finished processing input msg (NR 1+)
                    done()
                })

        }) // --- End of node input fn --- //

        // Tidy up if we need to
            //node.on("close", function() {
            // Called when the node is shutdown - eg on redeploy.
            // Allows ports to be closed, connections dropped etc.
            // eg: node.client.disconnect();
        //});

    } // --- End of fnFileLister --- //

    // Check if a folder name is safe
    function validFolderName(folder) {
        var isValid = true
        if ( typeof folder !== 'string' ) {
            isValid = false
        } else {
            if ( folder.length > 1023 ) {
                isValid = false
            }
        }
        return isValid
    } // --- End of validFolderName function --- //

    // Register the node by name. This must be called before overriding any of the
    // Node functions.
    RED.nodes.registerType(moduleName, nodeGo)
} // ---- End of Mobule ---- //
<|endoftext|>


<commit_before>/**
* Copyright 2012-2017, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var d3 = require('d3');
var Colorscale = require('../../components/colorscale');
var endPlus = require('./end_plus');

module.exports = function makeColorMap(trace) {
    var contours = trace.contours,
        start = contours.start,
        end = endPlus(contours),
        cs = contours.size || 1,
        nc = Math.floor((end - start) / cs) + 1,
        extra = contours.coloring === 'lines' ? 0 : 1;

    var scl = trace.colorscale,
        len = scl.length;

    var domain = new Array(len),
        range = new Array(len);

    var si, i;

    if(contours.coloring === 'heatmap') {
        if(trace.zauto && trace.autocontour === false) {
            trace.zmin = start - cs / 2;
            trace.zmax = trace.zmin + nc * cs;
        }

        for(i = 0; i < len; i++) {
            si = scl[i];

            domain[i] = si[0] * (trace.zmax - trace.zmin) + trace.zmin;
            range[i] = si[1];
        }

        // do the contours extend beyond the colorscale?
        // if so, extend the colorscale with constants
        var zRange = d3.extent([trace.zmin, trace.zmax, contours.start,
                contours.start + cs * (nc - 1)]),
            zmin = zRange[trace.zmin < trace.zmax ? 0 : 1],
            zmax = zRange[trace.zmin < trace.zmax ? 1 : 0];

        if(zmin !== trace.zmin) {
            domain.splice(0, 0, zmin);
            range.splice(0, 0, Range[0]);
        }

        if(zmax !== trace.zmax) {
            domain.push(zmax);
            range.push(range[range.length - 1]);
        }
    }
    else {
        for(i = 0; i < len; i++) {
            si = scl[i];

            domain[i] = (si[0] * (nc + extra - 1) - (extra / 2)) * cs + start;
            range[i] = si[1];
        }
    }

    return Colorscale.makeColorScaleFunc({
        domain: domain,
        range: range,
    }, {
        noNumericCheck: true
    });
};
<commit_msg>Tweak contour colorscale logic since carpet may set infinities<commit_after>/**
* Copyright 2012-2017, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var d3 = require('d3');
var Colorscale = require('../../components/colorscale');
var endPlus = require('./end_plus');

module.exports = function makeColorMap(trace) {
    var contours = trace.contours,
        start = contours.start,
        end = endPlus(contours),
        cs = contours.size || 1,
        nc = Math.floor((end - start) / cs) + 1,
        extra = contours.coloring === 'lines' ? 0 : 1;

    if(!isFinite(cs)) {
        cs = 1;
        nc = 1;
    }

    var scl = trace.colorscale,
        len = scl.length;

    var domain = new Array(len),
        range = new Array(len);

    var si, i;

    if(contours.coloring === 'heatmap') {
        if(trace.zauto && trace.autocontour === false) {
            trace.zmin = start - cs / 2;
            trace.zmax = trace.zmin + nc * cs;
        }

        for(i = 0; i < len; i++) {
            si = scl[i];

            domain[i] = si[0] * (trace.zmax - trace.zmin) + trace.zmin;
            range[i] = si[1];
        }

        // do the contours extend beyond the colorscale?
        // if so, extend the colorscale with constants
        var zRange = d3.extent([trace.zmin, trace.zmax, contours.start,
                contours.start + cs * (nc - 1)]),
            zmin = zRange[trace.zmin < trace.zmax ? 0 : 1],
            zmax = zRange[trace.zmin < trace.zmax ? 1 : 0];

        if(zmin !== trace.zmin) {
            domain.splice(0, 0, zmin);
            range.splice(0, 0, Range[0]);
        }

        if(zmax !== trace.zmax) {
            domain.push(zmax);
            range.push(range[range.length - 1]);
        }
    }
    else {
        for(i = 0; i < len; i++) {
            si = scl[i];

            domain[i] = (si[0] * (nc + extra - 1) - (extra / 2)) * cs + start;
            range[i] = si[1];
        }
    }

    return Colorscale.makeColorScaleFunc({
        domain: domain,
        range: range,
    }, {
        noNumericCheck: true
    });
};
<|endoftext|>