<commit_before>import {Texture} from "./texture.js";
import {Shader} from "./shader.js";
import {filters} from "./filters.js";
import * as Generators from "./generators.js"

// create a VisSynthWeb Canvas from given HTML canvas element, session_url (for remote control)
export let Canvas = function(selector, session_url) {
    this.canvas=document.querySelector(selector);

    this.session_url=session_url;

    this.gl = this.canvas.getContext('experimental-webgl', { alpha: false, premultipliedAlpha: false });

    // container for filter state variables..
    // filters should not add properties outside of this.
    this._={};

    // initialize (use browser canvas size as default. may be changed by user-defined via "resolution"-filter)
    // create a template texture manually as template for future ones
    this.template = new Texture(this.gl, this.canvas.width, this.canvas.height, this.gl.RGBA, this.gl.UNSIGNED_BYTE);
    // hold a list of managed spare textures
    this.spareTextures=[];
    // hold a list of garbage collected textures
    this.tempTextures=[];
    // create default texture for simpleShader
    this.texture = this.getSpareTexture();
    // create shader registry
    this.shaders={};
    // create stack
    this.stack=[];
    this.stackUnused=[];

    // the currently running effect chain
    this.chain={};

    // states for delivery of preview, screenshots and stream recordings
    this.preview_cycle=0;
    this.preview_enabled=false;
    this.screenshot_flag=false;
    this.preview_canvas=null;
    this.mediaRecorder;
    this.recordedBlobs = [];
    this.recorderContext=null;
    this.recorderCanvas=null;

    // time for stats and time-dependent effects
    this.frame_time=0;
    this.last_time=0;

    // the proposed fps can be set to limit the rendering fps
    this.proposed_fps=0;

    // the remote control handle (for replying to remote commands)
    this.remote = null;
}

// create a texture from a given HTML element
Canvas.prototype.toTexture=function(element) {
    return Texture.fromElement(this.gl, element);
}

// render a frame,
// provide streaming / preview / screenshot output and
// update the visible canvas element.
Canvas.prototype.update=function() {
    // compute frame and animation time
    var current_time=Date.now();
    this.frame_time=this.frame_time*0.9 + (current_time-this.last_time)*0.1;
    this.last_time=current_time;

    // enqueue next update
    var update_handler=this.update.bind(this);
    if(this.proposed_fps)
      setTimeout(function(){
        requestAnimationFrame(update_handler);
      },1000/this.proposed_fps);
    else
      requestAnimationFrame(update_handler);

    // render effect chain !
    this.run_chain(current_time);

    // provide preview if requested
    if(this.preview_enabled && this.preview_cycle==1)
      this.capturePreview();
    if(this.preview_cycle==0)
      this.sendStatsAndPreview();
    this.preview_cycle^=1;

    // render final image to visible canvas.
    // update canvas size to texture size, if needed
    if(this.width!=this.texture.width || this.height!=this.texture.width)
    {
      this.width =this.texture.width;
      this.height=this.texture.height;
      this.canvas.width=this.width;
      this.canvas.height=this.height;
    }
    this.gl.viewport(0,0, this.width, this.height);
    // for some reason, picture is horizontally mirrored. Store it into the canvas the right way.
    filters.mirror_x.call(this,this);

    // release temporary textures
    this.gc();

    // take screenshot if requested
    if(this.screenshot_flag)
      this.sendScreenshot();

    // capture frame for stream sender / recorder if requested
    if(this.recorderContext)
      this.captureStream();

    return this;
}

Canvas.prototype.capturePreview=function() {
  // the preview is a downscaled image provided by the 'preview' effect
  // we crop the preview pixels out of the canvas just BEFORE canvas is finally drawn, which will redraw full resolution.
  //
  // in repsect to just downsizing the final image this has two benefits:
  //
  // 1) it is much faster, as rescaling is done in WebGL context and not by 2d context drawImage
  // TODO check if this still holds 2022. 2d context is using GPU scaling too.
  //
  // 2) The 'preview' filter may be added to any chain position manually to tap the preview image between effects
  //
  if(!this.preview_canvas)
  {
    this.preview_canvas=document.createElement('canvas');
    this.preview_canvas.width=this.preview_width;
    this.preview_canvas.height=this.preview_height;
  }
  var ctx=this.preview_canvas.getContext('2d');
  ctx.drawImage(this.canvas,0,this.height-this.preview_height,this.preview_width,this.preview_height, 0, 0, this.preview_width,this.preview_height);
}

Canvas.prototype.sendStatsAndPreview=function() {
  var jpeg=(this.preview_canvas && this.preview_enabled) ? this.preview_canvas.toDataURL('image/jpeg') : null;
  var data={frame_time:this.frame_time, jpeg:jpeg};
  var json=JSON.stringify(data);
  this.remote.put('preview',json);
}

Canvas.prototype.sendScreenshot=function() {
  var pixels=canvas.toDataURL('image/jpeg');
  this.remote.put('screenshot',pixels);
  this.screenshot_flag=false;
}

Canvas.prototype.captureStream=function() {
  // capture canvas for stream recording or WebRTC streaming
  // TODO if this is done for WebRTC only, do not do this copy if stream is not actually running.
  if(this.recorderContext) this.recorderContext.drawImage(this.canvas,0,0);

  // encode frame, if recording
  if(this.mediaRecorder) this.mediaRecorder.stream.getVideoTracks()[0].requestFrame();
}

// replace current working texture
Canvas.prototype.putTexture=function(texture)
{
    this.releaseTexture(this.texture);
    this.texture=texture;
}

// render canvas-sized rectangle to apply a given shader to every pixel
Canvas.prototype.simpleShader=function(shader, uniforms, textureIn, textureOut) {
    var texture=(textureIn  || this.texture        );
    var target =(textureOut || this.getSpareTexture());

    texture.use();
    target .setAsTarget();
    shader.uniforms(uniforms).drawRect();

    if(!textureOut)
      this.putTexture(target);
};

// set the visible canvas as render target
Canvas.prototype.setAsTarget=function(){
    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); // remove framebuffer binding left from last offscreen rendering (as set by Texture.setAsTarget)
}

// Retrieve a working texture matched to this canvas or size settings.
//
// -If a candidate texture is given, it is validated to match the current format and returned unchanged (image is kept) if so.
//  So getSpareTexture can be easily used for lazy creation.
//
// -if there is no candidate or it is not matching the current format, a valid texture is returned (either recycled or created). The image is undefined then.
//
// If the texture is for transient use, it should be freed later by releaseTexture().
// Otherwise filters are encouraged to pass their private working textures here every frame, ensuring format updates are adopted (transiently losing the image).
//
Canvas.prototype.getSpareTexture=function(candidate_texture,width,height,format,type)
{
  var t;

  if(width && height)
    t={width:width, height:height, format:format || this.gl.RGBA, type: type || this.gl.UNSIGNED_BYTE};
  else
    t=this.template;

  var k=Texture.formatKey(t);

  if(candidate_texture)
    if(k==candidate_texture.getFormatKey())
      return candidate_texture;
    else
      this.releaseTexture(candidate_texture);

  if(!this.spareTextures[k])
    this.spareTextures[k]=[];

  if(this.spareTextures[k].length)
    return this.spareTextures[k].pop();
  else{
    console.log("canvas.getSpareTexture "+k);
    return new Texture(this.gl, t.width, t.height, t.format, t.type, t.filter);
  }
}

// release all temporary textures (called after each rendering)
Canvas.prototype.gc=function()
{
  var texture;
  while(texture=this.tempTextures.pop())
    this.releaseTexture(texture);
}

// Put a texture back to the spare pool.
//
// Do NOT use a texture afterwards, as its binding and image is undefined.
// (If the texture is bound as a target again, the application may issue a feedback loop warning)
Canvas.prototype.releaseTexture=function(texture)
{
  var k=texture .getFormatKey();
  if(!this.spareTextures[k])
    this.spareTextures[k]=[];

  this.spareTextures[k].push(texture);
}

Canvas.prototype.getShader=function(name, vertexSource, fragmentSource){
  if(!this.shaders[name])
    this.shaders[name] = new Shader(this.gl, vertexSource, fragmentSource);

  return this.shaders[name];
}

// push (copy) a texture to the "stack"
Canvas.prototype.stack_push=function(from_texture)
{
  // push given or current image onto stack
  if(!from_texture) from_texture=this.texture;

  // add another texture to empty stack pool if needed
  if(!this.stackUnused.length)
    this.stackUnused.push(this.getSpareTexture());

  // check for stack overflow
  if(this.stack.length>10)
  {
    console.log('glfx.js video stack overflow!');
    return this;
  }

  // copy current frame on top of the stack
  var nt=this.stackUnused.pop();
  from_texture.copyTo(nt);
  this.stack.push(nt);

  return nt;
}

// fetch and remove the topmost texture from the "stack"
Canvas.prototype.stack_pop=function()
{
  var texture=this.stack.pop();
  if(!texture)
  {
    console.log('glfx.js video stack underflow!');
    return this.texture;
  }
  this.stackUnused.push(texture);

  return texture;
}

// prepare stack in front of running a chain.
// checks for textures still left on the stack.
Canvas.prototype.stack_prepare=function() {
  // report if stack is still full
  if(this.stack.length)
    console.log("glfx.js video stack leaks "+this.stack.length+" elements.");

  // pop any remaining elements
  while(this.stack.length)
    this.releaseTexture(this.stack.pop());

  // release all freed elements
  while(this.stackUnused.length)
    this.releaseTexture(this.stackUnused.pop());
}

// helper functions for chain code generation
// fetch parameters from effect, and optionally execute "generator" assigned to.
var get_param_values=function(param,t)
{
  var args=[];
  if(!(param instanceof Object)) return param;
  var fn=Generators.generators[param.type];
  return fn.call(window,t,param);
}

// render a single effect
Canvas.prototype.run_effect=function(effect,t)
{
  if(typeof effect == "string") return;
  var args=[];
  var fn=filters[effect.effect] ? filters[effect.effect] : window[effect.effect];
  for(var key in effect)
  {
    if(key=='effect') continue;
    args=args.concat(get_param_values(effect[key],t));
  }
  fn.apply(this,args);
}

// render the whole effect chain
Canvas.prototype.run_chain=function(current_time)
{
  Generators.prepare(); // reset effect chain generators to distinguish all random invocations in a single frame
  this.stack_prepare();
  for(var i=0; i<this.chain.length; i++)
    this.run_effect(this.chain[i],current_time*0.001);
  // reset switched flag, it is used by some filters to clear buffers on chain switch
  this.switched=false;
}

// set current effect chain
Canvas.prototype.setChain=function (effects)
{
  // make sure there is always a "preview" output,
  // if there is none in the chain, it is appended to the end
  // (showing the final output)
  var havePreview=false;
  for(var i=0; i<effects.length; i++)
    if(effects[i].effect=='preview')
      havePreview=true;
  if(!havePreview)
    effects.push({'effect':'preview'});

  // set chain
  this.chain=effects;

  // set canvas 'switched' flag, that can be used by filters to reset buffers
  this.switched=true;
}

// receive preview request from remote
// called by UI
Canvas.prototype.preview=function(enabled) {
  // engage preview process
  this.preview_enabled=enabled;
}

// receive screenshot request from remote
// called by UI
Canvas.prototype.screenshot=function() {
  // engage screenshot process
  this.screenshot_flag=true;
}

