<commit_before>const { SourceMapConsumer } = require('source-map')
const { buildBinary } = require('./esbuild')
const childProcess = require('child_process')
const mkdirp = require('mkdirp')
const rimraf = require('rimraf')
const path = require('path')
const util = require('util')
const fs = require('fs')

const execFileAsync = util.promisify(childProcess.execFile)
const readFileAsync = util.promisify(fs.readFile)
const writeFileAsync = util.promisify(fs.writeFile)

const testDir = path.join(__dirname, '.verify-source-map')
let tempDirCount = 0

const toSearchBundle = [
  'a0', 'a1', 'a2',
  'b0', 'b1', 'b2',
  'c0', 'c1', 'c2',
]

const toSearchNoBundle = [
  'a0', 'a1', 'a2',
]

const testCaseES6 = {
  'a.js': `
    import {b0} from './b-dir/b'
    function a0() { a1("a0") }
    function a1() { a2("a1") }
    function a2() { b0("a2") }
    a0()
  `,
  'b-dir/b.js': `
    import {c0} from './c-dir/c'
    export function b0() { b1("b0") }
    function b1() { b2("b1") }
    function b2() { c0("b2") }
  `,
  'b-dir/c-dir/c.js': `
    export function c0() { c1("c0") }
    function c1() { c2("c1") }
    function c2() { throw new Error("c2") }
  `,
}

const testCaseCommonJS = {
  'a.js': `
    const {b0} = require('./b-dir/b')
    function a0() { a1("a0") }
    function a1() { a2("a1") }
    function a2() { b0("a2") }
    a0()
  `,
  'b-dir/b.js': `
    const {c0} = require('./c-dir/c')
    exports.b0 = function() { b1("b0") }
    function b1() { b2("b1") }
    function b2() { c0("b2") }
  `,
  'b-dir/c-dir/c.js': `
    exports.c0 = function() { c1("c0") }
    function c1() { c2("c1") }
    function c2() { throw new Error("c2") }
  `,
}

const testCaseTypeScriptRuntime = {
  'a.ts': `
    namespace Foo {
      export var {a, ...b} = foo() // This requires a runtime function to handle
      console.log(a, b)
    }
    function a0() { a1("a0") }
    function a1() { a2("a1") }
    function a2() { throw new Error("a2") }
    a0()
  `,
}

const testCaseStdin = {
  '<stdin>': `#!/usr/bin/env node
    function a0() { a1("a0") }
    function a1() { a2("a1") }
    function a2() { throw new Error("a2") }
    a0()
  `,
}

async function check(kind, testCase, toSearch, flags) {
  let failed = 0

  try {
    const recordCheck = (success, message) => {
      if (!success) {
        failed++
        console.error(`❌ [${kind}] ${message}`)
      }
    }

    const tempDir = path.join(testDir, '' + tempDirCount++)
    mkdirp.sync(tempDir)

    for (const name in testCase) {
      if (name !== '<stdin>') {
        const tempPath = path.join(tempDir, name)
        mkdirp.sync(path.dirname(tempPath))
        await writeFileAsync(tempPath, testCase[name])
      }
    }

    const esbuildPath = buildBinary()
    const files = Object.keys(testCase)
    const args = ['--sourcemap'].concat(flags)
    const isStdin = '<stdin>' in testCase
    let stdout = ''

    await new Promise((resolve, reject) => {
      if (!isStdin) args.unshift(files[0])
      const child = childProcess.spawn(esbuildPath, args, { cwd: tempDir, stdio: 'pipe' })
      if (isStdin) child.stdin.write(testCase['<stdin>'])
      child.stdin.end()
      child.stdout.on('data', chunk => stdout += chunk.toString())
      child.stdout.on('end', resolve)
      child.on('error', reject)
    })

    let outJs
    let outJsMap

    if (isStdin) {
      outJs = stdout
      recordCheck(outJs.includes(`//# sourceMappingURL=data:application/json;base64,`), `.js file contains source map`)
      outJsMap = Buffer.from(outJs.slice(outJs.indexOf('base64,') + 'base64,'.length).trim(), 'base64').toString()
    }

    else {
      outJs = await readFileAsync(path.join(tempDir, 'out.js'), 'utf8')
      recordCheck(outJs.includes(`//# sourceMappingURL=out.js.map\n`), `.js file links to .js.map`)
      outJsMap = await readFileAsync(path.join(tempDir, 'out.js.map'), 'utf8')
    }

    // Check the mapping of various key locations back to the original source
    const checkMap = (out, map, relativeTo) => {
      for (const id of toSearch) {
        const inSource = isStdin ? '<stdin>' : files.find(x => path.basename(x).startsWith(id[0]))
        const inJs = testCase[inSource]
        const inIndex = inJs.indexOf(`"${id}"`)
        const outIndex = out.indexOf(`"${id}"`)

        if (inIndex < 0) throw new Error(`Failed to find "${id}" in input`)
        if (outIndex < 0) throw new Error(`Failed to find "${id}" in output`)

        const inLines = inJs.slice(0, inIndex).split('\n')
        const inLine = inLines.length
        const inColumn = inLines[inLines.length - 1].length

        const outLines = out.slice(0, outIndex).split('\n')
        const outLine = outLines.length
        const outColumn = outLines[outLines.length - 1].length

        const { source, line, column } = map.originalPositionFor({ line: outLine, column: outColumn })
        const expected = JSON.stringify({ source: path.join(relativeTo, inSource).replace(/\\/g, '/'), line: inLine, column: inColumn })
        const observed = JSON.stringify({ source, line, column })
        recordCheck(expected === observed, `expected: ${expected} observed: ${observed}`)
      }
    }

    const outMap = await new SourceMapConsumer(outJsMap)
    checkMap(outJs, outMap, '')

    // Check that every generated location has an associated original position.
    // This only works when not bundling because bundling includes runtime code.
    if (flags.indexOf('--bundle') < 0) {
      // The last line doesn't have a source map entry, but that should be ok.
      const outLines = outJs.trimRight().split('\n');

      for (let outLine = 0; outLine < outLines.length; outLine++) {
        if (outLines[outLine].startsWith('#!') || outLines[outLine].startsWith('//')) {
          // Ignore the hashbang line and the source map comment itself
          continue;
        }

        for (let outColumn = 0; outColumn <= outLines[outLine].length; outColumn++) {
          const { line, column } = outMap.originalPositionFor({ line: outLine + 1, column: outColumn })
          recordCheck(line !== null && column !== null, `missing location for line ${outLine} and column ${outColumn}`)
        }
      }
    }

    // Bundle again to test nested source map chaining
    for (let order of [0, 1, 2]) {
      const fileToTest = isStdin ? 'stdout.js' : 'out.js'
      const nestedEntry = path.join(tempDir, 'nested-entry.js')
      if (isStdin) await writeFileAsync(path.join(tempDir, fileToTest), outJs)
      await writeFileAsync(path.join(tempDir, 'extra.js'), `console.log('extra')`)
      await writeFileAsync(nestedEntry,
        order === 1 ? `import './${fileToTest}'; import './extra.js'` :
          order === 2 ? `import './extra.js'; import './${fileToTest}'` :
            `import './${fileToTest}'`)
      await execFileAsync(esbuildPath, [nestedEntry, '--bundle', '--outfile=' + path.join(tempDir, 'out2.js'), '--sourcemap'], { cwd: testDir })

      const out2Js = await readFileAsync(path.join(tempDir, 'out2.js'), 'utf8')
      recordCheck(out2Js.includes(`//# sourceMappingURL=out2.js.map\n`), `.js file links to .js.map`)
      const out2JsMap = await readFileAsync(path.join(tempDir, 'out2.js.map'), 'utf8')

      const out2Map = await new SourceMapConsumer(out2JsMap)
      checkMap(out2Js, out2Map, path.relative(testDir, tempDir))
    }

    if (!failed) rimraf.sync(tempDir, { disableGlob: true })
  }

  catch (e) {
    console.error(`❌ [${kind}] ${e && e.message || e}`)
    failed++
  }

  return failed
}

async function main() {
  const promises = []
  for (const minify of [false, true]) {
    const flags = minify ? ['--minify'] : []
    const suffix = minify ? '-min' : ''
    promises.push(
      check('commonjs' + suffix, testCaseCommonJS, toSearchBundle, flags.concat('--outfile=out.js', '--bundle')),
      check('es6' + suffix, testCaseES6, toSearchBundle, flags.concat('--outfile=out.js', '--bundle')),
      check('ts' + suffix, testCaseTypeScriptRuntime, toSearchNoBundle, flags.concat('--outfile=out.js')),
      check('stdin-stdout' + suffix, testCaseStdin, toSearchNoBundle, flags.concat('--sourcefile=<stdin>')),
    )
  }

  const failed = (await Promise.all(promises)).reduce((a, b) => a + b, 0)
  if (failed > 0) {
    console.error(`❌ verify source map failed`)
    process.exit(1)
  } else {
    console.log(`✅ verify source map passed`)
    rimraf.sync(testDir, { disableGlob: true })
  }
}

main().catch(e => setTimeout(() => { throw e }))
<commit_msg>build binary once for source map tests<commit_after>const { SourceMapConsumer } = require('source-map')
const { buildBinary } = require('./esbuild')
const childProcess = require('child_process')
const mkdirp = require('mkdirp')
const rimraf = require('rimraf')
const path = require('path')
const util = require('util')
const fs = require('fs')

const execFileAsync = util.promisify(childProcess.execFile)
const readFileAsync = util.promisify(fs.readFile)
const writeFileAsync = util.promisify(fs.writeFile)

const esbuildPath = buildBinary()
const testDir = path.join(__dirname, '.verify-source-map')
let tempDirCount = 0

const toSearchBundle = [
  'a0', 'a1', 'a2',
  'b0', 'b1', 'b2',
  'c0', 'c1', 'c2',
]

const toSearchNoBundle = [
  'a0', 'a1', 'a2',
]

const testCaseES6 = {
  'a.js': `
    import {b0} from './b-dir/b'
    function a0() { a1("a0") }
    function a1() { a2("a1") }
    function a2() { b0("a2") }
    a0()
  `,
  'b-dir/b.js': `
    import {c0} from './c-dir/c'
    export function b0() { b1("b0") }
    function b1() { b2("b1") }
    function b2() { c0("b2") }
  `,
  'b-dir/c-dir/c.js': `
    export function c0() { c1("c0") }
    function c1() { c2("c1") }
    function c2() { throw new Error("c2") }
  `,
}

const testCaseCommonJS = {
  'a.js': `
    const {b0} = require('./b-dir/b')
    function a0() { a1("a0") }
    function a1() { a2("a1") }
    function a2() { b0("a2") }
    a0()
  `,
  'b-dir/b.js': `
    const {c0} = require('./c-dir/c')
    exports.b0 = function() { b1("b0") }
    function b1() { b2("b1") }
    function b2() { c0("b2") }
  `,
  'b-dir/c-dir/c.js': `
    exports.c0 = function() { c1("c0") }
    function c1() { c2("c1") }
    function c2() { throw new Error("c2") }
  `,
}

const testCaseTypeScriptRuntime = {
  'a.ts': `
    namespace Foo {
      export var {a, ...b} = foo() // This requires a runtime function to handle
      console.log(a, b)
    }
    function a0() { a1("a0") }
    function a1() { a2("a1") }
    function a2() { throw new Error("a2") }
    a0()
  `,
}

const testCaseStdin = {
  '<stdin>': `#!/usr/bin/env node
    function a0() { a1("a0") }
    function a1() { a2("a1") }
    function a2() { throw new Error("a2") }
    a0()
  `,
}

