<commit_before>import doctypes from 'photos/targets/browser/doctypes'
import log from 'cozy-logger'
import CozyClient from 'cozy-client'
import { getAllPhotos, getFilesFromDate } from 'photos/ducks/clustering/files'
import {
  readSetting,
  createSetting,
  updateParameters,
  getDefaultParameters,
  updateSettingStatus,
  getDefaultParametersMode,
  updateParamsPeriod
} from 'photos/ducks/clustering/settings'
import {
  computeEpsTemporal,
  computeEpsSpatial,
  reachabilities
} from 'photos/ducks/clustering/service'
import {
  PERCENTILE,
  DEFAULT_MODE,
  EVALUATION_THRESHOLD,
  CHANGES_RUN_LIMIT
} from 'photos/ducks/clustering/consts'
import { spatioTemporalScaled } from 'photos/ducks/clustering/metrics'
import { gradientClustering } from 'photos/ducks/clustering/gradient'
import { saveClustering, findAutoAlbums } from 'photos/ducks/clustering/albums'
import { albumsToClusterize } from 'photos/ducks/clustering/reclusterize'
import { prepareDataset } from 'photos/ducks/clustering/utils'
import { getMatchingParameters } from 'photos/ducks/clustering/matching'

// Compute the actual clustering based on the new dataset and the existing albums
const createNewClusters = async (client, params, clusterAlbums, dataset) => {
  const reachs = reachabilities(dataset, spatioTemporalScaled, params)
  const clusters = gradientClustering(dataset, reachs, params)
  if (clusters.length > 0) {
    return saveClustering(client, clusters, clusterAlbums)
  }
  return 0
}

// Compute the inital clustering
const createInitialClusters = async (client, paramsMode, dataset) => {
  const reachs = reachabilities(dataset, spatioTemporalScaled, paramsMode)
  const clusters = gradientClustering(dataset, reachs, paramsMode)
  return saveClustering(client, clusters)
}

// Clusterize the given photos, i.e. organize them depending on metrics
const clusterizePhotos = async (client, setting, dataset, albums) => {
  log('info', `Start clustering on ${dataset.length} photos`)

  let clusteredCount = 0
  try {
    if (albums && albums.length > 0) {
      // Build the clusterize Map, based on the dataset and existing photos
      const clusterize = await albumsToClusterize(client, dataset, albums)
      if (clusterize) {
        for (const [clusterAlbums, photos] of clusterize.entries()) {
          // Retrieve the relevant parameters to compute this cluster
          const params = getMatchingParameters(setting.parameters, photos)
          const paramsMode = getDefaultParametersMode(params)
          if (!paramsMode) {
            log('warn', 'No parameters for clustering found')
            continue
          }
          // Actual clustering
          clusteredCount += await createNewClusters(
            client,
            paramsMode,
            clusterAlbums,
            photos
          )
          setting = await updateParamsPeriod(client, setting, params, dataset)
        }
      } else {
        return
      }
    } else {
      // No album found: this is an initialization
      const params = setting.parameters[setting.parameters.length - 1]
      const paramsMode = getDefaultParametersMode(params)
      if (!paramsMode) {
        log('warn', 'No parameters for clustering found')
        return
      }
      clusteredCount = await createInitialClusters(client, paramsMode, dataset)
      setting = await updateParamsPeriod(client, setting, params, dataset)
    }
  } catch (e) {
    log('error', `An error occured during the clustering: ${JSON.stringify(e)}`)
    return
  }
  return { setting, clusteredCount }
}

const createParameter = (dataset, epsTemporal, epsSpatial) => {
  return {
    period: {
      start: dataset[0].datetime,
      end: dataset[dataset.length - 1].datetime
    },
    modes: [
      {
        name: DEFAULT_MODE,
        epsTemporal: epsTemporal,
        epsSpatial: epsSpatial
      }
    ]
  }
}
const initParameters = dataset => {
  log('info', `Compute clustering parameters on ${dataset.length} photos`)
  const epsTemporal = computeEpsTemporal(dataset, PERCENTILE)
  const epsSpatial = computeEpsSpatial(dataset, PERCENTILE)
  return createParameter(dataset, epsTemporal, epsSpatial)
}

const recomputeParameters = async (client, setting) => {
  const lastParams = setting.parameters[setting.parameters.length - 1]
  // The defaultEvaluation field is used at init if there are not enough files
  // for a proper parameters evaluation: we use default metrics and therefore,
  // this end period should not be taken into consideration.
  const lastPeriodEnd = lastParams.defaultEvaluation
    ? lastParams.period.start
    : lastParams.period.end

  const files = await getFilesFromDate(client, lastPeriodEnd)
  // Safety check
  if (files.length < EVALUATION_THRESHOLD) {
    return
  }
  log('info', `Compute clustering parameters on ${files.length} photos`)

  const dataset = prepareDataset(files)
  const epsTemporal = computeEpsTemporal(dataset, PERCENTILE)
  const epsSpatial = computeEpsSpatial(dataset, PERCENTILE)
  return createParameter(dataset, epsTemporal, epsSpatial)
}

const runClustering = async (client, setting) => {
  const sinceDate = setting.lastDate ? setting.lastDate : 0
  const photos = await getFilesFromDate(client, sinceDate, {
    indexDateField: 'created_at',
    limit: CHANGES_RUN_LIMIT
  })
  if (photos.length < 1) {
    log('warn', 'No photo found to clusterize')
    return 0
  }
  const albums = await findAutoAlbums(client)
  const dataset = prepareDataset(photos, albums)
  const result = await clusterizePhotos(client, setting, dataset, albums)
  if (!result) {
    return 0
  }

  log('info', `${result.clusteredCount} photos clustered since ${sinceDate}`)
  const newLastDate = photos[photos.length - 1].attributes.created_at
  setting = await updateSettingStatus(
    client,
    result.setting,
    result.clusteredCount,
    newLastDate
  )
  return photos
}

const onPhotoUpload = async () => {
  log('info', `Service called with COZY_URL: ${process.env.COZY_URL}`)

  const options = {
    schema: doctypes
  }
  const client = CozyClient.fromEnv(null, options)
  let setting = await readSetting(client)
  if (!setting) {
    // Create setting
    const files = await getAllPhotos(client)
    const dataset = prepareDataset(files)
    if (dataset.length == 0) {
      return 0
    }
    const params =
      dataset.length > EVALUATION_THRESHOLD
        ? initParameters(dataset)
        : getDefaultParameters(dataset)
    setting = await createSetting(client, params)
    log(
      'info',
      `Setting saved with ${JSON.stringify(
        params.modes
      )} on period ${JSON.stringify(params.period)}`
    )
  }

  if (setting.evaluationCount > EVALUATION_THRESHOLD) {
    // Recompute parameters when enough photos had been processed
    const newParams = await recomputeParameters(client, setting)
    if (newParams) {
      setting = await updateParameters(client, setting, newParams)
      log('info', `Setting updated with ${JSON.stringify(newParams)}`)
    }
  }

  /*
    NOTE: A service has a limited execution window, defined in the stack config,
    e.g. 200s. As the clustering of thousands of photos can be time-consuming,
    we force a CHANGES_RUN_LIMIT to serialize the execution and be able to
    restart the clustering from the last run.
    */
  const processedPhotos = await runClustering(client, setting)
  if (processedPhotos.length >= CHANGES_RUN_LIMIT) {
    // There are still changes to process: re-launch the service
    const args = {
      message: {
        name: 'onPhotoUpload',
        slug: 'photos'
      }
    }
    await client.getStackClient().jobs.create('service', args)
  }
}

onPhotoUpload()
<commit_msg>fix: Use global fetch<commit_after>import doctypes from 'photos/targets/browser/doctypes'
import log from 'cozy-logger'
import CozyClient from 'cozy-client'
import fetch from 'node-fetch'
import { getAllPhotos, getFilesFromDate } from 'photos/ducks/clustering/files'
import {
  readSetting,
  createSetting,
  updateParameters,
  getDefaultParameters,
  updateSettingStatus,
  getDefaultParametersMode,
  updateParamsPeriod
} from 'photos/ducks/clustering/settings'
import {
  computeEpsTemporal,
  computeEpsSpatial,
  reachabilities
} from 'photos/ducks/clustering/service'
import {
  PERCENTILE,
  DEFAULT_MODE,
  EVALUATION_THRESHOLD,
  CHANGES_RUN_LIMIT
} from 'photos/ducks/clustering/consts'
import { spatioTemporalScaled } from 'photos/ducks/clustering/metrics'
import { gradientClustering } from 'photos/ducks/clustering/gradient'
import { saveClustering, findAutoAlbums } from 'photos/ducks/clustering/albums'
import { albumsToClusterize } from 'photos/ducks/clustering/reclusterize'
import { prepareDataset } from 'photos/ducks/clustering/utils'
import { getMatchingParameters } from 'photos/ducks/clustering/matching'

// This is required for using cozy-client as a node service.
// See https://github.com/cozy/cozy-client/blob/5f5939aef5f55f069b88f0bc1b897a7027a128e5/docs/details.md#how-to-use-the-client-on-node-environment-referenceerror-fetch-is-not-defined-
global.fetch = fetch

// Compute the actual clustering based on the new dataset and the existing albums
const createNewClusters = async (client, params, clusterAlbums, dataset) => {
  const reachs = reachabilities(dataset, spatioTemporalScaled, params)
  const clusters = gradientClustering(dataset, reachs, params)
  if (clusters.length > 0) {
    return saveClustering(client, clusters, clusterAlbums)
  }
  return 0
}

// Compute the inital clustering
const createInitialClusters = async (client, paramsMode, dataset) => {
  const reachs = reachabilities(dataset, spatioTemporalScaled, paramsMode)
  const clusters = gradientClustering(dataset, reachs, paramsMode)
  return saveClustering(client, clusters)
}