// start canvas capture stream and deliver video to UI on stop.
// this records compressed video to RAM, until stop is called,
// the video is sent to the UI then as a single file.
// called by UI
Canvas.prototype.recording=function(enabled) {
  if(enabled)
  {
    var options = {mimeType: 'video/webm'};

    if(!this.recorderCanvas) {
      this.recorderCanvas=document.createElement('canvas');
      this.recorderCanvas.width=this.width;
      this.recorderCanvas.height=this.height;
      this.recorderContext=this.recorderCanvas.getContext('2d');
    }

    const stream = this.recorderCanvas.captureStream(0);
    stream.getVideoTracks()[0].contentHint="detail";
    console.log('Started stream capture from canvas element: ', stream);
    this.mediaRecorder = new MediaRecorder(stream, options);
    console.log('Created MediaRecorder', this.mediaRecorder, 'with options', options);

    let recordedBlobs=[];

    let remote = this.remote;
    this.mediaRecorder.onstop = function(event)
    {
      console.log('Recorder stopped: ', event);
      const blob = new Blob(recordedBlobs, {type: 'video/webm'});
      const a = new FileReader();
      a.onload = function(e) {
        remote.put('screenshot',e.target.result);
      }
      a.readAsDataURL(blob);
      recordedBlobs = [];
    };

    this.mediaRecorder.ondataavailable = function(event)
    {
      if (event.data && event.data.size > 0) {
        recordedBlobs.push(event.data);
      }
    }
    this.mediaRecorder.start(1000);
    console.log('MediaRecorder started', this.mediaRecorder);
  }
  else
  {
    this.mediaRecorder.stop();
    this.mediaRecorder=null;
  }
}

// start canvas WebRTC stream
// called by UI
Canvas.prototype.webrtc=function(enabled) {
  if(enabled)
  {
    if(!this.recorderContext) {
      this.recorderCanvas=document.createElement('canvas');
      this.recorderCanvas.width=this.width;
      this.recorderCanvas.height=this.height;
      this.recorderContext=this.recorderCanvas.getContext('2d');
    }
    if(!this.webrtcOut) {
      this.webrtcOut=true;
      import("./webrtc.js").then(async(webrtc) => {
        this.webrtcOut=await webrtc.WebRTC("",this.recorderCanvas);
      });
    }
  }else{
    this.webrtcOut.hangup();
    this.webrtcOut=null;
    this.recorderContext=null;
  }
}

// switch chain by index,
// called by UI
Canvas.prototype.switchChain=function(chain_index)
{
  chain_index+=2;

  // load startup chain (first three of chains.json : setup pre, current, setup after)
  var xmlHttp = new XMLHttpRequest();
  xmlHttp.open('GET','saves'+this.session_url+'chains.json',true);
  xmlHttp.send(null);
  xmlHttp.onreadystatechange=function(){
    if(xmlHttp.readyState!=4) return;
    if(xmlHttp.responseText)
    {
      var chains=JSON.parse(xmlHttp.responseText);
      if(chain_index>=chains.length) return;
      var full_chain=chains[0].concat(chains[chain_index],chains[1]);
      this.setChain(full_chain);
    }
  }
}

<commit_msg>simplified update timing<commit_after>import {Texture} from "./texture.js";
import {Shader} from "./shader.js";
import {filters} from "./filters.js";
import * as Generators from "./generators.js"

// create a VisSynthWeb Canvas from given HTML canvas element, session_url (for remote control)
export let Canvas = function(selector, session_url) {
    this.canvas=document.querySelector(selector);

    this.session_url=session_url;

    this.gl = this.canvas.getContext('experimental-webgl', { alpha: false, premultipliedAlpha: false });

    // container for filter state variables..
    // filters should not add properties outside of this.
    this._={};

    // initialize (use browser canvas size as default. may be changed by user-defined via "resolution"-filter)
    // create a template texture manually as template for future ones
    this.template = new Texture(this.gl, this.canvas.width, this.canvas.height, this.gl.RGBA, this.gl.UNSIGNED_BYTE);
    // hold a list of managed spare textures
    this.spareTextures=[];
    // hold a list of garbage collected textures
    this.tempTextures=[];
    // create default texture for simpleShader
    this.texture = this.getSpareTexture();
    // create shader registry
    this.shaders={};
    // create stack
    this.stack=[];
    this.stackUnused=[];

    // the currently running effect chain
    this.chain={};

    // states for delivery of preview, screenshots and stream recordings
    this.preview_cycle=0;
    this.preview_enabled=false;
    this.screenshot_flag=false;
    this.preview_canvas=null;
    this.mediaRecorder;
    this.recordedBlobs = [];
    this.recorderContext=null;
    this.recorderCanvas=null;

    // time for stats and time-dependent effects
    this.frame_time=0;
    this.last_time=0;

    // the proposed fps can be set to limit the rendering fps
    this.proposed_fps=0;

    // the remote control handle (for replying to remote commands)
    this.remote = null;
}

// create a texture from a given HTML element
Canvas.prototype.toTexture=function(element) {
    return Texture.fromElement(this.gl, element);
}

// render a frame,
// provide streaming / preview / screenshot output and
// update the visible canvas element.
Canvas.prototype.update=function() {
    // compute frame and animation time
    var current_time=Date.now();
    this.frame_time=this.frame_time*0.9 + (current_time-this.last_time)*0.1;
    this.last_time=current_time;

    // enqueue next update
    var update_handler=this.update.bind(this);
    if(this.proposed_fps)
      setTimeout(update_handler,1000/this.proposed_fps);
    else
      requestAnimationFrame(update_handler);

    // render effect chain !
    this.run_chain(current_time);

    // provide preview if requested
    if(this.preview_enabled && this.preview_cycle==1)
      this.capturePreview();
    if(this.preview_cycle==0)
      this.sendStatsAndPreview();
    this.preview_cycle^=1;

    // render final image to visible canvas.
    // update canvas size to texture size, if needed
    if(this.width!=this.texture.width || this.height!=this.texture.width)
    {
      this.width =this.texture.width;
      this.height=this.texture.height;
      this.canvas.width=this.width;
      this.canvas.height=this.height;
    }
    this.gl.viewport(0,0, this.width, this.height);
    // for some reason, picture is horizontally mirrored. Store it into the canvas the right way.
    filters.mirror_x.call(this,this);

    // release temporary textures
    this.gc();

    // take screenshot if requested
    if(this.screenshot_flag)
      this.sendScreenshot();

    // capture frame for stream sender / recorder if requested
    if(this.recorderContext)
      this.captureStream();

    return this;
}

Canvas.prototype.capturePreview=function() {
  // the preview is a downscaled image provided by the 'preview' effect
  // we crop the preview pixels out of the canvas just BEFORE canvas is finally drawn, which will redraw full resolution.
  //
  // in repsect to just downsizing the final image this has two benefits:
  //
  // 1) it is much faster, as rescaling is done in WebGL context and not by 2d context drawImage
  // TODO check if this still holds 2022. 2d context is using GPU scaling too.
  //
  // 2) The 'preview' filter may be added to any chain position manually to tap the preview image between effects
  //
  if(!this.preview_canvas)
  {
    this.preview_canvas=document.createElement('canvas');
    this.preview_canvas.width=this.preview_width;
    this.preview_canvas.height=this.preview_height;
  }
  var ctx=this.preview_canvas.getContext('2d');
  ctx.drawImage(this.canvas,0,this.height-this.preview_height,this.preview_width,this.preview_height, 0, 0, this.preview_width,this.preview_height);
}

Canvas.prototype.sendStatsAndPreview=function() {
  var jpeg=(this.preview_canvas && this.preview_enabled) ? this.preview_canvas.toDataURL('image/jpeg') : null;
  var data={frame_time:this.frame_time, jpeg:jpeg};
  var json=JSON.stringify(data);
  this.remote.put('preview',json);
}

Canvas.prototype.sendScreenshot=function() {
  var pixels=canvas.toDataURL('image/jpeg');
  this.remote.put('screenshot',pixels);
  this.screenshot_flag=false;
}

Canvas.prototype.captureStream=function() {
  // capture canvas for stream recording or WebRTC streaming
  // TODO if this is done for WebRTC only, do not do this copy if stream is not actually running.
  if(this.recorderContext) this.recorderContext.drawImage(this.canvas,0,0);

  // encode frame, if recording
  if(this.mediaRecorder) this.mediaRecorder.stream.getVideoTracks()[0].requestFrame();
}

// replace current working texture
Canvas.prototype.putTexture=function(texture)
{
    this.releaseTexture(this.texture);
    this.texture=texture;
}

// render canvas-sized rectangle to apply a given shader to every pixel
Canvas.prototype.simpleShader=function(shader, uniforms, textureIn, textureOut) {
    var texture=(textureIn  || this.texture        );
    var target =(textureOut || this.getSpareTexture());

    texture.use();
    target .setAsTarget();
    shader.uniforms(uniforms).drawRect();

    if(!textureOut)
      this.putTexture(target);
};

// set the visible canvas as render target
Canvas.prototype.setAsTarget=function(){
    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); // remove framebuffer binding left from last offscreen rendering (as set by Texture.setAsTarget)
}

// Retrieve a working texture matched to this canvas or size settings.
//
// -If a candidate texture is given, it is validated to match the current format and returned unchanged (image is kept) if so.
//  So getSpareTexture can be easily used for lazy creation.
//
// -if there is no candidate or it is not matching the current format, a valid texture is returned (either recycled or created). The image is undefined then.
//
// If the texture is for transient use, it should be freed later by releaseTexture().
// Otherwise filters are encouraged to pass their private working textures here every frame, ensuring format updates are adopted (transiently losing the image).
//
Canvas.prototype.getSpareTexture=function(candidate_texture,width,height,format,type)
{
  var t;

  if(width && height)
    t={width:width, height:height, format:format || this.gl.RGBA, type: type || this.gl.UNSIGNED_BYTE};
  else
    t=this.template;

  var k=Texture.formatKey(t);

  if(candidate_texture)
    if(k==candidate_texture.getFormatKey())
      return candidate_texture;
    else
      this.releaseTexture(candidate_texture);

  if(!this.spareTextures[k])
    this.spareTextures[k]=[];

  if(this.spareTextures[k].length)
    return this.spareTextures[k].pop();
  else{
    console.log("canvas.getSpareTexture "+k);
    return new Texture(this.gl, t.width, t.height, t.format, t.type, t.filter);
  }
}

// release all temporary textures (called after each rendering)
Canvas.prototype.gc=function()
{
  var texture;
  while(texture=this.tempTextures.pop())
    this.releaseTexture(texture);
}

// Put a texture back to the spare pool.
//
// Do NOT use a texture afterwards, as its binding and image is undefined.
// (If the texture is bound as a target again, the application may issue a feedback loop warning)
Canvas.prototype.releaseTexture=function(texture)
{
  var k=texture .getFormatKey();
  if(!this.spareTextures[k])
    this.spareTextures[k]=[];

  this.spareTextures[k].push(texture);
}

Canvas.prototype.getShader=function(name, vertexSource, fragmentSource){
  if(!this.shaders[name])
    this.shaders[name] = new Shader(this.gl, vertexSource, fragmentSource);

  return this.shaders[name];
}

// push (copy) a texture to the "stack"
Canvas.prototype.stack_push=function(from_texture)
{
  // push given or current image onto stack
  if(!from_texture) from_texture=this.texture;

  // add another texture to empty stack pool if needed
  if(!this.stackUnused.length)
    this.stackUnused.push(this.getSpareTexture());

  // check for stack overflow
  if(this.stack.length>10)
  {
    console.log('glfx.js video stack overflow!');
    return this;
  }

  // copy current frame on top of the stack
  var nt=this.stackUnused.pop();
  from_texture.copyTo(nt);
  this.stack.push(nt);

  return nt;
}

