<commit_before>/**
 * Object that we can throw audio data into and have it drain out.
 *
 * Because changing the number of channels on the fly is hard, hardcoding
 * to 2 output channels. That's also all we can do on IE with Flash output.
 *
 * @todo better timing!
 */
function AudioFeeder() {
	// assume W3C Audio API

	var AudioContext = window.AudioContext || window.webkitAudioContext;
	if (!AudioContext) {
		// use Flash fallback
		console.log("No W3C Web Audio API available");
		this.flashaudio = new DynamicAudio();
	}

	var bufferSize = 1024,
		channels = 0, // call init()!
		rate = 0; // call init()!

	// Always create stereo output. For iOS we have to set this stuff up
	// before we've actually gotten the info from the codec because we
	// must initialize from a UI event. Bah!
	var outputChannels = 2;

	function freshBuffer() {
		var buffer = [];
		for (var channel = 0; channel < outputChannels; channel++) {
			buffer[channel] = new Float32Array(bufferSize);
		}
		return buffer;
	}

	var buffers = [],
		context,
		node,
		pendingBuffer = freshBuffer(),
		pendingPos = 0,
		muted = false;

	if(AudioContext) {
		context = new AudioContext;
		if (context.createScriptProcessor) {
			node = context.createScriptProcessor(bufferSize, 0, outputChannels)
		} else if (context.createJavaScriptNode) {
			node = context.createJavaScriptNode(bufferSize, 0, outputChannels)
		} else {
			throw new Error("Bad version of web audio API?");
		}
	}

	function popNextBuffer() {
		// hack hack
		// fixme: grab the right number of samples
		// and... rescale
		if (buffers.length > 0) {
			return buffers.shift();
		}
	}

	if(node) {
		node.onaudioprocess = function(event) {
			var inputBuffer = popNextBuffer(bufferSize);
			if (!muted && inputBuffer) {
				for (var channel = 0; channel < outputChannels; channel++) {
					var input = inputBuffer[channel],
						output = event.outputBuffer.getChannelData(channel);
					for (var i = 0; i < Math.min(bufferSize, input.length); i++) {
						output[i] = input[i];
					}
				}
			} else {
				if (!inputBuffer) {
					console.log("Starved for audio!");
				}
				for (var channel = 0; channel < outputChannels; channel++) {
					var output = event.outputBuffer.getChannelData(channel);
					for (var i = 0; i < bufferSize; i++) {
						output[i] = 0;
					}
				}
			}
		};
		node.connect(context.destination);
	}

	/**
	 * This is horribly naive and wrong.
	 * Replace me with a better algo!
	 */
	function resample(samples) {
		var targetRate = context.sampleRate;
		if (rate == targetRate) {
			return samples;
		} else {
			var newSamples = [];
			for (var channel = 0; channel < outputChannels; channel++) {
				var inputChannel = channel;
				if (channel >= channels) {
					inputChannel = 0;
				}
				var input = samples[inputChannel],
					output = new Float32Array(Math.round(input.length * targetRate / rate));
				for (var i = 0; i < output.length; i++) {
					output[i] = input[Math.floor(i * rate / targetRate)];
				}
				newSamples.push(output);
			}
			return newSamples;
		}
	}

	/**
	 * Resampling, scaling and reordering for the Flash fallback.
	 * The Flash fallback expects 44.1 kHz, stereo
	 * Resampling: This is horribly naive and wrong.
	 * TODO: Replace me with a better algo!
	 */
	function resampleFlash(samples) {
		var sampleincr = rate / 44100;
		var samplecount = Math.floor(samples[0].length * (44100 / rate));
		var newSamples = new Array(samplecount * channels);
		var channel1 = channels > 1 ? 1 : 0;
		for(var s = 0; s < samplecount; s++) {
			var idx = Math.floor(s * sampleincr);
			var idx_out = s * 2;
			newSamples[idx_out] = Math.floor(samples[0][idx] * 32768);
			newSamples[idx_out + 1] = Math.floor(samples[channel1][idx] * 32768);
		}
		return newSamples;
	}

	function pushSamples(samples) {
		var firstChannel = samples[0],
			sampleCount = firstChannel.length;
		for (var i = 0; i < sampleCount; i++) {
			for (var channel = 0; channel < outputChannels; channel++) {
				pendingBuffer[channel][pendingPos] = samples[channel][i];
			}
			if (++pendingPos == bufferSize) {
				buffers.push(pendingBuffer);
				pendingPos = 0;
				pendingBuffer = freshBuffer();
			}
		}
	}

	var self = this;

	this.init = function(numChannels, sampleRate) {
		// warning: can't change channels here reliably
		rate = sampleRate;
		channels = numChannels;
		pendingBuffer = freshBuffer();
	};

	this.bufferData = function(samplesPerChannel) {
		if(this.flashaudio) {
			var resamples = resampleFlash(samplesPerChannel);
			var flashElement = this.flashaudio.flashElement;
			if(resamples.length > 0 && flashElement.write) {
				flashElement.write(resamples.join(' '));
			}
		} else if (buffers) {
			samples = resample(samplesPerChannel);
			pushSamples(samples);
		} else {
			self.close();
		}
	};

	this.mute = function() {
		muted = true;
	};

	this.unmute = function() {
		muted = false;
	}

	this.close = function() {
		if(this.flashaudio) {
			var wrapper = this.flashaudio.flashWrapper;
			wrapper.parentNode.removeChild(wrapper);
			this.flashaudio = null;
		} else if (node) {
			node.onaudioprocess = null;
			node.disconnect();
		}
		node = null;
		context = null;
		buffers = null;
	};
}

/** Flash fallback **/

/*
The Flash fallback is based on https://github.com/an146/dynamicaudio.js

This is the contents of the LICENSE file:

Copyright (c) 2010, Ben Firshman
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 * The names of its contributors may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

function DynamicAudio(args) {
	if (this instanceof arguments.callee) {
		if (typeof this.init === "function") {
			this.init.apply(this, (args && args.callee) ? args : arguments);
		}
	} else {
		return new arguments.callee(arguments);
	}
}

DynamicAudio.nextId = 1;

DynamicAudio.prototype = {
	nextId: null,
	swf: 'dynamicaudio.swf',

	flashWrapper: null,
	flashElement: null,

	init: function(opts) {
		var self = this;
		self.id = DynamicAudio.nextId++;

		if (opts && typeof opts['swf'] !== 'undefined') {
			self.swf = opts['swf'];
		}

		self.flashWrapper = document.createElement('div');
		self.flashWrapper.id = 'dynamicaudio-flashwrapper-'+self.id;
		// Credit to SoundManager2 for this:
		var s = self.flashWrapper.style;
		s['position'] = 'fixed';
		s['width'] = '11px'; // must be at least 6px for flash to run fast
		s['height'] = '11px';
		s['bottom'] = s['left'] = '0px';
		s['overflow'] = 'hidden';
		self.flashElement = document.createElement('div');
		self.flashElement.id = 'dynamicaudio-flashelement-'+self.id;
		self.flashWrapper.appendChild(self.flashElement);

		document.body.appendChild(self.flashWrapper);

		var id = self.flashElement.id;

		self.flashWrapper.innerHTML = "<object id='"+id+"' width='10' height='10' type='application/x-shockwave-flash' data='"+self.swf+"' style='visibility: visible;'><param name='allowscriptaccess' value='always'></object>";
		self.flashElement = document.getElementById(id);
	},
};

<commit_msg>one more resampling fix<commit_after>/**
 * Object that we can throw audio data into and have it drain out.
 *
 * Because changing the number of channels on the fly is hard, hardcoding
 * to 2 output channels. That's also all we can do on IE with Flash output.
 *
 * @todo better timing!
 */
function AudioFeeder() {
	// assume W3C Audio API

	var AudioContext = window.AudioContext || window.webkitAudioContext;
	if (!AudioContext) {
		// use Flash fallback
		console.log("No W3C Web Audio API available");
		this.flashaudio = new DynamicAudio();
	}

	var bufferSize = 1024,
		channels = 0, // call init()!
		rate = 0; // call init()!

	// Always create stereo output. For iOS we have to set this stuff up
	// before we've actually gotten the info from the codec because we
	// must initialize from a UI event. Bah!
	var outputChannels = 2;

	function freshBuffer() {
		var buffer = [];
		for (var channel = 0; channel < outputChannels; channel++) {
			buffer[channel] = new Float32Array(bufferSize);
		}
		return buffer;
	}

	var buffers = [],
		context,
		node,
		pendingBuffer = freshBuffer(),
		pendingPos = 0,
		muted = false;

	if(AudioContext) {
		context = new AudioContext;
		if (context.createScriptProcessor) {
			node = context.createScriptProcessor(bufferSize, 0, outputChannels)
		} else if (context.createJavaScriptNode) {
			node = context.createJavaScriptNode(bufferSize, 0, outputChannels)
		} else {
			throw new Error("Bad version of web audio API?");
		}
	}

	function popNextBuffer() {
		// hack hack
		// fixme: grab the right number of samples
		// and... rescale
		if (buffers.length > 0) {
			return buffers.shift();
		}
	}

	if(node) {
		node.onaudioprocess = function(event) {
			var inputBuffer = popNextBuffer(bufferSize);
			if (!muted && inputBuffer) {
				for (var channel = 0; channel < outputChannels; channel++) {
					var input = inputBuffer[channel],
						output = event.outputBuffer.getChannelData(channel);
					for (var i = 0; i < Math.min(bufferSize, input.length); i++) {
						output[i] = input[i];
					}
				}
			} else {
				if (!inputBuffer) {
					console.log("Starved for audio!");
				}
				for (var channel = 0; channel < outputChannels; channel++) {
					var output = event.outputBuffer.getChannelData(channel);
					for (var i = 0; i < bufferSize; i++) {
						output[i] = 0;
					}
				}
			}
		};
		node.connect(context.destination);
	}

	/**
	 * This is horribly naive and wrong.
	 * Replace me with a better algo!
	 */
	function resample(samples) {
		var targetRate = context.sampleRate;
		if (rate == targetRate && channels == outputChannels) {
			return samples;
		} else {
			var newSamples = [];
			for (var channel = 0; channel < outputChannels; channel++) {
				var inputChannel = channel;
				if (channel >= channels) {
					inputChannel = 0;
				}
				var input = samples[inputChannel],
					output = new Float32Array(Math.round(input.length * targetRate / rate));
				for (var i = 0; i < output.length; i++) {
					output[i] = input[Math.floor(i * rate / targetRate)];
				}
				newSamples.push(output);
			}
			return newSamples;
		}
	}

	/**
	 * Resampling, scaling and reordering for the Flash fallback.
	 * The Flash fallback expects 44.1 kHz, stereo
	 * Resampling: This is horribly naive and wrong.
	 * TODO: Replace me with a better algo!
	 */
	function resampleFlash(samples) {
		var sampleincr = rate / 44100;
		var samplecount = Math.floor(samples[0].length * (44100 / rate));
		var newSamples = new Array(samplecount * channels);
		var channel1 = channels > 1 ? 1 : 0;
		for(var s = 0; s < samplecount; s++) {
			var idx = Math.floor(s * sampleincr);
			var idx_out = s * 2;
			newSamples[idx_out] = Math.floor(samples[0][idx] * 32768);
			newSamples[idx_out + 1] = Math.floor(samples[channel1][idx] * 32768);
		}
		return newSamples;
	}

	function pushSamples(samples) {
		var firstChannel = samples[0],
			sampleCount = firstChannel.length;
		for (var i = 0; i < sampleCount; i++) {
			for (var channel = 0; channel < outputChannels; channel++) {
				pendingBuffer[channel][pendingPos] = samples[channel][i];
			}
			if (++pendingPos == bufferSize) {
				buffers.push(pendingBuffer);
				pendingPos = 0;
				pendingBuffer = freshBuffer();
			}
		}
	}

	var self = this;

	this.init = function(numChannels, sampleRate) {
		// warning: can't change channels here reliably
		rate = sampleRate;
		channels = numChannels;
		pendingBuffer = freshBuffer();
	};

	this.bufferData = function(samplesPerChannel) {
		if(this.flashaudio) {
			var resamples = resampleFlash(samplesPerChannel);
			var flashElement = this.flashaudio.flashElement;
			if(resamples.length > 0 && flashElement.write) {
				flashElement.write(resamples.join(' '));
			}
		} else if (buffers) {
			samples = resample(samplesPerChannel);
			pushSamples(samples);
		} else {
			self.close();
		}
	};

	this.mute = function() {
		muted = true;
	};

	this.unmute = function() {
		muted = false;
	}

	this.close = function() {
		if(this.flashaudio) {
			var wrapper = this.flashaudio.flashWrapper;
			wrapper.parentNode.removeChild(wrapper);
			this.flashaudio = null;
		} else if (node) {
			node.onaudioprocess = null;
			node.disconnect();
		}
		node = null;
		context = null;
		buffers = null;
	};
}