async function check(kind, testCase, toSearch, flags) {
  let failed = 0

  try {
    const recordCheck = (success, message) => {
      if (!success) {
        failed++
        console.error(`❌ [${kind}] ${message}`)
      }
    }

    const tempDir = path.join(testDir, '' + tempDirCount++)
    mkdirp.sync(tempDir)

    for (const name in testCase) {
      if (name !== '<stdin>') {
        const tempPath = path.join(tempDir, name)
        mkdirp.sync(path.dirname(tempPath))
        await writeFileAsync(tempPath, testCase[name])
      }
    }

    const files = Object.keys(testCase)
    const args = ['--sourcemap'].concat(flags)
    const isStdin = '<stdin>' in testCase
    let stdout = ''

    await new Promise((resolve, reject) => {
      if (!isStdin) args.unshift(files[0])
      const child = childProcess.spawn(esbuildPath, args, { cwd: tempDir, stdio: 'pipe' })
      if (isStdin) child.stdin.write(testCase['<stdin>'])
      child.stdin.end()
      child.stdout.on('data', chunk => stdout += chunk.toString())
      child.stdout.on('end', resolve)
      child.on('error', reject)
    })

    let outJs
    let outJsMap

    if (isStdin) {
      outJs = stdout
      recordCheck(outJs.includes(`//# sourceMappingURL=data:application/json;base64,`), `.js file contains source map`)
      outJsMap = Buffer.from(outJs.slice(outJs.indexOf('base64,') + 'base64,'.length).trim(), 'base64').toString()
    }

    else {
      outJs = await readFileAsync(path.join(tempDir, 'out.js'), 'utf8')
      recordCheck(outJs.includes(`//# sourceMappingURL=out.js.map\n`), `.js file links to .js.map`)
      outJsMap = await readFileAsync(path.join(tempDir, 'out.js.map'), 'utf8')
    }

    // Check the mapping of various key locations back to the original source
    const checkMap = (out, map, relativeTo) => {
      for (const id of toSearch) {
        const inSource = isStdin ? '<stdin>' : files.find(x => path.basename(x).startsWith(id[0]))
        const inJs = testCase[inSource]
        const inIndex = inJs.indexOf(`"${id}"`)
        const outIndex = out.indexOf(`"${id}"`)

        if (inIndex < 0) throw new Error(`Failed to find "${id}" in input`)
        if (outIndex < 0) throw new Error(`Failed to find "${id}" in output`)

        const inLines = inJs.slice(0, inIndex).split('\n')
        const inLine = inLines.length
        const inColumn = inLines[inLines.length - 1].length

        const outLines = out.slice(0, outIndex).split('\n')
        const outLine = outLines.length
        const outColumn = outLines[outLines.length - 1].length

        const { source, line, column } = map.originalPositionFor({ line: outLine, column: outColumn })
        const expected = JSON.stringify({ source: path.join(relativeTo, inSource).replace(/\\/g, '/'), line: inLine, column: inColumn })
        const observed = JSON.stringify({ source, line, column })
        recordCheck(expected === observed, `expected: ${expected} observed: ${observed}`)
      }
    }

    const outMap = await new SourceMapConsumer(outJsMap)
    checkMap(outJs, outMap, '')

    // Check that every generated location has an associated original position.
    // This only works when not bundling because bundling includes runtime code.
    if (flags.indexOf('--bundle') < 0) {
      // The last line doesn't have a source map entry, but that should be ok.
      const outLines = outJs.trimRight().split('\n');

      for (let outLine = 0; outLine < outLines.length; outLine++) {
        if (outLines[outLine].startsWith('#!') || outLines[outLine].startsWith('//')) {
          // Ignore the hashbang line and the source map comment itself
          continue;
        }

        for (let outColumn = 0; outColumn <= outLines[outLine].length; outColumn++) {
          const { line, column } = outMap.originalPositionFor({ line: outLine + 1, column: outColumn })
          recordCheck(line !== null && column !== null, `missing location for line ${outLine} and column ${outColumn}`)
        }
      }
    }

    // Bundle again to test nested source map chaining
    for (let order of [0, 1, 2]) {
      const fileToTest = isStdin ? 'stdout.js' : 'out.js'
      const nestedEntry = path.join(tempDir, 'nested-entry.js')
      if (isStdin) await writeFileAsync(path.join(tempDir, fileToTest), outJs)
      await writeFileAsync(path.join(tempDir, 'extra.js'), `console.log('extra')`)
      await writeFileAsync(nestedEntry,
        order === 1 ? `import './${fileToTest}'; import './extra.js'` :
          order === 2 ? `import './extra.js'; import './${fileToTest}'` :
            `import './${fileToTest}'`)
      await execFileAsync(esbuildPath, [nestedEntry, '--bundle', '--outfile=' + path.join(tempDir, 'out2.js'), '--sourcemap'], { cwd: testDir })

      const out2Js = await readFileAsync(path.join(tempDir, 'out2.js'), 'utf8')
      recordCheck(out2Js.includes(`//# sourceMappingURL=out2.js.map\n`), `.js file links to .js.map`)
      const out2JsMap = await readFileAsync(path.join(tempDir, 'out2.js.map'), 'utf8')

      const out2Map = await new SourceMapConsumer(out2JsMap)
      checkMap(out2Js, out2Map, path.relative(testDir, tempDir))
    }

    if (!failed) rimraf.sync(tempDir, { disableGlob: true })
  }

  catch (e) {
    console.error(`❌ [${kind}] ${e && e.message || e}`)
    failed++
  }

  return failed
}

async function main() {
  const promises = []
  for (const minify of [false, true]) {
    const flags = minify ? ['--minify'] : []
    const suffix = minify ? '-min' : ''
    promises.push(
      check('commonjs' + suffix, testCaseCommonJS, toSearchBundle, flags.concat('--outfile=out.js', '--bundle')),
      check('es6' + suffix, testCaseES6, toSearchBundle, flags.concat('--outfile=out.js', '--bundle')),
      check('ts' + suffix, testCaseTypeScriptRuntime, toSearchNoBundle, flags.concat('--outfile=out.js')),
      check('stdin-stdout' + suffix, testCaseStdin, toSearchNoBundle, flags.concat('--sourcefile=<stdin>')),
    )
  }

  const failed = (await Promise.all(promises)).reduce((a, b) => a + b, 0)
  if (failed > 0) {
    console.error(`❌ verify source map failed`)
    process.exit(1)
  } else {
    console.log(`✅ verify source map passed`)
    rimraf.sync(testDir, { disableGlob: true })
  }
}

main().catch(e => setTimeout(() => { throw e }))
<|endoftext|>


<commit_before>GCMRC.Dygraphs.overrides(CONFIG.networkHoursOffset);