// fetch and remove the topmost texture from the "stack"
Canvas.prototype.stack_pop=function()
{
  var texture=this.stack.pop();
  if(!texture)
  {
    console.log('glfx.js video stack underflow!');
    return this.texture;
  }
  this.stackUnused.push(texture);

  return texture;
}

// prepare stack in front of running a chain.
// checks for textures still left on the stack.
Canvas.prototype.stack_prepare=function() {
  // report if stack is still full
  if(this.stack.length)
    console.log("glfx.js video stack leaks "+this.stack.length+" elements.");

  // pop any remaining elements
  while(this.stack.length)
    this.releaseTexture(this.stack.pop());

  // release all freed elements
  while(this.stackUnused.length)
    this.releaseTexture(this.stackUnused.pop());
}

// helper functions for chain code generation
// fetch parameters from effect, and optionally execute "generator" assigned to.
var get_param_values=function(param,t)
{
  var args=[];
  if(!(param instanceof Object)) return param;
  var fn=Generators.generators[param.type];
  return fn.call(window,t,param);
}

// render a single effect
Canvas.prototype.run_effect=function(effect,t)
{
  if(typeof effect == "string") return;
  var args=[];
  var fn=filters[effect.effect] ? filters[effect.effect] : window[effect.effect];
  for(var key in effect)
  {
    if(key=='effect') continue;
    args=args.concat(get_param_values(effect[key],t));
  }
  fn.apply(this,args);
}

// render the whole effect chain
Canvas.prototype.run_chain=function(current_time)
{
  Generators.prepare(); // reset effect chain generators to distinguish all random invocations in a single frame
  this.stack_prepare();
  for(var i=0; i<this.chain.length; i++)
    this.run_effect(this.chain[i],current_time*0.001);
  // reset switched flag, it is used by some filters to clear buffers on chain switch
  this.switched=false;
}

// set current effect chain
Canvas.prototype.setChain=function (effects)
{
  // make sure there is always a "preview" output,
  // if there is none in the chain, it is appended to the end
  // (showing the final output)
  var havePreview=false;
  for(var i=0; i<effects.length; i++)
    if(effects[i].effect=='preview')
      havePreview=true;
  if(!havePreview)
    effects.push({'effect':'preview'});

  // set chain
  this.chain=effects;

  // set canvas 'switched' flag, that can be used by filters to reset buffers
  this.switched=true;
}

// receive preview request from remote
// called by UI
Canvas.prototype.preview=function(enabled) {
  // engage preview process
  this.preview_enabled=enabled;
}

// receive screenshot request from remote
// called by UI
Canvas.prototype.screenshot=function() {
  // engage screenshot process
  this.screenshot_flag=true;
}

// start canvas capture stream and deliver video to UI on stop.
// this records compressed video to RAM, until stop is called,
// the video is sent to the UI then as a single file.
// called by UI
Canvas.prototype.recording=function(enabled) {
  if(enabled)
  {
    var options = {mimeType: 'video/webm'};

    if(!this.recorderCanvas) {
      this.recorderCanvas=document.createElement('canvas');
      this.recorderCanvas.width=this.width;
      this.recorderCanvas.height=this.height;
      this.recorderContext=this.recorderCanvas.getContext('2d');
    }

    const stream = this.recorderCanvas.captureStream(0);
    stream.getVideoTracks()[0].contentHint="detail";
    console.log('Started stream capture from canvas element: ', stream);
    this.mediaRecorder = new MediaRecorder(stream, options);
    console.log('Created MediaRecorder', this.mediaRecorder, 'with options', options);

    let recordedBlobs=[];

    let remote = this.remote;
    this.mediaRecorder.onstop = function(event)
    {
      console.log('Recorder stopped: ', event);
      const blob = new Blob(recordedBlobs, {type: 'video/webm'});
      const a = new FileReader();
      a.onload = function(e) {
        remote.put('screenshot',e.target.result);
      }
      a.readAsDataURL(blob);
      recordedBlobs = [];
    };

    this.mediaRecorder.ondataavailable = function(event)
    {
      if (event.data && event.data.size > 0) {
        recordedBlobs.push(event.data);
      }
    }
    this.mediaRecorder.start(1000);
    console.log('MediaRecorder started', this.mediaRecorder);
  }
  else
  {
    this.mediaRecorder.stop();
    this.mediaRecorder=null;
  }
}

// start canvas WebRTC stream
// called by UI
Canvas.prototype.webrtc=function(enabled) {
  if(enabled)
  {
    if(!this.recorderContext) {
      this.recorderCanvas=document.createElement('canvas');
      this.recorderCanvas.width=this.width;
      this.recorderCanvas.height=this.height;
      this.recorderContext=this.recorderCanvas.getContext('2d');
    }
    if(!this.webrtcOut) {
      this.webrtcOut=true;
      import("./webrtc.js").then(async(webrtc) => {
        this.webrtcOut=await webrtc.WebRTC("",this.recorderCanvas);
      });
    }
  }else{
    this.webrtcOut.hangup();
    this.webrtcOut=null;
    this.recorderContext=null;
  }
}

// switch chain by index,
// called by UI
Canvas.prototype.switchChain=function(chain_index)
{
  chain_index+=2;

  // load startup chain (first three of chains.json : setup pre, current, setup after)
  var xmlHttp = new XMLHttpRequest();
  xmlHttp.open('GET','saves'+this.session_url+'chains.json',true);
  xmlHttp.send(null);
  xmlHttp.onreadystatechange=function(){
    if(xmlHttp.readyState!=4) return;
    if(xmlHttp.responseText)
    {
      var chains=JSON.parse(xmlHttp.responseText);
      if(chain_index>=chains.length) return;
      var full_chain=chains[0].concat(chains[chain_index],chains[1]);
      this.setChain(full_chain);
    }
  }
}

<|endoftext|>


<commit_before>L.SVG.include ({
    _updateEllipse: function (layer) {
        var c = layer._point,
            rx = layer._radiusX,
            ry = layer._radiusY,
            phi = layer._tiltDeg,
            endPoint = layer._endPointParams;

        var d = 'M' + endPoint.x0 + ',' + endPoint.y0 +
            'A' + rx + ',' + ry + ',' + phi + ',' +
            endPoint.largeArc + ',' + endPoint.sweep + ',' +
            endPoint.x1 + ',' + endPoint.y1 + ' z';
        this._setPath(layer, d);
    },
});

L.Canvas.include ({
    _updateEllipse: function (layer) {
        if (layer._empty()) { return; }

        var p = layer._point,
            ctx = this._ctx,
            r = layer._radiusX,
            s = (layer._radiusY || r) / r;

        this._drawnLayers[layer._leaflet_id] = layer;

        if (s !== 1) {
            ctx.save();
            ctx.scale(1, s);
        }

        ctx.beginPath();
        ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);

        if (s !== 1) {
            ctx.restore();
        }

        this._fillStroke(ctx, layer);
    },
});

L.Ellipse = L.Path.extend({

	options: {
		fill: true,
        startAngle: 0,
        endAngle: 359.9
	},

    initialize: function (latlng, radii, tilt, options) {

        L.setOptions(this, options);
        this._latlng = L.latLng(latlng);

        if (tilt) {
            this._tiltDeg = tilt;
        } else {
            this._tiltDeg = 0;
        }

        if (radii) {
            this._mRadiusX = radii[0];
            this._mRadiusY = radii[1];
            this._mRadius = this._mRadiusX;
        }
    },

    setRadius: function (radii) {
        this._mRadiusX = radii[0];
        this._mRadiusY = radii[1];
        return this.redraw();
    },

    getRadius: function () {
        return new L.point(this._mRadiusX, this._mRadiusY);
    },

    setTilt: function (tilt) {
        this._tiltDeg = tilt;
        return this.redraw();
    },

    getBounds: function () {
        var lngRadius = this._getLngRadius(),
            latRadius = this._getLatRadius(),
            latlng = this._latlng;

        return new L.LatLngBounds(
            [latlng.lat - latRadius, latlng.lng - lngRadius],
            [latlng.lat + latRadius, latlng.lng + lngRadius]);
    },

	// @method setLatLng(latLng: LatLng): this
	// Sets the position of a circle marker to a new location.
	setLatLng: function (latlng) {
		this._latlng = L.latLng(latlng);
		this.redraw();
		return this.fire('move', {latlng: this._latlng});
	},

	// @method getLatLng(): LatLng
	// Returns the current geographical position of the circle marker
	getLatLng: function () {
		return this._latlng;
	},

    setStyle: L.Path.prototype.setStyle,

    _project: function () {
        var lngRadius = this._getLngRadius(),
            latRadius = this._getLatRadius(),
            latlng = this._latlng,
            pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]),
            pointBelow = this._map.latLngToLayerPoint([latlng.lat - latRadius, latlng.lng]);

        this._point = this._map.latLngToLayerPoint(latlng);
        this._radiusX = Math.max(this._point.x - pointLeft.x, 1);
        this._radiusY = Math.max(pointBelow.y - this._point.y, 1);
        this._endPointParams = this._centerPointToEndPoint();
        this._updateBounds();
    },

	_updateBounds: function () {
		var rx = this._radiusX,
		    ry = this._radiusY,
		    w = this._clickTolerance(),
		    p = [rx + w, ry + w];
		this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
	},

	_update: function () {
		if (this._map) {
			this._updatePath();
		}
	},

	_updatePath: function () {
		this._renderer._updateEllipse(this);
	},

    _getLatRadius: function () {
        return (this._mRadiusY / 40075017) * 360;
    },

    _getLngRadius: function () {
        return ((this._mRadiusX / 40075017) * 360) / Math.cos((Math.PI / 180) * this._latlng.lat);
    },

    _centerPointToEndPoint: function () {
        // Convert between center point parameterization of an ellipse
        // too SVG's end-point and sweep parameters.  This is an
        // adaptation of the perl code found here:
        // http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Paths
        var c = this._point,
            rx = this._radiusX,
            ry = this._radiusY,
            theta2 = (this.options.startAngle + this.options.endAngle) * (Math.PI / 180),
            theta1 = this.options.startAngle * (Math.PI / 180),
            delta = this.options.endAngle,
            phi = this._tiltDeg * (Math.PI / 180);

        // Determine start and end-point coordinates
        var x0 = c.x + Math.cos(phi) * rx * Math.cos(theta1) +
            Math.sin(-phi) * ry * Math.sin(theta1);
        var y0 = c.y + Math.sin(phi) * rx * Math.cos(theta1) +
            Math.cos(phi) * ry * Math.sin(theta1);

        var x1 = c.x + Math.cos(phi) * rx * Math.cos(theta2) +
            Math.sin(-phi) * ry * Math.sin(theta2);
        var y1 = c.y + Math.sin(phi) * rx * Math.cos(theta2) +
            Math.cos(phi) * ry * Math.sin(theta2);

        var largeArc = (delta > 180) ? 1 : 0;
        var sweep = (delta > 0) ? 1 : 0;

        return {'x0': x0, 'y0': y0, 'tilt': phi, 'largeArc': largeArc,
            'sweep': sweep, 'x1': x1, 'y1': y1};
    },

	_empty: function () {
		return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
	},

    _containsPoint : function (p) {
        // TODO

        return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
    }
});

