<commit_before>"use strict";

L.TileLayer.OpenDAP = L.Class.extend( {

  options: {
    opacity        : 1
  },

  initialize: function ( options ) {
    L.setOptions( this, options );
    this.data = [];
  },

  findData: function( data, name ) {
    return _.find( data, function( i ) { return i.das.name == name } ).data;
  },

  process_data: function( variable, data ) {

    var all_data = this.findData( data, variable );
    var var_data = this.findData( all_data, variable );

    var result = [];
    //var times = this.findData( all_data, 'time' );
    var latitudes = this.findData( all_data, 'latitude' );
    var longitudes = this.findData( all_data, 'longitude' );

    for ( var time = 0; time < var_data.length ; time++ ) {
      var d1 = var_data[0];
      for ( var lat = 0; lat < d1.length; lat++ ) {
        var d2 = d1[lat];
        for ( var lng = 0; lng < d2.length; lng++ ) {
          var _lat = latitudes[lat];
          var _lng = longitudes[lng];
          result.push( [ _lat, _lng, d2[lng] ] );
        }
      }
    }

    //console.log( "Actual = " + result.length );
    //console.log( "Latitudes = " + latitudes.length );
    //console.log( "Longitudes = " + longitudes.length );
    //console.log( "Expected = " + ( latitudes.length * longitudes.length ) );

    return {
      data: result,
      rows: latitudes.length,
      cols: longitudes.length
    };
  },

  onAdd: function ( map ) {
    this.map = map;
    var mapsize = map.getSize();
    var options = this.options;

    var canvas = options.canvas;
    canvas.id = 'webgl-leaflet-' + L.Util.stamp( this );
    canvas.width = mapsize.x;
    canvas.height = mapsize.y;
    canvas.style.opacity = options.opacity;
    canvas.style.position = 'absolute';

    map.getPanes().overlayPane.appendChild( canvas );

    var config = {
      canvas  : canvas,
      fragment: options.fragment,
      vertex  : options.vertex
    };
    this.overlay = createOverlay( config );

    this.canvas = canvas;

    this._createTimeSlider();
    this._createThresholdSlider();

    this._currentTime = moment();
    this._currentThreshold = 0;

    map.on( "move", this._update, this );

    /* hide layer on zoom, because it doesn't animate zoom */
    map.on( "zoomstart", this._hide, this );
    map.on( "zoomend", this._show, this );
    map.on( "resize", this._update, this );

  },

  onRemove: function ( map ) {
    map.getPanes().overlayPane.removeChild( this.canvas );
    map.off( "move", this._plot, this );
    map.off( "zoomstart", this._hide, this );
    map.off( "zoomend", this._show, this );
  },

  _createTimeSlider: function() {
    var slider = this.options.time.slider;
    var stop = L.DomEvent.stopPropagation;
    this.options.kettstreet.dim( "time", function( err, data ) {
      slider.step = 60 * 60;
      var times = this.findData( data, 'time' );
      slider.min = times[0];
      slider.max = times[times.length - 1];
      slider.value = parseInt( moment().unix() );
      this._rollTime( { target: { value: parseInt( moment().unix() ) } } )
    }.bind( this ) );

    L.DomEvent
      .on(slider, 'click',     stop)
      .on(slider, 'mousedown', stop)
      .on(slider, 'dblclick',  stop)
      .on(slider, 'change',    L.DomEvent.preventDefault)
      .on(slider, 'input',     this._rollTime, this)
      .on(slider, 'change',    this._rollTime, this);

    return slider;
  },

  _createThresholdSlider: function() {
    var slider = this.options.threshold.slider;
    var stop = L.DomEvent.stopPropagation;

    this.options.kettstreet.das( function( err, data ) {
      slider.step = 1;
      slider.min = parseInt( data[this.options.variable].attributes['valid_min'] );
      slider.max = parseInt( data[this.options.variable].attributes['valid_max'] );
      slider.value = slider.min;
    }.bind( this ) );

    //this.options.kettstreet.dim( "time", function( err, data ) {
    //  slider.step = 60 * 60;
    //  var times = this.findData( data, 'time' );
    //  slider.min = times[0];
    //  slider.max = times[times.length - 1];
    //  slider.value = parseInt( moment().unix() );
    //  this._rollTime( { target: { value: parseInt( moment().unix() ) } } )
    //}.bind( this ) );

    L.DomEvent
      .on(slider, 'click',     stop)
      .on(slider, 'mousedown', stop)
      .on(slider, 'dblclick',  stop)
      .on(slider, 'change',    L.DomEvent.preventDefault)
      .on(slider, 'input',     this._updateThreshold, this)
      .on(slider, 'change',    this._updateThreshold, this);

    return slider;
  },

  _updateThreshold: function( e ) {
    this._currentThreshold = e.target.value;
    this._plot();
  },

  _rollTime: function(e) {
    this._currentTime = moment( parseInt( e.target.value ) * 1000 );
    this._update();
  },

  _update: function() {
    var sth = this.map.getBounds().getSouth(),
        nth = this.map.getBounds().getNorth(),
        est = this.map.getBounds().getEast(),
        wst = this.map.getBounds().getWest();
    var variable = this.options.variable;
    var query = {
      time     : {
        min: this._currentTime.unix(),
        max: this._currentTime.unix(),
        step: 1
      },
      latitude : {
        min : sth,
        max : nth,
        step: 3
      },
      longitude: {
        min : wst,
        max : est,
        step: 3
      }
    };
    this.options.kettstreet.dap( variable, query, function( err, resp ){
      var data = this.process_data( variable, resp );
      this.rows = data.rows;
      this.cols = data.cols;
      this.data = data.data;
      this._plot();
    }.bind( this ) );
  },

  _hide: function () {
    this.canvas.style.display = 'none';
  },

  _show: function () {
    this.canvas.style.display = 'block';
  },

  _clear: function () {
    var overlay = this.overlay;
    overlay.clear();
    overlay.display( this.map );
  },

  _resizeRequest: undefined,

  _plot: function () {
    this.active = true;
    var map = this.map;
    if ( this._resizeRequest !== map._resizeRequest ) {
      this.resize();
      this._resizeRequest = map._resizeRequest;
    }
    var overlay = this.overlay;
    overlay.clear();
    L.DomUtil.setPosition( this.canvas, map.latLngToLayerPoint( map.getBounds().getNorthWest() ) );
    var dataLen = this.data.length;
    if ( dataLen ) {
      var data = [];
      for ( var i = 0; i < dataLen; i++ ) {
        var dataVal = this.data[i],
          latlng = new L.LatLng( dataVal[0], dataVal[1] ),
          point = map.latLngToContainerPoint( latlng );
        data.push( [ Math.floor( point.x ), Math.floor( point.y ), dataVal[2] ] );
      }
      overlay.update( { min: this._currentThreshold } );
      overlay.display( data, this.rows, this.cols );
    }
  },

  resize: function () {
    //helpful for maps that change sizes
    var mapsize = this.map.getSize();
    this.canvas.width = mapsize.x;
    this.canvas.height = mapsize.y;

    this.overlay.resize();
  },

  update: function () {
    this._plot();
  }
} );

L.layer = function ( options ) {
};

L.layer.opendap = function ( options ) {
  return new L.TileLayer.OpenDAP( options );
};

Polymer( 'leaflet-opendap-layer', {

  url : "", variable : "",

  provider: function( xhr ) {
    return function( url, callback ) {
      var options = {
        url: url, responseType: 'arraybuffer', callback: function ( buffer ) {
          callback( undefined, buffer );
        }
      };
      xhr.request( options );
    };
  },

  created: function () {
  },

  containerChanged: function () {
    if ( this.container ) {
      var config = {
        url     : this.url,
        provider: this.provider( this.$.xhr )
      };
      var options = {
        variable  : this.variable,
        canvas    : this.$.canvas,
        time      : {
          slider: this.$.time
        },
        threshold : {
          slider: this.$.threshold
        },
        fragment  : this.$.fragment,
        vertex    : this.$.points,
        kettstreet: kettstreet( config )
      };
      this.layer = new L.layer.opendap( options );
      this.container.addLayer( this.layer );
    }
  },

  detached: function () {
    if ( this.container && this.layer ) {
      this.container.removeControl( this.layer );
    }
  }
} );
<commit_msg>remove double trigger<commit_after>"use strict";

L.TileLayer.OpenDAP = L.Class.extend( {

  options: {
    opacity        : 1
  },

  initialize: function ( options ) {
    L.setOptions( this, options );
    this.data = [];
  },

  findData: function( data, name ) {
    return _.find( data, function( i ) { return i.das.name == name } ).data;
  },

  process_data: function( variable, data ) {

    var all_data = this.findData( data, variable );
    var var_data = this.findData( all_data, variable );

    var result = [];
    //var times = this.findData( all_data, 'time' );
    var latitudes = this.findData( all_data, 'latitude' );
    var longitudes = this.findData( all_data, 'longitude' );

    for ( var time = 0; time < var_data.length ; time++ ) {
      var d1 = var_data[0];
      for ( var lat = 0; lat < d1.length; lat++ ) {
        var d2 = d1[lat];
        for ( var lng = 0; lng < d2.length; lng++ ) {
          var _lat = latitudes[lat];
          var _lng = longitudes[lng];
          result.push( [ _lat, _lng, d2[lng] ] );
        }
      }
    }

    //console.log( "Actual = " + result.length );
    //console.log( "Latitudes = " + latitudes.length );
    //console.log( "Longitudes = " + longitudes.length );
    //console.log( "Expected = " + ( latitudes.length * longitudes.length ) );

    return {
      data: result,
      rows: latitudes.length,
      cols: longitudes.length
    };
  },

  onAdd: function ( map ) {
    this.map = map;
    var mapsize = map.getSize();
    var options = this.options;

    var canvas = options.canvas;
    canvas.id = 'webgl-leaflet-' + L.Util.stamp( this );
    canvas.width = mapsize.x;
    canvas.height = mapsize.y;
    canvas.style.opacity = options.opacity;
    canvas.style.position = 'absolute';

    map.getPanes().overlayPane.appendChild( canvas );

    var config = {
      canvas  : canvas,
      fragment: options.fragment,
      vertex  : options.vertex
    };
    this.overlay = createOverlay( config );

    this.canvas = canvas;

    this._createTimeSlider();
    this._createThresholdSlider();

    this._currentTime = moment();
    this._currentThreshold = 0;

    map.on( "move", this._update, this );

    /* hide layer on zoom, because it doesn't animate zoom */
    map.on( "zoomstart", this._hide, this );
    map.on( "zoomend", this._show, this );
    map.on( "resize", this._update, this );

  },

  onRemove: function ( map ) {
    map.getPanes().overlayPane.removeChild( this.canvas );
    map.off( "move", this._plot, this );
    map.off( "zoomstart", this._hide, this );
    map.off( "zoomend", this._show, this );
  },

  _createTimeSlider: function() {
    var slider = this.options.time.slider;
    var stop = L.DomEvent.stopPropagation;
    this.options.kettstreet.dim( "time", function( err, data ) {
      slider.step = 60 * 60;
      var times = this.findData( data, 'time' );
      slider.min = times[0];
      slider.max = times[times.length - 1];
      slider.value = parseInt( moment().unix() );
      this._rollTime( { target: { value: parseInt( moment().unix() ) } } )
    }.bind( this ) );

    L.DomEvent
      .on(slider, 'click',     stop)
      .on(slider, 'mousedown', stop)
      .on(slider, 'dblclick',  stop)
      .on(slider, 'change',    L.DomEvent.preventDefault)
      .on(slider, 'input',     this._rollTime, this);
      //.on(slider, 'change',    this._rollTime, this);

    return slider;
  },

  _createThresholdSlider: function() {
    var slider = this.options.threshold.slider;
    var stop = L.DomEvent.stopPropagation;

    this.options.kettstreet.das( function( err, data ) {
      slider.step = 1;
      slider.min = parseInt( data[this.options.variable].attributes['valid_min'] );
      slider.max = parseInt( data[this.options.variable].attributes['valid_max'] );
      slider.value = slider.min;
    }.bind( this ) );

    //this.options.kettstreet.dim( "time", function( err, data ) {
    //  slider.step = 60 * 60;
    //  var times = this.findData( data, 'time' );
    //  slider.min = times[0];
    //  slider.max = times[times.length - 1];
    //  slider.value = parseInt( moment().unix() );
    //  this._rollTime( { target: { value: parseInt( moment().unix() ) } } )
    //}.bind( this ) );

    L.DomEvent
      .on(slider, 'click',     stop)
      .on(slider, 'mousedown', stop)
      .on(slider, 'dblclick',  stop)
      .on(slider, 'change',    L.DomEvent.preventDefault)
      .on(slider, 'input',     this._updateThreshold, this);
      //.on(slider, 'change',    this._updateThreshold, this);

    return slider;
  },

  _updateThreshold: function( e ) {
    this._currentThreshold = e.target.value;
    this._plot();
  },

  _rollTime: function(e) {
    this._currentTime = moment( parseInt( e.target.value ) * 1000 );
    this._update();
  },

  _update: function() {
    var sth = this.map.getBounds().getSouth(),
        nth = this.map.getBounds().getNorth(),
        est = this.map.getBounds().getEast(),
        wst = this.map.getBounds().getWest();
    var variable = this.options.variable;
    var query = {
      time     : {
        min: this._currentTime.unix(),
        max: this._currentTime.unix(),
        step: 1
      },
      latitude : {
        min : sth,
        max : nth,
        step: 3
      },
      longitude: {
        min : wst,
        max : est,
        step: 3
      }
    };
    this.options.kettstreet.dap( variable, query, function( err, resp ){
      var data = this.process_data( variable, resp );
      this.rows = data.rows;
      this.cols = data.cols;
      this.data = data.data;
      this._plot();
    }.bind( this ) );
  },

  _hide: function () {
    this.canvas.style.display = 'none';
  },

  _show: function () {
    this.canvas.style.display = 'block';
  },

  _clear: function () {
    var overlay = this.overlay;
    overlay.clear();
    overlay.display( this.map );
  },

  _resizeRequest: undefined,

  _plot: function () {
    this.active = true;
    var map = this.map;
    if ( this._resizeRequest !== map._resizeRequest ) {
      this.resize();
      this._resizeRequest = map._resizeRequest;
    }
    var overlay = this.overlay;
    overlay.clear();
    L.DomUtil.setPosition( this.canvas, map.latLngToLayerPoint( map.getBounds().getNorthWest() ) );
    var dataLen = this.data.length;
    if ( dataLen ) {
      var data = [];
      for ( var i = 0; i < dataLen; i++ ) {
        var dataVal = this.data[i],
          latlng = new L.LatLng( dataVal[0], dataVal[1] ),
          point = map.latLngToContainerPoint( latlng );
        data.push( [ Math.floor( point.x ), Math.floor( point.y ), dataVal[2] ] );
      }
      overlay.update( { min: this._currentThreshold } );
      overlay.display( data, this.rows, this.cols );
    }
  },

  resize: function () {
    //helpful for maps that change sizes
    var mapsize = this.map.getSize();
    this.canvas.width = mapsize.x;
    this.canvas.height = mapsize.y;

    this.overlay.resize();
  },

  update: function () {
    this._plot();
  }
} );