/** Flash fallback **/

/*
The Flash fallback is based on https://github.com/an146/dynamicaudio.js

This is the contents of the LICENSE file:

Copyright (c) 2010, Ben Firshman
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 * The names of its contributors may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

function DynamicAudio(args) {
	if (this instanceof arguments.callee) {
		if (typeof this.init === "function") {
			this.init.apply(this, (args && args.callee) ? args : arguments);
		}
	} else {
		return new arguments.callee(arguments);
	}
}

DynamicAudio.nextId = 1;

DynamicAudio.prototype = {
	nextId: null,
	swf: 'dynamicaudio.swf',

	flashWrapper: null,
	flashElement: null,

	init: function(opts) {
		var self = this;
		self.id = DynamicAudio.nextId++;

		if (opts && typeof opts['swf'] !== 'undefined') {
			self.swf = opts['swf'];
		}

		self.flashWrapper = document.createElement('div');
		self.flashWrapper.id = 'dynamicaudio-flashwrapper-'+self.id;
		// Credit to SoundManager2 for this:
		var s = self.flashWrapper.style;
		s['position'] = 'fixed';
		s['width'] = '11px'; // must be at least 6px for flash to run fast
		s['height'] = '11px';
		s['bottom'] = s['left'] = '0px';
		s['overflow'] = 'hidden';
		self.flashElement = document.createElement('div');
		self.flashElement.id = 'dynamicaudio-flashelement-'+self.id;
		self.flashWrapper.appendChild(self.flashElement);

		document.body.appendChild(self.flashWrapper);

		var id = self.flashElement.id;

		self.flashWrapper.innerHTML = "<object id='"+id+"' width='10' height='10' type='application/x-shockwave-flash' data='"+self.swf+"' style='visibility: visible;'><param name='allowscriptaccess' value='always'></object>";
		self.flashElement = document.getElementById(id);
	},
};

<|endoftext|>


<commit_before>/* global define: false */
define(['angular', 'text!./dashboard.html'], function(angular, template) {
  'use strict';

  var Controller = [
    '$scope',
    '$rootScope',
    'Views',
    'Data',
    'dataDepend',
    'page',
  function (
    $scope,
    $rootScope,
    Views,
    Data,
    dataDepend,
    page
  ) {

    var processData = $scope.processData = dataDepend.create($scope);

    $scope.dashboardVars = { read: [ 'processData' ] };
    $scope.dashboardProviders = Views.getProviders({ component: 'cockpit.dashboard'});

    Data.instantiateProviders('cockpit.dashboard.data', {$scope: $scope, processData : processData});

    // INITIALIZE PLUGINS
    var dashboardPlugins = Views.getProviders({ component: 'cockpit.dashboard' });

    var initData = {
      $scope      : $scope,
      processData : processData
    };

    for(var i = 0; i < dashboardPlugins.length; i++) {
      if(typeof dashboardPlugins[i].initialize === 'function') {
         dashboardPlugins[i].initialize(initData);
      }
    }

    $rootScope.showBreadcrumbs = false;

    // reset breadcrumbs
    page.breadcrumbsClear();

    page.titleSet([
      'camunda Cockpit',
      'Dashboard'
    ].join(' | '));
  }];

  var RouteConfig = [ '$routeProvider', function($routeProvider) {
    $routeProvider.when('/dashboard', {
      template: template,
      controller: Controller,
      authentication: 'required',
      reloadOnSearch: false
    });
  }];

  return RouteConfig;
});
<commit_msg>impr(page-title): use capital C for Camunda<commit_after>/* global define: false */
define(['angular', 'text!./dashboard.html'], function(angular, template) {
  'use strict';

  var Controller = [
    '$scope',
    '$rootScope',
    'Views',
    'Data',
    'dataDepend',
    'page',
  function (
    $scope,
    $rootScope,
    Views,
    Data,
    dataDepend,
    page
  ) {

    var processData = $scope.processData = dataDepend.create($scope);

    $scope.dashboardVars = { read: [ 'processData' ] };
    $scope.dashboardProviders = Views.getProviders({ component: 'cockpit.dashboard'});

    Data.instantiateProviders('cockpit.dashboard.data', {$scope: $scope, processData : processData});

    // INITIALIZE PLUGINS
    var dashboardPlugins = Views.getProviders({ component: 'cockpit.dashboard' });

    var initData = {
      $scope      : $scope,
      processData : processData
    };

    for(var i = 0; i < dashboardPlugins.length; i++) {
      if(typeof dashboardPlugins[i].initialize === 'function') {
         dashboardPlugins[i].initialize(initData);
      }
    }

    $rootScope.showBreadcrumbs = false;

    // reset breadcrumbs
    page.breadcrumbsClear();

    page.titleSet([
      'Camunda Cockpit',
      'Dashboard'
    ].join(' | '));
  }];

  var RouteConfig = [ '$routeProvider', function($routeProvider) {
    $routeProvider.when('/dashboard', {
      template: template,
      controller: Controller,
      authentication: 'required',
      reloadOnSearch: false
    });
  }];

  return RouteConfig;
});
<|endoftext|>


<commit_before>
/*** = Description
  ** Simple small menu component for selecting one value amongst several.
  ** It's limited to 15px height by the default theme.
  ***/
var//RSence.Menus
HMiniMenu = HRadioButtonList.extend({

  componentName: 'minimenu',

  defaultEvents: {
    draggable: true,
    mouseUp: true,
    click: true,
    resize: true
  },

  subComponentHeight: 15,

  resize: function(){
    this.repositionMenuItems();
  },

  repositionMenuItems: function(){
    if(!this.listItems || (this.listItems && !this.listItems.length)){ return; }
    var
    x = this.pageX(),
    y = this.pageY(),
    w = this.rect.width,
    h = this.listItems.length*this.subComponentHeight,
    i = 0,
    listItem = null;
    for(;i<this.listItems.length && listItem === null;i++){
      if(this.listItems[i][0]===this.value){
        listItem = this.listItems[i];
      }
    }
    y -= (i-1)*this.subComponentHeight;
    if(y < 0){
      y = this.subComponentHeight%y;
    }
    if(this.options.menuItemGeom){
      if(this.options.menuItemGeom.width){
        w += this.options.menuItemGeom.width;
      }
      if(this.options.menuItemGeom.width){
        x += this.options.menuItemGeom.left;
      }
    }
    this.menuItemView.rect.set( x, y, x+w, y+h );
    this.menuItemView.refresh();
    this.menuItemView.hide();
    ELEM.setStyle( this.menuItemView.elemId, 'display', 'none' );
  },

  click: function(){
    if(!this.active){return false;}
    if( ELEM.getStyle(this.menuItemView.elemId,'visibility',true) === 'hidden' ) {
      this.menuShow();
    }else{
      this.menuHide();
    }
    return false;
  },

  refreshValue: function(){
    this.base();
    if(this.listItems && this.listItems.length !== 0 && this.valueMatrix !== undefined ) {
      for(var i=0;i<this.listItems.length;i++){
        if(this.listItems[i][0]===this.value){
          this.setLabel( this.listItems[i][1] );
          return;
        }
      }
    }
  },

  menuShow: function(){
    this.repositionMenuItems();
    this.menuItemView.bringToFront();
    this.menuItemView.show();
    ELEM.setStyle( this.menuItemView.elemId, 'display', 'block' );
    return true;
  },

  menuHide: function(){
    if( this.menuItemView ){
      this.menuItemView.sendToBack();
      this.menuItemView.hide();
      ELEM.setStyle( this.menuItemView.elemId, 'display', 'none' );
      this.menuItemView.offsetTo(0-this.menuItemView.rect.width,0-this.menuItemView.rect.height);
    }
  },

  startDrag: function(x,y){
    this.dragStart = [x,y];
    if(!this.active){return false;}
    this.menuShow();
    return false;
  },

  lostActiveStatus: function(_newActive){
    if( _newActive && !_newActive.isChildOf( this.menuItemView ) ){
      this.menuHide();
    }
    this.base(_newActive);
  },

  gainedActiveStatus: function(_prevActive){
    if( _prevActive && _prevActive.isChildOf( this.menuItemView ) ){
      this.menuHide();
      // EVENT.changeActiveControl(null);
    }
    this.base(_prevActive);
  },

  endDrag: function(x,y){
    if( (Math.round(this.dragStart[0]*0.2)===Math.round(x*0.2)) &&
        (Math.round(this.dragStart[1]*0.2)===Math.round(y*0.2))
    ){
      this.menuShow();
    }
    else {
      this.menuHide();
    }
    return false;
  },

  die: function(){
    this.valueMatrix = null;
    var _menuItemView = this.menuItemView;
    this.base();
    if( _menuItemView ){
      _menuItemView.die();
    }
  },

  drawSubviews: function(){
    var
    itemStyle = {
      'background-color': '#f6f6f6',
      'border': '1px solid #999',
      'overflow': 'auto',
      'overflow-x': 'hidden',
      'display': 'none'
    };
    if(!BROWSER_TYPE.ie){
      itemStyle.opacity = 0.9;
    }
    this.menuItemView = HView.nu(
      [ this.rect.left, this.rect.top, this.rect.width, 10 ],
      this.app, {
        visible: false,
        style: itemStyle,
        logicParent: this
      }
    );
  },

  setListItems: function(_listItems){
    this.base(_listItems);
    this.valueMatrix = this.menuItemView.valueMatrix;
    this.refreshValue();
    if( this.options.initialVisibility ){
      EVENT.changeActiveControl(null);
      this.menuShow();
    }
  },

  createComponent: function( i, _label ){
    return HMiniMenuItem.nu(
      [ 0, (i*this.subComponentHeight), null, this.subComponentHeight, 0, null ],
      this.menuItemView, {
        label: _label
      }
    );
  }

});
<commit_msg>HMiniMenu bug fixes.<commit_after>
/*** = Description
  ** Simple small menu component for selecting one value amongst several.
  ** It's limited to 15px height by the default theme.
  ***/