L.ellipse = function (latlng, radii, tilt, options) {
    return new L.Ellipse(latlng, radii, tilt, options);
};
<commit_msg>Fixed Canvas-based rendering of ellipses.<commit_after>L.SVG.include ({
    _updateEllipse: function (layer) {
        var c = layer._point,
            rx = layer._radiusX,
            ry = layer._radiusY,
            phi = layer._tiltDeg,
            endPoint = layer._endPointParams;

        var d = 'M' + endPoint.x0 + ',' + endPoint.y0 +
            'A' + rx + ',' + ry + ',' + phi + ',' +
            endPoint.largeArc + ',' + endPoint.sweep + ',' +
            endPoint.x1 + ',' + endPoint.y1 + ' z';
        this._setPath(layer, d);
    }
});

L.Canvas.include ({
    _updateEllipse: function (layer) {
        if (layer._empty()) { return; }

        var p = layer._point,
            ctx = this._ctx,
            r = layer._radiusX,
            s = (layer._radiusY || r) / r;

        this._drawnLayers[layer._leaflet_id] = layer;

        ctx.save();

        ctx.translate(p.x, p.y);
        if (layer._tilt !== 0) {
            ctx.rotate( layer._tilt );
        }
        if (s !== 1) {
            ctx.scale(1, s);
        }

        ctx.beginPath();
        ctx.arc(0, 0, r, 0, Math.PI * 2);
        ctx.restore();

        this._fillStroke(ctx, layer);
    },
});

L.Ellipse = L.Path.extend({

	options: {
		fill: true,
        startAngle: 0,
        endAngle: 359.9
	},

    initialize: function (latlng, radii, tilt, options) {

        L.setOptions(this, options);
        this._latlng = L.latLng(latlng);

        if (tilt) {
            this._tiltDeg = tilt;
        } else {
            this._tiltDeg = 0;
        }

        if (radii) {
            this._mRadiusX = radii[0];
            this._mRadiusY = radii[1];
        }
    },

    setRadius: function (radii) {
        this._mRadiusX = radii[0];
        this._mRadiusY = radii[1];
        return this.redraw();
    },

    getRadius: function () {
        return new L.point(this._mRadiusX, this._mRadiusY);
    },

    setTilt: function (tilt) {
        this._tiltDeg = tilt;
        return this.redraw();
    },

    getBounds: function () {
        // TODO respect tilt (bounds are too big)
        var lngRadius = this._getLngRadius(),
            latRadius = this._getLatRadius(),
            latlng = this._latlng;

        return new L.LatLngBounds(
            [latlng.lat - latRadius, latlng.lng - lngRadius],
            [latlng.lat + latRadius, latlng.lng + lngRadius]);
    },

	// @method setLatLng(latLng: LatLng): this
	// Sets the position of a circle marker to a new location.
	setLatLng: function (latlng) {
		this._latlng = L.latLng(latlng);
		this.redraw();
		return this.fire('move', {latlng: this._latlng});
	},

	// @method getLatLng(): LatLng
	// Returns the current geographical position of the circle marker
	getLatLng: function () {
		return this._latlng;
	},

    setStyle: L.Path.prototype.setStyle,

    _project: function () {
        var lngRadius = this._getLngRadius(),
            latRadius = this._getLatRadius(),
            latlng = this._latlng,
            pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]),
            pointBelow = this._map.latLngToLayerPoint([latlng.lat - latRadius, latlng.lng]);

        this._point = this._map.latLngToLayerPoint(latlng);
        this._radiusX = Math.max(this._point.x - pointLeft.x, 1);
        this._radiusY = Math.max(pointBelow.y - this._point.y, 1);
        this._radius = Math.max(this._radiusX, this._radiusY);
        this._tilt = Math.PI * this._tiltDeg / 180;
        this._endPointParams = this._centerPointToEndPoint();
        this._updateBounds();
    },

	_updateBounds: function () {
        // TODO respect tilt (bounds are too big)
		var rx = this._radius,
		    ry = this._radius,
		    w = this._clickTolerance(),
		    p = [rx + w, ry + w];
		this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
	},

	_update: function () {
		if (this._map) {
			this._updatePath();
		}
	},

	_updatePath: function () {
		this._renderer._updateEllipse(this);
	},

    _getLatRadius: function () {
        return (this._mRadiusY / 40075017) * 360;
    },

    _getLngRadius: function () {
        return ((this._mRadiusX / 40075017) * 360) / Math.cos((Math.PI / 180) * this._latlng.lat);
    },

    _centerPointToEndPoint: function () {
        // Convert between center point parameterization of an ellipse
        // too SVG's end-point and sweep parameters.  This is an
        // adaptation of the perl code found here:
        // http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Paths
        var c = this._point,
            rx = this._radiusX,
            ry = this._radiusY,
            theta2 = (this.options.startAngle + this.options.endAngle) * (Math.PI / 180),
            theta1 = this.options.startAngle * (Math.PI / 180),
            delta = this.options.endAngle,
            phi = this._tiltDeg * (Math.PI / 180);

        // Determine start and end-point coordinates
        var x0 = c.x + Math.cos(phi) * rx * Math.cos(theta1) +
            Math.sin(-phi) * ry * Math.sin(theta1);
        var y0 = c.y + Math.sin(phi) * rx * Math.cos(theta1) +
            Math.cos(phi) * ry * Math.sin(theta1);

        var x1 = c.x + Math.cos(phi) * rx * Math.cos(theta2) +
            Math.sin(-phi) * ry * Math.sin(theta2);
        var y1 = c.y + Math.sin(phi) * rx * Math.cos(theta2) +
            Math.cos(phi) * ry * Math.sin(theta2);

        var largeArc = (delta > 180) ? 1 : 0;
        var sweep = (delta > 0) ? 1 : 0;

        return {'x0': x0, 'y0': y0, 'tilt': phi, 'largeArc': largeArc,
            'sweep': sweep, 'x1': x1, 'y1': y1};
    },

	_empty: function () {
		return this._radiusX && this._radiusY && !this._renderer._bounds.intersects(this._pxBounds);
	},

    _containsPoint : function (p) {
        // http://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm
        var sin = Math.sin(this._tilt);
        var cos = Math.cos(this._tilt);
        var dx = p.x - this._point.x;
        var dy = p.y - this._point.y;
        var sumA = cos * dx + sin * dy;
        var sumB = sin * dx - cos * dy;
        return sumA * sumA / (this._radiusX * this._radiusX)  + sumB * sumB / (this._radiusY * this._radiusY) <= 1;
    }
});

L.ellipse = function (latlng, radii, tilt, options) {
    return new L.Ellipse(latlng, radii, tilt, options);
};
<|endoftext|>


<commit_before>/*
 * GSlider.js
 * Copyright (c) 2013, GAYRETSOFT (http://gayretsoft.com/)
 * is licensed under the MIT license.
 * http://opensource.org/licenses/MIT veya LICENSE.txt
 *
 * Version: 1.0
 * Author : Fatih Komaralp
 */

$(function(){
    var globals = {

        blockList : [],
        blockListLength : 0,
        GSClass : {},
        currentSlider : -1,
        timer : null

    }, options = {

        childElementTimeout : 800,
        sliderDelay : 5000,
        autoplay : false, // TODO
        startat : 0

    };

   function GSlider(){};

    GSlider.prototype.fire = function(index, action, callback){

        var currentBlock = globals.blockList[index];

            globals.currentSlider = index;

            $childElement       = $(currentBlock).children();
            $childElementLength = $(currentBlock).children().length;

            $childElement.each(function(index){

                    var $element = $(this);

                    var x           = $element.data("position").split(",")[0],
                        y           = $element.data("position").split(",")[1],
                        delay       = $element.data("delay"),
                        effect_name = $element.data("effect"),
                        direction;

                    if($element.data("direction") !== "undefined") direction = $element.data("direction");

                    $element.css({marginLeft : x + "px", marginTop : y + "px"});

                    $(currentBlock).show();

                    setTimeout(function(){
                        $element.effect(effect_name,
                                   {mode : action, direction : direction},
                                   delay,function(){
                                       if(action==="hide") if(index === $childElementLength - 1)
                                        {
                                            callback();
                                            $(currentBlock).hide();
                                        }
                                   });
                    },options.childElementTimeout * index);

            });

    };

    /**
     * Initialize slider elements and start slider
     * @returns {undefined}
     */
    GSlider.prototype.start = function(){

        if(!options.autoplay) return;

        var $this = this, i;

        (options.startat > globals.blockListLength - 1) ?
                i = options.startat = 0 :
                        i = options.startat;

        // Fire the first slide
        if(globals.currentSlider === -1){
            $this.fire(i, "show", null);
        }

        globals.timer = setInterval(function(){
            i++;

            if(i >= globals.blockListLength) i = 0;

                $this.fire(globals.currentSlider, "hide", function(){
                    $this.fire(i, "show", null);
                });

        }, options.sliderDelay * globals.blockListLength);

    };

    /**
     * Stop slider
     * @returns {undefined}
     */
    GSlider.prototype.stop = function(){
            clearInterval(globals.timer);
    };

    /**
     * Resume slider
     * @returns {undefined}
     */
    GSlider.prototype.resume = function(){
            this.start();
    };

    /**
     * Next slide
     * @returns {undefined}
     */
    GSlider.prototype.next = function(){

    };

    /**
     * Previus slide
     * @returns {undefined}
     */
    GSlider.prototype.prev = function(){

    };

var init = {
        /**
         * Private method
         * Setup slider elements
         * @param {type} customOptions Extra options
         */
        setup : function (_options){

            options = $.extend(options, _options);

            globals.blockListLength = $(this).children().length;
            globals.blockList = $(this).children();

            globals.GSClass = new GSlider();

            //if(options.autoplay) methods.start();

        }

    },
    /**
     * Private methods
     */
    _methods = {
        fire    : function(index){ globals.GSClass.start(index); }, // Fire slider item
    },

    /**
     * Public methods
     */
    methods = {
        start   : function(){ globals.GSClass.start(); },           // Start slider
        stop    : function(){ globals.GSClass.stop(); },            // Stop slider
        resume  : function(){ globals.GSClass.resume(); },        // Resume slider
        next    : function(){ globals.GSClass.next(); },            // Next slide
        prev    : function(){ globals.GSClass.prev(); }             // Previus slider
    };

    /**
     * Caller
     * @param {object} customOptions Extra options
     * @returns {object} Returning GSlider object for the some public methods
     */
    $.fn.gslider = function(options){
        init.setup.call(this, options);
        return methods;
    };

})(JQuery);<commit_msg>JQuery değişkeni jQuery kelimesi ile değiştirildi.<commit_after>/*
 * GSlider.js
 * Copyright (c) 2013, GAYRETSOFT (http://gayretsoft.com/)
 * is licensed under the MIT license.
 * http://opensource.org/licenses/MIT veya LICENSE.txt
 *
 * Version: 1.0
 * Author : Fatih Komaralp
 */