// Clusterize the given photos, i.e. organize them depending on metrics
const clusterizePhotos = async (client, setting, dataset, albums) => {
  log('info', `Start clustering on ${dataset.length} photos`)

  let clusteredCount = 0
  try {
    if (albums && albums.length > 0) {
      // Build the clusterize Map, based on the dataset and existing photos
      const clusterize = await albumsToClusterize(client, dataset, albums)
      if (clusterize) {
        for (const [clusterAlbums, photos] of clusterize.entries()) {
          // Retrieve the relevant parameters to compute this cluster
          const params = getMatchingParameters(setting.parameters, photos)
          const paramsMode = getDefaultParametersMode(params)
          if (!paramsMode) {
            log('warn', 'No parameters for clustering found')
            continue
          }
          // Actual clustering
          clusteredCount += await createNewClusters(
            client,
            paramsMode,
            clusterAlbums,
            photos
          )
          setting = await updateParamsPeriod(client, setting, params, dataset)
        }
      } else {
        return
      }
    } else {
      // No album found: this is an initialization
      const params = setting.parameters[setting.parameters.length - 1]
      const paramsMode = getDefaultParametersMode(params)
      if (!paramsMode) {
        log('warn', 'No parameters for clustering found')
        return
      }
      clusteredCount = await createInitialClusters(client, paramsMode, dataset)
      setting = await updateParamsPeriod(client, setting, params, dataset)
    }
  } catch (e) {
    log('error', `An error occured during the clustering: ${JSON.stringify(e)}`)
    return
  }
  return { setting, clusteredCount }
}

const createParameter = (dataset, epsTemporal, epsSpatial) => {
  return {
    period: {
      start: dataset[0].datetime,
      end: dataset[dataset.length - 1].datetime
    },
    modes: [
      {
        name: DEFAULT_MODE,
        epsTemporal: epsTemporal,
        epsSpatial: epsSpatial
      }
    ]
  }
}
const initParameters = dataset => {
  log('info', `Compute clustering parameters on ${dataset.length} photos`)
  const epsTemporal = computeEpsTemporal(dataset, PERCENTILE)
  const epsSpatial = computeEpsSpatial(dataset, PERCENTILE)
  return createParameter(dataset, epsTemporal, epsSpatial)
}

const recomputeParameters = async (client, setting) => {
  const lastParams = setting.parameters[setting.parameters.length - 1]
  // The defaultEvaluation field is used at init if there are not enough files
  // for a proper parameters evaluation: we use default metrics and therefore,
  // this end period should not be taken into consideration.
  const lastPeriodEnd = lastParams.defaultEvaluation
    ? lastParams.period.start
    : lastParams.period.end

  const files = await getFilesFromDate(client, lastPeriodEnd)
  // Safety check
  if (files.length < EVALUATION_THRESHOLD) {
    return
  }
  log('info', `Compute clustering parameters on ${files.length} photos`)

  const dataset = prepareDataset(files)
  const epsTemporal = computeEpsTemporal(dataset, PERCENTILE)
  const epsSpatial = computeEpsSpatial(dataset, PERCENTILE)
  return createParameter(dataset, epsTemporal, epsSpatial)
}

const runClustering = async (client, setting) => {
  const sinceDate = setting.lastDate ? setting.lastDate : 0
  const photos = await getFilesFromDate(client, sinceDate, {
    indexDateField: 'created_at',
    limit: CHANGES_RUN_LIMIT
  })
  if (photos.length < 1) {
    log('warn', 'No photo found to clusterize')
    return 0
  }
  const albums = await findAutoAlbums(client)
  const dataset = prepareDataset(photos, albums)
  const result = await clusterizePhotos(client, setting, dataset, albums)
  if (!result) {
    return 0
  }

  log('info', `${result.clusteredCount} photos clustered since ${sinceDate}`)
  const newLastDate = photos[photos.length - 1].attributes.created_at
  setting = await updateSettingStatus(
    client,
    result.setting,
    result.clusteredCount,
    newLastDate
  )
  return photos
}

const onPhotoUpload = async () => {
  log('info', `Service called with COZY_URL: ${process.env.COZY_URL}`)

  const options = {
    schema: doctypes
  }
  const client = CozyClient.fromEnv(null, options)
  let setting = await readSetting(client)
  if (!setting) {
    // Create setting
    const files = await getAllPhotos(client)
    const dataset = prepareDataset(files)
    if (dataset.length == 0) {
      return 0
    }
    const params =
      dataset.length > EVALUATION_THRESHOLD
        ? initParameters(dataset)
        : getDefaultParameters(dataset)
    setting = await createSetting(client, params)
    log(
      'info',
      `Setting saved with ${JSON.stringify(
        params.modes
      )} on period ${JSON.stringify(params.period)}`
    )
  }

  if (setting.evaluationCount > EVALUATION_THRESHOLD) {
    // Recompute parameters when enough photos had been processed
    const newParams = await recomputeParameters(client, setting)
    if (newParams) {
      setting = await updateParameters(client, setting, newParams)
      log('info', `Setting updated with ${JSON.stringify(newParams)}`)
    }
  }

  /*
    NOTE: A service has a limited execution window, defined in the stack config,
    e.g. 200s. As the clustering of thousands of photos can be time-consuming,
    we force a CHANGES_RUN_LIMIT to serialize the execution and be able to
    restart the clustering from the last run.
    */
  const processedPhotos = await runClustering(client, setting)
  if (processedPhotos.length >= CHANGES_RUN_LIMIT) {
    // There are still changes to process: re-launch the service
    const args = {
      message: {
        name: 'onPhotoUpload',
        slug: 'photos'
      }
    }
    await client.getStackClient().jobs.create('service', args)
  }
}

onPhotoUpload()
<|endoftext|>


<commit_before>

/* A calculator object for the Float texture based GEMM

	gl - WebGL object

	* uploads and downloads data
	* executes calculation
 */
function GEMMFloatCalculator(webgl){
	this.webgl = webgl;

	// create the webgl shader program for this calculation
	// based on the specific fragment shader for this calculation
	// and the generic pass through shader
	this.program = this.webgl.createProgram(GEMMFloatCalculator.FRAGMENT_SHADER);

}

module.exports = GEMMFloatCalculator;

GEMMFloatCalculator.TEXTURE_UNIFORM_NAME_1 = "A";
GEMMFloatCalculator.TEXTURE_UNIFORM_NAME_2 = "Bt";

GEMMFloatCalculator.FRAGMENT_SHADER = "                                  \n\
// fragment shader that calculates the sum of the passed row and         \n\
// column (texture coord).                                               \n\
// we loop over the row and column and sum the product.                  \n\
// product is then rendered to 32-bit IEEE754 floating point in the      \n\
// output RGBA canvas.                                                   \n\
// readPixel is used to read the bytes.                                  \n\
																		 \n\
precision highp float;                                                   \n\
																		 \n\
varying vec2      outTex;	// row, column to calculate                  \n\
uniform sampler2D A;		// padded A                                  \n\
uniform sampler2D Bt;		// padded B transpose                        \n\
uniform int       K;		// interior (shared) dimension               \n\
uniform float     delta;	// space between items in shared dimension   \n\
																		 \n\
// sum row i x col j                                                     \n\
float sumrowcol(float i, float j) {                                      \n\
	float sum = 0.;			// sum for this row/column pair              \n\
	float k = 0.5 * delta;	// position on source texture                \n\
	for (int l=0 ; l<4096 ; ++l) {                                       \n\
		if(l >= K) break;         // stop when we finish the row/column  \n\
		vec4 a_ik = texture2D( A, vec2(k, i));                           \n\
		vec4 b_kj = texture2D(Bt, vec2(k, j));                           \n\
		sum += dot(a_ik, b_kj);                                          \n\
		k += (delta * 4.0);      // (k * 4 + 0.5)*delta                  \n\
	}                                                                    \n\
	return sum;                                                          \n\
}                                                                        \n\
																		 \n\
// Render float to bytes according to IEEE 754 Floating Point            \n\
vec4 encodeFloat(float val) {                                            \n\
																		 \n\
	// TODO: correctly handle denormal numbers                           \n\
	// http://www.2ality.com/2012/04/number-encoding.html                \n\
	float a = abs(val);                           // encode absolute value + sign \n\
	float exp = floor(log2(a));                 // number of powers of 2 \n\
	float mant = pow(2.,log2(a)-exp) * pow(2.,23.);  // multiply to fill 24 bits (implied leading 1) \n\
	float mant1 = floor(mant / 256. / 256.);    // first 8 bits of mantissa \n\
	float mant2 = mod(floor(mant / 256.),256.); // second 8 bits         \n\
	float mant3 = mod(mant,256.);               // third 8 bits          \n\
																		 \n\
	highp float sign = 128.-128.*(a/val);			// sign bit is 256 or 0  \n\
	highp float e = (sign+exp+127.)/510.;		// exponent and sign     \n\
	highp float m1 = (mant1-(128.*(1.-mod(exp+127.,2.))))/255.; // handle leading bit \n\
	highp float m2 = (mant2)/255.;				// middle part           \n\
	highp float m3 = (mant3+.5)/255.;			// scale to 0 - 255      \n\
																		 \n\
	return vec4(m3,m2,m1,e);                                             \n\
}                                                                        \n\
																		 \n\
void main(void) {                                                        \n\
																		 \n\
	 // get the implied row and column from .y and .x                    \n\
	 // of passed (output) texel                                         \n\
 	float row = outTex.y;                                                \n\
	float col = outTex.x;                                                \n\
																		 \n\
	// sum row x col for the passed pixel                                \n\
	float sum = sumrowcol(row,col);                                      \n\
																		 \n\
	if (sum == 0.) {                                                     \n\
		gl_FragColor = vec4(0.,0.,0.,0.);                                \n\
		return;                                                          \n\
	}                                                                    \n\
																		 \n\
 	// output an IEEE754 32-bit floating point number 				  	 \n\
	gl_FragColor = encodeFloat(sum);									 \n\
}                                                                        \n\
";