L.layer = function ( options ) {
};

L.layer.opendap = function ( options ) {
  return new L.TileLayer.OpenDAP( options );
};

Polymer( 'leaflet-opendap-layer', {

  url : "", variable : "",

  provider: function( xhr ) {
    return function( url, callback ) {
      var options = {
        url: url, responseType: 'arraybuffer', callback: function ( buffer ) {
          callback( undefined, buffer );
        }
      };
      xhr.request( options );
    };
  },

  created: function () {
  },

  containerChanged: function () {
    if ( this.container ) {
      var config = {
        url     : this.url,
        provider: this.provider( this.$.xhr )
      };
      var options = {
        variable  : this.variable,
        canvas    : this.$.canvas,
        time      : {
          slider: this.$.time
        },
        threshold : {
          slider: this.$.threshold
        },
        fragment  : this.$.fragment,
        vertex    : this.$.points,
        kettstreet: kettstreet( config )
      };
      this.layer = new L.layer.opendap( options );
      this.container.addLayer( this.layer );
    }
  },

  detached: function () {
    if ( this.container && this.layer ) {
      this.container.removeControl( this.layer );
    }
  }
} );
<|endoftext|>


<commit_before>/**
* ownCloud
*
* @author Vincent Petry
* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library.  If not, see <http://www.gnu.org/licenses/>.
*
*/

(function() {
	var INFO_TEMPLATE =
		'' +
			'' +
			' and ' +
			'' +
			'' +
			'' +
		'';

	/**
	 * The FileSummary class encapsulates the file summary values and
	 * the logic to render it in the given container
	 *
	 * @constructs FileSummary
	 * @memberof OCA.Files
	 *
	 * @param $tr table row element
	 * @param {OC.Backbone.Model} [options.filesConfig] files app configuration
	 */
	var FileSummary = function($tr, options) {
		options = options || {};
		var self = this;
		this.$el = $tr;
		var filesConfig = options.config;
		if (filesConfig) {
			this._showHidden = !!filesConfig.get('showhidden');
			filesConfig.on('change:showhidden', function() {
				self._showHidden = !!this.get('showhidden');
				self.update();
			});
		}
		this.clear();
		this.render();
	};

	FileSummary.prototype = {
		_showHidden: null,

		summary: {
			totalFiles: 0,
			totalDirs: 0,
			totalHidden: 0,
			totalSize: 0,
			filter:'',
			sumIsPending:false
		},

		/**
		 * Returns whether the given file info must be hidden
		 *
		 * @param {OC.Files.FileInfo} fileInfo file info
		 *
		 * @return {boolean} true if the file is a hidden file, false otherwise
		 */
		_isHiddenFile: function(file) {
			return file.name && file.name.charAt(0) === '.';
		},

		/**
		 * Adds file
		 * @param {OC.Files.FileInfo} file file to add
		 * @param {boolean} update whether to update the display
		 */
		add: function(file, update) {
			if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
				return;
			}
			if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
				this.summary.totalDirs++;
			}
			else {
				this.summary.totalFiles++;
			}
			if (this._isHiddenFile(file)) {
				this.summary.totalHidden++;
			}

			var size = parseInt(file.size, 10) || 0;
			if (size >=0) {
				this.summary.totalSize += size;
			} else {
				this.summary.sumIsPending = true;
			}
			if (!!update) {
				this.update();
			}
		},
		/**
		 * Removes file
		 * @param {OC.Files.FileInfo} file file to remove
		 * @param {boolean} update whether to update the display
		 */
		remove: function(file, update) {
			if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
				return;
			}
			if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
				this.summary.totalDirs--;
			}
			else {
				this.summary.totalFiles--;
			}
			if (this._isHiddenFile(file)) {
				this.summary.totalHidden--;
			}
			var size = parseInt(file.size, 10) || 0;
			if (size >=0) {
				this.summary.totalSize -= size;
			}
			if (!!update) {
				this.update();
			}
		},
		setFilter: function(filter, files){
			this.summary.filter = filter.toLowerCase();
			this.calculate(files);
		},
		/**
		 * Returns the total of files and directories
		 */
		getTotal: function() {
			return this.summary.totalDirs + this.summary.totalFiles;
		},
		/**
		 * Recalculates the summary based on the given files array
		 * @param files array of files
		 */
		calculate: function(files) {
			var file;
			var summary = {
				totalDirs: 0,
				totalFiles: 0,
				totalHidden: 0,
				totalSize: 0,
				filter: this.summary.filter,
				sumIsPending: false
			};

			for (var i = 0; i < files.length; i++) {
				file = files[i];
				if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
					continue;
				}
				if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
					summary.totalDirs++;
				}
				else {
					summary.totalFiles++;
				}
				if (this._isHiddenFile(file)) {
					summary.totalHidden++;
				}
				var size = parseInt(file.size, 10) || 0;
				if (size >=0) {
					summary.totalSize += size;
				} else {
					summary.sumIsPending = true;
				}
			}
			this.setSummary(summary);
		},
		/**
		 * Clears the summary
		 */
		clear: function() {
			this.calculate([]);
		},
		/**
		 * Sets the current summary values
		 * @param summary map
		 */
		setSummary: function(summary) {
			this.summary = summary;
			if (typeof this.summary.filter === 'undefined') {
				this.summary.filter = '';
			}
			this.update();
		},

		_infoTemplate: function(data) {
			if (!this._infoTemplateCompiled) {
				this._infoTemplateCompiled = Handlebars.compile(INFO_TEMPLATE);
			}
			return this._infoTemplateCompiled(data);
		},

		/**
		 * Renders the file summary element
		 */
		update: function() {
			if (!this.$el) {
				return;
			}
			if (!this.summary.totalFiles && !this.summary.totalDirs) {
				this.$el.addClass('hidden');
				return;
			}
			// There's a summary and data -> Update the summary
			this.$el.removeClass('hidden');
			var $dirInfo = this.$el.find('.dirinfo');
			var $fileInfo = this.$el.find('.fileinfo');
			var $connector = this.$el.find('.connector');
			var $filterInfo = this.$el.find('.filter');
			var $hiddenInfo = this.$el.find('.hiddeninfo');

			// Substitute old content with new translations
			$dirInfo.html(n('files', '%n folder', '%n folders', this.summary.totalDirs));
			$fileInfo.html(n('files', '%n file', '%n files', this.summary.totalFiles));
			$hiddenInfo.html(' (' + n('files', 'including %n hidden', 'including %n hidden', this.summary.totalHidden) + ')');
			var fileSize = this.summary.sumIsPending ? t('files', 'Pending') : OC.Util.humanFileSize(this.summary.totalSize);
			this.$el.find('.filesize').html(fileSize);

			// Show only what's necessary (may be hidden)
			if (this.summary.totalDirs === 0) {
				$dirInfo.addClass('hidden');
				$connector.addClass('hidden');
			} else {
				$dirInfo.removeClass('hidden');
			}
			if (this.summary.totalFiles === 0) {
				$fileInfo.addClass('hidden');
				$connector.addClass('hidden');
			} else {
				$fileInfo.removeClass('hidden');
			}
			if (this.summary.totalDirs > 0 && this.summary.totalFiles > 0) {
				$connector.removeClass('hidden');
			}
			$hiddenInfo.toggleClass('hidden', this.summary.totalHidden === 0 || this._showHidden)
			if (this.summary.filter === '') {
				$filterInfo.html('');
				$filterInfo.addClass('hidden');
			} else {
				$filterInfo.html(' ' + n('files', 'matches \'{filter}\'', 'match \'{filter}\'', this.summary.totalDirs + this.summary.totalFiles, {filter: this.summary.filter}));
				$filterInfo.removeClass('hidden');
			}
		},
		render: function() {
			if (!this.$el) {
				return;
			}
			var summary = this.summary;

			// don't show the filesize column, if filesize is NaN (e.g. in trashbin)
			var fileSize = '';
			if (!isNaN(summary.totalSize)) {
				fileSize = summary.sumIsPending ? t('files', 'Pending') : OC.Util.humanFileSize(summary.totalSize);
				fileSize = '' + fileSize + '	';
			}

			var $summary = $(
				'' + this._infoTemplate() + '	' +
				fileSize +
				'	'
			);
			this.$el.addClass('hidden');
			this.$el.append($summary);
			this.update();
		}
	};
	OCA.Files.FileSummary = FileSummary;
})();