(function($){
    var globals = {

        blockList : [],
        blockListLength : 0,
        GSClass : {},
        currentSlider : -1,
        timer : null

    }, options = {

        childElementTimeout : 800,
        sliderDelay : 5000,
        autoplay : false, // TODO
        startat : 0

    };

   function GSlider(){};

    GSlider.prototype.fire = function(index, action, callback){

        var currentBlock = globals.blockList[index];

            globals.currentSlider = index;

            $childElement       = $(currentBlock).children();
            $childElementLength = $(currentBlock).children().length;

            $childElement.each(function(index){

                    var $element = $(this);

                    var x           = $element.data("position").split(",")[0],
                        y           = $element.data("position").split(",")[1],
                        delay       = $element.data("delay"),
                        effect_name = $element.data("effect"),
                        direction;

                    if($element.data("direction") !== "undefined") direction = $element.data("direction");

                    $element.css({marginLeft : x + "px", marginTop : y + "px"});

                    $(currentBlock).show();

                    setTimeout(function(){
                        $element.effect(effect_name,
                                   {mode : action, direction : direction},
                                   delay,function(){
                                       if(action==="hide") if(index === $childElementLength - 1)
                                        {
                                            callback();
                                            $(currentBlock).hide();
                                        }
                                   });
                    },options.childElementTimeout * index);

            });

    };

    /**
     * Initialize slider elements and start slider
     * @returns {undefined}
     */
    GSlider.prototype.start = function(){

        if(!options.autoplay) return;

        var $this = this, i;

        (options.startat > globals.blockListLength - 1) ?
                i = options.startat = 0 :
                        i = options.startat;

        // Fire the first slide
        if(globals.currentSlider === -1){
            $this.fire(i, "show", null);
        }

        globals.timer = setInterval(function(){
            i++;

            if(i >= globals.blockListLength) i = 0;

                $this.fire(globals.currentSlider, "hide", function(){
                    $this.fire(i, "show", null);
                });

        }, options.sliderDelay * globals.blockListLength);

    };

    /**
     * Stop slider
     * @returns {undefined}
     */
    GSlider.prototype.stop = function(){
            clearInterval(globals.timer);
    };

    /**
     * Resume slider
     * @returns {undefined}
     */
    GSlider.prototype.resume = function(){
            this.start();
    };

    /**
     * Next slide
     * @returns {undefined}
     */
    GSlider.prototype.next = function(){

    };

    /**
     * Previus slide
     * @returns {undefined}
     */
    GSlider.prototype.prev = function(){

    };

var init = {
        /**
         * Private method
         * Setup slider elements
         * @param {type} customOptions Extra options
         */
        setup : function (_options){

            options = $.extend(options, _options);

            globals.blockListLength = $(this).children().length;
            globals.blockList = $(this).children();

            globals.GSClass = new GSlider();

            //if(options.autoplay) methods.start();

        }

    },
    /**
     * Private methods
     */
    _methods = {
        fire    : function(index){ globals.GSClass.start(index); }, // Fire slider item
    },

    /**
     * Public methods
     */
    methods = {
        start   : function(){ globals.GSClass.start(); },           // Start slider
        stop    : function(){ globals.GSClass.stop(); },            // Stop slider
        resume  : function(){ globals.GSClass.resume(); },        // Resume slider
        next    : function(){ globals.GSClass.next(); },            // Next slide
        prev    : function(){ globals.GSClass.prev(); }             // Previus slider
    };

    /**
     * Caller
     * @param {object} customOptions Extra options
     * @returns {object} Returning GSlider object for the some public methods
     */
    $.fn.gslider = function(options){
        init.setup.call(this, options);
        return methods;
    };

}(jQuery));
<|endoftext|>


<commit_before>Helpers = initHelpers(window, document);