var//RSence.Menus
HMiniMenu = HRadioButtonList.extend({

  componentName: 'minimenu',

  defaultEvents: {
    draggable: true,
    mouseUp: true,
    click: true,
    resize: true
  },

  subComponentHeight: 15,

  resize: function(){
    this.repositionMenuItems();
  },

  repositionMenuItems: function(){
    var
    x = this.pageX(),
    y = this.pageY(),
    w = this.rect.width,
    _listItems = this.listItems ? (this.listItems.length ? this.listItems : [] ) : [],
    h = _listItems.length*this.subComponentHeight,
    i = 0,
    listItem = null;
    for(;i<_listItems.length && listItem === null;i++){
      if(_listItems[i][0]===this.value){
        listItem = _listItems[i];
      }
    }
    y -= (i-1)*this.subComponentHeight;
    if(y < 0){
      y = this.subComponentHeight%y;
    }
    if(this.options.menuItemGeom){
      if(this.options.menuItemGeom.width){
        w += this.options.menuItemGeom.width;
      }
      if(this.options.menuItemGeom.width){
        x += this.options.menuItemGeom.left;
      }
    }
    var
    mw = x+w,
    mh = y+h;
    this._menuItemViewShowPos = [ x, y ];
    this._menuItemViewHidePos = [ 0-mw, 0-mh ];
    this.menuItemView.rect.set( x, y, mw, mh );
    this.menuItemView.offsetTo( 0-mw, 0-mh );
    this.menuItemView.refresh();
    this.menuItemView.hide();
  },

  click: function(){
    if(!this.active){return false;}
    if( ELEM.getStyle(this.menuItemView.elemId,'visibility',true) === 'hidden' ) {
      this.menuShow();
    }else{
      this.menuHide();
    }
    return false;
  },

  refreshValue: function(){
    this.base();
    if(this.listItems && this.listItems.length !== 0 && this.valueMatrix !== undefined ) {
      for(var i=0;i<this.listItems.length;i++){
        if(this.listItems[i][0]===this.value){
          this.setLabel( this.listItems[i][1] );
          return;
        }
      }
    }
  },

  menuShow: function(){
    this.repositionMenuItems();
    var m = this._menuItemViewShowPos, x=m[0], y=m[1];
    this.menuItemView.offsetTo( x, y );
    this.menuItemView.bringToFront();
    this.menuItemView.show();
    return true;
  },

  menuHide: function(){
    if( this.menuItemView ){
      var m = this._menuItemViewHidePos, x=m[0], y=m[1];
      this.menuItemView.offsetTo( x, y );
      this.menuItemView.sendToBack();
      this.menuItemView.hide();
    }
  },

  startDrag: function(x,y){
    this.dragStart = [x,y];
    if(!this.active){return false;}
    this.menuShow();
    return false;
  },

  lostActiveStatus: function(_newActive){
    if( _newActive && !_newActive.isChildOf( this.menuItemView ) ){
      this.menuHide();
    }
    this.base(_newActive);
  },

  gainedActiveStatus: function(_prevActive){
    if( _prevActive && _prevActive.isChildOf( this.menuItemView ) ){
      this.menuHide();
    }
    this.base(_prevActive);
  },

  endDrag: function(x,y){
    if( (Math.round(this.dragStart[0]*0.2)===Math.round(x*0.2)) &&
        (Math.round(this.dragStart[1]*0.2)===Math.round(y*0.2))
    ){
      this.menuShow();
    }
    else {
      this.menuHide();
    }
    return false;
  },

  die: function(){
    this.valueMatrix = null;
    var _menuItemView = this.menuItemView;
    this.base();
    if( _menuItemView ){
      _menuItemView.die();
    }
  },

  drawSubviews: function(){
    var
    itemStyle = {
      'background-color': '#f6f6f6',
      'border': '1px solid #999',
      'overflow': 'auto',
      'overflow-x': 'hidden',
      'display': 'none'
    };
    if(!BROWSER_TYPE.ie){
      itemStyle.opacity = 0.9;
    }
    this.menuItemView = HView.nu(
      // [ this.rect.left, this.rect.top, this.rect.width, 10 ],
      [ 0-this.rect.width, 0-this.rect.height, this.rect.width, 10 ],
      this.app, {
        visible: false,
        style: itemStyle,
        logicParent: this
      }
    );
  },

  setListItems: function(_listItems){
    this.base(_listItems);
    this.valueMatrix = this.menuItemView.valueMatrix;
    this.refreshValue();
    if( this.options.initialVisibility ){
      EVENT.changeActiveControl(null);
      this.menuShow();
    }
  },

  createComponent: function( i, _label ){
    return HMiniMenuItem.nu(
      [ 0, (i*this.subComponentHeight), null, this.subComponentHeight, 0, null ],
      this.menuItemView, {
        label: _label
      }
    );
  }

});
<|endoftext|>


<commit_before>'use strict';