/* Pack the given matrix data into texel layout for use by texture shader.
   This layout places each matrix in a different plane of the texture,
   one in 'r' the second in 'g'
 */
GEMMFloatCalculator.packData = function(data, r, c, transpose) {

	var mod = (c % 4),
		pad = 4 - mod;

	if (mod === 0) {
		// special case if column count is a multiple of four
		//console.log("mod == 0, returning original data");
		return data;
	}

	// dimensions
	var BYTES_PER_TEXEL = 4; // RGBA: one byte per channel, four bytes per texel
	var dst = 0,
		src = 0,
		p = 0;

	var texelcount = r*(c + pad);
	// get texel data (rgb) as a Float32Array
	var texels = new Float32Array(texelcount);

	// pad each row to multiple of four, with zeros
	for(dst = 0, src = 0; dst < texelcount; dst++, src++){

		if(src != 0 && src % c == 0){
			// reached the end of a source column, pad with zeros
			for(p = 0; p < pad; p++){
				texels[dst + p] = 0.0;
			}
			dst += pad;
		}

		// copy the real data
		texels[dst] = data[src];

	}

	return texels;
};

/* Create a texture from the given texel data and bind it to our shader program.
  must set up shader first
*/
GEMMFloatCalculator.prototype.bindInputTexture = function(h, w, texels, textureUnit, name){
	var gl = this.webgl.context,
		program = this.program;

	// create the texture from our floats
	var texture = gl.createTexture();
	gl.activeTexture(textureUnit); // gl.TEXTURE0, gl.TEXTURE1, etc
	gl.bindTexture(	  gl.TEXTURE_2D, texture);
	gl.texImage2D(	  gl.TEXTURE_2D, /*level*/0, gl.RGBA, w / 4, h, 0,
					  gl.RGBA, gl.FLOAT, texels);

	// clamp to edge to support non-power of two textures
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
	// don't interpolate when getting data from texture
	gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
	gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

	var sampler = gl.getUniformLocation(program, name);
	gl.uniform1i(sampler, textureUnit - gl.TEXTURE0); // (textureUnit - gl.TEXTURE0)

	return texture;
};

GEMMFloatCalculator.prototype.bindDestinationTexture = function(h1, w2, dstTex) {
	var gl = this.webgl.context;

	// create and bind renderbuffer
	this.renderbuffer = this.renderbuffer || gl.createRenderbuffer();

	gl.bindRenderbuffer(gl.RENDERBUFFER, null);
	gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderbuffer);

	gl.renderbufferStorage(gl.RENDERBUFFER,gl.DEPTH_COMPONENT16, w2, h1);

	// create and bind framebuffer
	this.framebuffer = this.framebuffer || gl.createFramebuffer();

	gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);

	gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,dstTex,/*level*/0);
	gl.framebufferRenderbuffer(gl.FRAMEBUFFER,gl.DEPTH_ATTACHMENT,gl.RENDERBUFFER,this.renderbuffer);

	return this.framebuffer;
};

/* Calculate the GEMM, with the given data.

  Steps are:

  1. Activate our shader program
  2. Create a texture to hold the input data
  3. Set shader program parameters (based on matrix sizes)
  4. Create a texture to hold the output
  5. Activate calculation with `drawElements`
  6. Read result with `readPixels` and return it

 TODO: signature should look like this:
 ( TRANSA, TRANSB, M, N, K, ALPHA, A, LDA, B, LDB, BETA, C, LDC )
 http://www.math.utah.edu/software/lapack/lapack-blas/dgemm.html
 */
GEMMFloatCalculator.prototype.calculate = function(M, N, K, alpha, A, B, beta, C){

	var gl = this.webgl.context,
		destTexture,
		frameBuffer,
		rawbuffer;

	var h1 = M,
		w1 = K,
		h2 = N, // temp passing in transpose
		w2 = K; // temp passing in transpose

	//console.log(h1 + "x" + w1 + " times " + h2 + "x" + w2);
	// set calculator program to current shader program
	gl.useProgram(this.program);

	// pack each matrix into a single RGBA texel array, with the second transposed
	var texels1 = GEMMFloatCalculator.packData(A, h1, w1, false);
	 	texels2 = GEMMFloatCalculator.packData(B, h2, w2, true);

	//console.log(texels1);

	// set canvas and viewport size
	this.webgl.canvas.height = M;
	this.webgl.canvas.width = N;
	gl.viewport(0, 0, N, M);

	var mod = (K % 4),
		pad = mod == 0 ? 0 : 4 - mod;

	//console.log("height: {0}, width: {1}".format(this.webgl.canvas.height, this.webgl.canvas.width));
	//console.log("pad: {0}".format(pad));

	// create and bind our input texture using matrix data
	this.bindInputTexture(M, K + pad, texels1, gl.TEXTURE0, GEMMFloatCalculator.TEXTURE_UNIFORM_NAME_1);
	this.bindInputTexture(N, K + pad, texels2, gl.TEXTURE1, GEMMFloatCalculator.TEXTURE_UNIFORM_NAME_2);

	// set the data specific variables in our shader program
	this.bindUniforms(K + pad);
	this.bindVertices();

	// create our destination texture
	destTexture = this.webgl.createDestinationTexture(M, N);
	frameBuffer = this.bindDestinationTexture(M, N, destTexture);

	if( gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)
		throw new Error("Bound framebuffer is not complete.");

	// initiate calculation
	gl.drawElements(gl.TRIANGLES, /*num items*/6, gl.UNSIGNED_SHORT, 0);

	// create destination buffer
	rawbuffer = new ArrayBuffer(M*N*Float32Array.BYTES_PER_ELEMENT);

	// read the result into our buffer, as bytes
	prod = new Uint8Array(rawbuffer);
	gl.readPixels(0, 0, N, M, gl.RGBA, gl.UNSIGNED_BYTE, prod);

	// create and return a view over result bytes as a float array
	return new Float32Array(rawbuffer); // M x N
};

	// set up vars for the shaders
GEMMFloatCalculator.prototype.bindUniforms = function(K) {
	var gl = this.webgl.context;

	// get var locations
	var length	= gl.getUniformLocation(this.program, "K");
	var delta	= gl.getUniformLocation(this.program, "delta");

	// bind length of shared dimension
	gl.uniform1i(length, K / 4.);

	// bind step size for input texture
	// 3x10 x 10x2 -> 3x2 output, but requires 10x10 *input* texture
	gl.uniform1f(delta, 1./K); // 1./w
};

/* setup required to draw a square to our vertex shader and have
   fragment shader called for each pixel
 */
GEMMFloatCalculator.prototype.bindVertices = function() {
	var gl = this.webgl.context,
		renderer = this.program;

	// bind vertices
	var aPos = gl.getAttribLocation(renderer,"aPos");
	var vertexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

	// define a square that covers the screen
	var vertices = [-1.0, -1.0, 0.0,	// bottom left
					 1.0, -1.0, 0.0,	// bottom right
					 1.0,  1.0, 0.0,	// top right
					-1.0,  1.0, 0.0];	// top left
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
	gl.vertexAttribPointer(aPos, /*item size*/3, gl.FLOAT, false, 0, 0);
	gl.enableVertexAttribArray(aPos);

	// bind texture cords
	var aTex = gl.getAttribLocation(renderer,"aTex");
	var texCoords = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, texCoords);
	var textureCoords = [0.0, 0.0,
						 1.0, 0.0,
						 1.0, 1.0,
						 0.0, 1.0];
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
	gl.vertexAttribPointer(aTex, /*item size*/2, gl.FLOAT, false, 0, 0);
	gl.enableVertexAttribArray(aTex);

	// index to vertices
	var indices = gl.createBuffer();
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
	// tesselate square into triangles
	// indeces into vertex array creating triangles, with counter-clockwise winding
	var vertexIndices = [0, 1, 2,	// bottom right triangle
						 0, 2, 3];	// top left triangle
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertexIndices), gl.STATIC_DRAW);
};
<commit_msg>reduce texture shader to single uniform variable<commit_after>

/* A calculator object for the Float texture based GEMM

	gl - WebGL object

	* uploads and downloads data
	* executes calculation
 */
function GEMMFloatCalculator(webgl){
	this.webgl = webgl;

	// create the webgl shader program for this calculation
	// based on the specific fragment shader for this calculation
	// and the generic pass through shader
	this.program = this.webgl.createProgram(GEMMFloatCalculator.FRAGMENT_SHADER);

}

module.exports = GEMMFloatCalculator;

GEMMFloatCalculator.TEXTURE_UNIFORM_NAME_1 = "A";
GEMMFloatCalculator.TEXTURE_UNIFORM_NAME_2 = "B_t";