<commit_msg>[stable9.1] Translate file summary connector (#26221) (#26222)<commit_after>/**
* ownCloud
*
* @author Vincent Petry
* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library.  If not, see <http://www.gnu.org/licenses/>.
*
*/

(function() {
	var INFO_TEMPLATE =
		'' +
			'' +
			'{{connectorLabel}}' +
			'' +
			'' +
			'' +
		'';

	/**
	 * The FileSummary class encapsulates the file summary values and
	 * the logic to render it in the given container
	 *
	 * @constructs FileSummary
	 * @memberof OCA.Files
	 *
	 * @param $tr table row element
	 * @param {OC.Backbone.Model} [options.filesConfig] files app configuration
	 */
	var FileSummary = function($tr, options) {
		options = options || {};
		var self = this;
		this.$el = $tr;
		var filesConfig = options.config;
		if (filesConfig) {
			this._showHidden = !!filesConfig.get('showhidden');
			filesConfig.on('change:showhidden', function() {
				self._showHidden = !!this.get('showhidden');
				self.update();
			});
		}
		this.clear();
		this.render();
	};

	FileSummary.prototype = {
		_showHidden: null,

		summary: {
			totalFiles: 0,
			totalDirs: 0,
			totalHidden: 0,
			totalSize: 0,
			filter:'',
			sumIsPending:false
		},

		/**
		 * Returns whether the given file info must be hidden
		 *
		 * @param {OC.Files.FileInfo} fileInfo file info
		 *
		 * @return {boolean} true if the file is a hidden file, false otherwise
		 */
		_isHiddenFile: function(file) {
			return file.name && file.name.charAt(0) === '.';
		},

		/**
		 * Adds file
		 * @param {OC.Files.FileInfo} file file to add
		 * @param {boolean} update whether to update the display
		 */
		add: function(file, update) {
			if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
				return;
			}
			if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
				this.summary.totalDirs++;
			}
			else {
				this.summary.totalFiles++;
			}
			if (this._isHiddenFile(file)) {
				this.summary.totalHidden++;
			}

			var size = parseInt(file.size, 10) || 0;
			if (size >=0) {
				this.summary.totalSize += size;
			} else {
				this.summary.sumIsPending = true;
			}
			if (!!update) {
				this.update();
			}
		},
		/**
		 * Removes file
		 * @param {OC.Files.FileInfo} file file to remove
		 * @param {boolean} update whether to update the display
		 */
		remove: function(file, update) {
			if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
				return;
			}
			if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
				this.summary.totalDirs--;
			}
			else {
				this.summary.totalFiles--;
			}
			if (this._isHiddenFile(file)) {
				this.summary.totalHidden--;
			}
			var size = parseInt(file.size, 10) || 0;
			if (size >=0) {
				this.summary.totalSize -= size;
			}
			if (!!update) {
				this.update();
			}
		},
		setFilter: function(filter, files){
			this.summary.filter = filter.toLowerCase();
			this.calculate(files);
		},
		/**
		 * Returns the total of files and directories
		 */
		getTotal: function() {
			return this.summary.totalDirs + this.summary.totalFiles;
		},
		/**
		 * Recalculates the summary based on the given files array
		 * @param files array of files
		 */
		calculate: function(files) {
			var file;
			var summary = {
				totalDirs: 0,
				totalFiles: 0,
				totalHidden: 0,
				totalSize: 0,
				filter: this.summary.filter,
				sumIsPending: false
			};

			for (var i = 0; i < files.length; i++) {
				file = files[i];
				if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
					continue;
				}
				if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
					summary.totalDirs++;
				}
				else {
					summary.totalFiles++;
				}
				if (this._isHiddenFile(file)) {
					summary.totalHidden++;
				}
				var size = parseInt(file.size, 10) || 0;
				if (size >=0) {
					summary.totalSize += size;
				} else {
					summary.sumIsPending = true;
				}
			}
			this.setSummary(summary);
		},
		/**
		 * Clears the summary
		 */
		clear: function() {
			this.calculate([]);
		},
		/**
		 * Sets the current summary values
		 * @param summary map
		 */
		setSummary: function(summary) {
			this.summary = summary;
			if (typeof this.summary.filter === 'undefined') {
				this.summary.filter = '';
			}
			this.update();
		},

		_infoTemplate: function(data) {
			if (!this._infoTemplateCompiled) {
				this._infoTemplateCompiled = Handlebars.compile(INFO_TEMPLATE);
			}
			return this._infoTemplateCompiled(_.extend({
				connectorLabel: t('files', '{dirs} and {files}', {dirs: '', files: ''})
			}, data));
		},

		/**
		 * Renders the file summary element
		 */
		update: function() {
			if (!this.$el) {
				return;
			}
			if (!this.summary.totalFiles && !this.summary.totalDirs) {
				this.$el.addClass('hidden');
				return;
			}
			// There's a summary and data -> Update the summary
			this.$el.removeClass('hidden');
			var $dirInfo = this.$el.find('.dirinfo');
			var $fileInfo = this.$el.find('.fileinfo');
			var $connector = this.$el.find('.connector');
			var $filterInfo = this.$el.find('.filter');
			var $hiddenInfo = this.$el.find('.hiddeninfo');

			// Substitute old content with new translations
			$dirInfo.html(n('files', '%n folder', '%n folders', this.summary.totalDirs));
			$fileInfo.html(n('files', '%n file', '%n files', this.summary.totalFiles));
			$hiddenInfo.html(' (' + n('files', 'including %n hidden', 'including %n hidden', this.summary.totalHidden) + ')');
			var fileSize = this.summary.sumIsPending ? t('files', 'Pending') : OC.Util.humanFileSize(this.summary.totalSize);
			this.$el.find('.filesize').html(fileSize);

			// Show only what's necessary (may be hidden)
			if (this.summary.totalDirs === 0) {
				$dirInfo.addClass('hidden');
				$connector.addClass('hidden');
			} else {
				$dirInfo.removeClass('hidden');
			}
			if (this.summary.totalFiles === 0) {
				$fileInfo.addClass('hidden');
				$connector.addClass('hidden');
			} else {
				$fileInfo.removeClass('hidden');
			}
			if (this.summary.totalDirs > 0 && this.summary.totalFiles > 0) {
				$connector.removeClass('hidden');
			}
			$hiddenInfo.toggleClass('hidden', this.summary.totalHidden === 0 || this._showHidden)
			if (this.summary.filter === '') {
				$filterInfo.html('');
				$filterInfo.addClass('hidden');
			} else {
				$filterInfo.html(' ' + n('files', 'matches \'{filter}\'', 'match \'{filter}\'', this.summary.totalDirs + this.summary.totalFiles, {filter: this.summary.filter}));
				$filterInfo.removeClass('hidden');
			}
		},
		render: function() {
			if (!this.$el) {
				return;
			}
			var summary = this.summary;

			// don't show the filesize column, if filesize is NaN (e.g. in trashbin)
			var fileSize = '';
			if (!isNaN(summary.totalSize)) {
				fileSize = summary.sumIsPending ? t('files', 'Pending') : OC.Util.humanFileSize(summary.totalSize);
				fileSize = '' + fileSize + '	';
			}

			var $summary = $(
				'' + this._infoTemplate() + '	' +
				fileSize +
				'	'
			);
			this.$el.addClass('hidden');
			this.$el.append($summary);
			this.update();
		}
	};
	OCA.Files.FileSummary = FileSummary;
})();

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import DataStore from "lockbox-datastore";

const datastore = DataStore.create({
  prompts: {unlock: () => ""},
});

export default datastore;
<commit_msg>Remove superfluous config option from our datastore since it's not actually being used at the moment<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import DataStore from "lockbox-datastore";

const datastore = DataStore.create({});

export default datastore;
<|endoftext|>


<commit_before>//
// Global Values (DataSets and Templates)
//

"use strict";

// Compile templates
var nodeTemplate = Handlebars.compile($('#graph-sidebar-node-template').html());
var linksTemplate = Handlebars.compile($('#graph-sidebar-links-template').html());
var analyticsTemplate = Handlebars.compile($('#graph-sidebar-analytics-template').html());

// Define default icons
var icons = {
  'Observable.Ip': flaticon('\ue005'),
  'Observable.Hostname': flaticon('\ue01E'),
  'Observable.Url': flaticon('\ue013'),
  'Observable.Email': flaticon('\ue01A'),
  'Observable.Text': flaticon('\ue022'),
  'Observable.File': flaticon('\ue021'),
  'Observable.Hash': flaticon('\ue00e'),
  'Entity.Malware': flaticon('\ue001'),
  'Entity.TTP': flaticon('\ue019'),
  'Entity.Company': flaticon('\ue002'),
  'Indicator.Regex': flaticon('\ue015'),
};

var cssicons = {
  'Observable.Ip': 'flaticon-computer189',
  'Observable.Hostname': 'flaticon-server20',
  'Observable.Url': 'flaticon-links11',
  'Observable.Email': 'flaticon-message99',
  'Observable.Text': 'flaticon-typography2',
  'Observable.File': 'flaticon-text70',
  'Observable.Hash': 'flaticon-finger14',
  'Entity.Malware': 'flaticon-bug24',
  'Entity.TTP': 'flaticon-maths5',
  'Entity.Company': 'flaticon-building259',
  'Indicator.Regex': 'flaticon-magnifying-glass40',
};

//
// General purpose functions
//

function flaticon(code) {
  return {
    face: 'Flaticon',
    code: code,
    size: 40,
    color: '#495B6C',
  };
}

function dbref(nodeId) {
  var parts = nodeId.split('-');

  return {
    '$id': { '$oid': parts[1] },
    '$ref': parts[0],
  };
}

function buildNodeId(nodeCls, nodeId) {
  var collection = nodeCls.split('.')[0].toLowerCase();

  return collection + '-' + nodeId;
}

function linksFilter(nodeId, field) {
  return function(links) {
    return links[field] == nodeId;
  };
}

// Define Investigation logic
class Investigation {
  constructor(investigation) {
    this.id = investigation._id;
    console.log(this.id);

    // Create the nodes dataset and dataview
    this.nodes = new vis.DataSet([]);
    this.visibleNodes = new vis.DataView(this.nodes, {
      filter: function(item) {
        return item.visible;
      },
    });

    // Create the edges dataset and dataview
    this.edges = new vis.DataSet([]);
    this.visibleEdges = new vis.DataView(this.edges, {
      filter: function(item) {
        return item.visible;
      },
    });

    // Create the analytics dataset
    this.analytics = new vis.DataSet([]);

    // Setup initial data
    this.update(investigation);

    // Display graph
    this.initGraph();
  }

  update(investigation) {
    var self = this;

    var visibleNodes = new Set(self.visibleNodes.getIds());
    var visibleEdges = new Set(self.visibleEdges.getIds());

    investigation.nodes.forEach(function(node) {
      node = self.displayNode(node);
      visibleNodes.delete(node.id);
    });

    investigation.links.forEach(function(link) {
      console.log(link);
      link.from = link.fromnode;
      link.to = link.tonode;
      link.arrows = 'to';

      if (!self.hasLink(link)) {
        self.edges.add(link);
      }
      self.edges.update({id: link.id, visible: true});

      visibleEdges.delete(link.id);
    });

    visibleNodes.forEach(self.hideNode.bind(self));
    visibleEdges.forEach(self.hideLink.bind(self));
  }

  addNode(node) {
    node.id = buildNodeId(node._cls, node._id);

    var existingNode = this.nodes.get(node.id);

    if (!existingNode) {
      if ('value' in node) {
        node.label = node.value;
      } else {
        node.label = node.name;
      }

      node.shape = 'icon';
      node.icon = icons[node._cls];
      node.cssicon = cssicons[node._cls];

      this.nodes.add(node);

      return node;
    } else {
      return existingNode;
    }
  }

  displayNode(node) {
    node = this.addNode(node);
    this.nodes.update({id: node.id, visible: true});

    return node;
  }

  hideNode(nodeId) {
    this.nodes.update({id: node.id, visible: false});
  }

  hasLink(link) {
    var existingLink = this.edges.get(link.id);

    return existingLink;
  }

  addLink(link) {
    link.id = link._id;

    var existingLink = this.edges.get(link.id);

    if (!existingLink) {
      if (link.description) {
        link.label = link.description;
      } else if (link.Tag) {
        link.label = link.tag;
      }

      link.from = buildNodeId(link.src.collection, link.src.id);
      link.to = buildNodeId(link.dst.collection, link.dst.id);
      link.arrows = 'to';
      this.edges.add(link);

      return link;
    } else {
      return existingLink;
    }
  }

  displayLink(link) {
    link = this.addLink(link);
    this.edges.update({id: link.id, visible: true});

    return link;
  }

  hideLink(linkId) {
    this.edges.update({id: link.id, visible: false});
  }