angular.module('offlineApp').service('offlineService', function($http) {

    var view_model = this;

    /* --------------- Configuration --------------- */

    // API Parameters
    view_model.readAPI = "http://188.166.147.80/get?after=";
    view_model.updateAPI = "http://188.166.147.80/post";
    view_model.createAPI = "http://188.166.147.80/post";
    view_model.primaryKeyProperty = "id";
    view_model.timestampProperty = "timestamp";

    // Default Config:
    view_model.autoSync = 10000; /* Set to zero for no auto synchronisation */
    view_model.pushSync = false;
    view_model.initialSync = true;
    view_model.allowIndexedDB = true; /* Switching to false disables IndexedDB */
    view_model.allowRemote = true;

    // IndexedDB Config:
    view_model.indexedDBDatabaseName = "localDB-2";
    view_model.indexedDBVersionNumber = 177; /* Increment this to wipe and reset IndexedDB */
    view_model.objectStoreName = "testObjectStore";

    /* --------------- Offlinify Internals --------------- */

    // Service Variables
    view_model.idb = null;
    view_model.serviceDB = []; /* Local image of the data */
    view_model.observerCallbacks = [];
    view_model.lastChecked = new Date("1970-01-01T00:00:00.000Z").toISOString(); /* Initially the epoch */

    // Public Functions
    view_model.registerController = registerController;
    view_model._generateTimestamp = _generateTimestamp;
    view_model.sync = sync;
    view_model.objectUpdate = objectUpdate;
    view_model.wipeIndexedDB = wipeIndexedDB;

    // Determine IndexedDB Support
    view_model.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
    if(!view_model.allowIndexedDB) view_model.indexedDB = null;

    /* --------------- Create & Update --------------- */

    // Filters create or update ops by queue state:
    function objectUpdate(obj) {
      _.set(obj, view_model.timestampProperty, _generateTimestamp());
      obj = _stripAngularHashKeys(obj);
      if(obj.hasOwnProperty("syncState")) {
        if(obj.syncState > 0) { obj.syncState = 2; }
      } else {
        obj = _.cloneDeep(obj);
        obj.syncState = 0;
        _.set(obj, view_model.primaryKeyProperty, _generateUUID());
      }
      _patchLocal(_stripAngularHashKeys([obj]), function(response) {
        if(view_model.pushSync) sync(_notifyObservers);
      });
     };

     /* --------------- Observer Pattern --------------- */

     // Called by the controller to receive updates (observer pattern)
     // Called by the controller to receive updates (observer pattern)
    function registerController(ctrlCallback) {
       ensureIDBReady(function() {
         view_model.observerCallbacks.push(ctrlCallback);
         if(!view_model.initialSync) return;
         view_model.sync(function(response) {
           ctrlCallback(response);
         });
       });
     };

     function ensureIDBReady(callback) {
        if(view_model.idb == null)
          _establishIndexedDB(function() { callback(); });
        else
          callback();
     };

    /* --------------- Synchronisation --------------- */

    // Restores local state on first sync, or patches local and remote changes:
    function sync(callback) {
      var startClock = _generateTimestamp();
      var newLocalRecords = _getLocalRecords(view_model.lastChecked);
      if( newLocalRecords.length == 0 && view_model.serviceDB.length == 0 ) {
        _restoreLocalState( function(localResponse) {
          _patchRemoteChanges(function(remoteResponse) {
            _reduceQueue(function(queueResponse) {
              callback( {dataSource: remoteResponse, currentQueue: queueResponse} );
              console.log("Initial load took: " + (new Date(_generateTimestamp()) - new Date(startClock))/1000 + " sec." );
            });
          });
        });
      } else {
        _patchRemoteChanges(function(remoteResponse) {
          _reduceQueue(function(queueResponse) {
            callback( { dataSource: remoteResponse, currentQueue: queueResponse } );
          });
        });
      }
    };

    // Patches remote edits to serviceDB + IndexedDB:
    function _patchRemoteChanges(callback) {
      if(!view_model.allowRemote) {
          if(_hasIndexedDB()) callback(-1);
          else callback(-2);
          return;
      }
      _getRemoteRecords(function(response) {
        if(response.status == 200) {
          _patchLocal(response.data, function(localResponse) {
            callback(localResponse);
          });
        } else { callback("Could not connect to remote server: (" + response.status + ") error."); }
      });
    };

    // Patches the local storages with a dataset.
    function _patchLocal(data, callback) {
      _patchServiceDB(data);
      view_model.lastChecked = _generateTimestamp();
      if( _hasIndexedDB() ) {
        _putArrayToIndexedDB(data, function() {
          callback(1); // Patched to IDB + ServiceDB
        });
      } else {
        callback(0); // Patched to ServiceDB only.
      }
    };

    function _notifyObservers(status) {
      angular.forEach(view_model.observerCallbacks, function(callback){
        callback(status);
      });
    };

    /* --------------- Queue + State --------------- */

    // Puts IndexedDB store into scope:
    function _restoreLocalState(callback) {
      if(!_hasIndexedDB()) { callback(-1); return; }
      _getIndexedDB(function(idbRecords) {

        var sortedElements = _.reverse(_.sortBy(idbRecords, function(o) {
          return new Date(_.get(o, view_model.timestampProperty)).toISOString();
        }));
        var nonQueueElements = _.filter(sortedElements, {syncState: 1});
        var queueElements = _.filter(sortedElements, function(o) { return o.syncState != 1; });

        if(nonQueueElements.length > 0) {
          view_model.lastChecked = _.get(sortedElements[0], view_model.timestampProperty);
        } else {
          if(queueElements.length > 0)
            view_model.lastChecked = _.get(queueElements[queueElements.length - 1], view_model.timestampProperty);
        }

        _patchServiceDB(idbRecords);
        callback(idbRecords.length);
      });
    };

    // Synchronises elements to remote when connection is available:
    function _reduceQueue(callback) {
      if(!view_model.allowRemote) { callback(-1); return; }

      var createQueue = _.filter(view_model.serviceDB, { "syncState" : 0 });
      var updateQueue = _.filter(view_model.serviceDB, { "syncState" : 2 });

      // Get rid of the create queue:
      _safeArrayPost(createQueue, view_model.createAPI, function(successfulCreates) {
        _safeArrayPost(updateQueue, view_model.updateAPI, function(successfulUpdates) {

          var totalQueue = successfulCreates.concat(successfulUpdates);
          _.forEach(totalQueue, function(value) {
            _.set(value, view_model.timestampProperty, _generateTimestamp());
          });

          _patchLocal(_resetSyncState(totalQueue), function(response) {

            var queueLength = updateQueue.length + createQueue.length;
            var popLength = successfulCreates.length + successfulUpdates.length;

            // Check here for integrity:
            if( queueLength == popLength ) {
              callback(1); // Success: All queue elements synchronised.
            } else {
              callback(0); // Partial success: Some or none were synchronised.
            }

          });
        });
      });

    };

    /* --------------- ServiceDB Interface --------------- */

    function _patchServiceDB(data) {
      var operations = _filterOperations(data);
      _updatesToServiceDB(operations.updateOperations);
      _pushToServiceDB(operations.createOperations);
    };

    function _pushToServiceDB(array) {
      for(var i=0; i<array.length; i++) view_model.serviceDB.push(array[i]);
    };

    function _updatesToServiceDB(array) {
      for(var i=0; i<array.length; i++) {
        var indexJSON = {};
        _.set(indexJSON, view_model.primaryKeyProperty, _.get(array[i], view_model.primaryKeyProperty));
        var matchID = _.findIndex(view_model.serviceDB, indexJSON);
        if(matchID > -1) view_model.serviceDB[matchID] = array[i];
      }
    };

    function _getLocalRecords(sinceTime) {
      return _.filter(view_model.serviceDB, function(o) {
        return new Date(_.get(o, view_model.timestampProperty)).toISOString() > sinceTime;
      });
    };

    /* --------------- Data Handling --------------- */

    /* Filter remote data into create or update operations */
    function _filterOperations(data) {
      var updateOps = [];
      var createOps = [];
      for(var i=0; i<data.length; i++) {
        var queryJSON = {};
        _.set(queryJSON, view_model.primaryKeyProperty, _.get(data[i], view_model.primaryKeyProperty));
        var query = _.findIndex(view_model.serviceDB, queryJSON);
        if( query > -1 ) updateOps.push(data[i]);
        else createOps.push(data[i]);
      }
      return { updateOperations: updateOps, createOperations: createOps };
    }

    function _resetSyncState(records) {
      for(var i=0; i<records.length; i++) {
        records[i].syncState = 1;
      }
      return records;
    };

    /* --------------- Remote --------------- */

    function _postRemote(data, url, callback) {
      // Data should be a single record.
      $http({
          url: url,
          method: "POST",
          data: [data],
          headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }
      })
      .then(
        function successCallback(response) {
          callback(response.status); // return response code.
        }, function errorCallback(response) {
          callback(response.status);
        });
    };

    function _getRemoteRecords(callback) {
      $http({
          method: 'GET',
          url: view_model.readAPI + view_model.lastChecked
        })
        .then(
          function successCallback(response) {
            if(response.data.length > 0)
              callback({data: _resetSyncState(response.data), status: 200});
            else
              callback({data: [], status: 200});

        }, function errorCallback(response) {
            callback({data: [], status: response.status});
        });
    };

    // Tries to post an array one-by-one; returns successful elements.
    function _safeArrayPost(array, url, callback) {
      var x = 0;
      var successfulElements = [];
      if(array.length == 0) { callback([]); return; }
      function loopArray(array) {
        _postRemote(array[x],url,function(response) {
          if(response == 200) successfulElements.push(array[x]);
          x++;
          if(x < array.length) { loopArray(array); }
          else { callback(successfulElements); }
        });
      };
      loopArray(array);
    };

    /* --------------- IndexedDB --------------- */

    function _establishIndexedDB(callback) {
      if(!_hasIndexedDB()) { callback(); /* No browser support for IDB */ return; }
      var request = view_model.indexedDB.open(view_model.indexedDBDatabaseName, view_model.indexedDBVersionNumber);
      request.onupgradeneeded = function(e) {
        var db = e.target.result;
        e.target.transaction.onerror = function() { console.error(this.error); };
        if(db.objectStoreNames.contains(view_model.objectStoreName)) {
          db.deleteObjectStore(view_model.objectStoreName);
        }
        var offlineItems = db.createObjectStore(view_model.objectStoreName, { keyPath: view_model.primaryKeyProperty, autoIncrement: false } );
        var dateIndex = offlineItems.createIndex("byDate", view_model.timestampProperty, {unique: false});
        view_model.idb = db;
      };
      request.onsuccess = function(e) {
        view_model.idb = e.target.result;
        callback();
      };
      request.onerror = function() { console.error(this.error); };
    };

    function _hasIndexedDB() {
      return !(view_model.indexedDB === undefined || view_model.indexedDB === null );
    };

    // Get from IndexedDB. This function returns appropriate records.
    function _getIndexedDB(callback) {
      var transaction = _newIDBTransaction();
      var objStore = transaction.objectStore(view_model.objectStoreName);
      var keyRange = IDBKeyRange.lowerBound(0);
      var cursorRequest = objStore.index('byDate').openCursor(keyRange);
      var returnableItems = [];
      transaction.oncomplete = function(e) { callback(_stripAngularHashKeys(returnableItems)); };
      cursorRequest.onsuccess = function(e) {
        var result = e.target.result;
        if (!!result == false) { return; }
        returnableItems.push(result.value);
        result.continue();
      };
      cursorRequest.onerror = function() { console.error("error"); };
    };

    // Apply array of edited objects to IndexedDB.
    function _putArrayToIndexedDB(array, callback) {
      var x = 0;
      if(array.length == 0) {
        callback();
        return;
      }

      var objStore = _newIDBTransaction().objectStore(view_model.objectStoreName);

      function putNext() {
        if(x < array.length) {
          objStore.put(array[x]).onsuccess = function() {
            x++;
            putNext();
          };
        } else {
          callback();
        }
      }
      putNext();

    };

    function _newIDBTransaction() {
      return view_model.idb.transaction([view_model.objectStoreName], 'readwrite');
    };

    // Only used for diagnostics:
    function wipeIndexedDB(callback) {
      var req = view_model.indexedDB.deleteDatabase(view_model.indexedDBDatabaseName);
      req.onsuccess = function(event) { callback(); }
    };

    /* --------------- Utilities --------------- */

    function _generateTimestamp() {
      var d = new Date();
      return d.toISOString();
    };

    // (v4) With thanks to http://stackoverflow.com/a/8809472/3381433
    function _generateUUID() {
      var d = new Date().getTime();
      if(window.performance && typeof window.performance.now === "function"){
          d += performance.now(); // use high-precision timer if available
      }
      var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
          var r = (d + Math.random()*16)%16 | 0;
          d = Math.floor(d/16);
          return (c=='x' ? r : (r&0x3|0x8)).toString(16);
      });
      return uuid;
    };

    function _stripAngularHashKeys(array) {
      for(var i=0; i<array.length; i++) delete array[i].$$hashKey;
      return array;
    };

    /* --------------- Sync Loop -------------- */

    if(view_model.autoSync > 0 && parseInt(view_model.autoSync) === view_model.autoSync) {
      (function syncLoop() {
        setTimeout(function() {
          sync(function(response) {
            _notifyObservers(response);
          });
          syncLoop();
        }, view_model.autoSync);
      })();
    }

  });
<commit_msg>Tidy up<commit_after>'use strict';