GEMMFloatCalculator.FRAGMENT_SHADER = "                                  \n\
// fragment shader that calculates the sum of the passed row and         \n\
// column (texture coord).                                               \n\
// we loop over the row and column and sum the product.                  \n\
// product is then rendered to 32-bit IEEE754 floating point in the      \n\
// output RGBA canvas.                                                   \n\
// readPixel is used to read the bytes.                                  \n\
																		 \n\
precision highp float;                                                   \n\
																		 \n\
varying vec2      outTex;	// row, column to calculate                  \n\
uniform sampler2D A;		// padded A                                  \n\
uniform sampler2D B_t;		// padded B transpose                        \n\
uniform int       K;		// interior (shared) dimension               \n\
float delta = 1./float(K);	// space between items in shared dimension   \n\
																		 \n\
// sum row i x col j                                                     \n\
float sumrowcol(float i, float j) {                                      \n\
	float sum = 0.;			// sum for this row/column pair              \n\
	float k = 0.5 * delta;	// position on source texture                \n\
	for (int l=0 ; l<2048 ; ++l) {                                       \n\
		if(l >= K / 4) break;    // stop when we finish the row/column   \n\
																		 \n\
		// retrieve next four elements from each texture                 \n\
		vec4 a_ik = texture2D(  A, vec2(k, i));                          \n\
		vec4 b_kj = texture2D(B_t, vec2(k, j));                          \n\
																		 \n\
		// use `dot` to process four elements at a time                  \n\
		sum += dot(a_ik, b_kj);                                          \n\
		k += (delta * 4.0);      // (k * 4 + 0.5)*delta                  \n\
	}                                                                    \n\
	return sum;                                                          \n\
}                                                                        \n\
																		 \n\
// Render float to bytes according to IEEE 754 Floating Point            \n\
vec4 encodeFloat(float val) {                                            \n\
																		 \n\
	// TODO: correctly handle denormal numbers                           \n\
	// http://www.2ality.com/2012/04/number-encoding.html                \n\
	float a = abs(val);                           // encode absolute value + sign \n\
	float exp = floor(log2(a));                 // number of powers of 2 \n\
	float mant = pow(2.,log2(a)-exp) * pow(2.,23.);  // multiply to fill 24 bits (implied leading 1) \n\
	float mant1 = floor(mant / 256. / 256.);    // first 8 bits of mantissa \n\
	float mant2 = mod(floor(mant / 256.),256.); // second 8 bits         \n\
	float mant3 = mod(mant,256.);               // third 8 bits          \n\
																		 \n\
	highp float sign = 128.-128.*(a/val);			// sign bit is 256 or 0  \n\
	highp float e = (sign+exp+127.)/510.;		// exponent and sign     \n\
	highp float m1 = (mant1-(128.*(1.-mod(exp+127.,2.))))/255.; // handle leading bit \n\
	highp float m2 = (mant2)/255.;				// middle part           \n\
	highp float m3 = (mant3+.5)/255.;			// scale to 0 - 255      \n\
																		 \n\
	return vec4(m3,m2,m1,e);                                             \n\
}                                                                        \n\
																		 \n\
void main(void) {                                                        \n\
																		 \n\
	 // get the implied row and column from .y and .x                    \n\
	 // of passed (output) texel                                         \n\
 	float row = outTex.y;                                                \n\
	float col = outTex.x;                                                \n\
																		 \n\
	// sum row x col for the passed pixel                                \n\
	float sum = sumrowcol(row,col);                                      \n\
																		 \n\
	if (sum == 0.) {                                                     \n\
		gl_FragColor = vec4(0.,0.,0.,0.);                                \n\
		return;                                                          \n\
	}                                                                    \n\
																		 \n\
 	// output an IEEE754 32-bit floating point number 				  	 \n\
	gl_FragColor = encodeFloat(sum);									 \n\
}                                                                        \n\
";

/* Pack the given matrix data into texel layout for use by texture shader.
   This layout places each matrix in a different plane of the texture,
   one in 'r' the second in 'g'
 */
GEMMFloatCalculator.packData = function(data, r, c, transpose) {

	var mod = (c % 4),
		pad = 4 - mod;

	if (mod === 0) {
		// special case if column count is a multiple of four
		//console.log("mod == 0, returning original data");
		return data;
	}

	// dimensions
	var BYTES_PER_TEXEL = 4; // RGBA: one byte per channel, four bytes per texel
	var dst = 0,
		src = 0,
		p = 0;

	var texelcount = r*(c + pad);
	// get texel data (rgb) as a Float32Array
	var texels = new Float32Array(texelcount);

	// pad each row to multiple of four, with zeros
	for(dst = 0, src = 0; dst < texelcount; dst++, src++){

		if(src != 0 && src % c == 0){
			// reached the end of a source column, pad with zeros
			for(p = 0; p < pad; p++){
				texels[dst + p] = 0.0;
			}
			dst += pad;
		}

		// copy the real data
		texels[dst] = data[src];

	}

	return texels;
};

/* Create a texture from the given texel data and bind it to our shader program.
  must set up shader first
*/
GEMMFloatCalculator.prototype.bindInputTexture = function(h, w, texels, textureUnit, name){
	var gl = this.webgl.context,
		program = this.program;

	// create the texture from our floats
	var texture = gl.createTexture();
	gl.activeTexture(textureUnit); // gl.TEXTURE0, gl.TEXTURE1, etc
	gl.bindTexture(	  gl.TEXTURE_2D, texture);
	gl.texImage2D(	  gl.TEXTURE_2D, /*level*/0, gl.RGBA, w / 4, h, 0,
					  gl.RGBA, gl.FLOAT, texels);

	// clamp to edge to support non-power of two textures
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
	// don't interpolate when getting data from texture
	gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
	gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

	var sampler = gl.getUniformLocation(program, name);
	gl.uniform1i(sampler, textureUnit - gl.TEXTURE0); // (textureUnit - gl.TEXTURE0)

	return texture;
};

GEMMFloatCalculator.prototype.bindDestinationTexture = function(h1, w2, dstTex) {
	var gl = this.webgl.context;

	// create and bind renderbuffer
	this.renderbuffer = this.renderbuffer || gl.createRenderbuffer();

	gl.bindRenderbuffer(gl.RENDERBUFFER, null);
	gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderbuffer);

	gl.renderbufferStorage(gl.RENDERBUFFER,gl.DEPTH_COMPONENT16, w2, h1);

	// create and bind framebuffer
	this.framebuffer = this.framebuffer || gl.createFramebuffer();

	gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);

	gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,dstTex,/*level*/0);
	gl.framebufferRenderbuffer(gl.FRAMEBUFFER,gl.DEPTH_ATTACHMENT,gl.RENDERBUFFER,this.renderbuffer);

	return this.framebuffer;
};

/* Calculate the GEMM, with the given data.

  Steps are:

  1. Activate our shader program
  2. Create a texture to hold the input data
  3. Set shader program parameters (based on matrix sizes)
  4. Create a texture to hold the output
  5. Activate calculation with `drawElements`
  6. Read result with `readPixels` and return it

 TODO: signature should look like this:
 ( TRANSA, TRANSB, M, N, K, ALPHA, A, LDA, B, LDB, BETA, C, LDC )
 http://www.math.utah.edu/software/lapack/lapack-blas/dgemm.html
 */
GEMMFloatCalculator.prototype.calculate = function(M, N, K, alpha, A, B, beta, C){

	var gl = this.webgl.context,
		destTexture,
		frameBuffer,
		rawbuffer;

	var h1 = M,
		w1 = K,
		h2 = N, // temp passing in transpose
		w2 = K; // temp passing in transpose

	//console.log(h1 + "x" + w1 + " times " + h2 + "x" + w2);
	// set calculator program to current shader program
	gl.useProgram(this.program);

	// pack each matrix into a single RGBA texel array, with the second transposed
	var texels1 = GEMMFloatCalculator.packData(A, h1, w1, false);
	 	texels2 = GEMMFloatCalculator.packData(B, h2, w2, true);

	//console.log(texels1);

	// set canvas and viewport size
	this.webgl.canvas.height = M;
	this.webgl.canvas.width = N;
	gl.viewport(0, 0, N, M);

	var mod = (K % 4),
		pad = mod == 0 ? 0 : 4 - mod;

	//console.log("height: {0}, width: {1}".format(this.webgl.canvas.height, this.webgl.canvas.width));
	//console.log("pad: {0}".format(pad));

	// create and bind our input texture using matrix data
	this.bindInputTexture(M, K + pad, texels1, gl.TEXTURE0, GEMMFloatCalculator.TEXTURE_UNIFORM_NAME_1);
	this.bindInputTexture(N, K + pad, texels2, gl.TEXTURE1, GEMMFloatCalculator.TEXTURE_UNIFORM_NAME_2);

	// set the data specific variables in our shader program
	this.bindUniforms(K + pad);
	this.bindVertices();

	// create our destination texture
	destTexture = this.webgl.createDestinationTexture(M, N);
	frameBuffer = this.bindDestinationTexture(M, N, destTexture);

	if( gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)
		throw new Error("Bound framebuffer is not complete.");

	// initiate calculation
	gl.drawElements(gl.TRIANGLES, /*num items*/6, gl.UNSIGNED_SHORT, 0);

	// create destination buffer
	rawbuffer = new ArrayBuffer(M*N*Float32Array.BYTES_PER_ELEMENT);

	// read the result into our buffer, as bytes
	prod = new Uint8Array(rawbuffer);
	gl.readPixels(0, 0, N, M, gl.RGBA, gl.UNSIGNED_BYTE, prod);

	// create and return a view over result bytes as a float array
	return new Float32Array(rawbuffer); // M x N
};

	// set up vars for the shaders
GEMMFloatCalculator.prototype.bindUniforms = function(K) {
	var gl = this.webgl.context;

	// get var locations
	var length	= gl.getUniformLocation(this.program, "K");

	// bind length of shared dimension
	gl.uniform1i(length, K);

};

/* setup required to draw a square to our vertex shader and have
   fragment shader called for each pixel
 */
GEMMFloatCalculator.prototype.bindVertices = function() {
	var gl = this.webgl.context,
		renderer = this.program;

	// bind vertices
	var aPos = gl.getAttribLocation(renderer,"aPos");
	var vertexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

	// define a square that covers the screen
	var vertices = [-1.0, -1.0, 0.0,	// bottom left
					 1.0, -1.0, 0.0,	// bottom right
					 1.0,  1.0, 0.0,	// top right
					-1.0,  1.0, 0.0];	// top left
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
	gl.vertexAttribPointer(aPos, /*item size*/3, gl.FLOAT, false, 0, 0);
	gl.enableVertexAttribArray(aPos);

	// bind texture cords
	var aTex = gl.getAttribLocation(renderer,"aTex");
	var texCoords = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, texCoords);
	var textureCoords = [0.0, 0.0,
						 1.0, 0.0,
						 1.0, 1.0,
						 0.0, 1.0];
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
	gl.vertexAttribPointer(aTex, /*item size*/2, gl.FLOAT, false, 0, 0);
	gl.enableVertexAttribArray(aTex);

	// index to vertices
	var indices = gl.createBuffer();
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
	// tesselate square into triangles
	// indeces into vertex array creating triangles, with counter-clockwise winding
	var vertexIndices = [0, 1, 2,	// bottom right triangle
						 0, 2, 3];	// top left triangle
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertexIndices), gl.STATIC_DRAW);
};
<|endoftext|>