  enrichLink(nodeField) {
    var self = this;

    return function(link) {
      var node = self.nodes.get(link[nodeField]);
      link.nodeId = node.id;
      link.cssicon = node.cssicon;
      link.value = node.label;
      link.tags = node.tags;
    };
  }

  displayLinks(nodeId) {
    var incoming = this.edges.get({filter: linksFilter(nodeId, 'to')});
    incoming.forEach(this.enrichLink('from'));
    $('#graph-sidebar-links-to-' + nodeId).html(linksTemplate({links: incoming}));

    var outgoing = this.edges.get({filter: linksFilter(nodeId, 'from')});
    outgoing.forEach(this.enrichLink('to'));
    $('#graph-sidebar-links-from-' + nodeId).html(linksTemplate({links: outgoing}));
  }

  displayAnalytics(node) {
    var nodeType = node._cls.split('.');
    nodeType = nodeType[nodeType.length - 1];

    var availableAnalytics = this.analytics.get({
      filter: function(item) {
        return item.acts_on == nodeType;
      },
    });

    $('#graph-sidebar-analytics-' + node.id).html(analyticsTemplate({analytics: availableAnalytics}));
  }

  retrieveNodeNeighborsCallback(nodeId) {
    var self = this;

    return function(data) {
      data.links.forEach(self.addLink.bind(self));
      data.nodes.forEach(self.addNode.bind(self));
      self.nodes.update({id: nodeId, fetched: true});

      self.displayLinks(nodeId);
    };
  }

  retrieveNodeNeighbors(node) {
    $.getJSON('/api/neighbors/' + node._cls + '/' + node._id, this.retrieveNodeNeighborsCallback(node.id));
  }

  selectNode(nodeId) {
    var node = this.nodes.get(nodeId);

    // Update sidebar with content related to this node
    $('#graph-sidebar').html(nodeTemplate(node));

    // Display analytics
    this.displayAnalytics(node);

    // Display links
    if (node.fetched) {
      this.displayLinks(nodeId);
    } else {
      this.retrieveNodeNeighbors(node);
    }
  }

  refreshAnalytics() {
    var nodeId = $('#graph-sidebar-content').data('id');

    if (nodeId) {
      var node = this.nodes.get(nodeId);
      this.displayAnalytics(node);
    }
  }

  loadAnalytics() {
    var self = this;

    $.getJSON('/api/analytics/oneshot', function(data) {
      data.forEach(function(item) {
        if (item.enabled) {
          self.analytics.add(item);
        }
      });

      self.refreshAnalytics();
    });
  }

  fetchAnalyticsResultsCallback(name, resultsId, resultsDiv, button) {
    var self = this;
    return function() {
      return self.fetchAnalyticsResults(name, resultsId, resultsDiv, button);
    };
  }

  fetchAnalyticsResults(name, resultsId, resultsDiv, button) {
    var self = this;

    function callback(data) {
      if (data.status == 'finished') {
        var links = [];

        data.results.nodes.forEach(self.addNode.bind(self));
        data.results.links.forEach(function(link) {
          link = self.addLink(link);

          if (link.src.id == data.observable) {
            self.enrichLink('to')(link);
            links.push(link);
          } else if (link.dst.id == data.observable) {
            self.enrichLink('from')(link);
            links.push(link);
          }
        });

        resultsDiv.html(linksTemplate({links: links}));
        button.removeClass('glyphicon-spinner');
      } else {
        setTimeout(self.fetchAnalyticsResultsCallback(name, resultsId, resultsDiv, button), 1000);
      }
    }

    $.post(
      '/api/analytics/oneshot/' + name + '/status',
      {id: resultsId},
      callback,
      'json'
    );
  }

  runAnalytics(name, nodeId, resultsDiv, progress) {
    var self = this;

    function runCallback(data) {
      var resultsId = data._id;

      self.fetchAnalyticsResults(name, resultsId, resultsDiv, progress);
    }

    $.post(
      '/api/analytics/oneshot/' + name + '/run',
      {id: nodeId},
      runCallback,
      'json'
    );
  }

  enableLink(link) {
    var self = this;

    var data = {
      links: [link],
      nodes: [dbref(link.from), dbref(link.to)],
    };

    // Effectively display elements on the graph
    this.nodes.update([{id: link.from, visible: true}, {id: link.to, visible: true}]);
    this.edges.update({id: link.id, visible: true});

    function callback(investigation) {
      self.update(investigation);
    };

    // Persist changes, and update to last version
    $.ajax({
      type: 'POST',
      url: '/api/investigations/' + self.id + '/add',
      data: JSON.stringify(data),
      success: callback,
      dataType: 'json',
      contentType: 'application/json',
    });
  }

  initGraph() {
    // create a network
    var container = document.getElementById('graph');
    var data = {
      nodes: this.visibleNodes,
      edges: this.visibleEdges,
    };
    var options = {
      physics: {
        barnesHut: {
          springLength: 300,
        },
      },
    };
    var network = new vis.Network(container, data, options);

    // create analytics
    this.loadAnalytics();

    var self = this;

    // Define event handlers
    network.on('selectNode', function(params) {
      self.selectNode(params.nodes[params.nodes.length - 1]);
    });

    $('#graph-sidebar').on('click', '.graph-sidebar-display-link', function(e) {
      var linkId = $(this).data('link');
      var link = self.edges.get(linkId);

      self.enableLink(link);
    });

    $('#graph-sidebar').on('click', '.graph-sidebar-view-node', function(e) {
      var nodeId = $(this).data('node');
      self.selectNode(nodeId);
    });

    $('#graph-sidebar').on('click', '.graph-sidebar-run-analytics', function(e) {
      var button = $(this);

      var name = button.data('name');
      var nodeId = button.parents('#graph-sidebar-content').data('id');
      nodeId = nodeId.split('-');
      nodeId = nodeId[nodeId.length - 1];
      var resultsDiv = button.parent().prev();

      button.addClass('glyphicon-spinner');

      self.runAnalytics(name, nodeId, resultsDiv, button);
    });
  }

}
<commit_msg>remove debug output<commit_after>//
// Global Values (DataSets and Templates)
//

"use strict";

// Compile templates
var nodeTemplate = Handlebars.compile($('#graph-sidebar-node-template').html());
var linksTemplate = Handlebars.compile($('#graph-sidebar-links-template').html());
var analyticsTemplate = Handlebars.compile($('#graph-sidebar-analytics-template').html());

// Define default icons
var icons = {
  'Observable.Ip': flaticon('\ue005'),
  'Observable.Hostname': flaticon('\ue01E'),
  'Observable.Url': flaticon('\ue013'),
  'Observable.Email': flaticon('\ue01A'),
  'Observable.Text': flaticon('\ue022'),
  'Observable.File': flaticon('\ue021'),
  'Observable.Hash': flaticon('\ue00e'),
  'Entity.Malware': flaticon('\ue001'),
  'Entity.TTP': flaticon('\ue019'),
  'Entity.Company': flaticon('\ue002'),
  'Indicator.Regex': flaticon('\ue015'),
};

var cssicons = {
  'Observable.Ip': 'flaticon-computer189',
  'Observable.Hostname': 'flaticon-server20',
  'Observable.Url': 'flaticon-links11',
  'Observable.Email': 'flaticon-message99',
  'Observable.Text': 'flaticon-typography2',
  'Observable.File': 'flaticon-text70',
  'Observable.Hash': 'flaticon-finger14',
  'Entity.Malware': 'flaticon-bug24',
  'Entity.TTP': 'flaticon-maths5',
  'Entity.Company': 'flaticon-building259',
  'Indicator.Regex': 'flaticon-magnifying-glass40',
};

//
// General purpose functions
//

function flaticon(code) {
  return {
    face: 'Flaticon',
    code: code,
    size: 40,
    color: '#495B6C',
  };
}

function dbref(nodeId) {
  var parts = nodeId.split('-');

  return {
    '$id': { '$oid': parts[1] },
    '$ref': parts[0],
  };
}

function buildNodeId(nodeCls, nodeId) {
  var collection = nodeCls.split('.')[0].toLowerCase();

  return collection + '-' + nodeId;
}

function linksFilter(nodeId, field) {
  return function(links) {
    return links[field] == nodeId;
  };
}

// Define Investigation logic
class Investigation {
  constructor(investigation) {
    this.id = investigation._id;
    console.log(this.id);

    // Create the nodes dataset and dataview
    this.nodes = new vis.DataSet([]);
    this.visibleNodes = new vis.DataView(this.nodes, {
      filter: function(item) {
        return item.visible;
      },
    });

    // Create the edges dataset and dataview
    this.edges = new vis.DataSet([]);
    this.visibleEdges = new vis.DataView(this.edges, {
      filter: function(item) {
        return item.visible;
      },
    });

    // Create the analytics dataset
    this.analytics = new vis.DataSet([]);

    // Setup initial data
    this.update(investigation);

    // Display graph
    this.initGraph();
  }

  update(investigation) {
    var self = this;

    var visibleNodes = new Set(self.visibleNodes.getIds());
    var visibleEdges = new Set(self.visibleEdges.getIds());

    investigation.nodes.forEach(function(node) {
      node = self.displayNode(node);
      visibleNodes.delete(node.id);
    });

    investigation.links.forEach(function(link) {
      link.from = link.fromnode;
      link.to = link.tonode;
      link.arrows = 'to';

      if (!self.hasLink(link)) {
        self.edges.add(link);
      }
      self.edges.update({id: link.id, visible: true});

      visibleEdges.delete(link.id);
    });

    visibleNodes.forEach(self.hideNode.bind(self));
    visibleEdges.forEach(self.hideLink.bind(self));
  }

  addNode(node) {
    node.id = buildNodeId(node._cls, node._id);

    var existingNode = this.nodes.get(node.id);

    if (!existingNode) {
      if ('value' in node) {
        node.label = node.value;
      } else {
        node.label = node.name;
      }

      node.shape = 'icon';
      node.icon = icons[node._cls];
      node.cssicon = cssicons[node._cls];

      this.nodes.add(node);

      return node;
    } else {
      return existingNode;
    }
  }

  displayNode(node) {
    node = this.addNode(node);
    this.nodes.update({id: node.id, visible: true});

    return node;
  }

  hideNode(nodeId) {
    this.nodes.update({id: node.id, visible: false});
  }

  hasLink(link) {
    var existingLink = this.edges.get(link.id);

    return existingLink;
  }

  addLink(link) {
    link.id = link._id;

    var existingLink = this.edges.get(link.id);

    if (!existingLink) {
      if (link.description) {
        link.label = link.description;
      } else if (link.Tag) {
        link.label = link.tag;
      }

      link.from = buildNodeId(link.src.collection, link.src.id);
      link.to = buildNodeId(link.dst.collection, link.dst.id);
      link.arrows = 'to';
      this.edges.add(link);

      return link;
    } else {
      return existingLink;
    }
  }

  displayLink(link) {
    link = this.addLink(link);
    this.edges.update({id: link.id, visible: true});

    return link;
  }

  hideLink(linkId) {
    this.edges.update({id: link.id, visible: false});
  }

  enrichLink(nodeField) {
    var self = this;

    return function(link) {
      var node = self.nodes.get(link[nodeField]);
      link.nodeId = node.id;
      link.cssicon = node.cssicon;
      link.value = node.label;
      link.tags = node.tags;
    };
  }

  displayLinks(nodeId) {
    var incoming = this.edges.get({filter: linksFilter(nodeId, 'to')});
    incoming.forEach(this.enrichLink('from'));
    $('#graph-sidebar-links-to-' + nodeId).html(linksTemplate({links: incoming}));

    var outgoing = this.edges.get({filter: linksFilter(nodeId, 'from')});
    outgoing.forEach(this.enrichLink('to'));
    $('#graph-sidebar-links-from-' + nodeId).html(linksTemplate({links: outgoing}));
  }

  displayAnalytics(node) {
    var nodeType = node._cls.split('.');
    nodeType = nodeType[nodeType.length - 1];

    var availableAnalytics = this.analytics.get({
      filter: function(item) {
        return item.acts_on == nodeType;
      },
    });

    $('#graph-sidebar-analytics-' + node.id).html(analyticsTemplate({analytics: availableAnalytics}));
  }