angular.module('offlineApp').service('offlineService', function($http) {

    var view_model = this;

    /* --------------- Configuration --------------- */

    // API Parameters
    view_model.readAPI = "http://188.166.147.80/get?after=";
    view_model.updateAPI = "http://188.166.147.80/post";
    view_model.createAPI = "http://188.166.147.80/post";
    view_model.primaryKeyProperty = "id";
    view_model.timestampProperty = "timestamp";

    // Default Config:
    view_model.autoSync = 10000; /* Set to zero for no auto synchronisation */
    view_model.pushSync = false;
    view_model.initialSync = true;
    view_model.allowIndexedDB = true; /* Switching to false disables IndexedDB */
    view_model.allowRemote = true;

    // IndexedDB Config:
    view_model.indexedDBDatabaseName = "localDB-2";
    view_model.indexedDBVersionNumber = 177; /* Increment this to wipe and reset IndexedDB */
    view_model.objectStoreName = "testObjectStore";

    /* --------------- Offlinify Internals --------------- */

    // Service Variables
    view_model.idb = null;
    view_model.serviceDB = []; /* Local image of the data */
    view_model.observerCallbacks = [];
    view_model.lastChecked = new Date("1970-01-01T00:00:00.000Z").toISOString(); /* Initially the epoch */

    // Public Functions
    view_model.registerController = registerController;
    view_model._generateTimestamp = _generateTimestamp;
    view_model.sync = sync;
    view_model.objectUpdate = objectUpdate;
    view_model.wipeIndexedDB = wipeIndexedDB;

    // Determine IndexedDB Support
    view_model.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
    if(!view_model.allowIndexedDB) view_model.indexedDB = null;

    /* --------------- Create & Update --------------- */

    // Filters create or update ops by queue state:
    function objectUpdate(obj) {
      _.set(obj, view_model.timestampProperty, _generateTimestamp());
      obj = _stripAngularHashKeys(obj);
      if(obj.hasOwnProperty("syncState")) {
        if(obj.syncState > 0) { obj.syncState = 2; }
      } else {
        obj = _.cloneDeep(obj);
        obj.syncState = 0;
        _.set(obj, view_model.primaryKeyProperty, _generateUUID());
      }
      _patchLocal(_stripAngularHashKeys([obj]), function(response) {
        if(view_model.pushSync) sync(_notifyObservers);
      });
     };

     /* --------------- Observer Pattern --------------- */

     // Called by the controller to receive updates (observer pattern)
    function registerController(ctrlCallback) {
       ensureIDBReady(function() {
         view_model.observerCallbacks.push(ctrlCallback);
         if(!view_model.initialSync) return;
         view_model.sync(function(response) {
           ctrlCallback(response);
         });
       });
     };

     function ensureIDBReady(callback) {
        if(view_model.idb == null)
          _establishIndexedDB(function() { callback(); });
        else
          callback();
     };

    /* --------------- Synchronisation --------------- */

    // Restores local state on first sync, or patches local and remote changes:
    function sync(callback) {
      var startClock = _generateTimestamp();
      var newLocalRecords = _getLocalRecords(view_model.lastChecked);
      if( newLocalRecords.length == 0 && view_model.serviceDB.length == 0 ) {
        _restoreLocalState( function(localResponse) {
          _patchRemoteChanges(function(remoteResponse) {
            _reduceQueue(function(queueResponse) {
              callback( {dataSource: remoteResponse, currentQueue: queueResponse} );
              console.log("Initial load took: " + (new Date(_generateTimestamp()) - new Date(startClock))/1000 + " sec." );
            });
          });
        });
      } else {
        _patchRemoteChanges(function(remoteResponse) {
          _reduceQueue(function(queueResponse) {
            callback( { dataSource: remoteResponse, currentQueue: queueResponse } );
          });
        });
      }
    };

    // Patches remote edits to serviceDB + IndexedDB:
    function _patchRemoteChanges(callback) {
      if(!view_model.allowRemote) {
          if(_hasIndexedDB()) callback(-1);
          else callback(-2);
          return;
      }
      _getRemoteRecords(function(response) {
        if(response.status == 200) {
          _patchLocal(response.data, function(localResponse) {
            callback(localResponse);
          });
        } else { callback("Could not connect to remote server: (" + response.status + ") error."); }
      });
    };

    // Patches the local storages with a dataset.
    function _patchLocal(data, callback) {
      _patchServiceDB(data);
      view_model.lastChecked = _generateTimestamp();
      if( _hasIndexedDB() ) {
        _putArrayToIndexedDB(data, function() {
          callback(1); // Patched to IDB + ServiceDB
        });
      } else {
        callback(0); // Patched to ServiceDB only.
      }
    };

    function _notifyObservers(status) {
      angular.forEach(view_model.observerCallbacks, function(callback){
        callback(status);
      });
    };

    /* --------------- Queue + State --------------- */

    // Puts IndexedDB store into scope:
    function _restoreLocalState(callback) {
      if(!_hasIndexedDB()) { callback(-1); return; }
      _getIndexedDB(function(idbRecords) {

        var sortedElements = _.reverse(_.sortBy(idbRecords, function(o) {
          return new Date(_.get(o, view_model.timestampProperty)).toISOString();
        }));
        var nonQueueElements = _.filter(sortedElements, {syncState: 1});
        var queueElements = _.filter(sortedElements, function(o) { return o.syncState != 1; });

        if(nonQueueElements.length > 0) {
          view_model.lastChecked = _.get(sortedElements[0], view_model.timestampProperty);
        } else {
          if(queueElements.length > 0)
            view_model.lastChecked = _.get(queueElements[queueElements.length - 1], view_model.timestampProperty);
        }

        _patchServiceDB(idbRecords);
        callback(idbRecords.length);
      });
    };

    // Synchronises elements to remote when connection is available:
    function _reduceQueue(callback) {
      if(!view_model.allowRemote) { callback(-1); return; }

      var createQueue = _.filter(view_model.serviceDB, { "syncState" : 0 });
      var updateQueue = _.filter(view_model.serviceDB, { "syncState" : 2 });

      // Get rid of the create queue:
      _safeArrayPost(createQueue, view_model.createAPI, function(successfulCreates) {
        _safeArrayPost(updateQueue, view_model.updateAPI, function(successfulUpdates) {

          var totalQueue = successfulCreates.concat(successfulUpdates);
          _.forEach(totalQueue, function(value) {
            _.set(value, view_model.timestampProperty, _generateTimestamp());
          });

          _patchLocal(_resetSyncState(totalQueue), function(response) {

            var queueLength = updateQueue.length + createQueue.length;
            var popLength = successfulCreates.length + successfulUpdates.length;

            // Check here for integrity:
            if( queueLength == popLength ) {
              callback(1); // Success: All queue elements synchronised.
            } else {
              callback(0); // Partial success: Some or none were synchronised.
            }

          });
        });
      });

    };

    /* --------------- ServiceDB Interface --------------- */

    function _patchServiceDB(data) {
      var operations = _filterOperations(data);
      _updatesToServiceDB(operations.updateOperations);
      _pushToServiceDB(operations.createOperations);
    };

    function _pushToServiceDB(array) {
      for(var i=0; i<array.length; i++) view_model.serviceDB.push(array[i]);
    };

    function _updatesToServiceDB(array) {
      for(var i=0; i<array.length; i++) {
        var indexJSON = {};
        _.set(indexJSON, view_model.primaryKeyProperty, _.get(array[i], view_model.primaryKeyProperty));
        var matchID = _.findIndex(view_model.serviceDB, indexJSON);
        if(matchID > -1) view_model.serviceDB[matchID] = array[i];
      }
    };

    function _getLocalRecords(sinceTime) {
      return _.filter(view_model.serviceDB, function(o) {
        return new Date(_.get(o, view_model.timestampProperty)).toISOString() > sinceTime;
      });
    };

    /* --------------- Data Handling --------------- */

    /* Filter remote data into create or update operations */
    function _filterOperations(data) {
      var updateOps = [];
      var createOps = [];
      for(var i=0; i<data.length; i++) {
        var queryJSON = {};
        _.set(queryJSON, view_model.primaryKeyProperty, _.get(data[i], view_model.primaryKeyProperty));
        var query = _.findIndex(view_model.serviceDB, queryJSON);
        if( query > -1 ) updateOps.push(data[i]);
        else createOps.push(data[i]);
      }
      return { updateOperations: updateOps, createOperations: createOps };
    }

    function _resetSyncState(records) {
      for(var i=0; i<records.length; i++) {
        records[i].syncState = 1;
      }
      return records;
    };

    /* --------------- Remote --------------- */

    function _postRemote(data, url, callback) {
      // Data should be a single record.
      $http({
          url: url,
          method: "POST",
          data: [data],
          headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }
      })
      .then(
        function successCallback(response) {
          callback(response.status); // return response code.
        }, function errorCallback(response) {
          callback(response.status);
        });
    };

    function _getRemoteRecords(callback) {
      $http({
          method: 'GET',
          url: view_model.readAPI + view_model.lastChecked
        })
        .then(
          function successCallback(response) {
            if(response.data.length > 0)
              callback({data: _resetSyncState(response.data), status: 200});
            else
              callback({data: [], status: 200});

        }, function errorCallback(response) {
            callback({data: [], status: response.status});
        });
    };

    // Tries to post an array one-by-one; returns successful elements.
    function _safeArrayPost(array, url, callback) {
      var x = 0;
      var successfulElements = [];
      if(array.length == 0) { callback([]); return; }
      function loopArray(array) {
        _postRemote(array[x],url,function(response) {
          if(response == 200) successfulElements.push(array[x]);
          x++;
          if(x < array.length) { loopArray(array); }
          else { callback(successfulElements); }
        });
      };
      loopArray(array);
    };

    /* --------------- IndexedDB --------------- */

    function _establishIndexedDB(callback) {
      if(!_hasIndexedDB()) { callback(); /* No browser support for IDB */ return; }
      var request = view_model.indexedDB.open(view_model.indexedDBDatabaseName, view_model.indexedDBVersionNumber);
      request.onupgradeneeded = function(e) {
        var db = e.target.result;
        e.target.transaction.onerror = function() { console.error(this.error); };
        if(db.objectStoreNames.contains(view_model.objectStoreName)) {
          db.deleteObjectStore(view_model.objectStoreName);
        }
        var offlineItems = db.createObjectStore(view_model.objectStoreName, { keyPath: view_model.primaryKeyProperty, autoIncrement: false } );
        var dateIndex = offlineItems.createIndex("byDate", view_model.timestampProperty, {unique: false});
        view_model.idb = db;
      };
      request.onsuccess = function(e) {
        view_model.idb = e.target.result;
        callback();
      };
      request.onerror = function() { console.error(this.error); };
    };

    function _hasIndexedDB() {
      return !(view_model.indexedDB === undefined || view_model.indexedDB === null );
    };

    // Get from IndexedDB. This function returns appropriate records.
    function _getIndexedDB(callback) {
      var transaction = _newIDBTransaction();
      var objStore = transaction.objectStore(view_model.objectStoreName);
      var keyRange = IDBKeyRange.lowerBound(0);
      var cursorRequest = objStore.index('byDate').openCursor(keyRange);
      var returnableItems = [];
      transaction.oncomplete = function(e) { callback(_stripAngularHashKeys(returnableItems)); };
      cursorRequest.onsuccess = function(e) {
        var result = e.target.result;
        if (!!result == false) { return; }
        returnableItems.push(result.value);
        result.continue();
      };
      cursorRequest.onerror = function() { console.error("error"); };
    };

    // Apply array of edited objects to IndexedDB.
    function _putArrayToIndexedDB(array, callback) {
      var x = 0;
      if(array.length == 0) {
        callback();
        return;
      }

      var objStore = _newIDBTransaction().objectStore(view_model.objectStoreName);

      function putNext() {
        if(x < array.length) {
          objStore.put(array[x]).onsuccess = function() {
            x++;
            putNext();
          };
        } else {
          callback();
        }
      }
      putNext();

    };

    function _newIDBTransaction() {
      return view_model.idb.transaction([view_model.objectStoreName], 'readwrite');
    };

    // Only used for diagnostics:
    function wipeIndexedDB(callback) {
      var req = view_model.indexedDB.deleteDatabase(view_model.indexedDBDatabaseName);
      req.onsuccess = function(event) { callback(); }
    };

    /* --------------- Utilities --------------- */

    function _generateTimestamp() {
      var d = new Date();
      return d.toISOString();
    };

    // (v4) With thanks to http://stackoverflow.com/a/8809472/3381433
    function _generateUUID() {
      var d = new Date().getTime();
      if(window.performance && typeof window.performance.now === "function"){
          d += performance.now(); // use high-precision timer if available
      }
      var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
          var r = (d + Math.random()*16)%16 | 0;
          d = Math.floor(d/16);
          return (c=='x' ? r : (r&0x3|0x8)).toString(16);
      });
      return uuid;
    };

    function _stripAngularHashKeys(array) {
      for(var i=0; i<array.length; i++) delete array[i].$$hashKey;
      return array;
    };

    /* --------------- Sync Loop -------------- */

    if(view_model.autoSync > 0 && parseInt(view_model.autoSync) === view_model.autoSync) {
      (function syncLoop() {
        setTimeout(function() {
          sync(function(response) {
            _notifyObservers(response);
          });
          syncLoop();
        }, view_model.autoSync);
      })();
    }

  });