<commit_before>
function getReplyTo() {
  if(!document.dynreply) {
    var e = document.getElementById("postform_container");
    if (e) {
      // use existing postform
      document.dynreply = new DynReply(e);
    } else {
      // build a new postform
      document.dynreply = new DynReply();
    }
    e = document.dynreply.elem;
    e.style.position = "fixed";
    e.setAttribute("class", "shadow");
  }
  return document.dynreply;
}

function table_insert_row(table, header, items) {
  var tr = document.createElement("tr");
  // insert header element
  var th = document.createElement("th");
  th.appendChild(header);
  tr.appendChild(th);
  // insert the rest of the elements
  for (var idx = 0; idx < items.length; idx ++ ) {
    var elem = document.createElement("td");
    elem.appendChild(items[idx]);
    tr.appendChild(elem);
  }
  table.appendChild(tr);
}

/**
   build dynamic reply box
*/
function DynReply(existingElem) {
  if (existingElem) {
    // wrap existing post form
    // XXX: wrap it here
    this.elem = existingElem;
    this.form = this.elem.querySelector("form");
    this._error = document.getElementById("postform_msg");
    this.url = this.form.action + "?t=json";
    this.x = 1;
    this.y = 1;
    return;
  }

  // build new post form

  var elem = document.createElement("div");
  elem.setAttribute("id", "postform_container");
  this.elem = elem;
  // build post form
  this.form = document.createElement("form");
  this.form.enctype = "multipart/form-data";
  this.form.name = "post";
  this.form.method = "post";
  // reference
  elem = document.createElement("input");
  elem.setAttribute("id", "postform_reference");
  elem.name = "reference";
  elem.type = "hidden";
  this.form.appendChild(elem);

  var table = document.createElement("table");
  table.setAttribute("class", "postform");
  var tbody = document.createElement("tbody");

  var span = document.createElement("span");
  // name
  elem = document.createElement("input");
  elem.setAttribute("name", "name");
  elem.setAttribute("value", "Anonymous");
  elem.setAttribute("id", "postform_name");
  span.appendChild(elem);
  // error message
  var err_elem = document.createElement("span");
  err_elem.setAttribute("id", "postform_msg");
  span.appendChild(err_elem);
  this._error = err_elem;
  table_insert_row(tbody, document.createTextNode("Name"), [span])

  // subject
  elem = document.createElement("input");
  elem.setAttribute("name", "subject");
  elem.setAttribute("value", "");
  elem.setAttribute("id", "postform_subject");
  // submit
  var submit = document.createElement("input");
  submit.setAttribute("value", "reply");
  submit.setAttribute("class", "button");
  submit.setAttribute("type", "submit");
  submit.setAttribute("id", "postform_submit");
  table_insert_row(tbody, document.createTextNode("Subject"), [elem, submit]);

  // Comment
  elem = document.createElement("textarea");
  elem.setAttribute("id", "postform_message");
  elem.setAttribute("name", "message");
  elem.setAttribute("cols", "40");
  elem.setAttribute("rows", "5");
  table_insert_row(tbody, document.createTextNode("Comment"), [elem]);

  // file
  elem = document.createElement("input");
  elem.setAttribute("class", "postform_attachment");
  elem.setAttribute("id", "postform_attachments");
  elem.setAttribute("type", "file");
  elem.setAttribute("name", "attachment_uploaded");
  elem.setAttribute("multiple", "multiple");
  table_insert_row(tbody, document.createTextNode("Files"), [elem]);

  // dubs
  elem = document.createElement("input");
  elem.setAttribute("type", "checkbox");
  elem.setAttribute("name", "dubs");
  table_insert_row(tbody, document.createTextNode("Get Dubs"), [elem]);

  // captcha
  elem = document.createElement("img");
  elem.setAttribute("id", "captcha_img");
  elem.alt = "captcha";
  table_insert_row(tbody, document.createTextNode("Captcha"), [elem]);

  // captcha solution
  elem = document.createElement("input");
  elem.name = "captcha";
  elem.autocomplete = "off";
  elem.setAttribute("id", "captcha_solution");
  table_insert_row(tbody, document.createTextNode("Solution"), [elem])

  table.appendChild(tbody);
  this.form.appendChild(table);
  this.elem.appendChild(this.form);
  document.body.appendChild(this.elem);
  this.board = null;
  this.rootmsg = null;
  this.prefix = null;
  this.url = null;
  this.x = 1;
  this.y = 1;

}

DynReply.prototype.update = function() {
  if (this.prefix) {
    // update captcha
    this.updateCaptcha();
    if (this.board) {
      // update post form
      var ref = document.getElementById("postform_reference");
      if (this.rootmsg) {
        ref.setAttribute("value", this.rootmsg);
      } else {
        ref.setAttribute("value", "");
      }
      this.url = this.prefix + "post/" + this.board + "/json";
    }
  }
}

DynReply.prototype.show = function() {
  console.log("show dynreply");
  this.update();
  this.elem.style.display = 'inline';
}

DynReply.prototype.hide = function() {
  console.log("hide dynreply");
  this.elem.style.display = "none";
}

// clear all fields
DynReply.prototype.clear = function() {
  this.clearSolution();
  this.clearPostbox();
}

// clear captcha solution
DynReply.prototype.clearSolution = function() {
  var e = document.getElementById("captcha_solution");
  // reset value
  e.value = "";
}

// clear postform elements
DynReply.prototype.clearPostbox = function() {
  var e = document.getElementById("postform_subject");
  e.value = "";
  e = document.getElementById("postform_message");
  e.value = "";
  e = document.getElementById("postform_attachments");
  e.value = null;
}

DynReply.prototype.post = function(cb, err_cb) {
  if (this.url && this.form) {
    var data = new FormData(this.form);
    var ajax = new XMLHttpRequest();
    ajax.onreadystatechange = function(ev) {
      if (ajax.readyState == XMLHttpRequest.DONE) {
        var j = null;
        try {
          j = JSON.parse(ajax.responseText);
          cb(j);
        } catch (e) {
          if(err_cb) {
            err_cb(e);
          }
        }
      }
    }
    ajax.open("POST", this.url);
    ajax.send(data);
  }
}

DynReply.prototype.updateCaptcha = function() {
  if (this.prefix) {
    var captcha_img = document.getElementById("captcha_img");
    captcha_img.src = this.prefix + "captcha/img";
  }
  this.clearSolution();
}

DynReply.prototype.setPrefix = function(prefix) {
  this.prefix = prefix;
}

DynReply.prototype.hide = function() {
  this.elem.style.display = 'none';
}

DynReply.prototype.setBoard = function(boardname) {
  if (boardname) {
    this.board = boardname;
  }
}

DynReply.prototype.setRoot = function(rootmsg) {
  if (rootmsg) {
      this.rootmsg = rootmsg;
  }
}

DynReply.prototype.showError = function(msg) {
  console.log("error in dynreply: "+msg);
  this._error.setAttribute("class", "error message");
  this._error.appendChild(document.createTextNode(msg));
  this.updateCaptcha();
}

DynReply.prototype.showMessage = function(msg) {
  this._error.setAttribute("class", "message");
  this._error.innerHTML = "";
  this._error.appendChild(document.createTextNode(msg));
  var e = this._error;
  setTimeout(function() {
    // clear it
    e.innerHTML = "";
  }, 2000);
}

// reply box function
function nntpchan_reply(parent, shorthash) {
  if (parent && document.dynreply) {
    var boardname = parent.dataset.newsgroup;
    var rootmsg = parent.dataset.rootmsgid;
    var replyto = getReplyTo();
    // set target
    replyto.setBoard(boardname);
    replyto.setRoot(rootmsg);
    // show it
    replyto.show();
  }
  var elem = document.getElementById("postform_message");
  if ( elem )
  {
      elem.value += ">>" + shorthash.substr(0,10) + "\n";
  }
}

// inject post hover behavior
function inject_hover(prefix, el, parent) {
  if (!prefix) { throw "prefix is not defined"; }
  var linkhash = el.dataset.msgidhash;
  if (!linkhash) { throw "linkhash undefined"; }
  console.log("rewrite linkhash "+linkhash);

  var elem = document.createElement("span");
  elem.setAttribute("class", "backlink_rewritten");
  elem.appendChild(document.createTextNode(">>"+linkhash.substr(0,10)));
  if (!parent) {
    parent = el.parentNode;
  }
  parent.removeChild(el);
  parent.appendChild(elem);

  elem.onclick = function(ev) {
    if(parent.backlink) {
      nntpchan_apicall(prefix+"api/find?hash="+linkhash, function(j) {
        var wrapper = document.createElement("div");
        wrapper.setAttribute("class", "hover "+linkhash);
        if (j == null) {
          // not found?
          wrapper.setAttribute("class", "hover notfound-hover "+linkhash);
          wrapper.appendChild(document.createTextNode("not found"));
        } else {
          // wrap backlink
          nntpchan_buildpost(wrapper, j);
        }
        parent.appendChild(wrapper);
        parent.backlink = false;
      }, function(msg) {
        var wrapper = document.createElement("div");
        wrapper.setAttribute("class", "hover "+linkhash);
        wrapper.appendChild(document.createTextNode(msg));
        parent.appendChild(wrapper);
        parent.backlink = false;
      });
    } else {
      var elems = document.getElementsByClassName(linkhash);
      if (!elems) throw "bad state, no backlinks open?";
      for (var idx = 0 ; idx < elems.length; idx ++ ) {
        elems[idx].parentNode.removeChild(elems[idx]);
      }
      parent.backlink = true;
    }
  };
  parent.backlink = true;
}