  retrieveNodeNeighborsCallback(nodeId) {
    var self = this;

    return function(data) {
      data.links.forEach(self.addLink.bind(self));
      data.nodes.forEach(self.addNode.bind(self));
      self.nodes.update({id: nodeId, fetched: true});

      self.displayLinks(nodeId);
    };
  }

  retrieveNodeNeighbors(node) {
    $.getJSON('/api/neighbors/' + node._cls + '/' + node._id, this.retrieveNodeNeighborsCallback(node.id));
  }

  selectNode(nodeId) {
    var node = this.nodes.get(nodeId);

    // Update sidebar with content related to this node
    $('#graph-sidebar').html(nodeTemplate(node));

    // Display analytics
    this.displayAnalytics(node);

    // Display links
    if (node.fetched) {
      this.displayLinks(nodeId);
    } else {
      this.retrieveNodeNeighbors(node);
    }
  }

  refreshAnalytics() {
    var nodeId = $('#graph-sidebar-content').data('id');

    if (nodeId) {
      var node = this.nodes.get(nodeId);
      this.displayAnalytics(node);
    }
  }

  loadAnalytics() {
    var self = this;

    $.getJSON('/api/analytics/oneshot', function(data) {
      data.forEach(function(item) {
        if (item.enabled) {
          self.analytics.add(item);
        }
      });

      self.refreshAnalytics();
    });
  }

  fetchAnalyticsResultsCallback(name, resultsId, resultsDiv, button) {
    var self = this;
    return function() {
      return self.fetchAnalyticsResults(name, resultsId, resultsDiv, button);
    };
  }

  fetchAnalyticsResults(name, resultsId, resultsDiv, button) {
    var self = this;

    function callback(data) {
      if (data.status == 'finished') {
        var links = [];

        data.results.nodes.forEach(self.addNode.bind(self));
        data.results.links.forEach(function(link) {
          link = self.addLink(link);

          if (link.src.id == data.observable) {
            self.enrichLink('to')(link);
            links.push(link);
          } else if (link.dst.id == data.observable) {
            self.enrichLink('from')(link);
            links.push(link);
          }
        });

        resultsDiv.html(linksTemplate({links: links}));
        button.removeClass('glyphicon-spinner');
      } else {
        setTimeout(self.fetchAnalyticsResultsCallback(name, resultsId, resultsDiv, button), 1000);
      }
    }

    $.post(
      '/api/analytics/oneshot/' + name + '/status',
      {id: resultsId},
      callback,
      'json'
    );
  }

  runAnalytics(name, nodeId, resultsDiv, progress) {
    var self = this;

    function runCallback(data) {
      var resultsId = data._id;

      self.fetchAnalyticsResults(name, resultsId, resultsDiv, progress);
    }

    $.post(
      '/api/analytics/oneshot/' + name + '/run',
      {id: nodeId},
      runCallback,
      'json'
    );
  }

  enableLink(link) {
    var self = this;

    var data = {
      links: [link],
      nodes: [dbref(link.from), dbref(link.to)],
    };

    // Effectively display elements on the graph
    this.nodes.update([{id: link.from, visible: true}, {id: link.to, visible: true}]);
    this.edges.update({id: link.id, visible: true});

    function callback(investigation) {
      self.update(investigation);
    };

    // Persist changes, and update to last version
    $.ajax({
      type: 'POST',
      url: '/api/investigations/' + self.id + '/add',
      data: JSON.stringify(data),
      success: callback,
      dataType: 'json',
      contentType: 'application/json',
    });
  }

  initGraph() {
    // create a network
    var container = document.getElementById('graph');
    var data = {
      nodes: this.visibleNodes,
      edges: this.visibleEdges,
    };
    var options = {
      physics: {
        barnesHut: {
          springLength: 300,
        },
      },
    };
    var network = new vis.Network(container, data, options);

    // create analytics
    this.loadAnalytics();

    var self = this;

    // Define event handlers
    network.on('selectNode', function(params) {
      self.selectNode(params.nodes[params.nodes.length - 1]);
    });

    $('#graph-sidebar').on('click', '.graph-sidebar-display-link', function(e) {
      var linkId = $(this).data('link');
      var link = self.edges.get(linkId);

      self.enableLink(link);
    });

    $('#graph-sidebar').on('click', '.graph-sidebar-view-node', function(e) {
      var nodeId = $(this).data('node');
      self.selectNode(nodeId);
    });

    $('#graph-sidebar').on('click', '.graph-sidebar-run-analytics', function(e) {
      var button = $(this);

      var name = button.data('name');
      var nodeId = button.parents('#graph-sidebar-content').data('id');
      nodeId = nodeId.split('-');
      nodeId = nodeId[nodeId.length - 1];
      var resultsDiv = button.parent().prev();

      button.addClass('glyphicon-spinner');

      self.runAnalytics(name, nodeId, resultsDiv, button);
    });
  }

}
<|endoftext|>


<commit_before>// Compatibility
if (typeof String.prototype.trim != 'function') {
	String.prototype.trim = function () {
		return this.replace(/^\s+|\s+$/g, '');
	};
}

if (typeof String.prototype.startsWith != 'function') {
	String.prototype.startsWith = function (str){
		return this.substring(0, input.length) === input;
	};
}

if (typeof String.prototype.endsWith != 'function') {
	String.prototype.endsWith = function (str){
		return this.slice(-str.length) == str;
	};
}

if (typeof Object.prototype.hasOwnProperty != 'function') {
	Object.prototype.hasOwnProperty = function(str) {
		if (this.getAttribute(str)) { return true; }
		return false;
	};
}

if (typeof Object.prototype.hasAttribute != 'function') {
	Object.prototype.hasAttribute = function(str) {
		return this.hasOwnProperty(str);
	};
}

if ( typeof Object.prototype.stop != 'function' ) {
	Object.prototype.stop = function(e) {
		var ev = window.event ? window.event : e;
		if ( ev.preventDefault ) {
			ev.preventDefault(e);
		} else {
			ev.returnValue = false;
		}
		if ( ev.cancelBubble ) {
			ev.cancelBubble = true;
		}
		return false;
	}
}

function eId(e) {
	if (typeof e === 'string') {
		return document.getElementById(e);
	}
	return e;
}

function elements(e) {
	return document.getElementsByTagName(e);
}

function hide(e) {
	e = eId(e);
	e.style.display = 'none';
}

function show(e) {
	e = eId(e);
	e.style.display = '';
}

function tooltip(e) {

}

function has(e, t, c, r) {
	t = t || 'item';
	c = c || false;
	r = r || false;

	if (t === 'item' && eId(e)) {
		if (r) {
			return eId(e);
		}
		return true;
	}

	if (t === 'list' && elements(e).length) {
		if (r) {
			return elements(e);
		}
		return true;
	}

	if (c && r) {
		if (t === 'item') {
			return create(e);
		}
	} else if (c) {
		if (t === 'item') {
			create(e);
			return true;
		}
	}
	return false;
}

function create(e, p) {
	var e = document.createElement(e);

	if (typeof p !== 'undefined') {
		p.appendChild(e);
	}
	return e;
}

function remove(e, p) {
	if (typeof p !== 'undefined' && typeof e !== 'undefined') {
		p.removeChild(e);
	}
}

function matches(el, selector) {
  return (el.matches ||
		el.matchesSelector ||
		el.msMatchesSelector ||
		el.mozMatchesSelector ||
		el.webkitMatchesSelector ||
		el.oMatchesSelector).call(el, selector);
}

function next(e) {
	e.nextElementSibling;
}

function attr(e, a, v) {
	if (typeof v === 'undefined') {
		return (e.getAttribute(a)) ?
			e.getAttribute(a) : null ;
	}
	e.setAttribute(a, v);
}

function empty(d) {
	while(d.firstChild) {
		d.removeChild(d.firstChild);
	}
}

function ajax(url, rtype, callback) {
	var x;
	url	= url	|| '/';
	rtype	= rtype	|| 'text';

	if (typeof JSON !== 'object' && rtype === 'json') {
		load('/json2.js');
	}

	if (window.ActiveXObject) {
		x = new ActiveXObject("Microsoft.XMLHTTP");
	} else {
		x = new XMLHttpRequest();
	}

	x.open('GET', url, true);
	x.send(null);

	if (typeof callback !== 'function') {
		return;
	}

	x.onreadystatechange = function() {
		if (x.readyState === 4 && x.status === 200) {
			switch(rtype) {
				case 'xml':
					callback(x.responseXml);
					break;

				case 'json':
					callback(
						JSON.parse(x.responseText)
					);
					break;

				default:
					callback(x.responseText);
					break;
			}
		}
	};
}

function load(f, callback) {
	var h = elements('head')[0];
	if (f.endsWith('.js')) {
		var s = create('script', h);
		attr(s, 'type', 'text/javascript');
		//s.innerHTML = ajax(f, 'text', callback);
		attr(s, 'src', f);
	}
	else if (f.endsWith('.css')) {
		var s = create('link', h);
		attr(s, 'rel', 'stylesheet');
		attr(s, 'type', 'text/css');
		//s.innerHTML = ajax(f, 'text', callback);
		attr(s, 'href', f);
	}
}

//http://stackoverflow.com/questions/5272433/html5-form-required-attribute-set-custom-validation-message
//http://www.raymondcamden.com/index.cfm/2012/6/14/Example-of-a-dynamic-HTML5-datalist-control
function autocomplete(prefix, type) {
	type = type || 'phrase';
	var f = elements('input');
	for (var i = 0; i < f.length; i++) {
		if (!f[i].hasAttribute('list') &&
			!f[i].hasAttribute('data-url')) {
			continue;
		}
		var url = f[i].getAttribute('data-url');
		var id = f[i].getAttribute('list');
		var dl = has(id, 'item', true, true);
		dlAuto(f[i], dl, u);
	}
}

function hashtag(e) {
	if ( !e.hasAttribute('data-url') ) {
		return;
	}
}

function dlAuto(e, dl, url) {
	e.onchange = function() {
		if (e.value.length < 3) {
			return;
		}
		ajax(url, 'text', function(r) {
			dlPop(dl, r);
		});
	};
}

function dlPop(dl, data) {
	if (typeof data === 'undefined' || typeof dl === 'undefined') {
		return;
	}
	empty(dl);
	var d = data.split(/\n/);
	for (var i = 0; i < d.length; i++) {
		if (/\S/.test(d[i])) {
			var o = create('option', dl);
			attr(o, 'value', d[i].trim());
		}
	}
}

function ready(f) {
	var ol = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = f;
	} else {
		window.onload = function() {
			if (ol) { ol(); }
		}
		f();
	}
}

/**
 * UI
 **/
function vote(e, v) {
	var c = e.parentNode;
	var s = c.parentNode;
	var p = s.parentNode;
	var i = p.getAttribute('id');

	if ( null == i ) {
		p = p.parentNode;
		i = p.getAttribute('id');
	}

	var u;
	if ( v == 1 ) {
		u = '/?id='+i+'&vote=1';
		ajax( u, 'text', function( r ) {
			if ( r == 'problem' ) {
				c.innerHTML = 'E';
			} else {
				s.className += ' p1';
				c.innerHTML = '+1';
			}
		});
	} else {
		u = '/?id='+i+'&vote=-1';
		ajax( u, 'text', function( r ) {
			if ( r == 'problem' ) {
				c.innerHTML = 'E';
			} else {
				s.className += ' m1';
				c.innerHTML = '-1';
			}
		});
	}
}

function vlinks(e) {
	var m = e.querySelectorAll('.meta')[0];
	var t = attr( e.querySelectorAll('time')[0], 'datetime' );
	var n = new Date();
	t = utcFormat( t );

	var d = ((n.getTime() - t) * .001) >> 0;
	if ( ( d / 86400 ) > 1 ) { // Max vote period is 2 days 68.95
		m.innerHTML += " --";
		return;
	}

	var l  = e.classList;
	if ( l.contains( 'self' ) ) {
		m.innerHTML += " #";
	} else if ( l.contains( 'p1' ) ) {
		m.innerHTML += " +1";
	} else if ( l.contains( 'm1' ) ) {
		m.innerHTML += " -1";
	} else {
		m.innerHTML += " " +
			"" +
			" " +
			"" +
			"";
	}
}