<|endoftext|>


<commit_before>const qtek = require('../../../dist/qtek');
const { util, helper } = require('../../common');
const path = require('path');

describe('Integration.Anistropic.Spec', function () {
    it('texture anistropic', function (done) {
        const { renderer, scene, camera, canvas } = helper.createQtekScene({
            size : [600, 600],
            cameraPosition : [0, 4, 14]
        });
        camera.lookAt(new qtek.math.Vector3(0, 1, 0));
        camera.far = 500;

        const texture = qtek.util.texture.loadTexture(
            path.join(__dirname, 'textures', 'ground_tile.jpg'),
            function () {
                texture.wrapS = qtek.Texture.REPEAT;
                texture.wrapT = qtek.Texture.REPEAT;
                texture.anisotropic = 4;

                const shader = qtek.shader.library.get("qtek.basic", "diffuseMap");

                const material = new qtek.Material({
                    shader : shader
                });
                material.set("diffuseMap", texture);
                material.set("uvRepeat", [10, 10])

                const root = new qtek.Node();
                scene.add(root);

                const plane = new qtek.geometry.Plane({
                    widthSegments : 1,
                    heightSegments : 1
                });
                const planeMesh = new qtek.Mesh({
                    geometry : plane,
                    material : material,
                    scale : new qtek.math.Vector3(30, 30, 30)
                });

                planeMesh.rotation.rotateX(-Math.PI/2);
                root.add(planeMesh);

                const drawInfo = renderer.render(scene, camera);

                util.assertWith(canvas, { fixture : path.join(__dirname, 'anistropic.png') }, done);
            });

    });
});<commit_msg>update anistropic spec<commit_after>const qtek = require('../../../dist/qtek');
const { util, helper } = require('../../common');
const path = require('path');

describe('Integration.Anistropic.Spec', function () {
    it('texture anistropic', function (done) {
        const { renderer, scene, camera, canvas } = helper.createQtekScene({
            size : [600, 600],
            cameraPosition : [0, 4, 14]
        });
        if (!renderer.getGLExtension('EXT_texture_filter_anisotropic')) {
            done();
        }
        camera.lookAt(new qtek.math.Vector3(0, 1, 0));
        camera.far = 500;

        const texture = qtek.util.texture.loadTexture(
            path.join(__dirname, 'textures', 'ground_tile.jpg'),
            function () {
                texture.wrapS = qtek.Texture.REPEAT;
                texture.wrapT = qtek.Texture.REPEAT;
                texture.anisotropic = 4;

                const shader = qtek.shader.library.get("qtek.basic", "diffuseMap");

                const material = new qtek.Material({
                    shader : shader
                });
                material.set("diffuseMap", texture);
                material.set("uvRepeat", [10, 10])

                const root = new qtek.Node();
                scene.add(root);

                const plane = new qtek.geometry.Plane({
                    widthSegments : 1,
                    heightSegments : 1
                });
                const planeMesh = new qtek.Mesh({
                    geometry : plane,
                    material : material,
                    scale : new qtek.math.Vector3(30, 30, 30)
                });

                planeMesh.rotation.rotateX(-Math.PI/2);
                root.add(planeMesh);

                const drawInfo = renderer.render(scene, camera);

                util.assertWith(canvas, { fixture : path.join(__dirname, 'anistropic.png') }, done);
            });

    });
});<|endoftext|>


<commit_before><commit_msg>fix var name...<commit_after><|endoftext|>


<commit_before>var RCSS = require("rcss");
var _ = require("underscore");

var styleVars = require("./style-vars.js");

var gridSpacing = 15;
var nWide = 5;
var baseComponentSize = (styleVars.dashboardWidthPx - (nWide + 1) *
                         gridSpacing)/nWide;

var widgetStyleBase = {
    position: 'relative',
    marginTop: gridSpacing + 'px',
    marginLeft: gridSpacing + 'px',
    float: 'left',
    width: baseComponentSize + 'px',
    height: baseComponentSize + 'px',
    overflow: 'hidden',
}

var doubleWideWidget = _.defaults({
    width: (2*baseComponentSize + gridSpacing) + 'px',
}, widgetStyleBase);

var doubleTallWidget = _.defaults({
    height: (2*baseComponentSize + gridSpacing) + 'px',
}, widgetStyleBase);

var doubleDoubleWidget = _.defaults({
    width: (2*baseComponentSize + gridSpacing) + 'px',
    height: (2*baseComponentSize + gridSpacing) + 'px',
}, widgetStyleBase);

module.exports = {
    singleSize: RCSS.registerClass(widgetStyleBase),
    doubleWide: RCSS.registerClass(doubleWideWidget),
    doubleTall: RCSS.registerClass(doubleTallWidget),
    doubleDouble: RCSS.registerClass(doubleDoubleWidget),
}
<commit_msg>Fix widget sizes to actually fit a 5x3 grid.<commit_after>var RCSS = require("rcss");
var _ = require("underscore");

var styleVars = require("./style-vars.js");

var gridSpacing = 15;
var nWide = 5;
var nTall = 3;
var baseComponentWidth = (styleVars.dashboardWidthPx - (nWide + 1) *
                          gridSpacing)/nWide;
var baseComponentHeight = (styleVars.dashboardHeightPx - (nTall + 1) *
                          gridSpacing)/nTall;

var widgetStyleBase = {
    position: 'relative',
    marginTop: gridSpacing + 'px',
    marginLeft: gridSpacing + 'px',
    float: 'left',
    width: baseComponentWidth + 'px',
    height: baseComponentHeight + 'px',
    overflow: 'hidden',
}

var doubleWideWidget = _.defaults({
    width: (2*baseComponentWidth + gridSpacing) + 'px',
}, widgetStyleBase);

var doubleTallWidget = _.defaults({
    height: (2*baseComponentHeight + gridSpacing) + 'px',
}, widgetStyleBase);

var doubleDoubleWidget = _.defaults({
    width: (2*baseComponentWidth + gridSpacing) + 'px',
    height: (2*baseComponentHeight + gridSpacing) + 'px',
}, widgetStyleBase);

module.exports = {
    singleSize: RCSS.registerClass(widgetStyleBase),
    doubleWide: RCSS.registerClass(doubleWideWidget),
    doubleTall: RCSS.registerClass(doubleTallWidget),
    doubleDouble: RCSS.registerClass(doubleDoubleWidget),
}
<|endoftext|>


<commit_before>var browserUI = require('api-wrapper.js')
var focusMode = require('focusMode.js')

var taskContainer = document.getElementById('task-area')
var taskSwitcherButton = document.getElementById('switch-task-button')
var addTaskButton = document.getElementById('add-task')
var addTaskLabel = addTaskButton.querySelector('span')
var taskOverlayNavbar = document.getElementById('task-overlay-navbar')

taskSwitcherButton.title = l('viewTasks')
addTaskLabel.textContent = l('newTask')

taskSwitcherButton.addEventListener('click', function () {
  taskOverlay.toggle()
})

addTaskButton.addEventListener('click', function (e) {
  browserUI.switchToTask(tasks.add())
  taskOverlay.hide()
})

taskOverlayNavbar.addEventListener('click', function () {
  taskOverlay.hide()
})

var dragula = require('dragula')

window.taskOverlay = {
  overlayElement: document.getElementById('task-overlay'),
  isShown: false,
  tabDragula: dragula({
    direction: 'vertical'
  }),
  taskDragula: dragula({
    direction: 'vertical',
    containers: [taskContainer],
    moves: function (el, source, handle, sibling) {
      // ignore drag events that come from a tab element, since they will be handled by the other dragula instance
      // also ignore inputs, since using them as drag handles breaks text selection
      var n = handle
      while (n) {
        if (n.classList.contains('task-tab-item') || n.tagName === 'INPUT') {
          return false
        }
        n = n.parentElement
      }
      return true
    }
  }),
  show: function () {
    /* disabled in focus mode */
    if (focusMode.enabled()) {
      focusMode.warn()
      return
    }

    webviews.requestPlaceholder('taskOverlay')

    document.body.classList.add('task-overlay-is-shown')

    tabBar.leaveEditMode()

    this.isShown = true
    taskSwitcherButton.classList.add('active')

    this.tabDragula.containers = []
    empty(taskContainer)

    // show the task elements
    tasks.get().forEach(function (task, index) {
      var el = window.task_container_build_func(task, index)

      taskContainer.appendChild(el)
      taskOverlay.tabDragula.containers.push(el.getElementsByClassName('task-tabs-container')[0])
    })

    // scroll to the selected element and focus it

    var currentTabElement = document.querySelector('.task-tab-item[data-tab="{id}"]'.replace('{id}', currentTask.tabs.getSelected()))

    if (currentTabElement) {
      currentTabElement.scrollIntoViewIfNeeded()
      currentTabElement.classList.add('fakefocus')
    }

    // un-hide the overlay
    this.overlayElement.hidden = false
  },

  hide: function () {
    if (this.isShown) {
      this.isShown = false
      this.overlayElement.hidden = true

      // wait until the animation is complete to remove the tab elements
      setTimeout(function () {
        if (!taskOverlay.isShown) {
          empty(taskContainer)
          webviews.hidePlaceholder('taskOverlay')
        }
      }, 200)

      this.tabDragula.containers = []

      document.body.classList.remove('task-overlay-is-shown')

      // if the current tab has been deleted, switch to the most recent one

      if (!tabs.getSelected()) {
        var mostRecentTab = tabs.get().sort(function (a, b) {
          return b.lastActivity - a.lastActivity
        })[0]

        if (mostRecentTab) {
          browserUI.switchToTab(mostRecentTab.id)
        }
      }

      taskSwitcherButton.classList.remove('active')
    }
  },

  toggle: function () {
    if (this.isShown) {
      this.hide()
    } else {
      this.show()
    }
  }
}

// swipe down on the tabstrip to show the task overlay
// this was the old expanded mode gesture, so it's remapped to the overlay
document.getElementById('navbar').addEventListener('mousewheel', function (e) {
  if (e.deltaY < -30 && e.deltaX < 10) {
    taskOverlay.show()
    e.stopImmediatePropagation()
  }
})

function getTaskContainer (id) {
  return document.querySelector('.task-container[data-task="{id}"]'.replace('{id}', id))
}

/* rearrange tabs when they are dropped */

taskOverlay.tabDragula.on('drop', function (el, target, source, sibling) { // see https://github.com/bevacqua/dragula#drakeon-events
  var tabId = el.getAttribute('data-tab')
  if (sibling) {
    var adjacentTadId = sibling.getAttribute('data-tab')
  }

  var previousTask = tasks.get(source.getAttribute('data-task'))
  var newTask = tasks.get(target.getAttribute('data-task'))

  // remove tab from old task
  var oldTab = previousTask.tabs.splice(previousTask.tabs.getIndex(tabId), 1)[0]

  // if the old task has no tabs left in it, destroy it

  if (previousTask.tabs.length === 0) {
    browserUI.closeTask(previousTask.id)
    getTaskContainer(previousTask.id).remove()
  }

  // find where in the new task the tab should be inserted
  if (adjacentTadId) {
    var newIdx = newTask.tabs.getIndex(adjacentTadId)
  } else {
    // tab was inserted at end
    var newIdx = newTask.tabs.length
  }

  // insert the tab at the correct spot
  newTask.tabs.splice(newIdx, 0, oldTab)
})