function init(prefix) {
    var rpl  = getReplyTo();
    rpl.setPrefix(prefix);
    rpl.setBoard("");
    rpl.update();
    rpl.updateCaptcha();

    // position replyto widget
    var e = rpl.elem;
    var mouseDownX, mouseDownY;

    var $dragging = null;

    $(rpl.elem).on("mousemove", function(ev) {
      if ($dragging) {
        var x = ev.pageX - $(this).width() / 2,
            y = ev.pageY - $(this).height() / 2;
        $dragging.offset({
          top: y,
          left: x
        });
      }
    });

    $(rpl.elem).on("mousedown", e, function (ev) {
      $dragging = $(rpl.elem);
    });

    $(rpl.elem).on("mouseup", function (e) {
      $dragging = null;
    });

    // add replyto post handlers
    e = document.getElementById("postform_submit");
    var postit = function() {
      var f = document.querySelector("form");
      // do ajax request to post data
      var r = getReplyTo();
      r.showMessage("posting... ");
      r.post(function(j) {
        if(j.error) {
          // an error happened
          r.showError(j.error);
        } else {
          // we're good
          r.showMessage("posted :^)");
          r.updateCaptcha();
          r.clear();
        }
      }, function(err) {
        r.showError(err);
        r.clearSolution();
      });
    }
    var f = document.querySelector("form");
    f.onsubmit = function() {
      postit();
      return false;
    };
}

<commit_msg>idklol<commit_after>
function getReplyTo() {
  if(!document.dynreply) {
    var e = document.getElementById("postform_container");
    if (e) {
      // use existing postform
      document.dynreply = new DynReply(e);
    } else {
      // build a new postform
      document.dynreply = new DynReply();
    }
    e = document.dynreply.elem;
    e.style.position = "fixed";
    e.setAttribute("class", "shadow");
  }
  return document.dynreply;
}

function table_insert_row(table, header, items) {
  var tr = document.createElement("tr");
  // insert header element
  var th = document.createElement("th");
  th.appendChild(header);
  tr.appendChild(th);
  // insert the rest of the elements
  for (var idx = 0; idx < items.length; idx ++ ) {
    var elem = document.createElement("td");
    elem.appendChild(items[idx]);
    tr.appendChild(elem);
  }
  table.appendChild(tr);
}

/**
   build dynamic reply box
*/
function DynReply(existingElem) {
  if (existingElem) {
    // wrap existing post form
    // XXX: wrap it here
    this.elem = existingElem;
    this.form = this.elem.querySelector("form");
    this._error = document.getElementById("postform_msg");
    this.url = this.form.action + "?t=json";
    this.x = 1;
    this.y = 1;
    return;
  }

  // build new post form

  var elem = document.createElement("div");
  elem.setAttribute("id", "postform_container");
  this.elem = elem;
  // build post form
  this.form = document.createElement("form");
  this.form.enctype = "multipart/form-data";
  this.form.name = "post";
  this.form.method = "post";
  // reference
  elem = document.createElement("input");
  elem.setAttribute("id", "postform_reference");
  elem.name = "reference";
  elem.type = "hidden";
  this.form.appendChild(elem);

  var table = document.createElement("table");
  table.setAttribute("class", "postform");
  var tbody = document.createElement("tbody");

  var span = document.createElement("span");
  // name
  elem = document.createElement("input");
  elem.setAttribute("name", "name");
  elem.setAttribute("value", "Anonymous");
  elem.setAttribute("id", "postform_name");
  span.appendChild(elem);
  // error message
  var err_elem = document.createElement("span");
  err_elem.setAttribute("id", "postform_msg");
  span.appendChild(err_elem);
  this._error = err_elem;
  table_insert_row(tbody, document.createTextNode("Name"), [span])

  // subject
  elem = document.createElement("input");
  elem.setAttribute("name", "subject");
  elem.setAttribute("value", "");
  elem.setAttribute("id", "postform_subject");
  // submit
  var submit = document.createElement("input");
  submit.setAttribute("value", "reply");
  submit.setAttribute("class", "button");
  submit.setAttribute("type", "submit");
  submit.setAttribute("id", "postform_submit");
  table_insert_row(tbody, document.createTextNode("Subject"), [elem, submit]);

  // Comment
  elem = document.createElement("textarea");
  elem.setAttribute("id", "postform_message");
  elem.setAttribute("name", "message");
  elem.setAttribute("cols", "40");
  elem.setAttribute("rows", "5");
  table_insert_row(tbody, document.createTextNode("Comment"), [elem]);

  // file
  elem = document.createElement("input");
  elem.setAttribute("class", "postform_attachment");
  elem.setAttribute("id", "postform_attachments");
  elem.setAttribute("type", "file");
  elem.setAttribute("name", "attachment_uploaded");
  elem.setAttribute("multiple", "multiple");
  table_insert_row(tbody, document.createTextNode("Files"), [elem]);

  // dubs
  elem = document.createElement("input");
  elem.setAttribute("type", "checkbox");
  elem.setAttribute("name", "dubs");
  table_insert_row(tbody, document.createTextNode("Get Dubs"), [elem]);

  // captcha
  elem = document.createElement("img");
  elem.setAttribute("id", "captcha_img");
  elem.alt = "captcha";
  table_insert_row(tbody, document.createTextNode("Captcha"), [elem]);

  // captcha solution
  elem = document.createElement("input");
  elem.name = "captcha";
  elem.autocomplete = "off";
  elem.setAttribute("id", "captcha_solution");
  table_insert_row(tbody, document.createTextNode("Solution"), [elem])

  table.appendChild(tbody);
  this.form.appendChild(table);
  this.elem.appendChild(this.form);
  document.body.appendChild(this.elem);
  this.board = null;
  this.rootmsg = null;
  this.prefix = null;
  this.url = null;
  this.x = 1;
  this.y = 1;

}

DynReply.prototype.update = function() {
  if (this.prefix) {
    // update captcha
    this.updateCaptcha();
    if (this.board) {
      // update post form
      var ref = document.getElementById("postform_reference");
      if (this.rootmsg) {
        ref.setAttribute("value", this.rootmsg);
      } else {
        ref.setAttribute("value", "");
      }
      this.url = this.prefix + "post/" + this.board + "/json";
    }
  }
}

DynReply.prototype.show = function() {
  console.log("show dynreply");
  this.update();
  this.elem.style.display = 'inline';
}

DynReply.prototype.hide = function() {
  console.log("hide dynreply");
  this.elem.style.display = "none";
}

// clear all fields
DynReply.prototype.clear = function() {
  this.clearSolution();
  this.clearPostbox();
}

// clear captcha solution
DynReply.prototype.clearSolution = function() {
  var e = document.getElementById("captcha_solution");
  // reset value
  e.value = "";
}

// clear postform elements
DynReply.prototype.clearPostbox = function() {
  var e = document.getElementById("postform_subject");
  e.value = "";
  e = document.getElementById("postform_message");
  e.value = "";
  e = document.getElementById("postform_attachments");
  e.value = null;
}

DynReply.prototype.post = function(cb, err_cb) {
  if (this.url && this.form) {
    var data = new FormData(this.form);
    var ajax = new XMLHttpRequest();
    ajax.onreadystatechange = function(ev) {
      if (ajax.readyState == XMLHttpRequest.DONE) {
        var j = null;
        try {
          j = JSON.parse(ajax.responseText);
          cb(j);
        } catch (e) {
          if(err_cb) {
            err_cb(e);
          }
        }
      }
    }
    ajax.open("POST", this.url);
    ajax.send(data);
  }
}

DynReply.prototype.updateCaptcha = function() {
  if (this.prefix) {
    var captcha_img = document.getElementById("captcha_img");
    captcha_img.src = this.prefix + "captcha/img";
  }
  this.clearSolution();
}

DynReply.prototype.setPrefix = function(prefix) {
  this.prefix = prefix;
}

DynReply.prototype.hide = function() {
  this.elem.style.display = 'none';
}

DynReply.prototype.setBoard = function(boardname) {
  if (boardname) {
    this.board = boardname;
  }
}

DynReply.prototype.setRoot = function(rootmsg) {
  if (rootmsg) {
      this.rootmsg = rootmsg;
  }
}

DynReply.prototype.showError = function(msg) {
  console.log("error in dynreply: "+msg);
  this._error.setAttribute("class", "error message");
  this._error.appendChild(document.createTextNode(msg));
  this.updateCaptcha();
}

DynReply.prototype.showMessage = function(msg) {
  this._error.setAttribute("class", "message");
  this._error.innerHTML = "";
  this._error.appendChild(document.createTextNode(msg));
  var e = this._error;
  setTimeout(function() {
    // clear it
    e.innerHTML = "";
  }, 2000);
}

// reply box function
function nntpchan_reply(parent, shorthash) {
  if (parent && document.dynreply) {
    var boardname = parent.dataset.newsgroup;
    var rootmsg = parent.dataset.rootmsgid;
    var replyto = getReplyTo();
    // set target
    replyto.setBoard(boardname);
    replyto.setRoot(rootmsg);
    // show it
    replyto.show();
  }
  var elem = document.getElementById("postform_message");
  if ( elem )
  {
      elem.value += ">>" + shorthash.substr(0,10) + "\n";
  }
}