function mods(e) {
	var id = e.getAttribute('id');
	if ( votes[id] != undefined ) {
		if ( votes[id] === '1' ) {
			e.className += ' p1';
		} else {
			e.className += ' m1';
		}
	} else if ( edits[id] != undefined ) {
		var m = e.querySelectorAll('.meta')[0];
		e.className += ' self';
		m.innerHTML += " ( edit )";
	}
}

function light(e) {
	var p	= e.parentNode;
	var h	= p.querySelector('.highlight');
	var c	= p.querySelector('.container');

	if  ( h.style.display === 'none' ) {
		h.style.display = 'block';
		c.style.display = 'none';
		e.innerHTML = 'Plain';
	} else {
		h.style.display = 'none';
		c.style.display = 'block';
		e.innerHTML = 'Formatted';
	}
}

function code(e) {
	var h = e.innerHTML.trim();
	var l = h.match(/^.*((\r\n|\n|\r)|$)/gm);
	var d = "Plain";
	var o = '';
	if (l.length == 0) { return; }
	e.className = 'styled';
	for(var i = 0; i < l.length; i++) {
		o += '' + l[i] + '';
	}
	e.innerHTML = d + "
"+ e.innerHTML.trim() +'
';
	e.innerHTML += "
"+ o +'
';
}

// TODO
function validate(e) {
	//alert(e.innerHTML);
	//e.stop();
	//return false;
}

function utcFormat( time ) {
	time = time.replace(/\.\d+/, ""); // remove milliseconds
	time = time.replace(/-/, "/").replace(/-/, "/");
	time = time.replace(/T/, " ").replace(/Z/, " UTC");
	time = time.replace(/([\+\-]\d\d)\:?(\d\d)/, " $1$2"); // -04:00 -> -0400
	time = new Date(time * 1000 || time);

	return time;
}

ready(function() {
	var c = elements('code');
	var p = document.querySelectorAll('.post');
	var f = elements('form');
	if (c.length) {
		for (var i = 0; i<c.length; i++) {
			code(c[i]);
		}
	}
	if (p.length && p[0].tagName == 'DIV' ) {
		for (var i = 0; i<p.length; i++) {
			mods(p[i]);
			vlinks(p[i]);
		}
	}
	if (f.length) {
		for (var i = 0; i<f.length; i++) {
			f[i].addEventListener('submit', function(e) { return validate(this); }, false );
		}
	}
});

// Courtesy of Goker Cebeci at https://coderwall.com/p/uub3pw
(function ago(selector) {
	var templates = {
		prefix: "",
		suffix: " ago",
		seconds: "seconds",
		minute: "a minute",
		minutes: "%d minutes",
		hour: "an hour",
		hours: "%d hours",
		day: "a day",
		days: "%d days",
		month: "a month",
		months: "%d months",
		year: "a year",
		years: "%d years"
	};
	var template = function(t, n) {
		return templates[t] && templates[t].replace(/%d/i, Math.abs(Math.round(n)));
	};
	var timer = function(time) {
		if (!time)
			return;
		time = time.replace(/\.\d+/, ""); // remove milliseconds
		time = time.replace(/-/, "/").replace(/-/, "/");
		time = time.replace(/T/, " ").replace(/Z/, " UTC");
		time = time.replace(/([\+\-]\d\d)\:?(\d\d)/, " $1$2"); // -04:00 -> -0400
		time = new Date(time * 1000 || time);

		var now = new Date();
		var seconds = ((now.getTime() - time) * .001) >> 0;

		if ( seconds < 45 ) {
			return 'now';
		}

		var minutes = seconds / 60;
		var hours = minutes / 60;
		var days = hours / 24;
		var years = days / 365;

		return templates.prefix + (
			seconds < 45 && template('seconds', seconds) ||
			seconds < 90 && template('minute', 1) ||
			minutes < 45 && template('minutes', minutes) ||
			minutes < 90 && template('hour', 1) ||
			hours < 24 && template('hours', hours) ||
			hours < 42 && template('day', 1) ||
			days < 30 && template('days', days) ||
			days < 45 && template('month', 1) ||
			days < 365 && template('months', days / 30) ||
			years < 1.5 && template('year', 1) ||
			template('years', years)
		) + templates.suffix;
	};
	var el = elements('time');
	for (var i in el) {
		var $this = el[i];
		if (typeof $this === 'object') {
			$this.innerHTML = timer(attr($this, 'datetime'));
		}
	}
	// update time every minute
	setTimeout(ago, 60000);
})();
<commit_msg>Update m.js<commit_after>// Compatibility
if (typeof String.prototype.trim != 'function') {
	String.prototype.trim = function () {
		return this.replace(/^\s+|\s+$/g, '');
	};
}

if (typeof String.prototype.startsWith != 'function') {
	String.prototype.startsWith = function (str){
		return this.substring(0, input.length) === input;
	};
}

if (typeof String.prototype.endsWith != 'function') {
	String.prototype.endsWith = function (str){
		return this.slice(-str.length) == str;
	};
}

if (typeof Object.prototype.hasOwnProperty != 'function') {
	Object.prototype.hasOwnProperty = function(str) {
		if (this.getAttribute(str)) { return true; }
		return false;
	};
}

if (typeof Object.prototype.hasAttribute != 'function') {
	Object.prototype.hasAttribute = function(str) {
		return this.hasOwnProperty(str);
	};
}

if ( typeof Object.prototype.stop != 'function' ) {
	Object.prototype.stop = function(e) {
		var ev = window.event ? window.event : e;
		if ( ev.preventDefault ) {
			ev.preventDefault(e);
		} else {
			ev.returnValue = false;
		}
		if ( ev.cancelBubble ) {
			ev.cancelBubble = true;
		}
		return false;
	}
}

function eId(e) {
	if (typeof e === 'string') {
		return document.getElementById(e);
	}
	return e;
}

function elements(e) {
	return document.getElementsByTagName(e);
}

function hide(e) {
	e = eId(e);
	e.style.display = 'none';
}

function show(e) {
	e = eId(e);
	e.style.display = '';
}

function tooltip(e) {

}

function has(e, t, c, r) {
	t = t || 'item';
	c = c || false;
	r = r || false;

	if (t === 'item' && eId(e)) {
		if (r) {
			return eId(e);
		}
		return true;
	}

	if (t === 'list' && elements(e).length) {
		if (r) {
			return elements(e);
		}
		return true;
	}

	if (c && r) {
		if (t === 'item') {
			return create(e);
		}
	} else if (c) {
		if (t === 'item') {
			create(e);
			return true;
		}
	}
	return false;
}

function create(e, p) {
	var e = document.createElement(e);

	if (typeof p !== 'undefined') {
		p.appendChild(e);
	}
	return e;
}

function remove(e, p) {
	if (typeof p !== 'undefined' && typeof e !== 'undefined') {
		p.removeChild(e);
	}
}

function matches(el, selector) {
  return (el.matches ||
		el.matchesSelector ||
		el.msMatchesSelector ||
		el.mozMatchesSelector ||
		el.webkitMatchesSelector ||
		el.oMatchesSelector).call(el, selector);
}

function next(e) {
	e.nextElementSibling;
}

function attr(e, a, v) {
	if (typeof v === 'undefined') {
		return (e.getAttribute(a)) ?
			e.getAttribute(a) : null ;
	}
	e.setAttribute(a, v);
}

function empty(d) {
	while(d.firstChild) {
		d.removeChild(d.firstChild);
	}
}

function ajax(url, rtype, callback) {
	var x;
	url	= url	|| '/';
	rtype	= rtype	|| 'text';

	if (typeof JSON !== 'object' && rtype === 'json') {
		load('/json2.js');
	}

	if (window.ActiveXObject) {
		x = new ActiveXObject("Microsoft.XMLHTTP");
	} else {
		x = new XMLHttpRequest();
	}

	x.open('GET', url, true);
	x.send(null);

	if (typeof callback !== 'function') {
		return;
	}

	x.onreadystatechange = function() {
		if (x.readyState === 4 && x.status === 200) {
			switch(rtype) {
				case 'xml':
					callback(x.responseXml);
					break;

				case 'json':
					callback(
						JSON.parse(x.responseText)
					);
					break;

				default:
					callback(x.responseText);
					break;
			}
		}
	};
}

function load(f, callback) {
	var h = elements('head')[0];
	if (f.endsWith('.js')) {
		var s = create('script', h);
		attr(s, 'type', 'text/javascript');
		//s.innerHTML = ajax(f, 'text', callback);
		attr(s, 'src', f);
	}
	else if (f.endsWith('.css')) {
		var s = create('link', h);
		attr(s, 'rel', 'stylesheet');
		attr(s, 'type', 'text/css');
		//s.innerHTML = ajax(f, 'text', callback);
		attr(s, 'href', f);
	}
}

//http://stackoverflow.com/questions/5272433/html5-form-required-attribute-set-custom-validation-message
//http://www.raymondcamden.com/index.cfm/2012/6/14/Example-of-a-dynamic-HTML5-datalist-control
function autocomplete(prefix, type) {
	type = type || 'phrase';
	var f = elements('input');
	for (var i = 0; i < f.length; i++) {
		if (!f[i].hasAttribute('list') &&
			!f[i].hasAttribute('data-url')) {
			continue;
		}
		var url = f[i].getAttribute('data-url');
		var id = f[i].getAttribute('list');
		var dl = has(id, 'item', true, true);
		dlAuto(f[i], dl, u);
	}
}

function hashtag(e) {
	if ( !e.hasAttribute('data-url') ) {
		return;
	}
}

function dlAuto(e, dl, url) {
	e.onchange = function() {
		if (e.value.length < 3) {
			return;
		}
		ajax(url, 'text', function(r) {
			dlPop(dl, r);
		});
	};
}

function dlPop(dl, data) {
	if (typeof data === 'undefined' || typeof dl === 'undefined') {
		return;
	}
	empty(dl);
	var d = data.split(/\n/);
	for (var i = 0; i < d.length; i++) {
		if (/\S/.test(d[i])) {
			var o = create('option', dl);
			attr(o, 'value', d[i].trim());
		}
	}
}

function ready(f) {
	var ol = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = f;
	} else {
		window.onload = function() {
			if (ol) { ol(); }
		}
		f();
	}
}

/**
 * UI
 **/
function vote(e, v) {
	var c = e.parentNode;
	var s = c.parentNode;
	var p = s.parentNode;
	var i = p.getAttribute('id');

	if ( null == i ) {
		p = p.parentNode;
		i = p.getAttribute('id');
	}

	var u;

	if ( v == 1 ) {
		u = '/?id='+i+'&vote=1';
		ajax( u, 'text', function( r ) {
			if ( r == 'problem' ) {
				c.innerHTML = 'E';
			} else {
				s.className += ' p1';
				c.innerHTML = '+1';
			}
		});
	} else {
		u = '/?id='+i+'&vote=-1';
		ajax( u, 'text', function( r ) {
			if ( r == 'problem' ) {
				c.innerHTML = 'E';
			} else {
				s.className += ' m1';
				c.innerHTML = '-1';
			}
		});
	}
}

function vlinks(e) {
	var m = e.querySelectorAll('.meta')[0];
	var t = attr( e.querySelectorAll('time')[0], 'datetime' );
	var n = new Date();
	t = utcFormat( t );

	var d = ((n.getTime() - t) * .001) >> 0;
	if ( ( d / 86400 ) > 1 ) { // Max vote period is 2 days 68.95
		m.innerHTML += " --";
		return;
	}

	var l  = e.classList;
	if ( l.contains( 'self' ) ) {
		m.innerHTML += " #";
	} else if ( l.contains( 'p1' ) ) {
		m.innerHTML += " +1";
	} else if ( l.contains( 'm1' ) ) {
		m.innerHTML += " -1";
	} else {
		m.innerHTML += " " +
			"" +
			" " +
			"" +
			"";
	}
}