function initHelpers(w, d, undefined) {
  "use strict";
  var useCustomCss = false;
  var extended = extensions();

  var helperObj = {
    report: reportHTML,
    useJQ: loadJQ,
    useCSS: setCustomCss,
    log2Screen: log2Screen,
    initSO: SOInit,
    printDirect: printDirect,
    logClear: screenClear,
    Partial: Partial,
    cloneObj: cloneObj,
    randomID: randomID
  };

  function setCustomCss(yn) {
    useCustomCss = yn;
    if (useCustomCss) {
     loadCSS();
    } else {
     unloadCSS();
    }
  }

  function loadJQ(callback) {
      if (w.jQuery) {
        return callback && callback instanceof Function ? callback() : true;
      }

      var jqel  = createElementWithProps('script', {src: 'http://code.jquery.com/jquery-2.1.1.min.js', id: 'jqloaded'});

      d.querySelector('head').appendChild(jqel);

      if (callback && callback instanceof Function)
          jqel.addEventListener('load', callback);

      return void(0);
  }

  function loadCSS() {
    if (d.querySelector('#HelperCSS')) {
      return true;
    }
    var css = createElementWithProps(
                    'link',
                    { href: 'https://rawgit.com/KooiInc/Helpers/master/Helpers.css',
                      type: 'text/css',
                      rel: 'stylesheet',
                      id: 'HelperCSS' }
              );
    return d.querySelector('head').appendChild (css);
  }

  function unloadCSS() {
   var css = document.querySelector('#HelperCSS');
   void (css && document.querySelector('head').removeChild(css));
  }

   // a few usefull augments/polyfills
  function extensions() {
        if (extended) {
          return true;
        }
        // remove double values from an array
        function noDoubles(arr) {
          arr = arr || this;
          return arr.filter(function(val) {
           return !this[val] ? ((this[val] = true), true) : false;
          }, {});
        }

        function stringformat() {
                var args = args2Array(arguments);
                return this.replace(/(\{\d+\})/g, function(a){
                    return args[+(a.substr(1,a.length-2))||0];
                });
        };

        // determine value frequencies in an array
        function frequencies(arr) {
          var mapped = {sum: 0};
          (arr || this).map(function (a){
              if (!(a in this)) { this[a] = 1; }
              else { this[a] += 1; }
              this.sum += 1;
              return a; }, mapped
          );
          return mapped;
        }

        String.Format = function(){
            var args = args2Array(arguments);
            return stringformat.apply(args[0],args.slice(1));
        };

        String.prototype.format = function () {
          return stringformat.apply(this,arguments);
        }

        String.prototype.repeat = function(n){
            var s = this, r = '';
            while(n--) {
                r += s;
            }
            return r;
        };

        Number.prototype.toRange = Number.prototype.toRange || function (fn, startvalue) {
          startvalue = startvalue || 0;
          fn = fn instanceof Function ? fn : function (a,i) { return i+startvalue; };
          return String(new Array(this.valueOf())).split(',').map( fn );
        };

        Number.prototype.pretty = Number.prototype.pretty || function (usa, noprecision) {
            return sep1000(this, usa, noprecision);
        };

        // run functions sequentially
        Function.prototype.andThen = function () {
         var args = args2Array(arguments)
            ,next = args.slice(1);
         this();
         return args[0] && next.length
                 ? args[0].andThen.apply(args[0], next)
                 : args.length == 1  ? args[0]()
                 : true;
        };

        Function.prototype.partial = Function.prototype.partial || function () {
             var stored_args = [].slice.call(arguments)
                ,fn = this;
            return function () {
                return fn.apply(null, stored_args.concat([].slice.call(arguments)));
             };
        };

        Function.prototype.partialx = function(){
            var fn = this, args = Array.prototype.slice.call(arguments);
            return function(){
              var arg = 0;
              for ( var i = 0; i < args.length && arg < arguments.length; i++ )
                if ( args[i] === undefined || args[i] === null)
                  args[i] = arguments[arg++];
              return fn.apply(this, args);
            };
        };

        // is character @ [atpos] upperCase?
        boolean:String.prototype.charIsUpper = function (atpos){
          var chr = this.charAt(atpos);
          return /[A-Z]|[\u0080-\u024F]/.test(chr) && chr === chr.toUpperCase();
        };

        String.prototype.firstUp = function () {
          return this.slice(0,1).toUpperCase()+this.slice(1).toLowerCase();
        }

        String.prototype.isValidEmail = function() {
          // should be sufficient
          return  /^[\w._-]{1,}[+]?[\w._-]{0,}@[\w.-]+\.[a-zA-Z]{2,6}$/.test(this);
        }

        Array.prototype.toRE = function (){
          try { return RegExp.apply(null, this); }
          catch(e) { return /.*/; }
        }

        Array.toRE = function(arr) {
          return ([].toRE.call(arr));
        }

        Array.prototype.toCheckboxValues = Array.prototype.toCheckboxValues || function (checked) {
          checked = checked || [];
          var cbxs = [];
          this.map(function (a, i) {
                     cbxs.push({check: checked.indexOf(i)>-1 ? 1 : 0, val: String(a)});
                    }, cbxs);
          return cbxs;
        };

        String.prototype.reCleanup = function(encodeHTML){
          var str = encodeHTML ? this.replace(/[\u0080-\u024F]/g, function(a) {return '&#'+a.charCodeAt(0)+';';}) : this;
          return str.replace(/[?*|.+$\/]|\\/g, function(c) {return c==='\\' ? '' : '\\\\'+c;});
        }

        String.concat = function(joinchar){
          joinchar = joinchar || '';
          return args2Array(arguments).join(joinchar);
        }

        // MDN Array filter polyfill
        if (!Array.prototype.filter) {
          Array.prototype.filter = function(fun)
          {
            if (this === void 0 || this === null)
              throw new TypeError();

            var t = Object(this);
            var len = t.length >>> 0;
            if (typeof fun != "function")
              throw new TypeError();

            var res = [];
            var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
            for (var i = 0; i < len; i++) {
              if (i in t)  {
                var val = t[i];
                if (fun.call(thisArg, val, i, t))
                  res.push(val);
              }
            }

            return res;
          };
        }

        // MDN Array map polyfill
        if (!Array.prototype.map) {
          Array.prototype.map = function(fun)
          {
            if (this === void 0 || this === null)
              throw new TypeError();

            var t = Object(this);
            var len = t.length >>> 0;
            if (typeof fun !== "function")
              throw new TypeError();

            var res = new Array(len);
            var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
            for (var i = 0; i < len; i++) {
              if (i in t)
                res[i] = fun.call(thisArg, t[i], i, t);
            }

            return res;
        };
       }

       Array.prototype.uniquify = noDoubles;

       Array.prototype.frequencies = Array.prototype.frequencies || frequencies;

       Array.prototype.each = Array.prototype.each || function (fn,rewrite) {
          for (var i = 0; i < this.length; i++) {
           if (rewrite){
             this[i] = fn(this[i]);
           } else {
            fn(this[i]);
           }
          }
          return this;
       };

       // statics
       Object.print = function (obj, space) {
            space = space || '  ';
            return '
'+JSON.stringify(obj, null, space)+'
';
       }

       Object.format = function (obj, space) {
            space = space || '  ';
            return '
'+JSON.stringify(obj, null, space)+'
';
       }

       Object.isOneOf = isOfType;

       extended = true;
       return extended;
  }

  // SO specials
  function SOInit() {
    extensions();
    setCustomCss(true);
    var solink = d.querySelector('[data-linkid]');
    if (!solink.querySelector('.linkhover')) {
      solink.appendChild(createElementWithProps('div', { className: 'linkhover', 'data-dyn': 'true' } ));
    }
    loadJQ( jqcallback );

    function jqcallback() {
       $(document).on('mouseover', '.solink',  setSOLink);
       $(document).on('click', '[data-link]', clicklink);
    };
  }

  function setSOLink(e) {
      if ($(this).attr('data-link')) { return true; }

      $.ajax(
          {
              url: 'http://www.nicon.nl/node/stackx/questionx',
              data: {qid: $('.solink').first().attr('data-linkid')},
              method: 'post',
              success: SOcb
          }
      );

      function SOcb(data) {
          var resp = data.items && data.items[0] || data
          ,linkelement = $('.solink').first()
          ,linktip = linkelement.find('.linkhover').first()
          ;
          linktip.html(
              'Click logo to view the related question:

' + resp.title + '

' +
              'Asked by
'+ resp.owner.display_name + '
; '+
              'rep ' + resp.owner.reputation +'; question views: '+resp.view_count
          );
          linkelement.attr('data-link', resp.link);
      }

      return true;
  }

  // firefox needs a link added to the DOM, Chrome, IE don't
  function clicklink(e) {
      e.stopPropagation(); //just this link
      var linkurl = this.getAttribute('data-link')
         ,xlink = document.querySelector('a[href="'+linkurl+'"]') ||
                  function() {
                      var _link = createElementWithProps(
                                    'a', { href: linkurl, target:'_blank', style: { display: 'none' } }
                                  );
                      document.body.appendChild(_link);
                      return _link; }();
      xlink.click();
  }

  // Reporting
  // 1. simple
  function reportHTML() {
    var report = document.querySelector('#result') ||
        function() {
          var r = createElementWithProps( 'div', {id: 'result'} );
          document.querySelector('body').appendChild(r);
          return r;
        }();
    var entry = createElementWithProps('p');
    entry.innerHTML = args2Array(arguments).join('');
    report.appendChild(entry);

    if (useCustomCss) {
      var to = function () { entry.className = 'fadeIn'; };
      var dummy = setTimeout(to, 100);
    }
  }

  // 2. with parameter object usage
  function log2Screen() {
      var result = document.querySelector('#result') ||
                   function () {
                       var r = createElementWithProps('div', {id: 'result'});
                       document.body.appendChild(r);
                       return r; }()
         ,args = args2Array(arguments)
         ,lastarg = args.slice(-1)[0]
         ,optkeys = /clear|clrscr|direct|opts|useopts|continuous/i
         ,opts = Object.isOneOf(lastarg, Object) && Object.keys(lastarg).filter(function(v){return optkeys.test(v);}).length
                 ? lastarg.opts instanceof Object ? lastarg.opts : lastarg
                 : {empty: 1};
      void(!opts.empty && (args = args.slice(0,-1)));

      if (opts.clrscr) {
          return result.innerHTML = '';
      }

      if (opts.clear) {
          result.innerHTML = '';
      }

      if (opts.continuous) {
          return result.innerHTML += args.join('').replace(/\n/g,'
');
      }

      var p = createElementWithProps('p');
      p.innerHTML = args.join('').replace(/\n/g,'
');
      result.appendChild(p);
      return opts.direct ? (p.className = 'fadeIn')
                         : setTimeout(function () { p.className = 'fadeIn'; }, +opts.timed*1000 || 0);
  }

  function printDirect() {
    return log2Screen.apply( null, args2Array(arguments).concat({direct:true}) );
  }

  function screenClear() {
      var res = d.querySelector('#result');
      return  res && (res.innerHTML = '') || true;
  }

  // utilities
  function createElementWithProps(elType, props) {
    var el = d.createElement(elType);
    if (props && isOfType(props, Object)) {
      for (var l in props) {
        if (!props.hasOwnProperty(l)) continue;
        if (/style/i.test(l)) {
          for (var ll in props[l]) {
            if (!props[l].hasOwnProperty(ll)) continue;
            el.style[ll] = props[l][ll];
          }
        } else {
          el[l] = props[l];
        }
      }
    }
    return el;
  }

  function args2Array(args, sliceAt){
    var arr = [];
    while (arr.length < args.length) { arr.push(args[arr.length]); }
    return sliceAt ? arr.slice(sliceAt) : arr;
  }

  // see: http://codereview.stackexchange.com/questions/23317/istypeobj-gettypeobj-v0/23329#23329
  function isOfType(obj) {
    if (!obj) { return false; }
    var test = arguments.length ? args2Array(arguments).slice(1) : null
       ,self = obj.constructor;
    return test ? !!(test.filter(function(a){return a === self}).length)
           : (self.constructor.name ||
              (String(self).match ( /^function\s*([^\s(]+)/im)
                || [0,'ANONYMOUS_CONSTRUCTOR']) [1] );
  };

  function Partial (func) {
    this.initial = args2Array(arguments, 1);
    this.funcp = function () {
                 return func.partialx.apply(func, this.initial);
                };
    if (!Partial.prototype.x) {
      Partial.prototype.x = function () {
        return this.funcp().apply(null, args2Array(arguments));
      };
    }
  };

  function cloneObj(obj) {
    // clone the whole enchillada, recursive
    function clone(o, curr) {
        for (var l in o){
            if (!o.hasOwnProperty(l)) { continue; }
            if (o[l] instanceof Object) {
                curr[l] = cloneObj(o[l]);
            } else {
                curr[l] = o[l];
            }
        }
        return curr;
    }

    return obj instanceof Array
             ? obj.slice().map( function (v) { return cloneObj(v); } )
             : obj instanceof Date
               ? new Date(obj)
               : obj instanceof Object
                 ? clone(obj, {})
                 : obj;
  }

  function sep1000(somenum, usa, noprecision) {
    var dec = (''+somenum).split(/[.,]/)
       ,lendec = dec[1] ? dec[1].length : 0
       ,precision = lendec && !noprecision ? decPrecise(somenum,lendec) : dec[1]
       ,sep = usa ? ',' : '.'
       ,decsep = usa ? '.' : ',';

    // from http://stackoverflow.com/questions/10473994/javascript-adding-decimal-numbers-issue/10474209
    function decPrecise(d,l){
      return String(d.toFixed(12)).split(/[.,]/)[1].substr(0,l);
    }

    function xsep(num,sep) {
      var n = String(num).split('')
         ,i = -3;
      while (n.length + i > 0) {
          n.splice(i, 0, sep);
          i -= 4;
      }
      return n.join('');
    }
    return xsep(dec[0],sep) + (dec[1] ? decsep+precision :'');
  }

  function randomID() {
     return '_'+Math.floor(10000+Math.random()*10000000).toString(16);
  }

  return helperObj
}
<commit_msg>Cleanup (fnal for now)<commit_after>Helpers = initHelpers(window, document);

function initHelpers(w, d, undefined) {
  "use strict";
  var useCustomCss = false;
  var extended = extensions();

  var helperObj = {
    report: reportHTML,
    useJQ: loadJQ,
    useCSS: setCustomCss,
    log2Screen: log2Screen,
    initSO: SOInit,
    printDirect: printDirect,
    logClear: screenClear,
    Partial: Partial,
    cloneObj: cloneObj,
    randomID: randomID
  };

  function setCustomCss(yn) {
    useCustomCss = yn;
    if (useCustomCss) {
     loadCSS();
    } else {
     unloadCSS();
    }
  }

  function loadJQ(callback) {
      if (w.jQuery) {
        return callback && callback instanceof Function ? callback() : true;
      }

      var jqel  = createElementWithProps( 'script',
                                          { src: 'http://code.jquery.com/jquery-2.1.1.min.js', id: 'jqloaded' } );

      d.querySelector('head').appendChild(jqel);

      if (callback && callback instanceof Function)
          jqel.addEventListener('load', callback);

      return void(0);
  }

  function loadCSS() {
    if (d.querySelector('#HelperCSS')) {
      return true;
    }
    var css = createElementWithProps(
                    'link',
                    { href: 'https://rawgit.com/KooiInc/Helpers/master/Helpers.css',
                      type: 'text/css',
                      rel: 'stylesheet',
                      id: 'HelperCSS' }
              );
    return d.querySelector('head').appendChild (css);
  }

  function unloadCSS() {
   var css = document.querySelector('#HelperCSS');
   void (css && document.querySelector('head').removeChild(css));
  }

   // a few usefull augments/polyfills
  function extensions() {
        if (extended) {
          return true;
        }
        // remove double values from an array
        function noDoubles(arr) {
          arr = arr || this;
          return arr.filter(function(val) {
           return !this[val] ? ((this[val] = true), true) : false;
          }, {});
        }

        function stringformat() {
          var args = args2Array(arguments);
          return this.replace(/(\{\d+\})/g, function(a){
              return args[+(a.substr(1,a.length-2))||0];
          });
        };

        // determine value frequencies in an array
        function frequencies(arr) {
          var mapped = {sum: 0};
          (arr || this).map(function (a){
              if (!(a in this)) { this[a] = 1; }
              else { this[a] += 1; }
              this.sum += 1;
              return a; }, mapped
          );
          return mapped;
        }

        String.Format = function(){
          var args = args2Array(arguments);
          return stringformat.apply(args[0],args.slice(1));
        };

        String.prototype.format = function () {
          return stringformat.apply(this,arguments);
        }

        String.prototype.repeat = function(n){
          var s = this, r = '';
          while(n--) {
              r += s;
          }
          return r;
        };

        Number.prototype.toRange = Number.prototype.toRange || function (fn, startvalue) {
          startvalue = startvalue || 0;
          fn = isOfType(fn, Function) ? fn : function (a,i) { return i+startvalue; };
          return String(new Array(this.valueOf())).split(',').map( fn );
        };

        Number.prototype.pretty = Number.prototype.pretty || function (usa, noprecision) {
          return sep1000(this, usa, noprecision);
        };

        // run functions sequentially
        Function.prototype.andThen = function () {
         var args = args2Array(arguments)
            ,next = args.slice(1);
         this();
         return args[0] && next.length
                 ? args[0].andThen.apply(args[0], next)
                 : args.length == 1  ? args[0]()
                 : true;
        };

        Function.prototype.partial = Function.prototype.partial || function () {
          var stored_args = [].slice.call(arguments)
             ,fn = this;
          return function () {
             return fn.apply(null, stored_args.concat([].slice.call(arguments)));
          };
        };

        Function.prototype.partialx = function(){
          var fn = this, args = Array.prototype.slice.call(arguments);
          return function(){
            var arg = 0;
            for ( var i = 0; i < args.length && arg < arguments.length; i++ )
              if ( args[i] === undefined || args[i] === null)
                args[i] = arguments[arg++];
            return fn.apply(this, args);
          };
        };

        // is character @ [atpos] upperCase?
        boolean:String.prototype.charIsUpper = function (atpos){
          var chr = this.charAt(atpos);
          return /[A-Z]|[\u0080-\u024F]/.test(chr) && chr === chr.toUpperCase();
        };

        String.prototype.firstUp = function () {
          return this.slice(0,1).toUpperCase()+this.slice(1).toLowerCase();
        }

        String.prototype.isValidEmail = function() {
          // should be sufficient
          return  /^[\w._-]{1,}[+]?[\w._-]{0,}@[\w.-]+\.[a-zA-Z]{2,6}$/.test(this);
        }

        Array.prototype.toRE = function (){
          try { return RegExp.apply(null, this); }
          catch(e) { return /.*/; }
        }

        Array.toRE = function(arr) {
          return ([].toRE.call(arr));
        }

        Array.prototype.toCheckboxValues = Array.prototype.toCheckboxValues || function (checked) {
          checked = checked || [];
          var cbxs = [];
          this.map(function (a, i) {
                     cbxs.push({check: checked.indexOf(i)>-1 ? 1 : 0, val: String(a)});
                    }, cbxs);
          return cbxs;
        };

        String.prototype.reCleanup = function(encodeHTML){
          var str = encodeHTML ? this.replace(/[\u0080-\u024F]/g, function(a) {return '&#'+a.charCodeAt(0)+';';}) : this;
          return str.replace(/[?*|.+$\/]|\\/g, function(c) {return c==='\\' ? '' : '\\\\'+c;});
        }

        String.concat = function(joinchar){
          joinchar = joinchar || '';
          return args2Array(arguments).join(joinchar);
        }

        // MDN Array filter polyfill
        if (!Array.prototype.filter) {
          Array.prototype.filter = function(fun)
          {
            if (this === void 0 || this === null)
              throw new TypeError();

            var t = Object(this);
            var len = t.length >>> 0;
            if (typeof fun != "function")
              throw new TypeError();

            var res = [];
            var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
            for (var i = 0; i < len; i++) {
              if (i in t)  {
                var val = t[i];
                if (fun.call(thisArg, val, i, t))
                  res.push(val);
              }
            }

            return res;
          };
        }

        // MDN Array map polyfill
        if (!Array.prototype.map) {
          Array.prototype.map = function(fun)
          {
            if (this === void 0 || this === null)
              throw new TypeError();

            var t = Object(this);
            var len = t.length >>> 0;
            if (typeof fun !== "function")
              throw new TypeError();

            var res = new Array(len);
            var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
            for (var i = 0; i < len; i++) {
              if (i in t)
                res[i] = fun.call(thisArg, t[i], i, t);
            }

            return res;
        };
       }

       Array.prototype.uniquify = noDoubles;

       Array.prototype.frequencies = Array.prototype.frequencies || frequencies;

       Array.prototype.each = Array.prototype.each || function (fn,rewrite) {
        for (var i = 0; i < this.length; i++) {
         if (rewrite){
           this[i] = fn(this[i]);
         } else {
          fn(this[i]);
         }
        }
        return this;
       };

       // statics
       Object.print = function (obj, space) {
        space = space || '  ';
        return '
'+JSON.stringify(obj, null, space)+'
';
       }

       Object.format = function (obj, space) {
        space = space || '  ';
        return '
'+JSON.stringify(obj, null, space)+'
';
       }

       Object.isOneOf = isOfType;

       extended = true;
       return extended;
  }

  // SO specials
  function SOInit() {
    extensions();
    setCustomCss(true);
    var solink = d.querySelector('[data-linkid]');
    if (!solink.querySelector('.linkhover')) {
      solink.appendChild(createElementWithProps('div', { className: 'linkhover', 'data-dyn': 'true' } ));
    }
    loadJQ( jqcallback );

    function jqcallback() {
       $(document).on('mouseover', '.solink',  setSOLink);
       $(document).on('click', '[data-link]', clicklink);
    };
  }

  function setSOLink(e) {
      if ($(this).attr('data-link')) { return true; }

      $.ajax(
        {
            url: 'http://www.nicon.nl/node/stackx/questionx',
            data: {qid: $('.solink').first().attr('data-linkid')},
            method: 'post',
            success: SOcb
        }
      );

      function SOcb(data) {
          var resp = data.items && data.items[0] || data
             ,linkelement = $('.solink').first()
             ,linktip = linkelement.find('.linkhover').first()
          ;
          linktip.html(
              'Click logo to view the related question:

' + resp.title + '

' +
              'Asked by
'+ resp.owner.display_name + '
; '+
              'rep ' + resp.owner.reputation +'; question views: '+resp.view_count
          );
          linkelement.attr('data-link', resp.link);
      }

      return true;
  }

  // firefox needs a link added to the DOM, Chrome, IE don't
  function clicklink(e) {
    e.stopPropagation(); //just this link
    var linkurl = this.getAttribute('data-link')
       ,xlink = document.querySelector('a[href="'+linkurl+'"]') ||
                function() {
                    var _link = createElementWithProps(
                                  'a', { href: linkurl, target:'_blank', style: { display: 'none' } }
                                );
                    document.body.appendChild(_link);
                    return _link; }();
    xlink.click();
  }

  // Reporting
  // 1. simple
  function reportHTML() {
    var report = document.querySelector('#result') ||
        function() {
          var r = createElementWithProps( 'div', {id: 'result'} );
          document.querySelector('body').appendChild(r);
          return r;
        }();
    var entry = createElementWithProps('p');
    entry.innerHTML = args2Array(arguments).join('');
    report.appendChild(entry);

    if (useCustomCss) {
      var to = function () { entry.className = 'fadeIn'; };
      var dummy = setTimeout(to, 100);
    }
  }

  // 2. with parameter object usage
  function log2Screen() {
    var result = document.querySelector('#result') ||
                 function () {
                     var r = createElementWithProps('div', {id: 'result'});
                     document.body.appendChild(r);
                     return r; }()
       ,args = args2Array(arguments)
       ,lastarg = args.slice(-1)[0]
       ,optkeys = /clear|clrscr|direct|opts|useopts|continuous/i
       ,opts = Object.isOneOf(lastarg, Object) &&
               Object.keys(lastarg).filter(function(v){return optkeys.test(v);}).length
                ? lastarg.opts instanceof Object ? lastarg.opts : lastarg
                : {empty: 1};
    void(!opts.empty && (args = args.slice(0,-1)));

    if (opts.clrscr) {
        return result.innerHTML = '';
    }

    if (opts.clear) {
        result.innerHTML = '';
    }

    if (opts.continuous) {
        return result.innerHTML += args.join('').replace(/\n/g,'
');
    }

    var p = createElementWithProps('p');
    p.innerHTML = args.join('').replace(/\n/g,'
');
    result.appendChild(p);
    return opts.direct ? (p.className = 'fadeIn')
                       : setTimeout(function () { p.className = 'fadeIn'; }, +opts.timed*1000 || 0);
  }

  function printDirect() {
    return log2Screen.apply( null, args2Array(arguments).concat({direct:true}) );
  }

  function screenClear() {
    var res = d.querySelector('#result');
    return  res && (res.innerHTML = '') || true;
  }

  // utilities
  function createElementWithProps(elType, props) {
    var el = d.createElement(elType);
    if (props && isOfType(props, Object)) {
      for (var l in props) {
        if (!props.hasOwnProperty(l)) continue;
        if (/style/i.test(l)) {
          for (var ll in props[l]) {
            if (!props[l].hasOwnProperty(ll)) continue;
            el.style[ll] = props[l][ll];
          }
        } else {
          el[l] = props[l];
        }
      }
    }
    return el;
  }

  function args2Array(args, sliceAt){
    var arr = [];
    while (arr.length < args.length) { arr.push(args[arr.length]); }
    return sliceAt ? arr.slice(sliceAt) : arr;
  }

  // see: http://codereview.stackexchange.com/questions/23317/istypeobj-gettypeobj-v0/23329#23329
  function isOfType(obj) {
    if (!obj) { return false; }
    var test = arguments.length ? args2Array(arguments).slice(1) : null
       ,self = obj.constructor;
    return test
           ? !!(test.filter(function(a){return a === self}).length)
           : (self.constructor.name ||
              (String(self).match ( /^function\s*([^\s(]+)/im)
                || [0,'ANONYMOUS_CONSTRUCTOR']) [1] );
  };

  function Partial (func) {
    this.initial = args2Array(arguments, 1);
    this.funcp = function () {
                 return func.partialx.apply(func, this.initial);
                };
    if (!Partial.prototype.x) {
      Partial.prototype.x = function () {
        return this.funcp().apply(null, args2Array(arguments));
      };
    }
  };

  function cloneObj(obj) {
    // clone the whole enchillada, recursive
    function clone(o, curr) {
        for (var l in o){
            if (!o.hasOwnProperty(l)) { continue; }
            if (o[l] instanceof Object) {
                curr[l] = cloneObj(o[l]);
            } else {
                curr[l] = o[l];
            }
        }
        return curr;
    }

    return obj instanceof Array
             ? obj.slice().map( function (v) { return cloneObj(v); } )
             : obj instanceof Date
               ? new Date(obj)
               : obj instanceof Object
                 ? clone(obj, {})
                 : obj;
  }

  function sep1000(somenum, usa, noprecision) {
    var dec = (''+somenum).split(/[.,]/)
       ,lendec = dec[1] ? dec[1].length : 0
       ,precision = lendec && !noprecision ? decPrecise(somenum,lendec) : dec[1]
       ,sep = usa ? ',' : '.'
       ,decsep = usa ? '.' : ',';

    // from http://stackoverflow.com/questions/10473994/javascript-adding-decimal-numbers-issue/10474209
    function decPrecise(d,l){
      return String(d.toFixed(12)).split(/[.,]/)[1].substr(0,l);
    }

    function xsep(num,sep) {
      var n = String(num).split('')
         ,i = -3;
      while (n.length + i > 0) {
          n.splice(i, 0, sep);
          i -= 4;
      }
      return n.join('');
    }
    return xsep(dec[0],sep) + (dec[1] ? decsep+precision :'');
  }

  function randomID() {
     return '_'+Math.floor(10000+Math.random()*10000000).toString(16);
  }

  return helperObj;
}
<|endoftext|>


<commit_before> "use strict";
window.addEventListener('load', initHelpers);

function initHelpers() {
  var useCustomCss = false;

  window.Helpers = {
    report: reportHTML,
    isObj: isPlainObject,
    useJQ: loadJQ,
    augment: extensions,
    useCSS: setCustomCss
  };

  function reportHTML() {
    var report = document.querySelector('#result') ||
        function() {
          var r = document.createElement('div');
          r.id = 'result';
          document.querySelector('body').appendChild(r);
          return r;
        }();
    report.innerHTML += '
' + [].slice.call(arguments).join() + '
';
  }

  function setCustomCss(yn) {
    useCustomCss = yn;
    if (useCustomCss) {
     loadCSS();
    } else {
     unloadCSS();
    }
  }

  function isPlainObject(item) {
    return (
        item &&
        typeof item === "object" &&
        Object.prototype.toString.call(item) === "object Object" &&
        !item.nodeType
    );
  }

  function loadJQ(cb) {
      if (w.jQuery) {
        return function () {return void( cb && cb());}
      }
      return function () {
        var head  = d.querySelector('head')
           ,jq    = d.createElement('script');
        void( cb && jq.addEventListener('load',cb) );
        jq.src    = '//code.jquery.com/jquery-2.1.1.min.js';
        return head.appendChild(jq);
      };
  }

  function loadCSS() {
   var css = document.createElement('link');
   css.href = "https://rawgit.com/KooiInc/Helpers/master/Helpers.css";
   css.type = "text/css";
   css.rel = "stylesheet";
   css.id = "HelperCSS";
   document.querySelector('head').appendChild(css);
  }

  function unloadCSS() {
   var css = document.querySelector('#HelperCSS');
   void (css && document.querySelector('head').removeChild(css));
  }

   // a few usefull augments/polyfills
  function extensions() {
        // remove double values from an array
        function noDoubles(arr) {
          arr = arr || this;
          return arr.filter(function(val) {
           return !this[val] ? ((this[val] = true), true) : false;
          }, {});
        }

        function args2Array(args){
          var arr = [];
          for (var i=0;i<args.length;i+=1) {
            arr.push(args[i]);
          }
          return arr;
        }

        function stringformat() {
                var args = args2Array(arguments);
                return this.replace(/(\{\d+\})/g, function(a){
                    return args[+(a.substr(1,a.length-2))||0];
                });
        };

        // determine value frequencies in an array
        function frequencies(arr) {
          var mapped = {sum: 0};
          (arr || this).map(function (a){
              if (!(a in this)) { this[a] = 1; }
              else { this[a] += 1; }
              this.sum += 1;
              return a; }, mapped
          );
          return mapped;
        }

        String.Format = function(){
            var args = [].slice.call(arguments);
            return stringformat.apply(args[0],args.slice(1));
        };

        String.prototype.format = function () {
          return stringformat.apply(this,arguments);
        }

        String.prototype.repeat = function(n){
            var s = this, r = '';
            while(n--) {
                r += s;
            }
            return r;
        };

        // run functions sequentially
        Function.prototype.andThen = function () {
         var args = [].slice.call(arguments)
            ,next = args.slice(1);
         this();
         return args[0] && next.length
                 ? args[0].andThen.apply(args[0], next)
                 : args.length == 1  ? args[0]()
                 : true;
        };

        // is character @ [atpos] upperCase?
        boolean:String.prototype.charIsUpper = function (atpos){
          var chr = this.charAt(atpos);
          return /[A-Z]|[\u0080-\u024F]/.test(chr) && chr === chr.toUpperCase();
        };

        String.prototype.firstUp = function () {
          return this.slice(0,1).toUpperCase()+this.slice(1).toLowerCase();
        }

        String.prototype.isValidEmail = function() {
          // should be sufficient
          return  /^[\w._-]{1,}[+]?[\w._-]{0,}@[\w.-]+\.[a-zA-Z]{2,6}$/.test(this);
        }

        Array.prototype.toRE = function (){
          try { return RegExp.apply(null, this); }
          catch(e) { return /.*/; }
        }

        Array.toRE = function(arr) {
          return ([].toRE.call(arr));
        }

        String.prototype.reCleanup = function(encodeHTML){
          var str = encodeHTML ? this.replace(/[\u0080-\u024F]/g, function(a) {return '&#'+a.charCodeAt(0)+';';}) : this;
          return str.replace(/[?*|.+$\/]|\\/g, function(c) {return c==='\\' ? '' : '\\\\'+c;});
        }

        String.concat = function(joinchar){
          joinchar = joinchar || '';
          return [].slice.call(arguments).join(joinchar);
        }

        // MDN Array filter polyfill
        if (!Array.prototype.filter) {
          Array.prototype.filter = function(fun)
          {
            if (this === void 0 || this === null)
              throw new TypeError();

            var t = Object(this);
            var len = t.length >>> 0;
            if (typeof fun != "function")
              throw new TypeError();

            var res = [];
            var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
            for (var i = 0; i < len; i++) {
              if (i in t)  {
                var val = t[i];
                if (fun.call(thisArg, val, i, t))
                  res.push(val);
              }
            }

            return res;
          };
        }

        // MDN Array map polyfill
        if (!Array.prototype.map) {
          Array.prototype.map = function(fun)
          {
            if (this === void 0 || this === null)
              throw new TypeError();

            var t = Object(this);
            var len = t.length >>> 0;
            if (typeof fun !== "function")
              throw new TypeError();

            var res = new Array(len);
            var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
            for (var i = 0; i < len; i++) {
              if (i in t)
                res[i] = fun.call(thisArg, t[i], i, t);
            }

            return res;
        };
       }
       Array.prototype.uniquify = noDoubles;
       Array.prototype.frequencies = Array.prototype.frequencies || frequencies;

       Array.prototype.each = Array.prototype.each || function (fn,rewrite) {
       for (var i = 0; i < this.length; i++) {
        if (rewrite){
          this[i] = fn(this[i]);
        } else {
         fn(this[i]);
        }
       }
       return this;
     };
  }
};
<commit_msg>Update Helpers.js<commit_after> "use strict";
window.addEventListener('load', initHelpers);

function initHelpers() {
  var useCustomCss = false;

  window.Helpers = {
    report: reportHTML,
    isObj: isPlainObject,
    useJQ: loadJQ,
    augment: extensions,
    useCSS: setCustomCss
  };

  function reportHTML() {
    var report = document.querySelector('#result') ||
        function() {
          var r = document.createElement('div');
          r.id = 'result';
          document.querySelector('body').appendChild(r);
          return r;
        }();
    report.innerHTML += '
' + [].slice.call(arguments).join() + '
';
  }

  function setCustomCss(yn) {
    useCustomCss = yn;
    if (useCustomCss) {
     loadCSS();
    } else {
     unloadCSS();
    }
  }

  function isPlainObject(item) {
    return (
        item &&
        typeof item === "object" &&
        Object.prototype.toString.call(item) === "object Object" &&
        !item.nodeType
    );
  }

  function loadJQ(cb) {
      if (w.jQuery) {
        return function () {return void( cb && cb());}
      }
      return function () {
        var head  = d.querySelector('head')
           ,jq    = d.createElement('script');
        void( cb && jq.addEventListener('load',cb) );
        jq.src    = '//code.jquery.com/jquery-2.1.1.min.js';
        return head.appendChild(jq);
      };
  }

  function loadCSS() {
   var css = document.createElement('link');
   css.href = "https://rawgit.com/KooiInc/Helpers/master/Helpers.css";
   css.type = "text/css";
   css.rel = "stylesheet";
   css.id = "HelperCSS";
   document.querySelector('head').appendChild(css);
  }

  function unloadCSS() {
   var css = document.querySelector('#HelperCSS');
   void (css && document.querySelector('head').removeChild(css));
  }

   // a few usefull augments/polyfills
  function extensions() {
        // remove double values from an array
        function noDoubles(arr) {
          arr = arr || this;
          return arr.filter(function(val) {
           return !this[val] ? ((this[val] = true), true) : false;
          }, {});
        }

        function args2Array(args){
          var arr = [];
          for (var i=0;i<args.length;i+=1) {
            arr.push(args[i]);
          }
          return arr;
        }

        function stringformat() {
                var args = args2Array(arguments);
                return this.replace(/(\{\d+\})/g, function(a){
                    return args[+(a.substr(1,a.length-2))||0];
                });
        };

        // determine value frequencies in an array
        function frequencies(arr) {
          var mapped = {sum: 0};
          (arr || this).map(function (a){
              if (!(a in this)) { this[a] = 1; }
              else { this[a] += 1; }
              this.sum += 1;
              return a; }, mapped
          );
          return mapped;
        }

        String.Format = function(){
            var args = [].slice.call(arguments);
            return stringformat.apply(args[0],args.slice(1));
        };

        String.prototype.format = function () {
          return stringformat.apply(this,arguments);
        }

        String.prototype.repeat = function(n){
            var s = this, r = '';
            while(n--) {
                r += s;
            }
            return r;
        };

        // run functions sequentially
        Function.prototype.andThen = function () {
         var args = [].slice.call(arguments)
            ,next = args.slice(1);
         this();
         return args[0] && next.length
                 ? args[0].andThen.apply(args[0], next)
                 : args.length == 1  ? args[0]()
                 : true;
        };

        // is character @ [atpos] upperCase?
        boolean:String.prototype.charIsUpper = function (atpos){
          var chr = this.charAt(atpos);
          return /[A-Z]|[\u0080-\u024F]/.test(chr) && chr === chr.toUpperCase();
        };

        String.prototype.firstUp = function () {
          return this.slice(0,1).toUpperCase()+this.slice(1).toLowerCase();
        }

        String.prototype.isValidEmail = function() {
          // should be sufficient
          return  /^[\w._-]{1,}[+]?[\w._-]{0,}@[\w.-]+\.[a-zA-Z]{2,6}$/.test(this);
        }

        Array.prototype.toRE = function (){
          try { return RegExp.apply(null, this); }
          catch(e) { return /.*/; }
        }

        Array.toRE = function(arr) {
          return ([].toRE.call(arr));
        }

        String.prototype.reCleanup = function(encodeHTML){
          var str = encodeHTML ? this.replace(/[\u0080-\u024F]/g, function(a) {return '&#'+a.charCodeAt(0)+';';}) : this;
          return str.replace(/[?*|.+$\/]|\\/g, function(c) {return c==='\\' ? '' : '\\\\'+c;});
        }

        String.concat = function(joinchar){
          joinchar = joinchar || '';
          return [].slice.call(arguments).join(joinchar);
        }

        // MDN Array filter polyfill
        if (!Array.prototype.filter) {
          Array.prototype.filter = function(fun)
          {
            if (this === void 0 || this === null)
              throw new TypeError();

            var t = Object(this);
            var len = t.length >>> 0;
            if (typeof fun != "function")
              throw new TypeError();

            var res = [];
            var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
            for (var i = 0; i < len; i++) {
              if (i in t)  {
                var val = t[i];
                if (fun.call(thisArg, val, i, t))
                  res.push(val);
              }
            }

            return res;
          };
        }

        // MDN Array map polyfill
        if (!Array.prototype.map) {
          Array.prototype.map = function(fun)
          {
            if (this === void 0 || this === null)
              throw new TypeError();

            var t = Object(this);
            var len = t.length >>> 0;
            if (typeof fun !== "function")
              throw new TypeError();

            var res = new Array(len);
            var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
            for (var i = 0; i < len; i++) {
              if (i in t)
                res[i] = fun.call(thisArg, t[i], i, t);
            }

            return res;
        };
       }
       Array.prototype.uniquify = noDoubles;
       Array.prototype.frequencies = Array.prototype.frequencies || frequencies;

       Array.prototype.each = Array.prototype.each || function (fn,rewrite) {
       for (var i = 0; i < this.length; i++) {
        if (rewrite){
          this[i] = fn(this[i]);
        } else {
         fn(this[i]);
        }
       }
       return this;
     };
  }
}
<|endoftext|>


<commit_before>import * as actions from '../App/Actions/MapActions'
import * as types from '../App/Actions/Types'

describe('actions', () => {
  it('should create an action to update region', () => {
    const region = {
      latitude: 37.78825,
      longitude: -122.4324,
      latitudeDelta: 0.0922,
      longitudeDelta: 0.0421,
    }
    const expectedAction = {
      type: types.UPDATE_CURRENT_REGION,
      region
    }
    expect(actions.updateRegion(region)).toEqual(expectedAction)
  })
})
<commit_msg>MapActions testejä lisätyy<commit_after>import * as actions from '../App/Actions/MapActions'
import * as types from '../App/Actions/Types'

describe('actions', () => {
  it('should create an action to update region', () => {
    const region = {
      latitude: 37.78825,
      longitude: -122.4324,
      latitudeDelta: 0.0922,
      longitudeDelta: 0.0421,
    }
    const expectedAction = {
      type: types.UPDATE_CURRENT_REGION,
      region
    }
    expect(actions.updateRegion(region)).toEqual(expectedAction)
  })
})

describe('actions', () => {
  it('should create an action to update location', () => {
    const position = {
      latitude: 37.78825,
      longitude: -122.4324
    }
    const expectedAction = {
      type: types.UPDATE_CURRENT_LOCATION,
      position
    }
    expect(actions.updateLocation(position)).toEqual(expectedAction)
  })
})

describe('actions', () => {
  it('should create an action depending if location is known', () => {
    const isKnown = true
    const expectedAction = {
      type: types.LOCATION_KNOWN,
      isKnown
    }
    expect(actions.locationKnown(isKnown)).toEqual(expectedAction)
  })
})
<|endoftext|>


<commit_before>import React from 'react';
import MapManager from '../App/Map/MapManager';
import Marker from '../App/Map/Marker';

var mapManager = new MapManager();

// Test fails
it('is possible to add a new marker to MapManager', () => {
  var marker = new Marker(60.1699, 24.9384, 1);
  mapManager.addMarker(marker);
  expect(mapManager.getMarkers().length).toBe(1);
});
<commit_msg>refactoring test<commit_after>import React from 'react';
import MapManager from '../App/Map/MapManager';
import Marker from '../App/Map/Marker';

var mapManager = new MapManager();

describe('addMarker', function() {
  it('adds a new marker to the list', () => {
    var marker = new Marker(60.1699, 24.9384, 1);
    mapManager.addMarker(marker);
    expect(mapManager.getMarkers().length).toEqual(1);
  })
})
<|endoftext|>