// inject post hover behavior
function inject_hover(prefix, el, parent) {
  if (!prefix) { throw "prefix is not defined"; }
  var linkhash = el.dataset.msgidhash;
  if (!linkhash) { throw "linkhash undefined"; }
  console.log("rewrite linkhash "+linkhash);

  var elem = document.createElement("span");
  elem.setAttribute("class", "backlink_rewritten");
  elem.appendChild(document.createTextNode(">>"+linkhash.substr(0,10)));
  if (!parent) {
    parent = el.parentNode;
  }
  parent.removeChild(el);
  parent.appendChild(elem);

  elem.onclick = function(ev) {
    if(parent.backlink) {
      nntpchan_apicall(prefix+"api/find?hash="+linkhash, function(j) {
        var wrapper = document.createElement("div");
        wrapper.setAttribute("class", "hover "+linkhash);
        if (j == null) {
          // not found?
          wrapper.setAttribute("class", "hover notfound-hover "+linkhash);
          wrapper.appendChild(document.createTextNode("not found"));
        } else {
          // wrap backlink
          nntpchan_buildpost(wrapper, j);
        }
        parent.appendChild(wrapper);
        parent.backlink = false;
      }, function(msg) {
        var wrapper = document.createElement("div");
        wrapper.setAttribute("class", "hover "+linkhash);
        wrapper.appendChild(document.createTextNode(msg));
        parent.appendChild(wrapper);
        parent.backlink = false;
      });
    } else {
      var elems = document.getElementsByClassName(linkhash);
      if (!elems) throw "bad state, no backlinks open?";
      for (var idx = 0 ; idx < elems.length; idx ++ ) {
        elems[idx].parentNode.removeChild(elems[idx]);
      }
      parent.backlink = true;
    }
  };
  parent.backlink = true;
}

function init(prefix) {
    var rpl  = getReplyTo();
    rpl.setPrefix(prefix);
    rpl.setBoard("");
    rpl.update();
    rpl.updateCaptcha();

    // position replyto widget
    var e = rpl.elem;
    var mouseDownX, mouseDownY;

    var $dragging = null;

    $(rpl.elem).on("mousemove", function(ev) {
      if ($dragging) {
        var x = ev.pageX - $(this).width() / 2,
            y = ev.pageY - $(this).height() / 2;
        $dragging.offset({
          top: y - 50,
          left: x - 50
        });
      }
    });

    $(rpl.elem).on("mousedown", e, function (ev) {
      $dragging = $(rpl.elem);
    });

    $(rpl.elem).on("mouseup", function (e) {
      $dragging = null;
    });

    // add replyto post handlers
    e = document.getElementById("postform_submit");
    var postit = function() {
      var f = document.querySelector("form");
      // do ajax request to post data
      var r = getReplyTo();
      r.showMessage("posting... ");
      r.post(function(j) {
        if(j.error) {
          // an error happened
          r.showError(j.error);
        } else {
          // we're good
          r.showMessage("posted :^)");
          r.updateCaptcha();
          r.clear();
        }
      }, function(err) {
        r.showError(err);
        r.clearSolution();
      });
    }
    var f = document.querySelector("form");
    f.onsubmit = function() {
      postit();
      return false;
    };
}

<|endoftext|>


<commit_before>Alloy.Globals.extendsBaseViewController($, arguments[0]);

var d = new Date(), queryOptions = {
	dateFrom : d.getUTCTimeOfDateStart().toISOString(),
	dateTo : d.getUTCTimeOfDateEnd().toISOString(),
	transactionDisplayType : Alloy.Models.User.xGet("defaultTransactionDisplayType")
};

$.onWindowOpenDo(function() {
	if ($.getCurrentWindow().$attrs.queryOptions) {
		_.extend(queryOptions, $.getCurrentWindow().$attrs.queryOptions);
	}
	exports.refresh();
});

function onFooterbarTap(e) {
	if (e.source.id === "dateTransactions") {
		dateTransactions();
	} else if (e.source.id === "weekTransactions") {
		weekTransactions();
	} else if (e.source.id === "monthTransactions") {
		monthTransactions();
	} else if (e.source.id === "transactionsSummuryQuery") {
		Alloy.Globals.openWindow("money/moneyQuery", {
			selectorCallback : doQuery,
			queryOptions : queryOptions
		});
	}
}

function dateTransactions() {
	var dat = new Date();
	queryOptions = {
		dateFrom : dat.getUTCTimeOfDateStart().toISOString(),
		dateTo : dat.getUTCTimeOfDateEnd().toISOString(),
		transactionDisplayType : Alloy.Models.User.xGet("defaultTransactionDisplayType")
	};
	exports.refresh();
}

function weekTransactions() {
	queryOptions = {
		dateFrom : d.getUTCTimeOfWeekStart().toISOString(),
		dateTo : d.getUTCTimeOfWeekEnd().toISOString(),
		transactionDisplayType : Alloy.Models.User.xGet("defaultTransactionDisplayType")
	};
	exports.refresh();
}

function monthTransactions() {
	queryOptions = {
		dateFrom : d.getUTCTimeOfMonthStart().toISOString(),
		dateTo : d.getUTCTimeOfMonthEnd().toISOString(),
		transactionDisplayType : Alloy.Models.User.xGet("defaultTransactionDisplayType")
	};
	exports.refresh();
}

exports.getQueryString = function() {
	var filterStr = "";
	for (var f in queryOptions) {
		var value = queryOptions[f];
		if (_.isNull(value)) {
			continue;
		}
		if (f === "transactionDisplayType") {
			if (value === "Personal") {
				filterStr += " AND main.ownerUserId = '" + Alloy.Models.User.id + "'";
			}
			continue;
		}
		if (filterStr) {
			filterStr += " AND ";
		}
		f = "main." + f;
		if (_.isNumber(value)) {
			filterStr += f + " = " + value + " ";
		} else if (value !== undefined) {
			if (f === "main.dateFrom") {
				filterStr += "main.date >= '" + value + "' ";
			} else if (f === "main.dateTo") {
				filterStr += "main.date <= '" + value + "' ";
			} else if (f === "main.project") {
				filterStr += "main.projectId = '" + value.id + "' ";
			} else {
				filterStr += f + " = '" + value + "' ";
			}
		}
	}
	return filterStr;
};
function doQuery(queryController) {
	queryOptions = queryController.queryOptions;
	exports.refresh();
}

exports.refresh = function() {
	var queryStr = exports.getQueryString();
	console.info(queryStr);
	$.moneyIncomeTotal.query(queryStr);
	$.moneyExpenseTotal.query(queryStr);
	$.moneyBorrowTotal.query(queryStr);
	$.moneyReturnTotal.query(queryStr);
	$.moneyLendTotal.query(queryStr);
	$.moneyPaybackTotal.query(queryStr);
	$.moneyReturnInterestTotal.query(queryStr);
	$.moneyPaybackInterestTotal.query(queryStr);
	calculateTotalBalance();
};
function calculateTotalBalance() {
	var totalBalance = 0;
	totalBalance = $.moneyIncomeTotal.getValue() - $.moneyExpenseTotal.getValue() + $.moneyBorrowTotal.getValue() - $.moneyReturnTotal.getValue() - $.moneyLendTotal.getValue() + $.moneyPaybackTotal.getValue() - $.moneyReturnInterestTotal.getValue() + $.moneyPaybackInterestTotal.getValue();
	$.totalBalance.setText(totalBalance.toUserCurrency());
}

$.incomeTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.expenseTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.borrowTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.returnTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.lendTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.paybackTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.balanceTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.paybackInterestTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.returnInterestTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
<commit_msg>fix #855 fix #856<commit_after>Alloy.Globals.extendsBaseViewController($, arguments[0]);

var d = new Date(), queryOptions = {
	dateFrom : d.getUTCTimeOfDateStart().toISOString(),
	dateTo : d.getUTCTimeOfDateEnd().toISOString(),
	transactionDisplayType : Alloy.Models.User.xGet("defaultTransactionDisplayType")
};

$.onWindowOpenDo(function() {
	if ($.getCurrentWindow().$attrs.queryOptions) {
		_.extend(queryOptions, $.getCurrentWindow().$attrs.queryOptions);
	}
	exports.refresh();
});

function onFooterbarTap(e) {
	if (e.source.id === "dateTransactions") {
		dateTransactions();
	} else if (e.source.id === "weekTransactions") {
		weekTransactions();
	} else if (e.source.id === "monthTransactions") {
		monthTransactions();
	} else if (e.source.id === "transactionsSummuryQuery") {
		Alloy.Globals.openWindow("money/moneyQuery", {
			selectorCallback : doQuery,
			queryOptions : queryOptions
		});
	}
}

function dateTransactions() {
	var dat = new Date();
	queryOptions = {
		dateFrom : dat.getUTCTimeOfDateStart().toISOString(),
		dateTo : dat.getUTCTimeOfDateEnd().toISOString(),
		transactionDisplayType : Alloy.Models.User.xGet("defaultTransactionDisplayType")
	};
	exports.refresh();
}

function weekTransactions() {
	queryOptions = {
		dateFrom : d.getUTCTimeOfWeekStart().toISOString(),
		dateTo : d.getUTCTimeOfWeekEnd().toISOString(),
		transactionDisplayType : Alloy.Models.User.xGet("defaultTransactionDisplayType")
	};
	exports.refresh();
}

function monthTransactions() {
	queryOptions = {
		dateFrom : d.getUTCTimeOfMonthStart().toISOString(),
		dateTo : d.getUTCTimeOfMonthEnd().toISOString(),
		transactionDisplayType : Alloy.Models.User.xGet("defaultTransactionDisplayType")
	};
	exports.refresh();
}

exports.getQueryString = function() {
	var filterStr = "";
	for (var f in queryOptions) {
		var value = queryOptions[f];
		if (_.isNull(value)) {
			continue;
		}
		if (f === "transactionDisplayType") {
			if (value === "Personal") {
				filterStr += " AND main.ownerUserId = '" + Alloy.Models.User.id + "'";
			}
			continue;
		}
		if (filterStr) {
			filterStr += " AND ";
		}
		f = "main." + f;
		if (_.isNumber(value)) {
			filterStr += f + " = " + value + " ";
		} else if (value !== undefined) {
			if (f === "main.dateFrom") {
				filterStr += "main.date >= '" + value + "' ";
			} else if (f === "main.dateTo") {
				filterStr += "main.date <= '" + value + "' ";
			} else if (f === "main.project") {
				filterStr += "main.projectId = '" + value.id + "' ";
			} else {
				filterStr += f + " = '" + value + "' ";
			}
		}
	}
	return filterStr;
};
function doQuery(queryController) {
	queryOptions = queryController.queryOptions;
	exports.refresh();
}