function mods(e) {
	var id = e.getAttribute('id');
	if ( votes[id] != undefined ) {
		if ( votes[id] === '0' ) {
			e.className += ' self';
		} else if ( votes[id] === '1' ) {
			e.className += ' p1';
		} else {
			e.className += ' m1';
		}
	} else if ( edits[id] != undefined ) {
		var m = e.querySelectorAll('.meta')[0];
		e.className += ' self';
		m.innerHTML += " ( edit )";
	}
}

function light(e) {
	var p	= e.parentNode;
	var h	= p.querySelector('.highlight');
	var c	= p.querySelector('.container');

	if  ( h.style.display === 'none' ) {
		h.style.display = 'block';
		c.style.display = 'none';
		e.innerHTML = 'Plain';
	} else {
		h.style.display = 'none';
		c.style.display = 'block';
		e.innerHTML = 'Formatted';
	}
}

function code(e) {
	var h = e.innerHTML.trim();
	var l = h.match(/^.*((\r\n|\n|\r)|$)/gm);
	var d = "Plain";
	var o = '';
	if (l.length == 0) { return; }
	e.className = 'styled';
	for(var i = 0; i < l.length; i++) {
		o += '' + l[i] + '';
	}
	e.innerHTML = d + "
"+ e.innerHTML.trim() +'
';
	e.innerHTML += "
"+ o +'
';
}

// TODO
function validate(e) {
	//alert(e.innerHTML);
	//e.stop();
	//return false;
}

function utcFormat( time ) {
	time = time.replace(/\.\d+/, ""); // remove milliseconds
	time = time.replace(/-/, "/").replace(/-/, "/");
	time = time.replace(/T/, " ").replace(/Z/, " UTC");
	time = time.replace(/([\+\-]\d\d)\:?(\d\d)/, " $1$2"); // -04:00 -> -0400
	time = new Date(time * 1000 || time);

	return time;
}

ready(function() {
	var c = elements('code');
	var p = document.querySelectorAll('.post');
	var f = elements('form');
	if (c.length) {
		for (var i = 0; i<c.length; i++) {
			code(c[i]);
		}
	}
	if (p.length && p[0].tagName == 'DIV' ) {
		for (var i = 0; i<p.length; i++) {
			mods(p[i]);
			vlinks(p[i]);
		}
	}
	if (f.length) {
		for (var i = 0; i<f.length; i++) {
			f[i].addEventListener('submit', function(e) { return validate(this); }, false );
		}
	}
});

// Courtesy of Goker Cebeci at https://coderwall.com/p/uub3pw
(function ago(selector) {
	var templates = {
		prefix: "",
		suffix: " ago",
		seconds: "seconds",
		minute: "a minute",
		minutes: "%d minutes",
		hour: "an hour",
		hours: "%d hours",
		day: "a day",
		days: "%d days",
		month: "a month",
		months: "%d months",
		year: "a year",
		years: "%d years"
	};
	var template = function(t, n) {
		return templates[t] && templates[t].replace(/%d/i, Math.abs(Math.round(n)));
	};
	var timer = function(time) {
		if (!time)
			return;
		time = time.replace(/\.\d+/, ""); // remove milliseconds
		time = time.replace(/-/, "/").replace(/-/, "/");
		time = time.replace(/T/, " ").replace(/Z/, " UTC");
		time = time.replace(/([\+\-]\d\d)\:?(\d\d)/, " $1$2"); // -04:00 -> -0400
		time = new Date(time * 1000 || time);

		var now = new Date();
		var seconds = ((now.getTime() - time) * .001) >> 0;

		if ( seconds < 45 ) {
			return 'now';
		}

		var minutes = seconds / 60;
		var hours = minutes / 60;
		var days = hours / 24;
		var years = days / 365;

		return templates.prefix + (
			seconds < 45 && template('seconds', seconds) ||
			seconds < 90 && template('minute', 1) ||
			minutes < 45 && template('minutes', minutes) ||
			minutes < 90 && template('hour', 1) ||
			hours < 24 && template('hours', hours) ||
			hours < 42 && template('day', 1) ||
			days < 30 && template('days', days) ||
			days < 45 && template('month', 1) ||
			days < 365 && template('months', days / 30) ||
			years < 1.5 && template('year', 1) ||
			template('years', years)
		) + templates.suffix;
	};
	var el = elements('time');
	for (var i in el) {
		var $this = el[i];
		if (typeof $this === 'object') {
			$this.innerHTML = timer(attr($this, 'datetime'));
		}
	}
	// update time every minute
	setTimeout(ago, 60000);
})();
<|endoftext|>


<commit_before>// ==========================================================================
// Project:   MySystem.NodeView
// Copyright: ©2010 Concord Consortium
// under the MIT License (see LICENSE file for more info)
// ==========================================================================
/*globals MySystem LinkIt SCUI */

/** @class

  (Document Your View Here)

  @extends SC.View
*/
sc_require('models/link');

MySystem.NodeView = SC.View.extend(SCUI.Cleanup, LinkIt.NodeView, SC.DropTarget,
/** @scope MySystem.NodeView.prototype */ {

  layout: { top: 0, left: 0, width: 100, height: 120 },
  classNames: 'node'.w(),

  displayProperties: 'content isSelected'.w(),
  content: null,
  isSelected: false,

  // implement action behavior of see sproutcore/desktop/view/button
  target: "MySystem.nodesController",
  action: "showAlert",

  // childViews: 'icon label aTerminal bTerminal'.w(),
  childViews: 'icon label aTerminal bTerminal transformationIcon'.w(),

  /** @private */
  _runAction: function(evt) {
    var action = this.get('action'),
        target = this.get('target') || null;

    if (action) {
      this.getPath('pane.rootResponder').sendAction(action, target, this, this.get('pane'));
    }
  },

  render: function (context) {
    sc_super();
    if (this.get('isSelected')) context.addClass('selected');
  },

  icon: SC.ImageView.design({
    classNames: 'image',
    useImageCache: true,
    layout: { top: 20, width:50, height:70, centerX: 0},
    valueBinding: '.parentView*content.image'
  }),

  label: SC.LabelView.design({
    layout: { bottom: 12, centerX: 0, width: 100, height: 25 },
    classNames: ['name'],
    textAlign: SC.ALIGN_CENTER,
    valueBinding: '.parentView*content.title',
    isEditable: YES
  }),

  aTerminal: SC.View.design(LinkIt.Terminal, {
    layout: { left: 45, top: +5, width: 10, height: 10 },
    classNames: 'input terminal'.w(),
    nodeBinding: '.parentView*content',
    terminal: 'a',
    linkClass: 'MySystem.Link'
    // direction: LinkIt.INPUT_TERMINAL
  }),

  bTerminal: SC.View.design(LinkIt.Terminal, {
    layout: { left: 45, bottom: +5, width: 10, height: 10 },
    classNames: 'output terminal'.w(),
    nodeBinding: '.parentView*content',
    terminal: 'b',
    linkClass: 'MySystem.Link'
    // direction: LinkIt.OUTPUT_TERMINAL
  }),

  transformationIcon: SC.ImageView.design({
    classNames: 'image',
    useImageCache: true,
    layout: {left: 5, bottom: +5, width: 20, height:20 },
    valueBinding: '.parentView*content.transformationIcon',
    toolTipBinding: '.parentView*content.toolTip',
    click: function(evt) {
      MySystem.nodesController.selectFirstTransformation(this.getPath('parentView.content'));
      // MySystem.storySentenceController.createSentence(this.getPath('parentView.content'));
      return YES;
    }
  }),

  // ..........................................................
  // LINKIT Specific for the view
  //
  /**
    Implements LinkIt.NodeView.terminalViewFor()
  */
  terminalViewFor: function (terminalKey) {
    return this[terminalKey + 'Terminal'];
  },

  /**
  * implement action behavior of see sproutcore/desktop/view/button
  */
  // doubleClick: function(evt, skipHoldRepeat) {
  //     this._runAction(evt);
  // }

  /**
  * Stuff for DropTarget
  */
  dragStarted: function(drag, evt) {},
  dragEntered: function(drag, evt) {},
  dragUpdated: function(drag, evt) {},
  dragExited: function(drag, evt) {},
  dragEnded: function(drag, evt) {},
  computeDragOperations: function(drag, evt, ops) {
    if (drag.hasDataType('Boolean')) {
      return SC.DRAG_LINK;
    } else {
      return SC.DRAG_NONE;
    }
  },
  acceptDragOperation: function(drag, operation) { return true; },
  performDragOperation: function(drag, operation) {
    this.content.set('transformer', true);
    return operation;
  }
});
<commit_msg>Make compilation work<commit_after>// ==========================================================================
// Project:   MySystem.NodeView
// Copyright: ©2010 Concord Consortium
// under the MIT License (see LICENSE file for more info)
// ==========================================================================
/*globals MySystem LinkIt SCUI */

/** @class

  (Document Your View Here)

  @extends SC.View
*/
sc_require('models/link');

MySystem.NodeView = SC.View.extend(SCUI.Cleanup, LinkIt.NodeView,
/** @scope MySystem.NodeView.prototype */ {

  layout: { top: 0, left: 0, width: 100, height: 120 },
  classNames: 'node'.w(),

  displayProperties: 'content isSelected'.w(),
  content: null,
  isSelected: false,

  // implement action behavior of see sproutcore/desktop/view/button
  target: "MySystem.nodesController",
  action: "showAlert",

  // childViews: 'icon label aTerminal bTerminal'.w(),
  childViews: 'icon label aTerminal bTerminal transformationIcon'.w(),

  /** @private */
  _runAction: function(evt) {
    var action = this.get('action'),
        target = this.get('target') || null;

    if (action) {
      this.getPath('pane.rootResponder').sendAction(action, target, this, this.get('pane'));
    }
  },

  render: function (context) {
    sc_super();
    if (this.get('isSelected')) context.addClass('selected');
  },

  icon: SC.ImageView.design({
    classNames: 'image',
    useImageCache: true,
    layout: { top: 20, width:50, height:70, centerX: 0},
    valueBinding: '.parentView*content.image'
  }),

  label: SC.LabelView.design({
    layout: { bottom: 12, centerX: 0, width: 100, height: 25 },
    classNames: ['name'],
    textAlign: SC.ALIGN_CENTER,
    valueBinding: '.parentView*content.title',
    isEditable: YES
  }),

  aTerminal: SC.View.design(LinkIt.Terminal, {
    layout: { left: 45, top: +5, width: 10, height: 10 },
    classNames: 'input terminal'.w(),
    nodeBinding: '.parentView*content',
    terminal: 'a',
    linkClass: 'MySystem.Link'
    // direction: LinkIt.INPUT_TERMINAL
  }),

  bTerminal: SC.View.design(LinkIt.Terminal, {
    layout: { left: 45, bottom: +5, width: 10, height: 10 },
    classNames: 'output terminal'.w(),
    nodeBinding: '.parentView*content',
    terminal: 'b',
    linkClass: 'MySystem.Link'
    // direction: LinkIt.OUTPUT_TERMINAL
  }),

  transformationIcon: SC.ImageView.design({
    classNames: 'image',
    useImageCache: true,
    layout: {left: 5, bottom: +5, width: 20, height:20 },
    valueBinding: '.parentView*content.transformationIcon',
    toolTipBinding: '.parentView*content.toolTip',
    click: function(evt) {
      MySystem.nodesController.selectFirstTransformation(this.getPath('parentView.content'));
      // MySystem.storySentenceController.createSentence(this.getPath('parentView.content'));
      return YES;
    }
  }),

  // ..........................................................
  // LINKIT Specific for the view
  //
  /**
    Implements LinkIt.NodeView.terminalViewFor()
  */
  terminalViewFor: function (terminalKey) {
    return this[terminalKey + 'Terminal'];
  },

  /**
  * implement action behavior of see sproutcore/desktop/view/button
  */
  // doubleClick: function(evt, skipHoldRepeat) {
  //     this._runAction(evt);
  // }

  /**
  * Stuff for DropTarget
  */
  dragStarted: function(drag, evt) {},
  dragEntered: function(drag, evt) {},
  dragUpdated: function(drag, evt) {},
  dragExited: function(drag, evt) {},
  dragEnded: function(drag, evt) {},
  computeDragOperations: function(drag, evt, ops) {
    if (drag.hasDataType('Boolean')) {
      return SC.DRAG_LINK;
    } else {
      return SC.DRAG_NONE;
    }
  },
  acceptDragOperation: function(drag, operation) { return true; },
  performDragOperation: function(drag, operation) {
    this.content.set('transformer', true);
    return operation;
  }
});
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';