GCMRC.Graphing = function(hoursOffset) {
	var blockRedraw = false;
	var blockHighlight = false;

	var graphs = {};

	var isResizeListenerAttached = false;

	var urls = {
		agg: 'services/agg/'
	};

	var showInfoMessage = function(locator, msg) {
		$(locator).append('
<button type="button" class="close" data-dismiss="alert">×</button>' + msg + '
');
	};

	var showWarningMessage = function(msg) {
		$("#errorMsg").append('
<button type="button" class="close" data-dismiss="alert">×</button>' + msg + '
');
	};
	var clearWarningMessage = function(msg) {
		$("#errorMsg").empty();
	};

	var showErrorMessage = function(msg) {
		$("#errorMsg").append('
<button type="button" class="close" data-dismiss="alert">×</button>' + msg + '
');
	};
	var clearErrorMessage = function(msg) {
		$("#errorMsg").empty();
	};

	var isNaN = function(x) {
		return x !== x;
	};

	var isNullUndefinedOrNaN = function(x, undefined) {
		return (x === null ||
				x === undefined ||
				isNaN(x));
	};

	var dealWithResponse = function(graphToMake, data, config, buildGraph) {
		var parseColData = function(str) {
			var result = null;
			var low = null, med = null, high = null;

			if (str) {
				var strSplit = str.split(';');
				if (2 < strSplit.length) {
					low = parseFloat(strSplit[0]);
					med = parseFloat(strSplit[1]);
					high = parseFloat(strSplit[2]);
				} else {
					med = parseFloat(str);
				}
			}

			if (!isNullUndefinedOrNaN(med)) {
				if (isNullUndefinedOrNaN(low)) {
					low = med;
				}
				if (isNullUndefinedOrNaN(high)) {
					high = med;
				}
			}
			result = [low, med, high];

			return result;
		};
		var timeColumn = "time";
		var conf = $.extend({}, config);
		var hasData = false;
		conf.data = data.success.data.map(function(el) {
			var result;

			result = [parseInt(el[timeColumn])];

			var columns = graphToMake.responseColumns || graphToMake.columns;
			var dataColumns = columns.filter(function(n){return !n.startsWith(timeColumn)}).map(function(col) {
				return parseColData(el[col]);
			});

			[].push.apply(result, dataColumns);

			if (!hasData && dataColumns.reduce(function(a, b) {
				return a || b.some(function(e){
					return e === 0 || !!e;
				});
			}, false)) {
				hasData = true;
			}

			return result;
		});

		var identifier = graphToMake.groupId;
		var parameterMetadata = GCMRC.Page.params[identifier].description;
		var graphName = parameterMetadata['displayName'];

		if (hasData) {
			conf.labels = [timeColumn];
			[].push.apply(conf.labels, graphToMake.columns.filter(function(n){return !n.startsWith(timeColumn)}).map(function(el) {
				var param = GCMRC.Page.params[identifier][el.split("!")[0]];
				var result = ("inst" === param.sampleMethod) ? param.displayName : param.sampleMethod;
				return result;
			}));
			conf['yAxisLabel'] = graphToMake.yAxisLabel || graphName + " (" + parameterMetadata['unitsShort'] + ")";
			conf['dataformatter'] = GCMRC.Dygraphs.DataFormatter(parameterMetadata['decimalPlaces']);
			conf['decimalPlaces'] = parameterMetadata['decimalPlaces'];
			conf["parameterName"] = identifier;
			conf["div"] = $('#' + conf.divId + ' div.p' + identifier).get(0);
			conf["labelDiv"] = $('#' + conf.labelDivId + ' div.p' + identifier).get(0);
			conf["colors"] = [];
			conf["highlightColor"] = {};
			[].push.apply(conf.colors, graphToMake.columns.filter(function(n){return !n.startsWith(timeColumn)}).map(function(el) {
				var param = GCMRC.Page.params[identifier][el.split("!")[0]];
				var idiot = ("inst" === param.sampleMethod) ? param.displayName : param.sampleMethod;
				this[idiot] = param['highlightColor'];
				return param['color'];
			}, conf.highlightColor));

			conf["series"] = {};
			graphToMake.columns.filter(function(n){return !n.startsWith(timeColumn)}).map(function(el) {
				var param = GCMRC.Page.params[identifier][el.split("!")[0]];
				var idiot = ("inst" === param.sampleMethod) ? param.displayName : param.sampleMethod;
				this[idiot] = param.series;
			}, conf["series"]);

			buildGraph(conf);
		} else {
			showInfoMessage("#" + conf.divId + ' div.p' + identifier, "There were no data during this period for " + graphName);
		}

	};

	var buildGraph = function(config) {
		if (!config)
			config = {};

		var containerId = config.divId || "unknown";
		var parameterName = config.parameterName || "unknown";
		var div = config.div;
//		var labelDiv = config.labelDiv;

		var labels = config['labels'];

		var dateWindow = config['dateWindow'].clone();
		dateWindow[1] = dateWindow[1] + (24 * 60 * 60 * 1000) - 1;

		div.style.display = "inline-block";
		div.style.margin = "4px";

		var data = config['data'];

		var title = config['graphTitle'] || '';

		var yAxisLabel = config['yAxisLabel'] || 'Data';

		var dataformatter = config['dataformatter'] || GCMRC.Dygraphs.DataFormatter(0);
		var decimalPlaces = config['decimalPlaces'] || 0;

		var confColors = config['colors'] || [CONFIG.instColor, CONFIG.pumpColor, CONFIG.sampColor, CONFIG.sampColor];
		var highlightColor = config['highlightColor'];

		var axes = {
			y: {
				axisLabelFormatter: dataformatter,
				valueFormatter: dataformatter
			},
			x: {
				ticker: Dygraph.dateTicker,
				axisLabelFormatter: GCMRC.Dygraphs.timeFormatter(hoursOffset),
				valueFormatter: GCMRC.Dygraphs.timeLabelFormatter(hoursOffset),
				pixelsPerLabel: 95
			}
		};

		var lighterColorHighlightPoint = function(g, name, ctx, canvasx, canvasy, color, radius) {
			var lighterColor = highlightColor[name] || color;
			ctx.beginPath();
			ctx.fillStyle = lighterColor;
			ctx.arc(canvasx, canvasy, radius, 0, 2 * Math.PI, false);
			ctx.fill();
		};

		var graphWidth = $('#' + containerId).width() - 15;//TODO add $(window).resize() listener

		var opts = {
			title: title,
			labelsDivStyles: {
				'textAlign': 'right'
			},
			width: graphWidth,
			height: 420,
			xlabel: 'Time',
			ylabel: yAxisLabel,
			axisLabelWidth: 85,
			yAxisLabelWidth: 85,
			xAxisLabelWidth: 85,
			xAxisHeight: 50,
			axes: axes,
			yRangePad: 5,
//			includeZero: true,
			labels: labels,
			dateWindow : dateWindow,
			originalDateWindow : dateWindow,
			//To be used in "fixed" legend
//			labelsDiv: labelDiv,
//			labelsSeparateLines: true,
//			legend: 'always',
			showRangeSelector: true,
			connectSeparatedPoints: false,
			highlightCircleSize: 4,
			strokeWidth: 2,
			pixelsPerTimeLabel : 95,
			ticker: GCMRC.Dygraphs.ScaledTicker(decimalPlaces),
			drawHighlightPointCallback: lighterColorHighlightPoint,
			customBars: true,
			drawPoints: false,
			stackedGraph: false,
			colors: confColors,
			drawCallback: function(me, initial) {
				if (blockRedraw || initial)
					return;
				blockRedraw = true;
				var range = me.xAxisRange();
				$.each(graphs[containerId], function(key, val) {
					if (val !== me) {
						val.updateOptions({
							dateWindow: range
						});
					}
				});
				blockRedraw = false;
			},
			highlightCallback: function(event, x, points, row, seriesName) {
				if (blockHighlight) {
					return;
				}
				blockHighlight = true;
				$.each(graphs[containerId], function(key, graph) {
					graph.setSelection(row);
					var canvasx;
					if (graph && graph.selPoints_.length > 0 && graph.selPoints_[0]) {
						canvasx = graph.selPoints_[0].canvasx;
					} else {
						//if no selected point?  TODO, is this needed now that we interpolate?
						if (points && points.length > 0 && points[0]) {
							canvasx = points[0].canvasx;
						} else {
							var canvasCoords = graph.eventToDomCoords(event);
							canvasx = canvasCoords[0];
						}
					}
					var ctx = graph.canvas_ctx_;
					ctx.fillStyle = '#FF0000';
					ctx.fillRect(canvasx - 0.5, 0, 1, graph.height_);
				});
				blockHighlight = false;
			},
			unhighlightCallback: function(event, x, points, row, seriesName) {
				if (blockHighlight) {
					return;
				}
				blockHighlight = true;
				$.each(graphs[containerId], function(key, val) {
					val.clearSelection();
				});
				blockHighlight = false;
			}
		};

		if (config.series) {
			opts.series = config.series.clone();
		}

		graphs[containerId][parameterName] = new Dygraph(
				div,
				data,
				opts
				);
	};

	return {
		graphs: graphs,
		urls: urls,
		isResizeListenerAttached : isResizeListenerAttached,
		showInfoMsg : showInfoMessage,
		showWarningMsg : showWarningMessage,
		clearWarningMsg : clearWarningMessage,
		showErrorMsg : showErrorMessage,
		clearErrorMsg : clearErrorMessage,
		createDataGraph: function(param, config, urlParams) {
			$('#infoMsg').empty();
			$("#errorMsg").empty();

			if (urlParams['downscale']) {
				clearWarningMessage();
				showWarningMessage('Due to the length of your request, some timesteps may be filtered from the graph. You may still download the unfiltered dataset, or shorten the date range of your request.');
			}

			urlParams["output"] = "json";
			$.ajax({
				type: 'GET',
				dataType: 'json',
				url: CONFIG.relativePath + urls[param],
				data: urlParams,
				timeout: 1200000, /* 20 minutes allowed, from start to data complete */
				success: function(data, textStatus, jqXHR) {
					if (!data || (!data.contentType && data.contentType === "text/xml")) {
						clearErrorMessage();
						showErrorMessage("An error has occurred.  Please contact " + GCMRC.administrator + "@usgs.gov");
					} else {
						if (data.data && !data.data.ERROR && data.data.time) {
							data = {
								success : {
									"@rowCount" : "-1",
									data : [
										data.data
									]
								}
							};
						}
						//success
						if (data.success && data.success.data && $.isArray(data.success.data)) {
							var containerDiv = $("#" + config.divId);
							var labelDiv = $("#" + config.labelDivId);

							if (graphs[config.divId]) {
								graphs[config.divId].values(function(el) {
									el.destroy();
								});
							}

							/*
							 * Clean out and repopulated the container/graph divs
							 * for the correct display order
							 */
							containerDiv.empty();
							labelDiv.empty();
							GCMRC.Page.params.values().sortBy(function(n) {
								return parseInt(n.description.displayOrder || 9999999);
							}).map(function(n) {
								return n.description.groupId;
							}).forEach(function(el) {
								containerDiv.append($('

'));
								labelDiv.append($('

'));
							});

							graphs[config.divId] = {};

							config.graphsToMake.forEach(function(graphToMake) {
								if (graphToMake.dealWithResponse) {
									graphToMake.dealWithResponse(graphToMake, data, config, buildGraph);
								} else {
									dealWithResponse(graphToMake, data, config, buildGraph);
								}
							});
						} else if (data.data && data.data.ERROR) {
							clearErrorMessage();
							showErrorMessage("Please select a parameter to graph!");
						} else {
							LOG.error("what the heck just happened?");
						}
					}
				},
				error: function() {
					clearErrorMessage();
					showErrorMessage("A Network Error has occurred. Please check your configuration and try again.  If you repeatedly receive this message, contact " + GCMRC.administrator + "@usgs.gov");
				}
			});
		}
	};
}(CONFIG.networkHoursOffset);<commit_msg>Added some error type detection to make it more clear what type of error occurred.<commit_after>GCMRC.Dygraphs.overrides(CONFIG.networkHoursOffset);

GCMRC.Graphing = function(hoursOffset) {
	var blockRedraw = false;
	var blockHighlight = false;

	var graphs = {};

	var isResizeListenerAttached = false;

	var urls = {
		agg: 'services/agg/'
	};

	var showInfoMessage = function(locator, msg) {
		$(locator).append('
<button type="button" class="close" data-dismiss="alert">×</button>' + msg + '
');
	};

	var showWarningMessage = function(msg) {
		$("#errorMsg").append('
<button type="button" class="close" data-dismiss="alert">×</button>' + msg + '
');
	};
	var clearWarningMessage = function(msg) {
		$("#errorMsg").empty();
	};

	var showErrorMessage = function(msg) {
		$("#errorMsg").append('
<button type="button" class="close" data-dismiss="alert">×</button>' + msg + '
');
	};
	var clearErrorMessage = function(msg) {
		$("#errorMsg").empty();
	};

	var isNaN = function(x) {
		return x !== x;
	};

	var isNullUndefinedOrNaN = function(x, undefined) {
		return (x === null ||
				x === undefined ||
				isNaN(x));
	};

	var dealWithResponse = function(graphToMake, data, config, buildGraph) {
		var parseColData = function(str) {
			var result = null;
			var low = null, med = null, high = null;

			if (str) {
				var strSplit = str.split(';');
				if (2 < strSplit.length) {
					low = parseFloat(strSplit[0]);
					med = parseFloat(strSplit[1]);
					high = parseFloat(strSplit[2]);
				} else {
					med = parseFloat(str);
				}
			}

			if (!isNullUndefinedOrNaN(med)) {
				if (isNullUndefinedOrNaN(low)) {
					low = med;
				}
				if (isNullUndefinedOrNaN(high)) {
					high = med;
				}
			}
			result = [low, med, high];

			return result;
		};
		var timeColumn = "time";
		var conf = $.extend({}, config);
		var hasData = false;
		conf.data = data.success.data.map(function(el) {
			var result;

			result = [parseInt(el[timeColumn])];

			var columns = graphToMake.responseColumns || graphToMake.columns;
			var dataColumns = columns.filter(function(n){return !n.startsWith(timeColumn)}).map(function(col) {
				return parseColData(el[col]);
			});

			[].push.apply(result, dataColumns);

			if (!hasData && dataColumns.reduce(function(a, b) {
				return a || b.some(function(e){
					return e === 0 || !!e;
				});
			}, false)) {
				hasData = true;
			}

			return result;
		});

		var identifier = graphToMake.groupId;
		var parameterMetadata = GCMRC.Page.params[identifier].description;
		var graphName = parameterMetadata['displayName'];

		if (hasData) {
			conf.labels = [timeColumn];
			[].push.apply(conf.labels, graphToMake.columns.filter(function(n){return !n.startsWith(timeColumn)}).map(function(el) {
				var param = GCMRC.Page.params[identifier][el.split("!")[0]];
				var result = ("inst" === param.sampleMethod) ? param.displayName : param.sampleMethod;
				return result;
			}));
			conf['yAxisLabel'] = graphToMake.yAxisLabel || graphName + " (" + parameterMetadata['unitsShort'] + ")";
			conf['dataformatter'] = GCMRC.Dygraphs.DataFormatter(parameterMetadata['decimalPlaces']);
			conf['decimalPlaces'] = parameterMetadata['decimalPlaces'];
			conf["parameterName"] = identifier;
			conf["div"] = $('#' + conf.divId + ' div.p' + identifier).get(0);
			conf["labelDiv"] = $('#' + conf.labelDivId + ' div.p' + identifier).get(0);
			conf["colors"] = [];
			conf["highlightColor"] = {};
			[].push.apply(conf.colors, graphToMake.columns.filter(function(n){return !n.startsWith(timeColumn)}).map(function(el) {
				var param = GCMRC.Page.params[identifier][el.split("!")[0]];
				var idiot = ("inst" === param.sampleMethod) ? param.displayName : param.sampleMethod;
				this[idiot] = param['highlightColor'];
				return param['color'];
			}, conf.highlightColor));

			conf["series"] = {};
			graphToMake.columns.filter(function(n){return !n.startsWith(timeColumn)}).map(function(el) {
				var param = GCMRC.Page.params[identifier][el.split("!")[0]];
				var idiot = ("inst" === param.sampleMethod) ? param.displayName : param.sampleMethod;
				this[idiot] = param.series;
			}, conf["series"]);

			buildGraph(conf);
		} else {
			showInfoMessage("#" + conf.divId + ' div.p' + identifier, "There were no data during this period for " + graphName);
		}

	};

	var buildGraph = function(config) {
		if (!config)
			config = {};

		var containerId = config.divId || "unknown";
		var parameterName = config.parameterName || "unknown";
		var div = config.div;
//		var labelDiv = config.labelDiv;

		var labels = config['labels'];

		var dateWindow = config['dateWindow'].clone();
		dateWindow[1] = dateWindow[1] + (24 * 60 * 60 * 1000) - 1;

		div.style.display = "inline-block";
		div.style.margin = "4px";

		var data = config['data'];

		var title = config['graphTitle'] || '';

		var yAxisLabel = config['yAxisLabel'] || 'Data';

		var dataformatter = config['dataformatter'] || GCMRC.Dygraphs.DataFormatter(0);
		var decimalPlaces = config['decimalPlaces'] || 0;

		var confColors = config['colors'] || [CONFIG.instColor, CONFIG.pumpColor, CONFIG.sampColor, CONFIG.sampColor];
		var highlightColor = config['highlightColor'];

		var axes = {
			y: {
				axisLabelFormatter: dataformatter,
				valueFormatter: dataformatter
			},
			x: {
				ticker: Dygraph.dateTicker,
				axisLabelFormatter: GCMRC.Dygraphs.timeFormatter(hoursOffset),
				valueFormatter: GCMRC.Dygraphs.timeLabelFormatter(hoursOffset),
				pixelsPerLabel: 95
			}
		};

		var lighterColorHighlightPoint = function(g, name, ctx, canvasx, canvasy, color, radius) {
			var lighterColor = highlightColor[name] || color;
			ctx.beginPath();
			ctx.fillStyle = lighterColor;
			ctx.arc(canvasx, canvasy, radius, 0, 2 * Math.PI, false);
			ctx.fill();
		};

		var graphWidth = $('#' + containerId).width() - 15;//TODO add $(window).resize() listener

		var opts = {
			title: title,
			labelsDivStyles: {
				'textAlign': 'right'
			},
			width: graphWidth,
			height: 420,
			xlabel: 'Time',
			ylabel: yAxisLabel,
			axisLabelWidth: 85,
			yAxisLabelWidth: 85,
			xAxisLabelWidth: 85,
			xAxisHeight: 50,
			axes: axes,
			yRangePad: 5,
//			includeZero: true,
			labels: labels,
			dateWindow : dateWindow,
			originalDateWindow : dateWindow,
			//To be used in "fixed" legend
//			labelsDiv: labelDiv,
//			labelsSeparateLines: true,
//			legend: 'always',
			showRangeSelector: true,
			connectSeparatedPoints: false,
			highlightCircleSize: 4,
			strokeWidth: 2,
			pixelsPerTimeLabel : 95,
			ticker: GCMRC.Dygraphs.ScaledTicker(decimalPlaces),
			drawHighlightPointCallback: lighterColorHighlightPoint,
			customBars: true,
			drawPoints: false,
			stackedGraph: false,
			colors: confColors,
			drawCallback: function(me, initial) {
				if (blockRedraw || initial)
					return;
				blockRedraw = true;
				var range = me.xAxisRange();
				$.each(graphs[containerId], function(key, val) {
					if (val !== me) {
						val.updateOptions({
							dateWindow: range
						});
					}
				});
				blockRedraw = false;
			},
			highlightCallback: function(event, x, points, row, seriesName) {
				if (blockHighlight) {
					return;
				}
				blockHighlight = true;
				$.each(graphs[containerId], function(key, graph) {
					graph.setSelection(row);
					var canvasx;
					if (graph && graph.selPoints_.length > 0 && graph.selPoints_[0]) {
						canvasx = graph.selPoints_[0].canvasx;
					} else {
						//if no selected point?  TODO, is this needed now that we interpolate?
						if (points && points.length > 0 && points[0]) {
							canvasx = points[0].canvasx;
						} else {
							var canvasCoords = graph.eventToDomCoords(event);
							canvasx = canvasCoords[0];
						}
					}
					var ctx = graph.canvas_ctx_;
					ctx.fillStyle = '#FF0000';
					ctx.fillRect(canvasx - 0.5, 0, 1, graph.height_);
				});
				blockHighlight = false;
			},
			unhighlightCallback: function(event, x, points, row, seriesName) {
				if (blockHighlight) {
					return;
				}
				blockHighlight = true;
				$.each(graphs[containerId], function(key, val) {
					val.clearSelection();
				});
				blockHighlight = false;
			}
		};

		if (config.series) {
			opts.series = config.series.clone();
		}

		graphs[containerId][parameterName] = new Dygraph(
				div,
				data,
				opts
				);
	};

	return {
		graphs: graphs,
		urls: urls,
		isResizeListenerAttached : isResizeListenerAttached,
		showInfoMsg : showInfoMessage,
		showWarningMsg : showWarningMessage,
		clearWarningMsg : clearWarningMessage,
		showErrorMsg : showErrorMessage,
		clearErrorMsg : clearErrorMessage,
		createDataGraph: function(param, config, urlParams) {
			$('#infoMsg').empty();
			$("#errorMsg").empty();

			if (urlParams['downscale']) {
				clearWarningMessage();
				showWarningMessage('Due to the length of your request, some timesteps may be filtered from the graph. You may still download the unfiltered dataset, or shorten the date range of your request.');
			}

			urlParams["output"] = "json";
			$.ajax({
				type: 'GET',
				dataType: 'json',
				url: CONFIG.relativePath + urls[param],
				data: urlParams,
				timeout: 1200000, /* 20 minutes allowed, from start to data complete */
				success: function(data, textStatus, jqXHR) {
					if (!data || (!data.contentType && data.contentType === "text/xml")) {
						clearErrorMessage();
						showErrorMessage("An error has occurred.  Please contact " + GCMRC.administrator + "@usgs.gov");
					} else {
						if (data.data && !data.data.ERROR && data.data.time) {
							data = {
								success : {
									"@rowCount" : "-1",
									data : [
										data.data
									]
								}
							};
						}
						//success
						if (data.success && data.success.data && $.isArray(data.success.data)) {
							var containerDiv = $("#" + config.divId);
							var labelDiv = $("#" + config.labelDivId);

							if (graphs[config.divId]) {
								graphs[config.divId].values(function(el) {
									el.destroy();
								});
							}

							/*
							 * Clean out and repopulated the container/graph divs
							 * for the correct display order
							 */
							containerDiv.empty();
							labelDiv.empty();
							GCMRC.Page.params.values().sortBy(function(n) {
								return parseInt(n.description.displayOrder || 9999999);
							}).map(function(n) {
								return n.description.groupId;
							}).forEach(function(el) {
								containerDiv.append($('

'));
								labelDiv.append($('

'));
							});

							graphs[config.divId] = {};

							config.graphsToMake.forEach(function(graphToMake) {
								if (graphToMake.dealWithResponse) {
									graphToMake.dealWithResponse(graphToMake, data, config, buildGraph);
								} else {
									dealWithResponse(graphToMake, data, config, buildGraph);
								}
							});
						} else if (data.data && data.data.ERROR) {
							clearErrorMessage();
							showErrorMessage("Please select a parameter to graph!");
						} else {
							LOG.error("what the heck just happened?");
						}
					}
				},
				error: function(jqXHR, textStatus, errorThrown) {
					clearErrorMessage();
					var msg = "";
					switch(textStatus) {
						case 'timeout':
							msg = "The browser timed out waiting for a response from the server.";
						break;
						case 'abort':
							msg = "The request was aborted.";
						break;
						case 'parsererror':
							msg = "A response was received, but it was unreadable.";
						break;
						case 'error':
						//break; fall thru
					default:
						msg = "Some type of server or network error occured.";
					}
					showErrorMessage("Your request could not be completed.  Reason: '" + msg + "'  If you repeatedly receive this message, contact " + GCMRC.administrator + "@usgs.gov");
				}
			});
		}
	};
}(CONFIG.networkHoursOffset);<|endoftext|>


<commit_before>/* eslint-disable max-lines,no-magic-numbers */

const db = require('tresdb-db');
const urls = require('georap-urls-server');
const path = require('path');
const _ = require('lodash');
const purifyMarkdown = require('purify-markdown');
const eventsDal = require('../events/dal');

// Private methods

const insertOne = (entry, callback) => {
  // Parameters:
  //   entry
  //     raw entry to insert
  //   callback
  //     function (err, entryId);
  const coll = db.collection('entries');

  coll.insertOne(entry, (err, result) => {
    if (err) {
      return callback(err);
    }
    return callback(null, result.insertedId);
  });
};

// Public methods

exports.createLocationEntry = (params, callback) => {
  // Parameters:
  //   params:
  //     locationId
  //       ObjectId
  //     locationName
  //       string, for event
  //     username
  //       string
  //     markdown
  //       optional string or null
  //     attachments
  //       optional array of attachment keys
  //     flags
  //       optional array of instance-specific flags e.g. 'visit'
  //   callback
  //     function (err, insertedId)
  //

  if (!params.locationId || !params.username) {
    throw new Error('Missing parameter: locationId or username');
  }

  const sanitizedMarkdown = purifyMarkdown(params.markdown).trim();

  if (typeof params.markdown !== 'string') {
    params.markdown = '';
  }
  if (typeof params.attachments !== 'object') {
    params.attachments = [];
  }
  if (typeof params.flags !== 'object') {
    params.flags = [];
  }

  const newEntry = {
    user: params.username,
    time: db.timestamp(),
    locationId: params.locationId,
    deleted: false,
    published: false,
    markdown: sanitizedMarkdown,
    attachments: params.attachments,
    comments: [],
    flags: params.flags,
  };

  insertOne(newEntry, (err, newEntryId) => {
    if (err) {
      return callback(err);
    }

    newEntry._id = newEntryId;
    const eventParams = {
      username: params.username,
      locationId: params.locationId,
      locationName: params.locationName,
      entry: newEntry,
    };

    eventsDal.createLocationEntryCreated(eventParams, (errr) => {
      if (errr) {
        return callback(errr);
      }
      return callback(null, newEntry);
    });
  });
};

exports.changeLocationEntry = (params, callback) => {
  // Modify entry markdown, attachments, or flags.
  //
  // Parameters:
  //   params:
  //     oldEntry
  //       raw entry object
  //     username
  //       string
  //     locationName
  //       because entries dont store location name but events do
  //     delta
  //       object of changed values
  //   callback
  //     function (err)
  //
  const oldEntry = params.oldEntry;
  const coll = db.collection('entries');
  const q = { _id: oldEntry._id };
  const delta = Object.assign({}, params.delta); // keep original intact

  // Sanitize possible markdown
  if ('markdown' in delta) {
    delta.markdown = purifyMarkdown(delta.markdown).trim();
  }

  // Ensure minimal delta by including only values that differ.
  // This step also ensures that forbidden properties have no effect.
  const minDelta = {};
  const original = {};
  if (delta.markdown !== oldEntry.markdown) {
    minDelta.markdown = delta.markdown;
    original.markdown = oldEntry.markdown;
  }
  if (!_.isEqual(delta.attachments, oldEntry.attachments)) {
    minDelta.attachments = delta.attachments;
    original.attachments = oldEntry.attachments;
  }
  if (!_.isEqual(delta.flags, oldEntry.flags)) {
    minDelta.flags = delta.flags;
    original.flags = oldEntry.flags;
  }

  const changedEntry = Object.assign({}, oldEntry, minDelta);

  coll.replaceOne(q, changedEntry, (err) => {
    if (err) {
      return callback(err);
    }

    const eventParams = {
      entryId: oldEntry._id,
      locationId: oldEntry.locationId,
      locationName: params.locationName,
      username: params.username,
      delta: minDelta,
      original: original,
    };

    eventsDal.createLocationEntryChanged(eventParams, (everr) => {
      if (everr) {
        return callback(everr);
      }

      return callback(null, changedEntry);
    });
  });
};

exports.filterUniqueLocationEntries = function (args, callback) {
  // Append given entries into the given location if they do not yet exist.
  //
  // Parameters
  //   args
  //     locationId
  //       ObjectId
  //     entryCandidates
  //       array of objects:
  //         username
  //         markdown
  //         filepath
  //           basename is compared
  //   callback
  //     function (err, uniqueEntries)
  //       err
  //       uniqueEntries
  //         subset of given entryCandidates

  const locId = args.locationId;
  exports.getAllOfLocationComplete(locId, function (err, realEntries) {
    if (err) {
      return callback(err);
    }

    var newEntries = args.entryCandidates.filter(function (ec) {
      // Pass only those entry candidates that differ from every current
      // entry. Entry is different if creator, text, OR file basename differs.
      return _.every(realEntries, function isDifferent(re) {
        // Paths are null for descriptions
        let reBase = null;
        if (re.attachments.length > 0) {
          reBase = path.basename(re.attachments[0].filepath);
        }
        var ecBase = ec.filepath ? path.basename(ec.filepath) : null;
        return (re.user !== ec.username ||
                re.markdown !== ec.markdown ||
                reBase !== ecBase);
      });
    });

    return callback(null, newEntries);
  });
};

exports.getOneComplete = function (entryId, callback) {
  // Find single entry with attachments.
  // Expand their URLs.
  //
  return db.collection('entries')
    .aggregate([
      {
        $match: {
          _id: entryId,
        },
      },
      {
        $lookup: {
          from: 'attachments',
          localField: 'attachments',
          foreignField: 'key',
          as: 'attachments',
        },
      },
    ])
    .toArray((err, entries) => {
      if (err) {
        return callback(err);
      }

      if (entries.length === 0) {
        return callback(null, null);
      }

      const entry = entries[0];

      // Complete attachment URLs
      entry.attachments = entry.attachments.map(urls.completeAttachment);

      return callback(null, entry);
    });
};

exports.getOneRaw = function (entryId, callback) {
  // Find single entry
  //
  // Parameters:
  //   entryId
  //   callback
  //     function (err, entryDoc)
  //
  var coll = db.collection('entries');
  var q = {
    _id: entryId,
  };

  coll.findOne(q, function (err, doc) {
    if (err) {
      return callback(err);
    }

    if (!doc) {
      return callback(null, null);
    }

    return callback(null, doc);
  });
};

exports.getAllOfLocationComplete = (locationId, callback) => {
  // Get all non-deleted entries of a location with their attachments.
  //
  // Parameters:
  //   locationId
  //     object id
  //   callback
  //     function (err, entries)
  //
  return db.collection('entries')
    .aggregate([
      {
        $match: {
          locationId: locationId,
          deleted: false,
        },
      },
      {
        $sort: {
          time: -1, // most recent first
        },
      },
      // Collect attachment keys from comments for further processing.
      // NOTE $concatArrays cannot be used to concatenate already nested arrays
      // such as the one returned by '$comments.attachments'
      {
        $addFields: {
          commentAttachments: {
            $reduce: {
              input: '$comments.attachments',
              initialValue: [],
              in: {
                $concatArrays: ['$$value', '$$this'],
              },
            },
          },
        },
      },
      // Replace attachment keys with attachment objects
      {
        $lookup: {
          from: 'attachments',
          localField: 'attachments',
          foreignField: 'key',
          as: 'attachments',
        },
      },
      // Replace gathered comment attachment keys with attachment objects
      {
        $lookup: {
          from: 'attachments',
          localField: 'commentAttachments',
          foreignField: 'key',
          as: 'commentAttachments',
        },
      },
    ]).toArray((err, entries) => {
      if (err) {
        return callback(err);
      }

      // Complete attachment URLs
      entries.forEach(entry => {
        entry.attachments = entry.attachments.map(urls.completeAttachment);
      });

      // Complete comment attachment objects and their URLs
      const catReducer = (dict, cat) => {
        dict[cat.key] = cat;
        return dict;
      };
      entries.forEach(entry => {
        // Gather comment attachments to a fast-access dict
        const cats = entry.commentAttachments.reduce(catReducer, {});
        // Replace in-comment attachment keys with attachment objects.
        entry.comments.forEach((comment) => {
          comment.attachments = comment.attachments.map((catKey) => {
            const cat = cats[catKey];
            // Complete url
            return urls.completeAttachment(cat);
          });
        });
        // Forget the temporary cats array
        delete entry.commentAttachments;
      });

      return callback(null, entries);
    });
};

exports.getAllOfLocationRaw = function (locationId, callback) {
  // Get all non-deleted entries of a location, most recent first.
  //
  // Parameters
  //   locationId
  //     object id
  //   callback
  //     function (err, entries)

  var coll = db.collection('entries');
  var q = {
    locationId: locationId,
    deleted: false,
  };
  var opt = { sort: { time: -1 } };

  return coll.find(q, opt).toArray(callback);
};

exports.getAllOfUser = function (username, callback) {
  // Return all non-deleted entries created by user,
  // ordered from oldest to newest.
  //
  // Parameters
  //   username
  //     string
  //   callback
  //     function (err, entries)

  var coll = db.collection('entries');
  var q = {
    user: username,
    deleted: false,
  };

  coll.find(q).sort({ time: 1 }).toArray(callback);
};

exports.removeLocationEntry = function (params, callback) {
  // Mark entry as deleted. The worker or a migration step
  // will remove deleted entries and their attachments at some point.
  //
  // Parameters:
  //   params
  //     entryId
  //     locationId
  //     locationName
  //     username
  //     entry
  //       entry object
  //   callback
  //     function (err)
  //

  const q = { _id: params.entryId };
  const u = {
    $set: {
      deleted: true,
    },
  };

  db.collection('entries').updateOne(q, u, function (err) {
    if (err) {
      return callback(err);
    }
    eventsDal.createLocationEntryRemoved(params, callback);
  });
};

exports.createLocationEntryComment = (params, callback) => {
  // Add new comment object to entry.comments
  //
  // Parameters:
  //   params
  //     locationId
  //     entryId
  //     locationName
  //     username
  //     markdown: markdown UTF8 string
  //     attachments: optional array of attachment keys
  //   callback
  //     function (err)
  //
  // Precondition:
  //   markdown is sanitized
  //
  const time = db.timestamp();
  const rand1 = Math.random().toString().substr(2, 10);
  const rand2 = Math.random().toString().substr(2, 10);
  const commentId = time.substr(0, 4) + rand1 + rand2; // 24 chars

  const coll = db.collection('entries');
  const filter = { _id: params.entryId };

  let attachments = [];
  if (params.attachments) {
    attachments = params.attachments;
  }

  const comment = {
    id: commentId,
    time: time,
    user: params.username,
    markdown: params.markdown,
    attachments: attachments,
  };

  const update = {
    $push: {
      comments: comment,
    },
  };

  coll.updateOne(filter, update, (err) => {
    if (err) {
      return callback(err);
    }

    const eventParams = {
      locationId: params.locationId,
      locationName: params.locationName,
      entryId: params.entryId,
      comment: comment,
    };

    eventsDal.createLocationEntryCommentCreated(eventParams, callback);
  });
};

exports.changeLocationEntryComment = function (params, callback) {
  // Parameters:
  //   params
  //     locationId
  //     locationName
  //     entryId
  //     username
  //     commentId
  //     original
  //       original values of props in delta
  //     delta
  //       markdown: markdown UTF8 string
  //       attachments: an array of attachment keys
  //   callback
  //     function (err)
  //
  // Precondition
  //   original and delta are minimal (contain only changed prop values)
  //
  const coll = db.collection('entries');
  const filter = {
    _id: params.entryId,
    'comments.id': params.commentId,
  };

  // Sanitize input and build update
  let update = {
    $set: {},
  };
  if (params.delta.markdown) {
    update.$set['comments.$.markdown'] = params.delta.markdown;
  }
  if (params.delta.attachments) {
    update.$set['comments.$.attachments'] = params.delta.attachments;
  }

  coll.updateOne(filter, update, function (err) {
    if (err) {
      return callback(err);
    }

    eventsDal.createLocationEntryCommentChanged(params, callback);
  });
};

exports.removeLocationEntryComment = function (params, callback) {
  // Parameters:
  //   params
  //     username
  //     locationId
  //     locationName
  //     entryId
  //     commentId
  //   callback
  //     function (err)

  var coll = db.collection('entries');
  var filter = { _id: params.entryId };

  var commentId = params.commentId;

  var update = {
    $pull: {
      comments: {
        id: commentId,
      },
    },
  };

  coll.updateOne(filter, update, function (err) {
    if (err) {
      return callback(err);
    }

    var eventParams = params;
    eventsDal.createLocationEntryCommentRemoved(eventParams, callback);
  });
};
<commit_msg>entriesDal.count<commit_after>/* eslint-disable max-lines,no-magic-numbers */

const db = require('tresdb-db');
const urls = require('georap-urls-server');
const path = require('path');
const _ = require('lodash');
const purifyMarkdown = require('purify-markdown');
const eventsDal = require('../events/dal');

// Private methods

const insertOne = (entry, callback) => {
  // Parameters:
  //   entry
  //     raw entry to insert
  //   callback
  //     function (err, entryId);
  const coll = db.collection('entries');

  coll.insertOne(entry, (err, result) => {
    if (err) {
      return callback(err);
    }
    return callback(null, result.insertedId);
  });
};

// Public methods

exports.count = (callback) => {
  // Count non-deleted entries
  //
  // Parameters:
  //   callback
  //     function (err, number)
  //
  db.collection('entries')
    .countDocuments({ deleted: false })
    .then((number) => {
      return callback(null, number);
    })
    .catch((err) => {
      return callback(err);
    });
};

exports.createLocationEntry = (params, callback) => {
  // Parameters:
  //   params:
  //     locationId
  //       ObjectId
  //     locationName
  //       string, for event
  //     username
  //       string
  //     markdown
  //       optional string or null
  //     attachments
  //       optional array of attachment keys
  //     flags
  //       optional array of instance-specific flags e.g. 'visit'
  //   callback
  //     function (err, insertedId)
  //

  if (!params.locationId || !params.username) {
    throw new Error('Missing parameter: locationId or username');
  }

  const sanitizedMarkdown = purifyMarkdown(params.markdown).trim();

  if (typeof params.markdown !== 'string') {
    params.markdown = '';
  }
  if (typeof params.attachments !== 'object') {
    params.attachments = [];
  }
  if (typeof params.flags !== 'object') {
    params.flags = [];
  }

  const newEntry = {
    user: params.username,
    time: db.timestamp(),
    locationId: params.locationId,
    deleted: false,
    published: false,
    markdown: sanitizedMarkdown,
    attachments: params.attachments,
    comments: [],
    flags: params.flags,
  };

  insertOne(newEntry, (err, newEntryId) => {
    if (err) {
      return callback(err);
    }

    newEntry._id = newEntryId;
    const eventParams = {
      username: params.username,
      locationId: params.locationId,
      locationName: params.locationName,
      entry: newEntry,
    };

    eventsDal.createLocationEntryCreated(eventParams, (errr) => {
      if (errr) {
        return callback(errr);
      }
      return callback(null, newEntry);
    });
  });
};

exports.changeLocationEntry = (params, callback) => {
  // Modify entry markdown, attachments, or flags.
  //
  // Parameters:
  //   params:
  //     oldEntry
  //       raw entry object
  //     username
  //       string
  //     locationName
  //       because entries dont store location name but events do
  //     delta
  //       object of changed values
  //   callback
  //     function (err)
  //
  const oldEntry = params.oldEntry;
  const coll = db.collection('entries');
  const q = { _id: oldEntry._id };
  const delta = Object.assign({}, params.delta); // keep original intact

  // Sanitize possible markdown
  if ('markdown' in delta) {
    delta.markdown = purifyMarkdown(delta.markdown).trim();
  }

  // Ensure minimal delta by including only values that differ.
  // This step also ensures that forbidden properties have no effect.
  const minDelta = {};
  const original = {};
  if (delta.markdown !== oldEntry.markdown) {
    minDelta.markdown = delta.markdown;
    original.markdown = oldEntry.markdown;
  }
  if (!_.isEqual(delta.attachments, oldEntry.attachments)) {
    minDelta.attachments = delta.attachments;
    original.attachments = oldEntry.attachments;
  }
  if (!_.isEqual(delta.flags, oldEntry.flags)) {
    minDelta.flags = delta.flags;
    original.flags = oldEntry.flags;
  }

  const changedEntry = Object.assign({}, oldEntry, minDelta);

  coll.replaceOne(q, changedEntry, (err) => {
    if (err) {
      return callback(err);
    }

    const eventParams = {
      entryId: oldEntry._id,
      locationId: oldEntry.locationId,
      locationName: params.locationName,
      username: params.username,
      delta: minDelta,
      original: original,
    };

    eventsDal.createLocationEntryChanged(eventParams, (everr) => {
      if (everr) {
        return callback(everr);
      }

      return callback(null, changedEntry);
    });
  });
};

exports.filterUniqueLocationEntries = function (args, callback) {
  // Append given entries into the given location if they do not yet exist.
  //
  // Parameters
  //   args
  //     locationId
  //       ObjectId
  //     entryCandidates
  //       array of objects:
  //         username
  //         markdown
  //         filepath
  //           basename is compared
  //   callback
  //     function (err, uniqueEntries)
  //       err
  //       uniqueEntries
  //         subset of given entryCandidates

  const locId = args.locationId;
  exports.getAllOfLocationComplete(locId, function (err, realEntries) {
    if (err) {
      return callback(err);
    }

    var newEntries = args.entryCandidates.filter(function (ec) {
      // Pass only those entry candidates that differ from every current
      // entry. Entry is different if creator, text, OR file basename differs.
      return _.every(realEntries, function isDifferent(re) {
        // Paths are null for descriptions
        let reBase = null;
        if (re.attachments.length > 0) {
          reBase = path.basename(re.attachments[0].filepath);
        }
        var ecBase = ec.filepath ? path.basename(ec.filepath) : null;
        return (re.user !== ec.username ||
                re.markdown !== ec.markdown ||
                reBase !== ecBase);
      });
    });

    return callback(null, newEntries);
  });
};

exports.getOneComplete = function (entryId, callback) {
  // Find single entry with attachments.
  // Expand their URLs.
  //
  return db.collection('entries')
    .aggregate([
      {
        $match: {
          _id: entryId,
        },
      },
      {
        $lookup: {
          from: 'attachments',
          localField: 'attachments',
          foreignField: 'key',
          as: 'attachments',
        },
      },
    ])
    .toArray((err, entries) => {
      if (err) {
        return callback(err);
      }

      if (entries.length === 0) {
        return callback(null, null);
      }

      const entry = entries[0];

      // Complete attachment URLs
      entry.attachments = entry.attachments.map(urls.completeAttachment);

      return callback(null, entry);
    });
};

exports.getOneRaw = function (entryId, callback) {
  // Find single entry
  //
  // Parameters:
  //   entryId
  //   callback
  //     function (err, entryDoc)
  //
  var coll = db.collection('entries');
  var q = {
    _id: entryId,
  };

  coll.findOne(q, function (err, doc) {
    if (err) {
      return callback(err);
    }

    if (!doc) {
      return callback(null, null);
    }

    return callback(null, doc);
  });
};

exports.getAllOfLocationComplete = (locationId, callback) => {
  // Get all non-deleted entries of a location with their attachments.
  //
  // Parameters:
  //   locationId
  //     object id
  //   callback
  //     function (err, entries)
  //
  return db.collection('entries')
    .aggregate([
      {
        $match: {
          locationId: locationId,
          deleted: false,
        },
      },
      {
        $sort: {
          time: -1, // most recent first
        },
      },
      // Collect attachment keys from comments for further processing.
      // NOTE $concatArrays cannot be used to concatenate already nested arrays
      // such as the one returned by '$comments.attachments'
      {
        $addFields: {
          commentAttachments: {
            $reduce: {
              input: '$comments.attachments',
              initialValue: [],
              in: {
                $concatArrays: ['$$value', '$$this'],
              },
            },
          },
        },
      },
      // Replace attachment keys with attachment objects
      {
        $lookup: {
          from: 'attachments',
          localField: 'attachments',
          foreignField: 'key',
          as: 'attachments',
        },
      },
      // Replace gathered comment attachment keys with attachment objects
      {
        $lookup: {
          from: 'attachments',
          localField: 'commentAttachments',
          foreignField: 'key',
          as: 'commentAttachments',
        },
      },
    ]).toArray((err, entries) => {
      if (err) {
        return callback(err);
      }

      // Complete attachment URLs
      entries.forEach(entry => {
        entry.attachments = entry.attachments.map(urls.completeAttachment);
      });

      // Complete comment attachment objects and their URLs
      const catReducer = (dict, cat) => {
        dict[cat.key] = cat;
        return dict;
      };
      entries.forEach(entry => {
        // Gather comment attachments to a fast-access dict
        const cats = entry.commentAttachments.reduce(catReducer, {});
        // Replace in-comment attachment keys with attachment objects.
        entry.comments.forEach((comment) => {
          comment.attachments = comment.attachments.map((catKey) => {
            const cat = cats[catKey];
            // Complete url
            return urls.completeAttachment(cat);
          });
        });
        // Forget the temporary cats array
        delete entry.commentAttachments;
      });

      return callback(null, entries);
    });
};

exports.getAllOfLocationRaw = function (locationId, callback) {
  // Get all non-deleted entries of a location, most recent first.
  //
  // Parameters
  //   locationId
  //     object id
  //   callback
  //     function (err, entries)

  var coll = db.collection('entries');
  var q = {
    locationId: locationId,
    deleted: false,
  };
  var opt = { sort: { time: -1 } };

  return coll.find(q, opt).toArray(callback);
};

exports.getAllOfUser = function (username, callback) {
  // Return all non-deleted entries created by user,
  // ordered from oldest to newest.
  //
  // Parameters
  //   username
  //     string
  //   callback
  //     function (err, entries)

  var coll = db.collection('entries');
  var q = {
    user: username,
    deleted: false,
  };

  coll.find(q).sort({ time: 1 }).toArray(callback);
};

exports.removeLocationEntry = function (params, callback) {
  // Mark entry as deleted. The worker or a migration step
  // will remove deleted entries and their attachments at some point.
  //
  // Parameters:
  //   params
  //     entryId
  //     locationId
  //     locationName
  //     username
  //     entry
  //       entry object
  //   callback
  //     function (err)
  //

  const q = { _id: params.entryId };
  const u = {
    $set: {
      deleted: true,
    },
  };

  db.collection('entries').updateOne(q, u, function (err) {
    if (err) {
      return callback(err);
    }
    eventsDal.createLocationEntryRemoved(params, callback);
  });
};

exports.createLocationEntryComment = (params, callback) => {
  // Add new comment object to entry.comments
  //
  // Parameters:
  //   params
  //     locationId
  //     entryId
  //     locationName
  //     username
  //     markdown: markdown UTF8 string
  //     attachments: optional array of attachment keys
  //   callback
  //     function (err)
  //
  // Precondition:
  //   markdown is sanitized
  //
  const time = db.timestamp();
  const rand1 = Math.random().toString().substr(2, 10);
  const rand2 = Math.random().toString().substr(2, 10);
  const commentId = time.substr(0, 4) + rand1 + rand2; // 24 chars

  const coll = db.collection('entries');
  const filter = { _id: params.entryId };

  let attachments = [];
  if (params.attachments) {
    attachments = params.attachments;
  }

  const comment = {
    id: commentId,
    time: time,
    user: params.username,
    markdown: params.markdown,
    attachments: attachments,
  };

  const update = {
    $push: {
      comments: comment,
    },
  };

  coll.updateOne(filter, update, (err) => {
    if (err) {
      return callback(err);
    }

    const eventParams = {
      locationId: params.locationId,
      locationName: params.locationName,
      entryId: params.entryId,
      comment: comment,
    };

    eventsDal.createLocationEntryCommentCreated(eventParams, callback);
  });
};

exports.changeLocationEntryComment = function (params, callback) {
  // Parameters:
  //   params
  //     locationId
  //     locationName
  //     entryId
  //     username
  //     commentId
  //     original
  //       original values of props in delta
  //     delta
  //       markdown: markdown UTF8 string
  //       attachments: an array of attachment keys
  //   callback
  //     function (err)
  //
  // Precondition
  //   original and delta are minimal (contain only changed prop values)
  //
  const coll = db.collection('entries');
  const filter = {
    _id: params.entryId,
    'comments.id': params.commentId,
  };

  // Sanitize input and build update
  let update = {
    $set: {},
  };
  if (params.delta.markdown) {
    update.$set['comments.$.markdown'] = params.delta.markdown;
  }
  if (params.delta.attachments) {
    update.$set['comments.$.attachments'] = params.delta.attachments;
  }

  coll.updateOne(filter, update, function (err) {
    if (err) {
      return callback(err);
    }

    eventsDal.createLocationEntryCommentChanged(params, callback);
  });
};

exports.removeLocationEntryComment = function (params, callback) {
  // Parameters:
  //   params
  //     username
  //     locationId
  //     locationName
  //     entryId
  //     commentId
  //   callback
  //     function (err)

  var coll = db.collection('entries');
  var filter = { _id: params.entryId };

  var commentId = params.commentId;

  var update = {
    $pull: {
      comments: {
        id: commentId,
      },
    },
  };

  coll.updateOne(filter, update, function (err) {
    if (err) {
      return callback(err);
    }

    var eventParams = params;
    eventsDal.createLocationEntryCommentRemoved(eventParams, callback);
  });
};
<|endoftext|>


<commit_before>var project = new DemoProject();
var sunLight;
var earthMaterial;
var earth;
var time = 0;

var settings = {
    sunIntensity: 50.0,
    scatterIntensityBoost: 1,
    cloudColor: new HX.Color(),
    effects: []
};

// units are million km

project.onInit = function()
{
    if (HX.OPTIONS.useHDR)
        initHDRSettings();
    else
        initLDRSettings();

    initCamera(this.camera);
    initScene(this.scene);

    time = 0;
};

project.onUpdate = function(dt)
{
    time += dt;
    earth.rotation.fromEuler(-23.5 * Math.PI / 180.0, time * .00001 + 1.0, 0.0);

    var v = this.camera.viewMatrix.transformVector(sunLight.direction);
    earthMaterial.setUniform("sunViewDirection", v);
};

window.onload = function ()
{
    var options = new HX.InitOptions();
    options.useHDR = true;
    options.lightingModel = HX.GGXLightingModel;
    project.init(document.getElementById('webglContainer'), options);
};

function initHDRSettings()
{
    var bloom1 = new HX.BloomEffect(30, 1.0, 4);
    bloom1.thresholdLuminance = 5.0;

    var bloom2 = new HX.BloomEffect(100,.5, 4,.8);
    bloom2.thresholdLuminance = 10.0;

    var bloom3 = new HX.BloomEffect(300,.1, 8,.75);
    bloom3.thresholdLuminance = 15.0;

    var tonemap = new HX.FilmicToneMapEffect(true);
    tonemap.exposure = -1;

    settings.effects = [bloom1, bloom2, bloom3, tonemap];
    settings.sunIntensity = 20.0;
    settings.cloudColor = new HX.Color(0.8, 0.78, 0.75);
    settings.scatterIntensityBoost = .25;
}

function initLDRSettings()
{
    var bloom1 = new HX.BloomEffect(300, 1.5, 8,.75);
    bloom1.thresholdLuminance = .95;

    //var bloom2 = new HX.BloomEffect(30, 20.0, 4);
    //bloom2.thresholdLuminance = .99;

    settings.effects = [ bloom1 ];
    settings.sunIntensity = 4.0;
    settings.cloudColor = new HX.Color(0.64, 0.624, 0.6);
    settings.scatterIntensityBoost = 2.0;
}

function initCamera(camera)
{
    camera.position.x = -.01;
    camera.position.y = -.0001;
    camera.position.z = -.01;
    camera.lookAt(HX.Float4.ORIGIN_POINT);
    // earth sun distance ~150

    camera.nearDistance = 0.0001;
    camera.farDistance = 1000.0;

    var controller = new FloatController();
    controller.speed = .07;
    controller.shiftMultiplier = 5.0;
    controller.yaw = Math.PI;
    camera.addComponent(controller);

    camera.addComponents(settings.effects);
}

function initSun(container)
{
    var sunPosX = 0;
    var sunPosY = 80;
    var sunPosZ = 150;

    sunLight = new HX.DirectionalLight();
    // sunlight actually has more green in its spectrum, but it's filtered out by the atmosphere
    sunLight.intensity = settings.sunIntensity;
    sunLight.color = new HX.Color(1.0, 1.0, 1.0);
    sunLight.direction = new HX.Float4(-sunPosX, -sunPosY, -sunPosZ);
    container.attach(sunLight);

    var sunSpherePrimitive = new HX.SpherePrimitive(
        {
            radius: 0.696
        }
    );
// TODO: Could replace with local light probes?
    var loader = new HX.AssetLoader(HX.HMT);
    var sunMaterial = loader.load("materials/sunMaterial.hmt");

    var sun = new HX.ModelInstance(sunSpherePrimitive, sunMaterial);
    // not heliocentric, apparently ;)
    // let's put the sun away and the earth at 0, so camera animation is easier
    sun.position.set(sunPosX, sunPosY, sunPosZ);
    container.attach(sun);
}

function initEarth(container)
{
    var earthRadius = 0.006371;
    var atmosphereScale = 1.025;
    var avgDensityHeight = .15;

    earth = new HX.GroupNode();
    var earthSpherePrimitive = new HX.SpherePrimitive(
        {
            radius: earthRadius,
            numSegmentsH: 60,
            numSegmentsW: 80
        }
    );

    var atmosphereRadius = earthRadius * atmosphereScale;
    var atmosphereTickness = atmosphereRadius - earthRadius;

    var lightDir = sunLight.worldMatrix.getColumn(2);

    var materialLoader = new HX.AssetLoader(HX.HMT);
    earthMaterial = materialLoader.load("materials/earthMaterial.hmt");
    earthMaterial.setUniform("lightDir", lightDir);
    earthMaterial.setUniform("atmosphereRadius", atmosphereRadius);
    earthMaterial.setUniform("earthRadius", earthRadius);
    earthMaterial.setUniform("rcpAtmosThickness", 1.0 / atmosphereTickness);
    earthMaterial.setUniform("rcpThicknessOverScaleDepth", 1.0 / atmosphereTickness / avgDensityHeight);
    earthMaterial.setUniform("expThicknessOverScaleDepth", Math.exp((earthRadius - atmosphereRadius) / avgDensityHeight));

    var globe = new HX.ModelInstance(earthSpherePrimitive, earthMaterial);
    earth.attach(globe);

    var atmosMaterial = materialLoader.load("materials/atmosphereMaterial.hmt");
    var atmosphere = new HX.ModelInstance(earthSpherePrimitive, atmosMaterial);

    atmosphere.scale.set(atmosphereScale, atmosphereScale, atmosphereScale);
    earth.attach(atmosphere);
    atmosMaterial.setUniform("atmosphereRadius", atmosphereRadius);
    atmosMaterial.setUniform("earthRadius", earthRadius);
    atmosMaterial.setUniform("rcpAtmosThickness", 1.0 / atmosphereTickness);
    atmosMaterial.setUniform("rcpThicknessOverScaleDepth", 1.0 / atmosphereTickness / avgDensityHeight);
    console.log(settings.sunIntensity * settings.scatterIntensityBoost);
    atmosMaterial.setUniform("boost", settings.sunIntensity * settings.scatterIntensityBoost);
    atmosMaterial.setUniform("lightDir", lightDir);
    atmosMaterial.transparencyMode = HX.TransparencyMode.ADDITIVE;

    earth.rotation.fromEuler(-23.5 * Math.PI / 180.0, 1.0, 0.0);

    container.attach(earth);
}

function initMoon(container)
{
    // bringing it 5x closer than it is
    var distanceToEarth = 0.384400 / 5;
    var moonRadius = 0.001737;
    var moonSpherePrimitive = new HX.SpherePrimitive(
        {
            radius: moonRadius,
            numSegmentsH: 30,
            numSegmentsW: 40
        }
    );

    var textureLoader = new HX.AssetLoader(HX.JPG);
    var colorMap = textureLoader.load("textures/moon/albedo.jpg");
    var normalMap = textureLoader.load("textures/moon/normals.png");

    var moonMaterial = new HX.PBRMaterial();
    moonMaterial.colorMap = colorMap;
    moonMaterial.normalMap = normalMap;
    moonMaterial.setRoughness(.99);

    var moon = new HX.ModelInstance(moonSpherePrimitive, moonMaterial);

    var dir = new HX.Float4(5.0,2.0,1.0);
    dir.normalize();
    dir.scale(distanceToEarth);
    moon.position.copyFrom(dir);
    moon.lookAt(HX.Float4.ORIGIN_POINT);

    container.attach(moon);
}

function initScene(scene)
{
    // rotate everything so the skybox is oriented
    var container = new HX.GroupNode();
    container.rotation.fromEuler(0, 0, .6);
    scene.attach(container);
    initSun(container);
    initEarth(container);
    initMoon(container);

    scene.detach(project.camera);
    container.attach(project.camera);

    //var cubeLoader = new HX.AssetLoader(HX.HCM);
    //var skyboxTexture = cubeLoader.load("textures/skybox/stars_skybox.hcm");

    var envMapLoader = new HX.AssetLoader(HX.JPG_EQUIRECTANGULAR);
    var skyboxTexture = envMapLoader.load("textures/skybox/milkyway.jpg");
    scene.skybox = new HX.Skybox(skyboxTexture);
}<commit_msg>Remove log<commit_after>var project = new DemoProject();
var sunLight;
var earthMaterial;
var earth;
var time = 0;

var settings = {
    sunIntensity: 50.0,
    scatterIntensityBoost: 1,
    cloudColor: new HX.Color(),
    effects: []
};

// units are million km

project.onInit = function()
{
    if (HX.OPTIONS.useHDR)
        initHDRSettings();
    else
        initLDRSettings();

    initCamera(this.camera);
    initScene(this.scene);

    time = 0;
};

project.onUpdate = function(dt)
{
    time += dt;
    earth.rotation.fromEuler(-23.5 * Math.PI / 180.0, time * .00001 + 1.0, 0.0);

    var v = this.camera.viewMatrix.transformVector(sunLight.direction);
    earthMaterial.setUniform("sunViewDirection", v);
};

window.onload = function ()
{
    var options = new HX.InitOptions();
    options.useHDR = true;
    options.lightingModel = HX.GGXLightingModel;
    project.init(document.getElementById('webglContainer'), options);
};

function initHDRSettings()
{
    var bloom1 = new HX.BloomEffect(30, 1.0, 4);
    bloom1.thresholdLuminance = 5.0;

    var bloom2 = new HX.BloomEffect(100,.5, 4,.8);
    bloom2.thresholdLuminance = 10.0;

    var bloom3 = new HX.BloomEffect(300,.1, 8,.75);
    bloom3.thresholdLuminance = 15.0;

    var tonemap = new HX.FilmicToneMapEffect(true);
    tonemap.exposure = -1;

    settings.effects = [bloom1, bloom2, bloom3, tonemap];
    settings.sunIntensity = 20.0;
    settings.cloudColor = new HX.Color(0.8, 0.78, 0.75);
    settings.scatterIntensityBoost = .25;
}

function initLDRSettings()
{
    var bloom1 = new HX.BloomEffect(300, 1.5, 8,.75);
    bloom1.thresholdLuminance = .95;

    //var bloom2 = new HX.BloomEffect(30, 20.0, 4);
    //bloom2.thresholdLuminance = .99;

    settings.effects = [ bloom1 ];
    settings.sunIntensity = 4.0;
    settings.cloudColor = new HX.Color(0.64, 0.624, 0.6);
    settings.scatterIntensityBoost = 2.0;
}

function initCamera(camera)
{
    camera.position.x = -.01;
    camera.position.y = -.0001;
    camera.position.z = -.01;
    camera.lookAt(HX.Float4.ORIGIN_POINT);
    // earth sun distance ~150

    camera.nearDistance = 0.0001;
    camera.farDistance = 1000.0;

    var controller = new FloatController();
    controller.speed = .07;
    controller.shiftMultiplier = 5.0;
    controller.yaw = Math.PI;
    camera.addComponent(controller);

    camera.addComponents(settings.effects);
}

function initSun(container)
{
    var sunPosX = 0;
    var sunPosY = 80;
    var sunPosZ = 150;

    sunLight = new HX.DirectionalLight();
    // sunlight actually has more green in its spectrum, but it's filtered out by the atmosphere
    sunLight.intensity = settings.sunIntensity;
    sunLight.color = new HX.Color(1.0, 1.0, 1.0);
    sunLight.direction = new HX.Float4(-sunPosX, -sunPosY, -sunPosZ);
    container.attach(sunLight);

    var sunSpherePrimitive = new HX.SpherePrimitive(
        {
            radius: 0.696
        }
    );
// TODO: Could replace with local light probes?
    var loader = new HX.AssetLoader(HX.HMT);
    var sunMaterial = loader.load("materials/sunMaterial.hmt");

    var sun = new HX.ModelInstance(sunSpherePrimitive, sunMaterial);
    // not heliocentric, apparently ;)
    // let's put the sun away and the earth at 0, so camera animation is easier
    sun.position.set(sunPosX, sunPosY, sunPosZ);
    container.attach(sun);
}

function initEarth(container)
{
    var earthRadius = 0.006371;
    var atmosphereScale = 1.025;
    var avgDensityHeight = .15;

    earth = new HX.GroupNode();
    var earthSpherePrimitive = new HX.SpherePrimitive(
        {
            radius: earthRadius,
            numSegmentsH: 60,
            numSegmentsW: 80
        }
    );

    var atmosphereRadius = earthRadius * atmosphereScale;
    var atmosphereTickness = atmosphereRadius - earthRadius;

    var lightDir = sunLight.worldMatrix.getColumn(2);

    var materialLoader = new HX.AssetLoader(HX.HMT);
    earthMaterial = materialLoader.load("materials/earthMaterial.hmt");
    earthMaterial.setUniform("lightDir", lightDir);
    earthMaterial.setUniform("atmosphereRadius", atmosphereRadius);
    earthMaterial.setUniform("earthRadius", earthRadius);
    earthMaterial.setUniform("rcpAtmosThickness", 1.0 / atmosphereTickness);
    earthMaterial.setUniform("rcpThicknessOverScaleDepth", 1.0 / atmosphereTickness / avgDensityHeight);
    earthMaterial.setUniform("expThicknessOverScaleDepth", Math.exp((earthRadius - atmosphereRadius) / avgDensityHeight));

    var globe = new HX.ModelInstance(earthSpherePrimitive, earthMaterial);
    earth.attach(globe);

    var atmosMaterial = materialLoader.load("materials/atmosphereMaterial.hmt");
    var atmosphere = new HX.ModelInstance(earthSpherePrimitive, atmosMaterial);

    atmosphere.scale.set(atmosphereScale, atmosphereScale, atmosphereScale);
    earth.attach(atmosphere);
    atmosMaterial.setUniform("atmosphereRadius", atmosphereRadius);
    atmosMaterial.setUniform("earthRadius", earthRadius);
    atmosMaterial.setUniform("rcpAtmosThickness", 1.0 / atmosphereTickness);
    atmosMaterial.setUniform("rcpThicknessOverScaleDepth", 1.0 / atmosphereTickness / avgDensityHeight);
    atmosMaterial.setUniform("boost", settings.sunIntensity * settings.scatterIntensityBoost);
    atmosMaterial.setUniform("lightDir", lightDir);
    atmosMaterial.transparencyMode = HX.TransparencyMode.ADDITIVE;

    earth.rotation.fromEuler(-23.5 * Math.PI / 180.0, 1.0, 0.0);

    container.attach(earth);
}

function initMoon(container)
{
    // bringing it 5x closer than it is
    var distanceToEarth = 0.384400 / 5;
    var moonRadius = 0.001737;
    var moonSpherePrimitive = new HX.SpherePrimitive(
        {
            radius: moonRadius,
            numSegmentsH: 30,
            numSegmentsW: 40
        }
    );

    var textureLoader = new HX.AssetLoader(HX.JPG);
    var colorMap = textureLoader.load("textures/moon/albedo.jpg");
    var normalMap = textureLoader.load("textures/moon/normals.png");

    var moonMaterial = new HX.PBRMaterial();
    moonMaterial.colorMap = colorMap;
    moonMaterial.normalMap = normalMap;
    moonMaterial.setRoughness(.99);

    var moon = new HX.ModelInstance(moonSpherePrimitive, moonMaterial);

    var dir = new HX.Float4(5.0,2.0,1.0);
    dir.normalize();
    dir.scale(distanceToEarth);
    moon.position.copyFrom(dir);
    moon.lookAt(HX.Float4.ORIGIN_POINT);

    container.attach(moon);
}

function initScene(scene)
{
    // rotate everything so the skybox is oriented
    var container = new HX.GroupNode();
    container.rotation.fromEuler(0, 0, .6);
    scene.attach(container);
    initSun(container);
    initEarth(container);
    initMoon(container);

    scene.detach(project.camera);
    container.attach(project.camera);

    //var cubeLoader = new HX.AssetLoader(HX.HCM);
    //var skyboxTexture = cubeLoader.load("textures/skybox/stars_skybox.hcm");

    var envMapLoader = new HX.AssetLoader(HX.JPG_EQUIRECTANGULAR);
    var skyboxTexture = envMapLoader.load("textures/skybox/milkyway.jpg");
    scene.skybox = new HX.Skybox(skyboxTexture);
}<|endoftext|>


<commit_before>import {moduleForComponent, test} from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import wait from 'ember-test-helpers/wait';
import startApp from '../../helpers/start-app';

let application;

moduleForComponent('json-form', 'Integration | Component | json form', {
  integration: true,
  beforeEach: function() {
    Ember.run(function() {
      application = startApp();
    });

    this.set('globalOptions', {
      allowBlank() {
        console.log('options.presence', this.get('options.presence'));
        return !this.get('options.presence');
      },
      allowString() {
        return this.get('_type') === 'number';
      }
    });

    this.on('onSubmit', function (data) {
      this.set('result', data);
    });

    this.on('onChange', function () {

    });

    this.set('toggleProperty', Ember.Object.prototype.toggleProperty);
  }
});

const template = hbs`
  {{#json-form
    globalOptions=globalOptions
    onSubmit=(action 'onSubmit')
    onChange=(action 'onChange')
    fieldsets=fieldsets
  as |form|
}}
  <form class='form'>
  {{#form.fieldsets as |fieldset|}}
    <fieldset>
      {{#if fieldset.self.legend}}
        <legend>
          {{fieldset.self.legend}}

          {{fieldset.self.sub-legend}}
        </legend>
      {{/if}}

    {{#fieldset.fields as |field|}}
      {{#field.input as |input|}}

          {{#if field.self.label}}
            {{field.self.label}}
          {{/if}}

          {{input.component classNames='form-control' id=(concat 'field-' field.self.id) errors=input.validation.messages}}

          {{#if input.validation.isValidating}}

validating...

          {{/if}}

          {{#if input.showMessage}}
            {{#each input.validation.messages as |message|}}

{{message}}

            {{/each}}
          {{/if}}

          {{#if checkValidations}}
            {{#each input.validation.messages as |message|}}

{{message}}

            {{/each}}
          {{/if}}

      {{/field.input}}
    {{/fieldset.fields}}
    </fieldset>
  {{/form.fieldsets}}

{{if form.self.validations.didValidate 'true' 'false'}}

    {{#each form.self.validations.errors as |error|}}

        {{error.attribute}} {{form.self.fieldsets}}: {{error.message}}

    {{/each}}

    {{#if form.self.validations.isInvalid}}
      <button class='first-button' onclick={{action toggleProperty 'checkValidations'}}>
      Toggle show validations
      </button>
    {{else}}
        <button onclick={{action form.onSubmit}}>Save</button>
    {{/if}}
  </form>
{{/json-form}}
  `;

test('field with one validations', function (assert) {

  assert.expect(5);

  this.set('fieldsets', [{
    id: 'fieldset1',
    fields: [{
      id: 'mytextfied',
      label: 'mytextfied',
      type: 'number',
      validations: {
        presence: true
      }
    }]
  }]);

  this.render(template);

  this.$('.form-control').val('');
  this.$('.form-control').change();

  wait().then(() => {
    assert.equal(this.$('.text-danger').text(), '');
    assert.equal(this.$('.text-danger').length, 0, 'should not show an error');
  });

  return wait().then(() => {
    this.set('checkValidations', true);
    assert.equal(this.$('.form-control').val(), '', 'input is not empty');
    assert.equal(this.$('.text-danger').text(), 'This field can\'t be blank');
    assert.equal(this.$('.text-danger').length, 1, 'should show an error');
    this.set('checkValidations', false);
  });
});

test('field with 2 validations and multiple configs', function (assert) {

  assert.expect(4);

  this.set('fieldsets', [{
    id: 'fieldset1',
    fields: [{
      id: 'mytextfied',
      label: 'mytextfied',
      type: 'text',
      validations: {
        presence: true,
        length: {
          min: 5,
          max: 10
        }
      }
    }]
  }]);

  this.render(template);

  this.$('.form-control').val('a234');
  this.$('.form-control').change();

  wait().then(() => {
    assert.equal(this.$('.text-danger').text(), 'This field is too short (minimum is 5 characters)');
    assert.equal(this.$('.text-danger').length, 1, 'should show an error');
  });

  wait().then(() => {
    this.$('.form-control').val('1234567891011');
    this.$('.form-control').change();
  });

  return wait().then(() => {
    assert.equal(this.$('.text-danger').text(), 'This field is too long (maximum is 10 characters)');
    assert.equal(this.$('.text-danger').length, 1, 'should show an error');
  });
});

test('field should hide', function (assert) {

  assert.expect(2);

  this.set('fieldsets', [{
    id: 'fieldset1',
    fields: [
      {
        id: 'mynumberfied',
        label: 'mynumberfied',
        type: 'number'
      },
      {
        id: 'mytextfied',
        label: 'mytextfied',
        type: 'text',
        only_if: {
          'fieldset1.mynumberfied': 'eq:5'
        }
      }
    ]
  }]);

  this.render(template);

  this.$('#field-mynumberfied').val(1);
  this.$('#field-mynumberfied').change();

  wait().then(() => {
    assert.equal(this.$('#field-mytextfied').length, 0, 'field should not exist');
  });

  this.$('#field-mynumberfied').val(5);
  this.$('#field-mynumberfied').change();

  return wait().then(() => {
    assert.equal(this.$('#field-mytextfied').length, 0, 'field should not exist');
  });
});
<commit_msg>fix test (#2)<commit_after>import {moduleForComponent, test} from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import wait from 'ember-test-helpers/wait';
import startApp from '../../helpers/start-app';

let application;

moduleForComponent('json-form', 'Integration | Component | json form', {
  integration: true,
  beforeEach: function() {
    Ember.run(function() {
      application = startApp();
    });

    this.set('globalOptions', {
      allowBlank() {
        return !this.get('options.presence');
      },
      allowString() {
        return this.get('_type') === 'number';
      }
    });

    this.on('onSubmit', function (data) {
      this.set('result', data);
    });

    this.on('onChange', function () {

    });

    this.set('toggleProperty', Ember.Object.prototype.toggleProperty);
  }
});

const template = hbs`
  {{#json-form
    globalOptions=globalOptions
    onSubmit=(action 'onSubmit')
    onChange=(action 'onChange')
    fieldsets=fieldsets
  as |form|
}}
  <form class='form'>
  {{#form.fieldsets as |fieldset|}}
    <fieldset>
      {{#if fieldset.self.legend}}
        <legend>
          {{fieldset.self.legend}}

          {{fieldset.self.sub-legend}}
        </legend>
      {{/if}}

    {{#fieldset.fields as |field|}}
      {{#field.input as |input|}}

          {{#if field.self.label}}
            {{field.self.label}}
          {{/if}}

          {{input.component classNames=(concat 'form-control ' 'field-' field.self.id) errors=input.validation.messages}}

          {{#if input.validation.isValidating}}

validating...

          {{/if}}

          {{#if input.showMessage}}
            {{#each input.validation.messages as |message|}}

{{message}}

            {{/each}}
          {{/if}}

          {{#if checkValidations}}
            {{#each input.validation.messages as |message|}}

{{message}}

            {{/each}}
          {{/if}}

      {{/field.input}}
    {{/fieldset.fields}}
    </fieldset>
  {{/form.fieldsets}}

{{if form.self.validations.didValidate 'true' 'false'}}

    {{#each form.self.validations.errors as |error|}}

        {{error.attribute}} {{form.self.fieldsets}}: {{error.message}}

    {{/each}}

    {{#if form.self.validations.isInvalid}}
      <button class='first-button' onclick={{action toggleProperty 'checkValidations'}}>
      Toggle show validations
      </button>
    {{else}}
        <button onclick={{action form.onSubmit}}>Save</button>
    {{/if}}
  </form>
{{/json-form}}
  `;

test('field with one validations', function (assert) {

  assert.expect(5);

  this.set('fieldsets', [{
    id: 'fieldset1',
    fields: [{
      id: 'mytextfied',
      label: 'mytextfied',
      type: 'number',
      validations: {
        presence: true
      }
    }]
  }]);

  this.render(template);

  this.$('.form-control').val('');
  this.$('.form-control').change();

  wait().then(() => {
    assert.equal(this.$('.text-danger').text(), '');
    assert.equal(this.$('.text-danger').length, 0, 'should not show an error');
  });

  return wait().then(() => {
    this.set('checkValidations', true);
    assert.equal(this.$('.form-control').val(), '', 'input is not empty');
    assert.equal(this.$('.text-danger').text(), 'This field can\'t be blank');
    assert.equal(this.$('.text-danger').length, 1, 'should show an error');
    this.set('checkValidations', false);
  });
});

test('field with 2 validations and multiple configs', function (assert) {

  assert.expect(4);

  this.set('fieldsets', [{
    id: 'fieldset1',
    fields: [{
      id: 'mytextfied',
      label: 'mytextfied',
      type: 'text',
      validations: {
        presence: true,
        length: {
          min: 5,
          max: 10
        }
      }
    }]
  }]);

  this.render(template);

  this.$('.form-control').val('a234');
  this.$('.form-control').change();

  wait().then(() => {
    assert.equal(this.$('.text-danger').text(), 'This field is too short (minimum is 5 characters)');
    assert.equal(this.$('.text-danger').length, 1, 'should show an error');
  });

  wait().then(() => {
    this.$('.form-control').val('1234567891011');
    this.$('.form-control').change();
  });

  return wait().then(() => {
    assert.equal(this.$('.text-danger').text(), 'This field is too long (maximum is 10 characters)');
    assert.equal(this.$('.text-danger').length, 1, 'should show an error');
  });
});

test('field should hide', function (assert) {

  assert.expect(5);

  this.set('fieldsets', [{
    id: 'fieldset1',
    fields: [
      {
        id: 'mynumberfied',
        label: 'mynumberfied',
        type: 'number'
      },
      {
        id: 'mytextfied',
        label: 'mytextfied',
        type: 'text',
        only_if: {
          'fieldset1.mynumberfied': 'eq:5'
        }
      }
    ]
  }]);

  this.render(template);

  this.$('.field-mynumberfied').val(1);
  this.$('.field-mynumberfied').change();

  wait().then(() => {
    assert.equal(this.$('.field-mynumberfied').length, 1, 'field not exists');
    assert.equal(this.$('.field-mynumberfied').val(), '1', 'val should be 1');
    assert.equal(this.$('.field-mytextfied').length, 0, 'field should not exist');
  });

  wait().then(() => {
    this.$('.field-mynumberfied').val(5);
    this.$('.field-mynumberfied').change();
  });

  return wait().then(() => {
    assert.equal(this.$('.field-mynumberfied').val(), '5', 'val should be 5');
    assert.equal(this.$('.field-mytextfied').length, 1, 'field should exist');
  });
});
<|endoftext|>


<commit_before>module.exports = function(app) {
    var path = require('path');
    var models = require(path.resolve(__dirname, '../model-config.json'));
    var datasources = require(path.resolve(__dirname, '../datasources.json'));

    function autoUpdateAll(){
        Object.keys(models).forEach(function(key) {
            if (typeof models[key].dataSource != 'undefined') {
                if (typeof datasources[models[key].dataSource] != 'undefined') {
                    app.dataSources[models[key].dataSource].autoupdate(key, function (err) {
                        if (err) throw err;
                        console.log('Model ' + key + ' updated');
                    });
                }
            }
        });
    }

    function autoMigrateAll(){
        Object.keys(models).forEach(function(key) {
            if (typeof models[key].dataSource != 'undefined') {
                if (typeof datasources[models[key].dataSource] != 'undefined') {
                    app.dataSources[models[key].dataSource].automigrate(key, function (err) {
                        if (err) throw err;
                        console.log('Model ' + key + ' migrated');
                    });
                }
            }
        });
    }
    //TODO: change to autoUpdateAll when ready for CI deployment to production
    autoMigrateAll();
    // autoUpdateAll();

};
<commit_msg>yah gitulah<commit_after>module.exports = function(app) {
    var path = require('path');
    var models = require(path.resolve(__dirname, '../model-config.json'));
    var datasources = require(path.resolve(__dirname, '../datasources.json'));

    function autoUpdateAll(){
        Object.keys(models).forEach(function(key) {
            if (typeof models[key].dataSource != 'undefined') {
                if (typeof datasources[models[key].dataSource] != 'undefined') {
                    app.dataSources[models[key].dataSource].autoupdate(key, function (err) {
                        if (err) throw err;
                        console.log('Model ' + key + ' updated');
                    });
                }
            }
        });
    }

    function autoMigrateAll(){
        Object.keys(models).forEach(function(key) {
            if (typeof models[key].dataSource != 'undefined') {
                if (typeof datasources[models[key].dataSource] != 'undefined') {
                    app.dataSources[models[key].dataSource].automigrate(key, function (err) {
                        if (err) throw err;
                        console.log('Model ' + key + ' migrated');
                    });
                }
            }
        });
    }
    //TODO: change to autoUpdateAll when ready for CI deployment to production
    // autoMigrateAll();
    autoUpdateAll();

};
<|endoftext|>


<commit_before>define('collectionRules', ['collections'], function(collections) {
  'use strict';

  var anyInsert = {
    insert: function(userId, doc) {
      return true;
    }
  }

  // currently no restrictions on creating sessions or workItems
  collections.sessions.allow(anyInsert);
  collections.workItems.allow(anyInsert);

  function isValidEstimate(value) {
    return _.isFinite(value) && value >= 0;
  };

  collections.estimates.allow({

    insert: function(userId, doc) {
      return isValidEstimate(doc.value);
    },

    update: function(userId, doc, fields, modifer) {
      // TODO
      return true;
    }

  });

  function isNonBlankString(value) {
    return _.isString(value) && value.replace(/ /g, '').length > 0;
  }

  collections.users.allow({

    insert: function(userId, doc) {
      return isNonBlankString(doc.name);
    },

    update: function(userId, doc, fields, modifer) {
      // TODO
      return true;
    }

  });

  return null;

});<commit_msg>allow updates<commit_after>define('collectionRules', ['collections'], function(collections) {
  'use strict';

  var anyInsertOrUpdate = {
    insert: function(userId, doc) { return true; },
    update: function(userId, doc) { return true; }
  }

  // currently no restrictions on creating sessions or workItems
  collections.sessions.allow(anyInsertOrUpdate);
  collections.workItems.allow(anyInsertOrUpdate);

  function isValidEstimate(value) {
    return _.isFinite(value) && value >= 0;
  };

  collections.estimates.allow({

    insert: function(userId, doc) {
      return isValidEstimate(doc.value);
    },

    update: function(userId, doc, fields, modifer) {
      // TODO
      return true;
    }

  });

  function isNonBlankString(value) {
    return _.isString(value) && value.replace(/ /g, '').length > 0;
  }

  collections.users.allow({

    insert: function(userId, doc) {
      return isNonBlankString(doc.name);
    },

    update: function(userId, doc, fields, modifer) {
      // TODO
      return true;
    }

  });

  return null;

});<|endoftext|>