exports.refresh = function() {
	var queryStr = exports.getQueryString();
	console.info(queryStr);
	$.moneyIncomeTotal.query(queryStr);
	$.moneyExpenseTotal.query(queryStr);
	$.moneyBorrowTotal.query(queryStr);
	$.moneyReturnTotal.query(queryStr);
	$.moneyLendTotal.query(queryStr);
	$.moneyPaybackTotal.query(queryStr);
	$.moneyReturnInterestTotal.query(queryStr);
	$.moneyPaybackInterestTotal.query(queryStr);
	calculateTotalBalance();
};
function calculateTotalBalance() {
	var totalBalance = 0;
	totalBalance = $.moneyIncomeTotal.getValue() - $.moneyExpenseTotal.getValue() + $.moneyBorrowTotal.getValue() - $.moneyReturnTotal.getValue() - $.moneyLendTotal.getValue() + $.moneyPaybackTotal.getValue() - $.moneyReturnInterestTotal.getValue() + $.moneyPaybackInterestTotal.getValue();
	$.totalBalance.setText(totalBalance.toUserCurrency());
}

$.incomeTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.expenseTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.borrowTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.returnTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.lendTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.paybackTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.balanceTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.paybackInterestTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
$.returnInterestTotalCurrencySymbol.UIInit($, $.getCurrentWindow());
<|endoftext|>


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

/**
 * @fileoverview Definitions for W3C's range specification.
 *  This file depends on w3c_dom2.js.
 *  The whole file has been fully type annotated.
 *  Created from
 *   http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
 *
 * @externs
 * @author stevey@google.com (Steve Yegge)
 */

/**
 * @constructor
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Interface
 */
function Range() {}

// constants on the constructor

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.START_TO_START;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.START_TO_END;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.END_TO_END;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.END_TO_START;

// constants repeated on the prototype

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.prototype.START_TO_START;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.prototype.START_TO_END;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.prototype.END_TO_END;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.prototype.END_TO_START;

/**
 * @type {Node}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-startParent
 */
Range.prototype.startContainer;

/**
 * @type {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-startOffset
 */
Range.prototype.startOffset;

/**
 * @type {Node}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-endParent
 */
Range.prototype.endContainer;

/**
 * @type {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-endOffset
 */
Range.prototype.endOffset;

/**
 * @type {boolean}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-collapsed
 */
Range.prototype.collapsed;

/**
 * @type {Node}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-commonParent
 */
Range.prototype.commonAncestorContainer;

/**
 * @param {Node} refNode
 * @param {number} offset
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-setStart
 */
Range.prototype.setStart = function(refNode, offset) {};

/**
 * @param {Node} refNode
 * @param {number} offset
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-setEnd
 */
Range.prototype.setEnd = function(refNode, offset) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-setStartBefore
 */
Range.prototype.setStartBefore = function(refNode) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-setStartAfter
 */
Range.prototype.setStartAfter = function(refNode) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-setEndBefore
 */
Range.prototype.setEndBefore = function(refNode) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-setEndAfter
 */
Range.prototype.setEndAfter = function(refNode) {};

/**
 * @param {boolean} toStart
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-collapse
 */
Range.prototype.collapse = function(toStart) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-selectNode
 */
Range.prototype.selectNode = function(refNode) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-selectNodeContents
 */
Range.prototype.selectNodeContents = function(refNode) {};

/**
 * @param {number} how
 * @param {Range} sourceRange
 * @return {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-compareBoundaryPoints
 */
Range.prototype.compareBoundaryPoints = function(how, sourceRange) {};

/**
 * @return {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-deleteContents
 */
Range.prototype.deleteContents = function() {};

/**
 * @return {DocumentFragment}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-extractContents
 */
Range.prototype.extractContents = function() {};

/**
 * @return {DocumentFragment}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-cloneContents
 */
Range.prototype.cloneContents = function() {};

/**
 * @param {Node} newNode
 * @return {DocumentFragment}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-insertNode
 */
Range.prototype.insertNode = function(newNode) {};

/**
 * @param {Node} newParent
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-surroundContents
 */
Range.prototype.surroundContents = function(newParent) {};

/**
 * @return {Range}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-clone
 */
Range.prototype.cloneRange = function() {};

/**
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-detach
 */
Range.prototype.detach = function() {};

// Introduced in DOM Level 2:
/**
 * @constructor
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-DocumentRange-idl
 */
function DocumentRange() {}

/**
 * @return {Range}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-DocumentRange-method-createRange
 */
DocumentRange.prototype.createRange = function() {};
<commit_msg>DocumentRange should be an interface<commit_after>/*
 * Copyright 2008 The Closure Compiler Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Definitions for W3C's range specification.
 *  This file depends on w3c_dom2.js.
 *  The whole file has been fully type annotated.
 *  Created from
 *   http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
 *
 * @externs
 * @author stevey@google.com (Steve Yegge)
 */

/**
 * @constructor
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Interface
 */
function Range() {}

// constants on the constructor

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.START_TO_START;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.START_TO_END;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.END_TO_END;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.END_TO_START;

// constants repeated on the prototype

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.prototype.START_TO_START;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.prototype.START_TO_END;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.prototype.END_TO_END;

/**
 * @const {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-compareHow
 */
Range.prototype.END_TO_START;

/**
 * @type {Node}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-startParent
 */
Range.prototype.startContainer;

/**
 * @type {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-startOffset
 */
Range.prototype.startOffset;

/**
 * @type {Node}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-endParent
 */
Range.prototype.endContainer;

/**
 * @type {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-endOffset
 */
Range.prototype.endOffset;

/**
 * @type {boolean}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-collapsed
 */
Range.prototype.collapsed;

/**
 * @type {Node}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-attr-commonParent
 */
Range.prototype.commonAncestorContainer;

/**
 * @param {Node} refNode
 * @param {number} offset
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-setStart
 */
Range.prototype.setStart = function(refNode, offset) {};

/**
 * @param {Node} refNode
 * @param {number} offset
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-setEnd
 */
Range.prototype.setEnd = function(refNode, offset) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-setStartBefore
 */
Range.prototype.setStartBefore = function(refNode) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-setStartAfter
 */
Range.prototype.setStartAfter = function(refNode) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-setEndBefore
 */
Range.prototype.setEndBefore = function(refNode) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-setEndAfter
 */
Range.prototype.setEndAfter = function(refNode) {};

/**
 * @param {boolean} toStart
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-collapse
 */
Range.prototype.collapse = function(toStart) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-selectNode
 */
Range.prototype.selectNode = function(refNode) {};

/**
 * @param {Node} refNode
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-selectNodeContents
 */
Range.prototype.selectNodeContents = function(refNode) {};

/**
 * @param {number} how
 * @param {Range} sourceRange
 * @return {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-compareBoundaryPoints
 */
Range.prototype.compareBoundaryPoints = function(how, sourceRange) {};

/**
 * @return {number}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-deleteContents
 */
Range.prototype.deleteContents = function() {};

/**
 * @return {DocumentFragment}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-extractContents
 */
Range.prototype.extractContents = function() {};

/**
 * @return {DocumentFragment}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-cloneContents
 */
Range.prototype.cloneContents = function() {};

/**
 * @param {Node} newNode
 * @return {DocumentFragment}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-insertNode
 */
Range.prototype.insertNode = function(newNode) {};

/**
 * @param {Node} newParent
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-surroundContents
 */
Range.prototype.surroundContents = function(newParent) {};

/**
 * @return {Range}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-clone
 */
Range.prototype.cloneRange = function() {};

/**
 * @return {undefined}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-Range-method-detach
 */
Range.prototype.detach = function() {};

// Introduced in DOM Level 2:
/**
 * @interface
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-DocumentRange-idl
 */
function DocumentRange() {}

/**
 * @return {Range}
 * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-DocumentRange-method-createRange
 */
DocumentRange.prototype.createRange = function() {};
<|endoftext|>


<commit_before>module.exports = {
  /**
   * Application configuration section
   * http://pm2.keymetrics.io/docs/usage/application-declaration/
   */
  apps: [
    {
      name: 'blair',
      script: 'dist/index.js',
      env_production: {
        NODE_ENV: 'production'
      },
      wait_ready: true,
      listen_timeout: 4000,
      kill_timeout: 4000
    }
  ],

  /**
   * Deployment section
   * http://pm2.keymetrics.io/docs/usage/deployment/
   */
  deploy: {
    production: {
      key: '~/.ssh/bot-deploy',
      // TODO: Create a deploy user
      user: 'gin',
      host: 'blair-vm.eastus.cloudapp.azure.com',
      ref: 'origin/master',
      repo: 'git@github.com:germanoeich/blair-bot.git',
      path: '/var/opt/node/blair',
      'ssh_options': 'StrictHostKeyChecking=no',
      'post-deploy': 'yarn && ./sentryRelease.sh && yarn build && pm2 startOrRestart ecosystem.config.js --env production'
    }
  }
}
<commit_msg>Fix permissions for release tracking script<commit_after>module.exports = {
  /**
   * Application configuration section
   * http://pm2.keymetrics.io/docs/usage/application-declaration/
   */
  apps: [
    {
      name: 'blair',
      script: 'dist/index.js',
      env_production: {
        NODE_ENV: 'production'
      },
      wait_ready: true,
      listen_timeout: 4000,
      kill_timeout: 4000
    }
  ],

  /**
   * Deployment section
   * http://pm2.keymetrics.io/docs/usage/deployment/
   */
  deploy: {
    production: {
      key: '~/.ssh/bot-deploy',
      // TODO: Create a deploy user
      user: 'gin',
      host: 'blair-vm.eastus.cloudapp.azure.com',
      ref: 'origin/master',
      repo: 'git@github.com:germanoeich/blair-bot.git',
      path: '/var/opt/node/blair',
      'ssh_options': 'StrictHostKeyChecking=no',
      'post-deploy': 'yarn && sudo ./sentryRelease.sh && yarn build && pm2 startOrRestart ecosystem.config.js --env production'
    }
  }
}
<|endoftext|>