import ModalContainer from './ModalContainer';
import { getModalTitle, MODAL_KEYS } from '../../utilities/getModalTitle';
import { AutocompleteSelector } from '../AutocompleteSelector';
import { TextEditor } from '../TextEditor';
import { UIDatabase } from '../../database';
import { modalStrings } from '../../localization';

/**
 * Wrapper around ModalContainer, containing common modals used in various
 * DataTable pages.
 * @prop {Bool}   fullScreen   Force the modal to cover the entire screen.
 * @prop {Bool}   isOpen       Whether the modal is open
 * @prop {Func}   onClose      A function to call if the close x is pressed
 * @prop {Func}   onSelect     The components to render within the modal
 * @prop {String} modalKey     The title to show in within the modal.
 * @prop {String} currentValue The current value a modal should be i.e. theirRef/comment
 */
export const DataTablePageModal = ({
  fullScreen,
  isOpen,
  onClose,
  modalKey,
  onSelect,
  currentValue,
}) => {
  const ModalContent = () => {
    switch (modalKey) {
      case MODAL_KEYS.ITEM_SELECT:
        return (
          <AutocompleteSelector
            options={UIDatabase.objects('Item')}
            queryString="name BEGINSWITH[c] $0 OR code BEGINSWITH[c] $0"
            queryStringSecondary="name CONTAINS[c] $0"
            sortByString="name"
            onSelect={onSelect}
            renderLeftText={item => `${item.name}`}
            renderRightText={item => `${item.totalQuantity}`}
          />
        );

      case MODAL_KEYS.COMMENT_EDIT:
        return <TextEditor text={currentValue} onEndEditing={onSelect} />;

      case MODAL_KEYS.THEIR_REF_EDIT:
        return <TextEditor text={currentValue} onEndEditing={onSelect} />;

      case MODAL_KEYS.SELECT_CUSTOMER:
        return (
          <AutocompleteSelector
            options={UIDatabase.objects('Customer')}
            isOpen={isOpen}
            placeholderText={modalStrings.start_typing_to_select_customer}
            queryString="name BEGINSWITH[c] $0"
            sortByString="name"
            onSelect={onSelect}
          />
        );

      case MODAL_KEYS.SELECT_SUPPLIER:
        return (
          <AutocompleteSelector
            options={UIDatabase.objects('ExternalSupplier')}
            isOpen={isOpen}
            placeholderText={modalStrings.start_typing_to_select_supplier}
            queryString="name BEGINSWITH[c] $0"
            sortByString="name"
            onSelect={onSelect}
          />
        );

      default:
        return null;
    }
  };

  return (
    <ModalContainer
      fullScreen={fullScreen}
      isVisible={isOpen}
      onClose={onClose}
      title={getModalTitle(modalKey)}
    >
      <ModalContent />
    </ModalContainer>
  );
};

DataTablePageModal.defaultProps = {
  fullScreen: false,
  modalKey: '',
  onSelect: null,
  currentValue: '',
};

DataTablePageModal.propTypes = {
  fullScreen: PropTypes.bool,
  isOpen: PropTypes.bool.isRequired,
  onClose: PropTypes.func.isRequired,
  modalKey: PropTypes.string,
  onSelect: PropTypes.func,
  currentValue: PropTypes.string,
};

export default DataTablePageModal;
<commit_msg>Add update to DataTablePageModal with ByProgramModal<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';

import ModalContainer from './ModalContainer';
import { getModalTitle, MODAL_KEYS } from '../../utilities/getModalTitle';
import { AutocompleteSelector } from '../AutocompleteSelector';
import { TextEditor } from '../TextEditor';
import { ByProgramModal } from './ByProgramModal';
import { UIDatabase } from '../../database';
import { modalStrings } from '../../localization';
import Settings from '../../settings/MobileAppSettings';

/**
 * Wrapper around ModalContainer, containing common modals used in various
 * DataTable pages.
 * @prop {Bool}   fullScreen   Force the modal to cover the entire screen.
 * @prop {Bool}   isOpen       Whether the modal is open
 * @prop {Func}   onClose      A function to call if the close x is pressed
 * @prop {Func}   onSelect     The components to render within the modal
 * @prop {String} modalKey     The title to show in within the modal.
 * @prop {String} currentValue The current value a modal should be i.e. theirRef/comment
 */
export const DataTablePageModal = ({
  fullScreen,
  isOpen,
  onClose,
  modalKey,
  onSelect,
  currentValue,
}) => {
  const ModalContent = () => {
    switch (modalKey) {
      case MODAL_KEYS.ITEM_SELECT:
        return (
          <AutocompleteSelector
            options={UIDatabase.objects('Item')}
            queryString="name BEGINSWITH[c] $0 OR code BEGINSWITH[c] $0"
            queryStringSecondary="name CONTAINS[c] $0"
            sortByString="name"
            onSelect={onSelect}
            renderLeftText={item => `${item.name}`}
            renderRightText={item => `${item.totalQuantity}`}
          />
        );

      case MODAL_KEYS.COMMENT_EDIT:
        return <TextEditor text={currentValue} onEndEditing={onSelect} />;

      case MODAL_KEYS.THEIR_REF_EDIT:
        return <TextEditor text={currentValue} onEndEditing={onSelect} />;

      case MODAL_KEYS.SELECT_CUSTOMER:
        return (
          <AutocompleteSelector
            options={UIDatabase.objects('Customer')}
            isOpen={isOpen}
            placeholderText={modalStrings.start_typing_to_select_customer}
            queryString="name BEGINSWITH[c] $0"
            sortByString="name"
            onSelect={onSelect}
          />
        );

      case MODAL_KEYS.SELECT_SUPPLIER:
        return (
          <AutocompleteSelector
            options={UIDatabase.objects('ExternalSupplier')}
            isOpen={isOpen}
            placeholderText={modalStrings.start_typing_to_select_supplier}
            queryString="name BEGINSWITH[c] $0"
            sortByString="name"
            onSelect={onSelect}
          />
        );
      case MODAL_KEYS.PROGRAM_REQUISITION:
        return (
          <ByProgramModal
            onConfirm={onSelect}
            database={UIDatabase}
            type="requisition"
            settings={Settings}
          />
        );
      default:
        return null;
    }
  };

  return (
    <ModalContainer
      fullScreen={fullScreen}
      isVisible={isOpen}
      onClose={onClose}
      title={getModalTitle(modalKey)}
    >
      <ModalContent />
    </ModalContainer>
  );
};

DataTablePageModal.defaultProps = {
  fullScreen: false,
  modalKey: '',
  onSelect: null,
  currentValue: '',
};

DataTablePageModal.propTypes = {
  fullScreen: PropTypes.bool,
  isOpen: PropTypes.bool.isRequired,
  onClose: PropTypes.func.isRequired,
  modalKey: PropTypes.string,
  onSelect: PropTypes.func,
  currentValue: PropTypes.string,
};

export default DataTablePageModal;
<|endoftext|>


<commit_before>define(function (require) {
  'use strict';

  var _ = require('underscore'),
    Dispatcher = require('dispatchers/Dispatcher'),
    BaseStore = require('stores/BaseStore'),
    VolumeCollection = require('collections/VolumeCollection'),
    VolumeConstants = require('constants/VolumeConstants');

  var VolumeStore = BaseStore.extend({
    collection: VolumeCollection,

    queryParams: {
      page_size: 100
    },

    initialize: function () {
      this.pollingEnabled = true;
      this.pollingFrequency = 15 * 1000;
    },

    //
    // Custom functions
    //

    getVolumesAttachedToInstance: function (instance) {
      if (!this.models) return this.fetchModels();

      var attachedVolumes = [],
          uuid = instance.get('uuid');

      this.models.each(function (volume) {
        var attachData = volume.get('attach_data');
        if (attachData.instance_id && attachData.instance_id === uuid) {
          attachedVolumes.push(volume);
        }
      });
      return attachedVolumes;
    },

    getVolumesNotInAProject: function () {
      if (!this.models) return this.fetchModels();

      var volumes = this.models.filter(function (volume) {
        return volume.get('projects').length === 0
      });

      return new VolumeCollection(volumes);
    },

    //
    // Polling functions
    //

    isInFinalState: function (volume) {
      return volume.get('state').isInFinalState();
    }

  });

  var store = new VolumeStore();

  Dispatcher.register(function (dispatch) {
    var actionType = dispatch.action.actionType;
    var payload = dispatch.action.payload;
    var options = dispatch.action.options || options;

    switch (actionType) {

      case VolumeConstants.ADD_VOLUME:
        store.add(payload.volume);
        break;

      case VolumeConstants.UPDATE_VOLUME:
        store.update(payload.volume);
        break;

      case VolumeConstants.REMOVE_VOLUME:
        store.remove(payload.volume);
        break;

      case VolumeConstants.POLL_VOLUME:
        store.pollNowUntilBuildIsFinished(payload.volume);
        break;

      case VolumeConstants.POLL_VOLUME_WITH_DELAY:
        store.pollUntilBuildIsFinished(payload.volume);
        break;

      default:
        return true;
    }

    if (!options.silent) {
      store.emitChange();
    }

    return true;
  });

  return store;
});
<commit_msg>Method to VolumeStore makes list of attached resources<commit_after>define(function (require) {
  'use strict';

  var _ = require('underscore'),
    Dispatcher = require('dispatchers/Dispatcher'),
    BaseStore = require('stores/BaseStore'),
    VolumeCollection = require('collections/VolumeCollection'),
    VolumeConstants = require('constants/VolumeConstants');

  var VolumeStore = BaseStore.extend({
    collection: VolumeCollection,

    queryParams: {
      page_size: 100
    },

    initialize: function () {
      this.pollingEnabled = true;
      this.pollingFrequency = 15 * 1000;
    },

    //
    // Custom functions
    //

    getVolumesAttachedToInstance: function (instance) {
      if (!this.models) return this.fetchModels();

      var attachedVolumes = [],
          uuid = instance.get('uuid');

      this.models.each(function (volume) {
        var attachData = volume.get('attach_data');
        if (attachData.instance_id && attachData.instance_id === uuid) {
          attachedVolumes.push(volume);
        }
      });
      return attachedVolumes;
    },

// Makes a clean list of attached resources from volume information for easy reference
    getAttachedResources: function () {
        if (!this.models) return this.fetchModels();
        var attachedResources = [];
        this.models.each(function (volume) {
            var attachData = volume.get('attach_data');
            if (attachData.instance_id !== null)
                var attached = {
                    volumeID: volume.get('uuid'),
                    instanceID: attachData.instance_id
                };
                attachedResources.push(attached);
        });
        return attachedResources;
    },

    getVolumesNotInAProject: function () {
      if (!this.models) return this.fetchModels();

      var volumes = this.models.filter(function (volume) {
        return volume.get('projects').length === 0
      });

      return new VolumeCollection(volumes);
    },

    //
    // Polling functions
    //

    isInFinalState: function (volume) {
      return volume.get('state').isInFinalState();
    }

  });

  var store = new VolumeStore();

  Dispatcher.register(function (dispatch) {
    var actionType = dispatch.action.actionType;
    var payload = dispatch.action.payload;
    var options = dispatch.action.options || options;

    switch (actionType) {

      case VolumeConstants.ADD_VOLUME:
        store.add(payload.volume);
        break;

      case VolumeConstants.UPDATE_VOLUME:
        store.update(payload.volume);
        break;

      case VolumeConstants.REMOVE_VOLUME:
        store.remove(payload.volume);
        break;

      case VolumeConstants.POLL_VOLUME:
        store.pollNowUntilBuildIsFinished(payload.volume);
        break;

      case VolumeConstants.POLL_VOLUME_WITH_DELAY:
        store.pollUntilBuildIsFinished(payload.volume);
        break;

      default:
        return true;
    }

    if (!options.silent) {
      store.emitChange();
    }

    return true;
  });

  return store;
});
<|endoftext|>