/* rearrange tasks when they are dropped */

taskOverlay.taskDragula.on('drop', function (el, target, source, sibling) {
  var droppedTaskId = el.getAttribute('data-task')
  if (sibling) {
    var adjacentTaskId = sibling.getAttribute('data-task')
  }

  // remove the task from the tasks list
  var droppedTask = tasks.splice(tasks.getIndex(droppedTaskId), 1)[0]

  // find where it should be inserted
  if (adjacentTaskId) {
    var newIdx = tasks.getIndex(adjacentTaskId)
  } else {
    var newIdx = tasks.length
  }

  // reinsert the task
  tasks.splice(newIdx, 0, droppedTask)
})
<commit_msg>rerender tabstrip when dragging tabs<commit_after>var browserUI = require('api-wrapper.js')
var focusMode = require('focusMode.js')

var taskContainer = document.getElementById('task-area')
var taskSwitcherButton = document.getElementById('switch-task-button')
var addTaskButton = document.getElementById('add-task')
var addTaskLabel = addTaskButton.querySelector('span')
var taskOverlayNavbar = document.getElementById('task-overlay-navbar')

taskSwitcherButton.title = l('viewTasks')
addTaskLabel.textContent = l('newTask')

taskSwitcherButton.addEventListener('click', function () {
  taskOverlay.toggle()
})

addTaskButton.addEventListener('click', function (e) {
  browserUI.switchToTask(tasks.add())
  taskOverlay.hide()
})

taskOverlayNavbar.addEventListener('click', function () {
  taskOverlay.hide()
})

var dragula = require('dragula')

window.taskOverlay = {
  overlayElement: document.getElementById('task-overlay'),
  isShown: false,
  tabDragula: dragula({
    direction: 'vertical'
  }),
  taskDragula: dragula({
    direction: 'vertical',
    containers: [taskContainer],
    moves: function (el, source, handle, sibling) {
      // ignore drag events that come from a tab element, since they will be handled by the other dragula instance
      // also ignore inputs, since using them as drag handles breaks text selection
      var n = handle
      while (n) {
        if (n.classList.contains('task-tab-item') || n.tagName === 'INPUT') {
          return false
        }
        n = n.parentElement
      }
      return true
    }
  }),
  show: function () {
    /* disabled in focus mode */
    if (focusMode.enabled()) {
      focusMode.warn()
      return
    }

    webviews.requestPlaceholder('taskOverlay')

    document.body.classList.add('task-overlay-is-shown')

    tabBar.leaveEditMode()

    this.isShown = true
    taskSwitcherButton.classList.add('active')

    this.tabDragula.containers = []
    empty(taskContainer)

    // show the task elements
    tasks.get().forEach(function (task, index) {
      var el = window.task_container_build_func(task, index)

      taskContainer.appendChild(el)
      taskOverlay.tabDragula.containers.push(el.getElementsByClassName('task-tabs-container')[0])
    })

    // scroll to the selected element and focus it

    var currentTabElement = document.querySelector('.task-tab-item[data-tab="{id}"]'.replace('{id}', currentTask.tabs.getSelected()))

    if (currentTabElement) {
      currentTabElement.scrollIntoViewIfNeeded()
      currentTabElement.classList.add('fakefocus')
    }

    // un-hide the overlay
    this.overlayElement.hidden = false
  },

  hide: function () {
    if (this.isShown) {
      this.isShown = false
      this.overlayElement.hidden = true

      // wait until the animation is complete to remove the tab elements
      setTimeout(function () {
        if (!taskOverlay.isShown) {
          empty(taskContainer)
          webviews.hidePlaceholder('taskOverlay')
        }
      }, 200)

      this.tabDragula.containers = []

      document.body.classList.remove('task-overlay-is-shown')

      // if the current tab has been deleted, switch to the most recent one

      if (!tabs.getSelected()) {
        var mostRecentTab = tabs.get().sort(function (a, b) {
          return b.lastActivity - a.lastActivity
        })[0]

        if (mostRecentTab) {
          browserUI.switchToTab(mostRecentTab.id)
        }
      }

      taskSwitcherButton.classList.remove('active')
    }
  },

  toggle: function () {
    if (this.isShown) {
      this.hide()
    } else {
      this.show()
    }
  }
}

// swipe down on the tabstrip to show the task overlay
// this was the old expanded mode gesture, so it's remapped to the overlay
document.getElementById('navbar').addEventListener('mousewheel', function (e) {
  if (e.deltaY < -30 && e.deltaX < 10) {
    taskOverlay.show()
    e.stopImmediatePropagation()
  }
})

function getTaskContainer (id) {
  return document.querySelector('.task-container[data-task="{id}"]'.replace('{id}', id))
}

/* rearrange tabs when they are dropped */

taskOverlay.tabDragula.on('drop', function (el, target, source, sibling) { // see https://github.com/bevacqua/dragula#drakeon-events
  var tabId = el.getAttribute('data-tab')
  if (sibling) {
    var adjacentTadId = sibling.getAttribute('data-tab')
  }

  var previousTask = tasks.get(source.getAttribute('data-task'))
  var newTask = tasks.get(target.getAttribute('data-task'))

  // remove tab from old task
  var oldTab = previousTask.tabs.splice(previousTask.tabs.getIndex(tabId), 1)[0]

  // if the old task has no tabs left in it, destroy it

  if (previousTask.tabs.length === 0) {
    browserUI.closeTask(previousTask.id)
    getTaskContainer(previousTask.id).remove()
  }

  // find where in the new task the tab should be inserted
  if (adjacentTadId) {
    var newIdx = newTask.tabs.getIndex(adjacentTadId)
  } else {
    // tab was inserted at end
    var newIdx = newTask.tabs.length
  }

  // insert the tab at the correct spot
  newTask.tabs.splice(newIdx, 0, oldTab)

  // update the visible tabs
  tabBar.rerenderAll()
})

/* rearrange tasks when they are dropped */

taskOverlay.taskDragula.on('drop', function (el, target, source, sibling) {
  var droppedTaskId = el.getAttribute('data-task')
  if (sibling) {
    var adjacentTaskId = sibling.getAttribute('data-task')
  }

  // remove the task from the tasks list
  var droppedTask = tasks.splice(tasks.getIndex(droppedTaskId), 1)[0]

  // find where it should be inserted
  if (adjacentTaskId) {
    var newIdx = tasks.getIndex(adjacentTaskId)
  } else {
    var newIdx = tasks.length
  }

  // reinsert the task
  tasks.splice(newIdx, 0, droppedTask)
})
<|endoftext|>


<commit_before>var fs = require('fs');
var minimist = require('minimist');

var common = require('../../tasks/util/common');
var getMockList = require('./assets/get_mock_list');
var getRequestOpts = require('./assets/get_image_request_options');
var getImagePaths = require('./assets/get_image_paths');

// packages inside the image server docker
var test = require('tape');
var request = require('request');
var gm = require('gm');

// pixel comparison tolerance
var TOLERANCE = 1e-6;

// wait time between each test batch
var BATCH_WAIT = 500;

// number of tests in each test batch
var BATCH_SIZE = 5;

// wait time between each test in test queue
var QUEUE_WAIT = 10;

/**
 *  Image pixel comparison test script.
 *
 *  Called by `tasks/test_image.sh in `npm run test-image`.
 *
 *  CLI arguments:
 *
 *  1. 'pattern' : glob determining which mock(s) are to be tested
 *  2. --queue : if sent, the image will be run in queue instead of in batch.
 *      Makes the test run significantly longer, but is recommended on weak hardware.
 *
 *  Examples:
 *
 *  Run all tests in batch:
 *
 *      npm run test-image
 *
 *  Run the 'contour_nolines' test:
 *
 *      npm run test-image -- contour_nolines
 *
 *  Run all gl3d image test in queue:
 *
 *      npm run test-image -- gl3d_* --queue
 *
 *
 */

var argv = minimist(process.argv.slice(2), {
    boolean: ['queue', 'filter', 'skip-flaky', 'just-flaky']
});

var allMock = false;
// If no pattern is provided, all mocks are compared
if(argv._.length === 0) {
    allMock = true;
    argv._.push('');
}

// Build list of mocks to compare
var allMockList = [];
argv._.forEach(function(pattern) {
    var mockList = getMockList(pattern);

    if(mockList.length === 0) {
        throw new Error('No mocks found with pattern ' + pattern);
    }

    allMockList = allMockList.concat(mockList);
});

// To get rid of duplicates
function unique(value, index, self) {
    return self.indexOf(value) === index;
}
allMockList = allMockList.filter(unique);

// filter out untestable mocks if no pattern is specified (ie. we're testing all mocks)
// or if flag '--filter' is provided
console.log('');
if(allMock || argv.filter) {
    console.log('Filtering out untestable mocks:');
    // Test cases:
    // - font-wishlist
    // - all mapbox
    // don't behave consistently from run-to-run and/or
    // machine-to-machine; skip over them for now.
    allMockList = allMockList.filter(function(mockName) {
        var cond = !(
            mockName === 'font-wishlist' ||
            mockName.indexOf('mapbox_') !== -1
        );
        if(!cond) console.log(' -', mockName);
        return cond;
    });
}

var FLAKY_LIST = [
    'treemap_coffee',
    'treemap_textposition',
    'treemap_with-without_values',
    'gl3d_directions-streamtube1',
];

console.log('');
if(argv['skip-flaky']) {
    allMockList = allMockList.filter(function(mockName) {
        var cond = FLAKY_LIST.indexOf(mockName) === -1;
        if(!cond) console.log('Skipping flaky mock', mockName);
        return cond;
    });
} else if(argv['just-flaky']) {
    allMockList = allMockList.filter(function(mockName) {
        return FLAKY_LIST.indexOf(mockName) !== -1;
    });
}

/* gl2d pointcloud and other non-regl gl2d mock(s)
 * must be tested first on in order to work;
 * sort them here.
 *
 * gl-shader appears to conflict with regl.
 * We suspect that the lone gl context on CircleCI is
 * having issues with dealing with the two different
 * program binding algorithm.
 *
 * The problem will be solved by switching all our
 * WebGL-based trace types to regl.
 *
 * More info here:
 * https://github.com/plotly/plotly.js/pull/1037
 */
function sortGl2dMockList(mockList) {
    var mockNames = ['gl2d_pointcloud-basic', 'gl2d_heatmapgl'];
    var pos = 0;

    mockNames.forEach(function(m) {
        var ind = mockList.indexOf(m);
        if(ind === -1) return;
        var tmp = mockList[pos];
        mockList[pos] = m;
        mockList[ind] = tmp;
        pos++;
    });
}

function runInBatch(mockList) {
    var running = 0;

    test('testing mocks in batch', function(t) {
        t.plan(mockList.length);

        for(var i = 0; i < mockList.length; i++) {
            run(mockList[i], t);
        }
    });

    function run(mockName, t) {
        if(running >= BATCH_SIZE) {
            setTimeout(function() {
                run(mockName, t);
            }, BATCH_WAIT);
            return;
        }
        running++;

        // throttle the number of tests running concurrently

        comparePixels(mockName, function(isEqual, mockName) {
            running--;
            t.ok(isEqual, mockName + ' should be pixel perfect');
        });
    }
}

function runInQueue(mockList) {
    var index = 0;

    test('testing mocks in queue', function(t) {
        t.plan(mockList.length);

        run(mockList[index], t);
    });

    function run(mockName, t) {
        comparePixels(mockName, function(isEqual, mockName) {
            t.ok(isEqual, mockName + ' should be pixel perfect');

            index++;
            if(index < mockList.length) {
                setTimeout(function() {
                    run(mockList[index], t);
                }, QUEUE_WAIT);
            }
        });
    }
}

function comparePixels(mockName, cb) {
    var requestOpts = getRequestOpts({ mockName: mockName });
    var imagePaths = getImagePaths(mockName);
    var saveImageStream = fs.createWriteStream(imagePaths.test);

    function log(msg) {
        process.stdout.write('Error for', mockName + ':', msg);
    }

    function checkImage() {
        // baseline image must be generated first
        if(!common.doesFileExist(imagePaths.baseline)) {
            var err = new Error('baseline image not found');
            return onEqualityCheck(err, false);
        }

        /*
         * N.B. The non-zero tolerance was added in
         * https://github.com/plotly/plotly.js/pull/243
         * where some legend mocks started generating different png outputs
         * on `npm run test-image` and `npm run test-image -- mock.json`.
         *
         * Note that the svg outputs for the problematic mocks were the same
         * and playing around with the batch size and timeout durations
         * did not seem to affect the results.
         *
         * With the above tolerance individual `npm run test-image` and
         * `npm run test-image -- mock.json` give the same result.
         *
         * Further investigation is needed.
         */

        var gmOpts = {
            file: imagePaths.diff,
            highlightColor: 'purple',
            tolerance: TOLERANCE
        };

        gm.compare(
            imagePaths.test,
            imagePaths.baseline,
            gmOpts,
            onEqualityCheck
        );
    }

    function onEqualityCheck(err, isEqual) {
        if(err) {
            common.touch(imagePaths.diff);
            log(err);
            return cb(false, mockName);
        }
        if(isEqual) {
            fs.unlinkSync(imagePaths.diff);
        }

        cb(isEqual, mockName);
    }

    // 525 means a plotly.js error
    function onResponse(response) {
        if(+response.statusCode === 525) {
            log('plotly.js error');
            return cb(false, mockName);
        }
    }

    // this catches connection errors
    // e.g. when the image server blows up
    function onError(err) {
        log(err);
        return cb(false, mockName);
    }

    request(requestOpts)
        .on('error', onError)
        .on('response', onResponse)
        .pipe(saveImageStream)
        .on('close', checkImage);
}

sortGl2dMockList(allMockList);
console.log('');

// main
if(argv.queue) {
    runInQueue(allMockList);
} else {
    runInBatch(allMockList);
}
<commit_msg>update flaky list<commit_after>var fs = require('fs');
var minimist = require('minimist');

var common = require('../../tasks/util/common');
var getMockList = require('./assets/get_mock_list');
var getRequestOpts = require('./assets/get_image_request_options');
var getImagePaths = require('./assets/get_image_paths');

// packages inside the image server docker
var test = require('tape');
var request = require('request');
var gm = require('gm');

// pixel comparison tolerance
var TOLERANCE = 1e-6;

// wait time between each test batch
var BATCH_WAIT = 500;

// number of tests in each test batch
var BATCH_SIZE = 5;

// wait time between each test in test queue
var QUEUE_WAIT = 10;

/**
 *  Image pixel comparison test script.
 *
 *  Called by `tasks/test_image.sh in `npm run test-image`.
 *
 *  CLI arguments:
 *
 *  1. 'pattern' : glob determining which mock(s) are to be tested
 *  2. --queue : if sent, the image will be run in queue instead of in batch.
 *      Makes the test run significantly longer, but is recommended on weak hardware.
 *
 *  Examples:
 *
 *  Run all tests in batch:
 *
 *      npm run test-image
 *
 *  Run the 'contour_nolines' test:
 *
 *      npm run test-image -- contour_nolines
 *
 *  Run all gl3d image test in queue:
 *
 *      npm run test-image -- gl3d_* --queue
 *
 *
 */

var argv = minimist(process.argv.slice(2), {
    boolean: ['queue', 'filter', 'skip-flaky', 'just-flaky']
});

var allMock = false;
// If no pattern is provided, all mocks are compared
if(argv._.length === 0) {
    allMock = true;
    argv._.push('');
}

// Build list of mocks to compare
var allMockList = [];
argv._.forEach(function(pattern) {
    var mockList = getMockList(pattern);

    if(mockList.length === 0) {
        throw new Error('No mocks found with pattern ' + pattern);
    }

    allMockList = allMockList.concat(mockList);
});

// To get rid of duplicates
function unique(value, index, self) {
    return self.indexOf(value) === index;
}
allMockList = allMockList.filter(unique);

// filter out untestable mocks if no pattern is specified (ie. we're testing all mocks)
// or if flag '--filter' is provided
console.log('');
if(allMock || argv.filter) {
    console.log('Filtering out untestable mocks:');
    // Test cases:
    // - font-wishlist
    // - all mapbox
    // don't behave consistently from run-to-run and/or
    // machine-to-machine; skip over them for now.
    allMockList = allMockList.filter(function(mockName) {
        var cond = !(
            mockName === 'font-wishlist' ||
            mockName.indexOf('mapbox_') !== -1
        );
        if(!cond) console.log(' -', mockName);
        return cond;
    });
}

var FLAKY_LIST = [
    'treemap_coffee',
    'treemap_sunburst_marker_colors',
    'treemap_textposition',
    'treemap_with-without_values',
    'gl3d_directions-streamtube1',
];

console.log('');
if(argv['skip-flaky']) {
    allMockList = allMockList.filter(function(mockName) {
        var cond = FLAKY_LIST.indexOf(mockName) === -1;
        if(!cond) console.log('Skipping flaky mock', mockName);
        return cond;
    });
} else if(argv['just-flaky']) {
    allMockList = allMockList.filter(function(mockName) {
        return FLAKY_LIST.indexOf(mockName) !== -1;
    });
}

/* gl2d pointcloud and other non-regl gl2d mock(s)
 * must be tested first on in order to work;
 * sort them here.
 *
 * gl-shader appears to conflict with regl.
 * We suspect that the lone gl context on CircleCI is
 * having issues with dealing with the two different
 * program binding algorithm.
 *
 * The problem will be solved by switching all our
 * WebGL-based trace types to regl.
 *
 * More info here:
 * https://github.com/plotly/plotly.js/pull/1037
 */
function sortGl2dMockList(mockList) {
    var mockNames = ['gl2d_pointcloud-basic', 'gl2d_heatmapgl'];
    var pos = 0;

    mockNames.forEach(function(m) {
        var ind = mockList.indexOf(m);
        if(ind === -1) return;
        var tmp = mockList[pos];
        mockList[pos] = m;
        mockList[ind] = tmp;
        pos++;
    });
}

function runInBatch(mockList) {
    var running = 0;

    test('testing mocks in batch', function(t) {
        t.plan(mockList.length);

        for(var i = 0; i < mockList.length; i++) {
            run(mockList[i], t);
        }
    });

    function run(mockName, t) {
        if(running >= BATCH_SIZE) {
            setTimeout(function() {
                run(mockName, t);
            }, BATCH_WAIT);
            return;
        }
        running++;

        // throttle the number of tests running concurrently

        comparePixels(mockName, function(isEqual, mockName) {
            running--;
            t.ok(isEqual, mockName + ' should be pixel perfect');
        });
    }
}

function runInQueue(mockList) {
    var index = 0;

    test('testing mocks in queue', function(t) {
        t.plan(mockList.length);

        run(mockList[index], t);
    });

    function run(mockName, t) {
        comparePixels(mockName, function(isEqual, mockName) {
            t.ok(isEqual, mockName + ' should be pixel perfect');

            index++;
            if(index < mockList.length) {
                setTimeout(function() {
                    run(mockList[index], t);
                }, QUEUE_WAIT);
            }
        });
    }
}

function comparePixels(mockName, cb) {
    var requestOpts = getRequestOpts({ mockName: mockName });
    var imagePaths = getImagePaths(mockName);
    var saveImageStream = fs.createWriteStream(imagePaths.test);

    function log(msg) {
        process.stdout.write('Error for', mockName + ':', msg);
    }

    function checkImage() {
        // baseline image must be generated first
        if(!common.doesFileExist(imagePaths.baseline)) {
            var err = new Error('baseline image not found');
            return onEqualityCheck(err, false);
        }

        /*
         * N.B. The non-zero tolerance was added in
         * https://github.com/plotly/plotly.js/pull/243
         * where some legend mocks started generating different png outputs
         * on `npm run test-image` and `npm run test-image -- mock.json`.
         *
         * Note that the svg outputs for the problematic mocks were the same
         * and playing around with the batch size and timeout durations
         * did not seem to affect the results.
         *
         * With the above tolerance individual `npm run test-image` and
         * `npm run test-image -- mock.json` give the same result.
         *
         * Further investigation is needed.
         */

        var gmOpts = {
            file: imagePaths.diff,
            highlightColor: 'purple',
            tolerance: TOLERANCE
        };

        gm.compare(
            imagePaths.test,
            imagePaths.baseline,
            gmOpts,
            onEqualityCheck
        );
    }

    function onEqualityCheck(err, isEqual) {
        if(err) {
            common.touch(imagePaths.diff);
            log(err);
            return cb(false, mockName);
        }
        if(isEqual) {
            fs.unlinkSync(imagePaths.diff);
        }

        cb(isEqual, mockName);
    }

    // 525 means a plotly.js error
    function onResponse(response) {
        if(+response.statusCode === 525) {
            log('plotly.js error');
            return cb(false, mockName);
        }
    }

    // this catches connection errors
    // e.g. when the image server blows up
    function onError(err) {
        log(err);
        return cb(false, mockName);
    }

    request(requestOpts)
        .on('error', onError)
        .on('response', onResponse)
        .pipe(saveImageStream)
        .on('close', checkImage);
}

sortGl2dMockList(allMockList);
console.log('');

// main
if(argv.queue) {
    runInQueue(allMockList);
} else {
    runInBatch(allMockList);
}
<|endoftext|>