<commit_before>'use strict'

var RelationalStore = require('./lib/relational-util')
var _ = require('lodash')
var OpParser = require('./lib/operator_parser')

var buildQueryFromExpression = function (entp, query_parameters, values) {
  var params = []
  values = values || []

  if (!_.isEmpty(query_parameters) && query_parameters.params.length > 0) {
    for (var i in query_parameters.params) {
      var current_name = query_parameters.params[i]
      var current_value = query_parameters.values[i]

      var result = parseExpression(current_name, current_value)
      if (result.err) {
        return result
      }
    }

    return {err: null, data: params.join(' AND '), values: values}
  }
  else {
    return {values: values}
  }

  function parseOr (current_name, current_value) {
    if (!_.isArray(current_value)) {
      return {err: 'or$ operator requires an array value'}
    }

    var results = []
    for (var i in current_value) {
      var w = whereargs(entp, current_value[i])
      var current_result = buildQueryFromExpression(entp, w, values)
      values = current_result.values
      results.push(current_result)
    }

    var resultStr = ''
    for (i in results) {
      if (resultStr.length > 0) {
        resultStr += ' OR '
      }
      resultStr += results[i].data
    }
    console.log('(' + resultStr + ')')
    params.push('(' + resultStr + ')')
  }

  function parseAnd (current_name, current_value) {
    if (!_.isArray(current_value)) {
      return {err: 'and$ operator requires an array value'}
    }

    var results = []
    for (var i in current_value) {
      var w = whereargs(entp, current_value[i])
      var current_result = buildQueryFromExpression(entp, w, values)
      values = current_result.values
      results.push(current_result)
    }

    var resultStr = ''
    for (i in results) {
      if (resultStr.length > 0) {
        resultStr += ' AND '
      }
      resultStr += results[i].data
    }
    console.log('(' + resultStr + ')')
    params.push('(' + resultStr + ')')
  }

  function parseExpression (current_name, current_value) {
    if (current_name === 'or$') {
      parseOr(current_name, current_value)
    }
    else if (current_name === 'and$') {
      parseAnd(current_name, current_value)
    }
    else {
      if (current_name.indexOf('$') !== -1) {
        return {}
      }

      if (current_value === null) {
        // we can't use the equality on null because NULL != NULL
        params.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(current_name)) + '` IS NULL')
      }
      else if (current_value instanceof RegExp) {
        var op = (current_value.ignoreCase) ? '~*' : '~'
        values.push(current_value.source)
        params.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(current_name)) + '`' + op + '?')
      }
      else if (_.isObject(current_value)) {
        var result = parseComplexSelectOperator(current_name, current_value, params)
        if (result.err) {
          return result
        }
      }
      else {
        values.push(current_value)
        params.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(current_name)) + '`=' + '?')
      }
    }
    return {}
  }

  function parseComplexSelectOperator (current_name, current_value, params) {
    for (var op in current_value) {
      var op_val = current_value[op]
      if (!OpParser[op]) {
        return {err: 'This operator is not yet implemented: ' + op}
      }
      var err = OpParser[op](current_name, op_val, params, values)
      if (err) {
        return {err: err}
      }
    }
    return {}
  }
}

function whereargs (entp, q) {
  var w = {}

  w.params = []
  w.values = []

  var qok = RelationalStore.fixquery(entp, q)

  for (var p in qok) {
    if (qok[p] !== undefined) {
      w.params.push(RelationalStore.camelToSnakeCase(p))
      w.values.push(qok[p])
    }
  }

  return w
}

function fixPrepStatement (stm) {
  var index = 1
  while (stm.indexOf('?') !== -1) {
    stm = stm.replace('?', '$' + index)
    index++
  }
  return stm
}

function savestm (ent) {
  var stm = {}

  var table = RelationalStore.tablename(ent)
  var entp = RelationalStore.makeentp(ent)
  var fields = _.keys(entp)

  var values = []
  var params = []

  // var cnt = 0

  var escapedFields = []
  fields.forEach(function (field) {
    escapedFields.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(field)) + '`')
    values.push(entp[field])
    params.push('?')
  })

  stm.text = 'INSERT INTO ' + RelationalStore.escapeStr(table) + ' (' + escapedFields + ') values (' + RelationalStore.escapeStr(params) + ')'
  stm.values = values

  return stm
}

function updatestm (ent) {
  var stm = {}

  var table = RelationalStore.tablename(ent)
  var entp = RelationalStore.makeentp(ent)
  var fields = _.keys(entp)

  var values = []
  var params = []
  // var cnt = 0

  fields.forEach(function (field) {
    if (field.indexOf('$') !== -1) {
      return
    }

    if (!_.isUndefined(entp[field])) {
      values.push(entp[field])
      params.push(RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(field)) + '=?')
    }
  })

  stm.text = 'UPDATE ' + RelationalStore.escapeStr(table) + ' SET ' + params + " WHERE id='" + RelationalStore.escapeStr(ent.id) + "'"
  stm.values = values

  return stm
}

function deletestm (qent, q) {
  var stm = {}

  var table = RelationalStore.tablename(qent)
  var entp = RelationalStore.makeentp(qent)

  var values = []
  var params = []

  // var cnt = 0

  var w = whereargs(entp, q)

  var wherestr = ''

  if (!_.isEmpty(w) && w.params.length > 0) {
    for (var i in w.params) {
      var param = w.params[i]
      var val = w.values[i]

      if (param.indexOf('$') !== -1) {
        continue
      }

      params.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(param)) + '`=?')
      values.push(RelationalStore.escapeStr(val))
    }

    if (params.length > 0) {
      wherestr = ' WHERE ' + params.join(' AND ')
    }
    else {
      wherestr = ' '
    }
  }

  stm.text = 'DELETE FROM ' + RelationalStore.escapeStr(table) + wherestr
  stm.values = values

  return stm
}

function selectstm (qent, q, done) {
  var specialOps = ['fields$']
  var specialOpsVal = {}

  var stm = {}

  for (var i in specialOps) {
    if (q[specialOps[i]]) {
      specialOpsVal[specialOps[i]] = q[specialOps[i]]
      delete q[specialOps[i]]
    }
  }

  var table = RelationalStore.tablename(qent)
  var entp = RelationalStore.makeentp(qent)

  var w = whereargs(entp, q)

  var response = buildQueryFromExpression(entp, w)
  if (response.err) {
    return done(response.err)
  }

  var wherestr = response.data

  var values = response.values

  var mq = metaquery(qent, q)

  var metastr = ' ' + mq.params.join(' ')

  var what = '*'
  if (specialOpsVal['fields$'] && _.isArray(specialOpsVal['fields$']) && specialOpsVal['fields$'].length > 0) {
    what = ' ' + specialOpsVal['fields$'].join(', ')
    what += ', id '
  }

  stm.text = 'SELECT ' + what + ' FROM ' + RelationalStore.escapeStr(table) + (wherestr ? ' WHERE ' + wherestr : '') + RelationalStore.escapeStr(metastr)
  stm.values = values

  done(null, stm)
}

function selectstmOr (qent, q) {
  var stm = {}

  var table = RelationalStore.tablename(qent)
  var entp = RelationalStore.makeentp(qent)

  var values = []
  var params = []

  // var cnt = 0

  var w = whereargs(entp, q.ids)

  var wherestr = ''

  if (!_.isEmpty(w) && w.params.length > 0) {
    w.params.forEach(function (param) {
      params.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase('id')) + '`=')
    })

    w.values.forEach(function (value) {
      values.push(value)
    })

    wherestr = ' WHERE ' + params.join(' OR ')
  }

  // This is required to set the limit$ to be the length of the 'ids' array, so that in situations
  // when it's not set in the query(q) it won't be applied the default limit$ of 20 records
  if (!q.limit$) {
    q.limit$ = q.ids.length
  }

  var mq = metaquery(qent, q)

  var metastr = ' ' + mq.params.join(' ')

  stm.text = 'SELECT * FROM ' + RelationalStore.escapeStr(table) + wherestr + RelationalStore.escapeStr(metastr)
  stm.values = values

  return stm
}

function metaquery (qent, q) {
  var mq = {}

  mq.params = []
  mq.values = []

  if (q.sort$) {
    for (var sf in q.sort$) break
    var sd = q.sort$[sf] > 0 ? 'ASC' : 'DESC'
    mq.params.push('ORDER BY ' + RelationalStore.camelToSnakeCase(sf) + ' ' + sd)
  }

  if (q.limit$) {
    mq.params.push('LIMIT ' + q.limit$)
  }

  if (q.skip$) {
    mq.params.push('OFFSET ' + q.skip$)
  }

  return mq
}

module.exports.fixPrepStatement = fixPrepStatement
module.exports.selectstm = selectstm
module.exports.selectstmOr = selectstmOr
module.exports.deletestm = deletestm
module.exports.savestm = savestm
module.exports.updatestm = updatestm
<commit_msg>remove console logs<commit_after>'use strict'

var RelationalStore = require('./lib/relational-util')
var _ = require('lodash')
var OpParser = require('./lib/operator_parser')

var buildQueryFromExpression = function (entp, query_parameters, values) {
  var params = []
  values = values || []

  if (!_.isEmpty(query_parameters) && query_parameters.params.length > 0) {
    for (var i in query_parameters.params) {
      var current_name = query_parameters.params[i]
      var current_value = query_parameters.values[i]

      var result = parseExpression(current_name, current_value)
      if (result.err) {
        return result
      }
    }

    return {err: null, data: params.join(' AND '), values: values}
  }
  else {
    return {values: values}
  }

  function parseOr (current_name, current_value) {
    if (!_.isArray(current_value)) {
      return {err: 'or$ operator requires an array value'}
    }

    var results = []
    for (var i in current_value) {
      var w = whereargs(entp, current_value[i])
      var current_result = buildQueryFromExpression(entp, w, values)
      values = current_result.values
      results.push(current_result)
    }

    var resultStr = ''
    for (i in results) {
      if (resultStr.length > 0) {
        resultStr += ' OR '
      }
      resultStr += results[i].data
    }
    params.push('(' + resultStr + ')')
  }

  function parseAnd (current_name, current_value) {
    if (!_.isArray(current_value)) {
      return {err: 'and$ operator requires an array value'}
    }

    var results = []
    for (var i in current_value) {
      var w = whereargs(entp, current_value[i])
      var current_result = buildQueryFromExpression(entp, w, values)
      values = current_result.values
      results.push(current_result)
    }

    var resultStr = ''
    for (i in results) {
      if (resultStr.length > 0) {
        resultStr += ' AND '
      }
      resultStr += results[i].data
    }
    params.push('(' + resultStr + ')')
  }

  function parseExpression (current_name, current_value) {
    if (current_name === 'or$') {
      parseOr(current_name, current_value)
    }
    else if (current_name === 'and$') {
      parseAnd(current_name, current_value)
    }
    else {
      if (current_name.indexOf('$') !== -1) {
        return {}
      }

      if (current_value === null) {
        // we can't use the equality on null because NULL != NULL
        params.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(current_name)) + '` IS NULL')
      }
      else if (current_value instanceof RegExp) {
        var op = (current_value.ignoreCase) ? '~*' : '~'
        values.push(current_value.source)
        params.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(current_name)) + '`' + op + '?')
      }
      else if (_.isObject(current_value)) {
        var result = parseComplexSelectOperator(current_name, current_value, params)
        if (result.err) {
          return result
        }
      }
      else {
        values.push(current_value)
        params.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(current_name)) + '`=' + '?')
      }
    }
    return {}
  }

  function parseComplexSelectOperator (current_name, current_value, params) {
    for (var op in current_value) {
      var op_val = current_value[op]
      if (!OpParser[op]) {
        return {err: 'This operator is not yet implemented: ' + op}
      }
      var err = OpParser[op](current_name, op_val, params, values)
      if (err) {
        return {err: err}
      }
    }
    return {}
  }
}

function whereargs (entp, q) {
  var w = {}

  w.params = []
  w.values = []

  var qok = RelationalStore.fixquery(entp, q)

  for (var p in qok) {
    if (qok[p] !== undefined) {
      w.params.push(RelationalStore.camelToSnakeCase(p))
      w.values.push(qok[p])
    }
  }

  return w
}

function fixPrepStatement (stm) {
  var index = 1
  while (stm.indexOf('?') !== -1) {
    stm = stm.replace('?', '$' + index)
    index++
  }
  return stm
}

function savestm (ent) {
  var stm = {}

  var table = RelationalStore.tablename(ent)
  var entp = RelationalStore.makeentp(ent)
  var fields = _.keys(entp)

  var values = []
  var params = []

  // var cnt = 0

  var escapedFields = []
  fields.forEach(function (field) {
    escapedFields.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(field)) + '`')
    values.push(entp[field])
    params.push('?')
  })

  stm.text = 'INSERT INTO ' + RelationalStore.escapeStr(table) + ' (' + escapedFields + ') values (' + RelationalStore.escapeStr(params) + ')'
  stm.values = values

  return stm
}

function updatestm (ent) {
  var stm = {}

  var table = RelationalStore.tablename(ent)
  var entp = RelationalStore.makeentp(ent)
  var fields = _.keys(entp)

  var values = []
  var params = []
  // var cnt = 0

  fields.forEach(function (field) {
    if (field.indexOf('$') !== -1) {
      return
    }

    if (!_.isUndefined(entp[field])) {
      values.push(entp[field])
      params.push(RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(field)) + '=?')
    }
  })

  stm.text = 'UPDATE ' + RelationalStore.escapeStr(table) + ' SET ' + params + " WHERE id='" + RelationalStore.escapeStr(ent.id) + "'"
  stm.values = values

  return stm
}

function deletestm (qent, q) {
  var stm = {}

  var table = RelationalStore.tablename(qent)
  var entp = RelationalStore.makeentp(qent)

  var values = []
  var params = []

  // var cnt = 0

  var w = whereargs(entp, q)

  var wherestr = ''

  if (!_.isEmpty(w) && w.params.length > 0) {
    for (var i in w.params) {
      var param = w.params[i]
      var val = w.values[i]

      if (param.indexOf('$') !== -1) {
        continue
      }

      params.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase(param)) + '`=?')
      values.push(RelationalStore.escapeStr(val))
    }

    if (params.length > 0) {
      wherestr = ' WHERE ' + params.join(' AND ')
    }
    else {
      wherestr = ' '
    }
  }

  stm.text = 'DELETE FROM ' + RelationalStore.escapeStr(table) + wherestr
  stm.values = values

  return stm
}

function selectstm (qent, q, done) {
  var specialOps = ['fields$']
  var specialOpsVal = {}

  var stm = {}

  for (var i in specialOps) {
    if (q[specialOps[i]]) {
      specialOpsVal[specialOps[i]] = q[specialOps[i]]
      delete q[specialOps[i]]
    }
  }

  var table = RelationalStore.tablename(qent)
  var entp = RelationalStore.makeentp(qent)

  var w = whereargs(entp, q)

  var response = buildQueryFromExpression(entp, w)
  if (response.err) {
    return done(response.err)
  }

  var wherestr = response.data

  var values = response.values

  var mq = metaquery(qent, q)

  var metastr = ' ' + mq.params.join(' ')

  var what = '*'
  if (specialOpsVal['fields$'] && _.isArray(specialOpsVal['fields$']) && specialOpsVal['fields$'].length > 0) {
    what = ' ' + specialOpsVal['fields$'].join(', ')
    what += ', id '
  }

  stm.text = 'SELECT ' + what + ' FROM ' + RelationalStore.escapeStr(table) + (wherestr ? ' WHERE ' + wherestr : '') + RelationalStore.escapeStr(metastr)
  stm.values = values

  done(null, stm)
}

function selectstmOr (qent, q) {
  var stm = {}

  var table = RelationalStore.tablename(qent)
  var entp = RelationalStore.makeentp(qent)

  var values = []
  var params = []

  // var cnt = 0

  var w = whereargs(entp, q.ids)

  var wherestr = ''

  if (!_.isEmpty(w) && w.params.length > 0) {
    w.params.forEach(function (param) {
      params.push('`' + RelationalStore.escapeStr(RelationalStore.camelToSnakeCase('id')) + '`=')
    })

    w.values.forEach(function (value) {
      values.push(value)
    })

    wherestr = ' WHERE ' + params.join(' OR ')
  }

  // This is required to set the limit$ to be the length of the 'ids' array, so that in situations
  // when it's not set in the query(q) it won't be applied the default limit$ of 20 records
  if (!q.limit$) {
    q.limit$ = q.ids.length
  }

  var mq = metaquery(qent, q)

  var metastr = ' ' + mq.params.join(' ')

  stm.text = 'SELECT * FROM ' + RelationalStore.escapeStr(table) + wherestr + RelationalStore.escapeStr(metastr)
  stm.values = values

  return stm
}

function metaquery (qent, q) {
  var mq = {}

  mq.params = []
  mq.values = []

  if (q.sort$) {
    for (var sf in q.sort$) break
    var sd = q.sort$[sf] > 0 ? 'ASC' : 'DESC'
    mq.params.push('ORDER BY ' + RelationalStore.camelToSnakeCase(sf) + ' ' + sd)
  }

  if (q.limit$) {
    mq.params.push('LIMIT ' + q.limit$)
  }

  if (q.skip$) {
    mq.params.push('OFFSET ' + q.skip$)
  }

  return mq
}

module.exports.fixPrepStatement = fixPrepStatement
module.exports.selectstm = selectstm
module.exports.selectstmOr = selectstmOr
module.exports.deletestm = deletestm
module.exports.savestm = savestm
module.exports.updatestm = updatestm
<|endoftext|>


<commit_before>import RunConnection from './connection/run-connection';
import LocalValueStorage from './local-value-storage';

// Create a copy of an error with a header appended to its message.
function errorWithHeader(e, header) {
  const err = new Error(e.message ? `${header}: ${e.message}` : header);
  err.original = e;
  err.stack = e.stack;
  if (e.type) err.type = e.type;
  return err;
}

// Return a function that will throw an error after appending a header.
function throwWithHeader(header) {
  return e => {
    throw errorWithHeader(e, header);
  };
}

/* eslint-disable no-await-in-loop */
/**
 * Run the trials of an experiment.
 * @param  {Object}  connection  The connection to the run on the xp server.
 * @param  {Object}  app  The app of the experiment.
 * @param  {int}  queueSize  Max number of pending trial result posts before starting a new trial.
 * @return {Promise}  A promise resolved when all trials have run.
 */
export async function runTrials(connection, app, queueSize) {
  // Init the loop.
  let trial = await connection
    .getCurrentTrial()
    .catch(throwWithHeader('Could not retrieve current trial info'));
  let block;

  while (trial) {
    // If the block has changed, init the new one.
    if (block !== trial.block) {
      block = trial.block;
      await Promise.resolve(app.initBlock && app.initBlock(block)).catch(
        throwWithHeader('Could not init block')
      );
    }

    // Make sure there is less than queueSize pending trial result posts
    // before starting the trial.
    await connection.flush(queueSize);

    // Run the trial and fetch the results.
    const results = await app.runTrial(trial);
    // Post the results.
    connection
      .endCurrentTrial(results)
      .catch(throwWithHeader('Could not register trial log'));

    // Fetch the next trial (resolve with undefined if there is no more trials).
    trial = await connection
      .getCurrentTrial()
      .catch(throwWithHeader('Could not retrieve current trial info'));
  }
  // Fully flush the post queue.
  await connection.flush();
}
/* eslint-enable no-await-in-loop */

/**
 * Run an experiment.
 * @param {Object} app The application of the experiment.
 * @param {function(): Promise} [app.start] Initialize the experiment.
 * @param {function(): Promise} [app.initRun] Initialize the run.
 * @param {function(): Promise} [app.initBlock] Initialize a block.
 * @param {function(): Promise} app.runTrial Run a trial.
 * @param {function(): Promise} [app.end] Notify that the experiment is finished.
 * @param {Object} config Configuration.
 * @param {string} config.experimentId The id of the experiment.
 *                                     This is required is config.connection is not provided.
 * @param {string} [config.runId] The id of a run to connect to.
 * @param {string} [config.serverAddress] The address of the xp server.
 * @param {string} [config.experimentDesignAddr] The path toward a touchstone
 *                                               experiment design xml file.
 * @param {Object} [config.runStorage] Used to store the running run.
 * @param {Object} [config.connection] The connection to the server. You do not usually need to
 *                                     provide this.
 */
export async function runExperiment(
  app,
  {
    experimentId,
    runId,
    serverAddress,
    experimentDesignAddr,
    queueSize = 1,
    // Test arguments.
    runStorage = new LocalValueStorage(`${experimentId}/running-run-id`),
    connection: potentialConnection = RunConnection.create(
      serverAddress || `http://${window.location.hostname}:5000`,
      experimentId,
      runId || runStorage.get(),
      experimentDesignAddr
    )
  }
) {
  let run_;
  try {
    // Returns a promise that inits the connection and register the run.
    const initConnection = async () => {
      const connection = await potentialConnection;
      const run = await connection.getRun();
      runStorage.set(run.id);
      return { connection, run };
    };
    // Connect to the run and start the app.
    const [{ connection, run }] = await Promise.all([
      initConnection().catch(
        throwWithHeader('Could not connect to the experiment')
      ),
      // Start the experiment app.
      Promise.resolve(app.start && app.start()).catch(
        throwWithHeader('Could not init the experiment task')
      )
    ]);
    // Export the run out of the context for the catch clause.
    run_ = run;
    // Ask the app to init the run.
    await Promise.resolve(app.initRun && app.initRun(run)).catch(
      throwWithHeader('Could not init the run task')
    );
    // Run the trials.
    await runTrials(connection, app, queueSize);
    // Clear the local storage so that next time the page is loaded, a new run will be requested.
    runStorage.remove();
    // Disconnect from the server.
    await connection.disconnect();
    // Notify the app that the experiment is finished.
    await app.end();
  } catch (e) {
    console.error(e, e.stack);
    app.crash(e.message, e, run_);
  }
}

export default runExperiment;
<commit_msg>Cosmetics.<commit_after>import RunConnection from './connection/run-connection';
import LocalValueStorage from './local-value-storage';

// Create a copy of an error with a header appended to its message.
function errorWithHeader(e, header) {
  const err = new Error(e.message ? `${header}: ${e.message}` : header);
  err.original = e;
  err.stack = e.stack;
  if (e.type) err.type = e.type;
  return err;
}

// Return a function that will throw an error after appending a header.
function throwWithHeader(header) {
  return e => {
    throw errorWithHeader(e, header);
  };
}

/**
 * Run the trials of an experiment.
 * @param  {Object}  connection  The connection to the run on the xp server.
 * @param  {Object}  app  The app of the experiment.
 * @param  {int}  queueSize  Max number of pending trial result posts before starting a new trial.
 * @return {Promise}  A promise resolved when all trials have run.
 */
export async function runTrials(connection, app, queueSize) {
  // Init the loop.
  let trial = await connection
    .getCurrentTrial()
    .catch(throwWithHeader('Could not retrieve current trial info'));
  let block;

  /* eslint-disable no-await-in-loop */
  while (trial) {
    // If the block has changed, init the new one.
    if (block !== trial.block) {
      block = trial.block;
      await Promise.resolve(app.initBlock && app.initBlock(block)).catch(
        throwWithHeader('Could not init block')
      );
    }

    // Make sure there is less than queueSize pending trial result posts
    // before starting the trial.
    await connection.flush(queueSize);

    // Run the trial and fetch the results.
    const results = await app.runTrial(trial);
    // Post the results.
    connection
      .endCurrentTrial(results)
      .catch(throwWithHeader('Could not register trial log'));

    // Fetch the next trial (resolve with undefined if there is no more trials).
    trial = await connection
      .getCurrentTrial()
      .catch(throwWithHeader('Could not retrieve current trial info'));
  }
  /* eslint-enable no-await-in-loop */

  // Fully flush the post queue.
  await connection.flush();
}

/**
 * Run an experiment.
 * @param {Object} app The application of the experiment.
 * @param {function(): Promise} [app.start] Initialize the experiment.
 * @param {function(): Promise} [app.initRun] Initialize the run.
 * @param {function(): Promise} [app.initBlock] Initialize a block.
 * @param {function(): Promise} app.runTrial Run a trial.
 * @param {function(): Promise} [app.end] Notify that the experiment is finished.
 * @param {Object} config Configuration.
 * @param {string} config.experimentId The id of the experiment.
 *                                     This is required is config.connection is not provided.
 * @param {string} [config.runId] The id of a run to connect to.
 * @param {string} [config.serverAddress] The address of the xp server.
 * @param {string} [config.experimentDesignAddr] The path toward a touchstone
 *                                               experiment design xml file.
 * @param {Object} [config.runStorage] Used to store the running run.
 * @param {Object} [config.connection] The connection to the server. You do not usually need to
 *                                     provide this.
 */
export async function runExperiment(
  app,
  {
    experimentId,
    runId,
    serverAddress,
    experimentDesignAddr,
    queueSize = 1,
    // Test arguments.
    runStorage = new LocalValueStorage(`${experimentId}/running-run-id`),
    connection: potentialConnection = RunConnection.create(
      serverAddress || `http://${window.location.hostname}:5000`,
      experimentId,
      runId || runStorage.get(),
      experimentDesignAddr
    )
  }
) {
  let run_;
  try {
    // Returns a promise that inits the connection and register the run.
    const initConnection = async () => {
      const connection = await potentialConnection;
      const run = await connection.getRun();
      runStorage.set(run.id);
      return { connection, run };
    };
    // Connect to the run and start the app.
    const [{ connection, run }] = await Promise.all([
      initConnection().catch(
        throwWithHeader('Could not connect to the experiment')
      ),
      // Start the experiment app.
      Promise.resolve(app.start && app.start()).catch(
        throwWithHeader('Could not init the experiment task')
      )
    ]);
    // Export the run out of the context for the catch clause.
    run_ = run;
    // Ask the app to init the run.
    await Promise.resolve(app.initRun && app.initRun(run)).catch(
      throwWithHeader('Could not init the run task')
    );
    // Run the trials.
    await runTrials(connection, app, queueSize);
    // Clear the local storage so that next time the page is loaded, a new run will be requested.
    runStorage.remove();
    // Disconnect from the server.
    await connection.disconnect();
    // Notify the app that the experiment is finished.
    await app.end();
  } catch (e) {
    console.error(e, e.stack);
    app.crash(e.message, e, run_);
  }
}

export default runExperiment;
<|endoftext|>


<commit_before>/* global Demo */

// ------------------------------------------------------------------
//
// Rendering function for a /Components/Camera object.
//
// ------------------------------------------------------------------
Demo.renderer.Camera = (function(core) {
	'use strict';
	var that = {};

	that.render = function(camera) {
		//
		// Draw a tiny little circle a the position of the camera
		core.drawCircle('rgba(255, 255, 0, 1)', camera.position, 0.005);
		//
		// Draw the left, right, and far plane line segments
		core.drawLine('rgba(255, 255, 0, 1)', camera.position, camera.frustum.leftPoint);
		core.drawLine('rgba(255, 255, 0, 1)', camera.position, camera.frustum.rightPoint);
		core.drawLine('rgba(255, 255, 0, 1)', camera.frustum.leftPoint, camera.frustum.rightPoint);

		//
		// Draw the bounding circle
		core.drawCircle(
			'rgba(255, 255, 0, 1)',
			camera.boundingCircle.center,
			camera.boundingCircle.radius);
		//
		// Draw a tiny circle to show the bounding circle center
		core.drawCircle(
			'rgba(255, 255, 0, 1)',
			camera.boundingCircle.center,
			0.005);
	};

	return that;
}(Demo.renderer.core));
<commit_msg>Hide the drawing of the bounding circle from the camera.<commit_after>/* global Demo */

// ------------------------------------------------------------------
//
// Rendering function for a /Components/Camera object.
//
// ------------------------------------------------------------------
Demo.renderer.Camera = (function(core) {
	'use strict';
	var that = {};

	that.render = function(camera) {
		//
		// Draw a tiny little circle a the position of the camera
		core.drawCircle('rgba(255, 255, 0, 1)', camera.position, 0.005);
		//
		// Draw the left, right, and far plane line segments
		core.drawLine('rgba(255, 255, 0, 1)', camera.position, camera.frustum.leftPoint);
		core.drawLine('rgba(255, 255, 0, 1)', camera.position, camera.frustum.rightPoint);
		core.drawLine('rgba(255, 255, 0, 1)', camera.frustum.leftPoint, camera.frustum.rightPoint);

		//
		// Draw the bounding circle
		// core.drawCircle(
		// 	'rgba(255, 255, 0, 1)',
		// 	camera.boundingCircle.center,
		// 	camera.boundingCircle.radius);
		//
		// Draw a tiny circle to show the bounding circle center
		// core.drawCircle(
		// 	'rgba(255, 255, 0, 1)',
		// 	camera.boundingCircle.center,
		// 	0.005);
	};

	return that;
}(Demo.renderer.core));
<|endoftext|>


<commit_before>
// SmoothScroll v0.9.1
// Licensed under the terms of the MIT license.

// People involved
// - Balazs Galambosi: maintainer (CHANGELOG.txt)
// - Patrick Brunner (patrickb1991@gmail.com)
// - Michael Herf: Pulse Algorithm
// - Frank Yan: Direction Switching Fixes
// - Ismael Barros: Keyboard Fixes

(function(window){

// Frame Variables
var frame         = false;
var noscrollframe = false;
var lastYOffset   = 0;
var lastXOffset   = 0;

// Scroll Variables (tweakable)
var framerate = 50;  // [Hz]
var animtime  = 400; // [px]
var stepsize  = 120; // [px]

// Pulse (less tweakable)
// ratio of 'tail' to 'acceleration'
var pulseAlgorithm = true;
var pulseScale     = 8;
var pulseNormalize = 1;

// Keyboard Settings
var keyboardsupport = true;
var disableKeyboard = false;
var arrowscroll     = 50; // [px]

// Excluded pages
var exclude = "";

// Arrays of timeouts
var up   = [];
var down = [];

// Other Variables
var scrolls;
var deltaX = 0;
var deltaY = 0;
var initdone = false;
var lastScrollTop  = 1337; // ad-hoc
var lastScrollLeft = 1337; // ad-hoc

var key = { up: 38, down: 40, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36 };

/***********************************************
 * SETTINGS
 ***********************************************/

var port = chrome.extension.connect({
    name: "smoothscroll"
});
port.onMessage.addListener(function (settings) {

    // NOTE: + converts to {Number}
    framerate = +settings.framerate;
    animtime  = +settings.animtime;
    stepsize  = +settings.scrollsz;
    exclude   = settings.exclude;
    pulseAlgorithm  = (settings.pulseAlgorithm == "true");
    pulseScale      = +settings.pulseScale;
    keyboardsupport = (settings.keyboardsupport == "true");
    arrowscroll     = +settings.arrscroll;

    scrolls = setupScrolls();
    computePulseScale();

    // it seems that sometimes settings come late
    // and we need to test again for excluded pages
    initTest();

    if (keyboardsupport && !disableKeyboard) {
        document.onkeydown = keydown;
    }

    // If extension settings were deleted somehow
    if (!framerate) {
        alert("SmoothScroll: Please restart Chrome");
    }
});

/***********************************************
 * INITIALIZE
 ***********************************************/

/**
 * Tests if smooth scrolling is allowed. Shuts down everything if not.
 */
function initTest() {

    // disable everything by default for built-in PDF Reader,
    // and keyboard support for google reader (spacebar conflict)
    var embed = document.getElementsByTagName('embed')[0];
    if (embed && embed.type === "application/pdf") {
        window.removeEventListener("mousewheel", wheel, false);
        disableKeyboard = true;
    } else if (document.URL.indexOf("google.com/reader/view") > -1) {
        disableKeyboard = true;
    }

    // disable everything if the page is blacklisted
    if (exclude) {
        var domains = exclude.split(/[,\n] ?/);
        for (var i = domains.length; i--;) {
            if (document.URL.indexOf(domains[i]) > -1) {
                window.removeEventListener("mousewheel", wheel, false);
                disableKeyboard = true;
                break;
            }
        }
    }

    // disable keyboard support if anything above requested it
    if (disableKeyboard) {
        document.onkeydown = null;
    }
}

/**
 * Fills up the scrolls array with easing values.
 * Uses pulse to make it smooth.
 */
function setupScrolls() {

    scrolls = [];

    var last = 0;
    var frames = Math.floor(framerate * animtime / 1000);

    for (var i = 0; i < frames; i++) {
        // scroll is [0, 1]
        var scroll = (i + 1) / frames;
        // transform [0, 1] -> [0, 1]:
        if (pulseAlgorithm) {
           scroll = pulse(scroll);
        }

        // scale and quantize to int so our pixel difference works:
        var iscroll = Math.floor(stepsize * scroll + 0.99);
        scrolls.push(iscroll - last);
        last = iscroll;
    }

    return scrolls;
}

/**
 * Sets up scrolls array, determines if frames are involved.
 */
function init() {

    var body = document.body;

    initTest();

    if (!scrolls) {
      scrolls = setupScrolls();
    }

    // Checks if this script is running in a frame
    if (top != self) {
        frame = true;
        // document.documentElement ?
        if (body.scrollHeight <= body.clientHeight + 10) {
            noscrollframe = true;
        }
    }

    /**
     * This fixes a bug where the areas left and right
     * to the content does not trigger the onmousewheel
     * event on some pages. Acid3 test is excluded.
     */
    if (document.URL !== "http://acid3.acidtests.org/") {
        var underlay = document.createElement('div');
        underlay.setAttribute( "style",
            "z-index: -1; position:absolute; top:0; left: 0; " +
            "width: 100%; height: " + body.scrollHeight + "px;" );
        body.appendChild(underlay);
    }
    initdone = true;
}

/***********************************************
 * EVENTS
 ***********************************************/

/**
 * Mouse wheel handler.
 * NOTE: function logic should be decomposed.
 * @param {Object} event
 */
function wheel(event) {

    if (!initdone) {
        init();
    }

    var scroll = true;
    var prevent = false;
    var scrollup = true;
    var scrolldown = true;
    var lastdeltaY = deltaY;
    var i, overflow, delay, elem, dirX, dirY;

    deltaX = event.wheelDeltaX || 0;
    deltaY = event.wheelDeltaY || 0;

    // normalize deltas
    if (deltaX) {
        deltaX /= 120;
        // synaptics seems to send 1 sometimes,
        // and 120 other times (fix)
        if (Math.abs(deltaX) < 0.01) {
            deltaX *= 120;
        }
    }
    if (deltaY) {
        deltaY /= 120;
        if (Math.abs(deltaY) < 0.01) {
            deltaY *= 120;
        }
    }

    dir = (deltaY > 0) ? up : down;
    elem = overflowingAncestor(event.target);

    if (!elem) {
        return true;
    }

    if (elem === document.body) {
        scroll = true;
    } else { // other overflowing element
        prevent = true;
        if (elem.scrollTop === lastScrollTop) {
            if (elem.scrollTop === 0) {
                scrollup   = true;
                scrolldown = false;
            } else {
                scrolldown = true;
                scrollup   = false;
            }
        } else {
            scroll = false;
        }
        lastScrollTop = elem.scrollTop;
        scrollElement(elem, deltaY, 1); // Fixes a bug
        clearTimeouts(dir === up ? down : up);
        for (i = 0; i < 10; i++) {
            delay = i * 1000 / framerate + 1;
            dir.push(setTimeout(function() {
                scrollElement(elem, deltaY, 10);
            }, delay));
        }
    }

    if (frame) {
        if (noscrollframe) {
            scroll = false;
        } else {
            // the last scroll did nothing
            if (lastYOffset === window.pageYOffset) {
                // scrolling downwards did nothing
                if (lastdeltaY < 0) {
                    scrollup   = true;
                    scrolldown = false;
                }
                // scrolling upwards did nothing
                else if (lastdeltaY > 0) {
                    scrollup   = false;
                    scrolldown = true;
                }
            }
            lastYOffset = window.pageYOffset;
        }
    }
    if (scroll) {
        if ((scrolldown && deltaY < 0) || (scrollup && deltaY > 0)) {
            scrollArray(dir, -deltaX, -deltaY);
            event.preventDefault();
        }
    }
    // Prevention for scrollable html elements
    if (prevent) {
        event.preventDefault();
    }
}

/**
 * Keydown event handler.
 * @param {Object} event
 */
function keydown(event) {

    var target   = event.target;
    var modifier = event.ctrlKey || event.altKey || event.metaKey;

    // do nothing if user is editing text
    // or using a modifier key (except shift)
    if ( isNodeName(target, "input")    ||
         isNodeName(target, "textarea") ||
         target.isContentEditable       ||
         modifier ) {
      return true;
    }
    // spacebar should trigger button press
    if (isNodeName(target, "button") &&
        event.keyCode === key.spacebar) {
      return true;
    }

    var scale, dir, shift;

    switch (event.keyCode) {
        case key.up:
            scale = -arrowscroll;
            dir = up;
            break;
        case key.down:
            scale = arrowscroll;
            dir = down;
            break;
        case key.spacebar: // (+ shift)
            shift = event.shiftKey ? 1 : -1;
            scale = -shift * window.innerHeight * 0.9;
            dir = (shift > 0) ? up : down;
            break;
        case key.pageup:
            scale = -window.innerHeight * 0.9;
            dir = up;
            break;
        case key.pagedown:
            scale = window.innerHeight * 0.9;
            dir = down;
            break;
        case key.home:
            scale = -target.scrollTop;
            dir = up;
            break;
        case key.end:
            var damt = target.scrollHeight - target.scrollTop - window.innerHeight;
            scale = (damt > 0) ? damt : 0;
            dir = down;
            break;
        default:
            return true; // a key we don't care about
    }
    scale /= stepsize;
    scale = (scale > 0) ? Math.ceil(scale) : Math.floor(scale);
    scrollArray(dir, 0, scale, 1000);
    event.preventDefault();
}

/***********************************************
 * HELPERS
 ***********************************************/

function isNodeName(el, tag) {
    return el.nodeName.toLowerCase() === tag.toLowerCase();
}

function clearTimeouts(array) {
    while (array.length) {
        clearTimeout(array.pop());
    }
}

function overflowingAncestor(el) {
    var bodyScrollHeight = document.body.scrollHeight;
    do {
        if (bodyScrollHeight === el.scrollHeight) {
            return document.body;
        }
        else if (el.clientHeight + 10 < el.scrollHeight) {
            overflow = getComputedStyle(el, "").getPropertyValue("overflow");
            if (overflow === "scroll" || overflow === "auto") {
                return el;
            }
        }
    } while (el = el.parentNode);
}

/***********************************************
 * PULSE
 ***********************************************/

/**
 * Viscous fluid with a pulse for part and decay for the rest.
 * - Applies a fixed force over an interval (a damped acceleration), and
 * - Lets the exponential bleed away the velocity over a longer interval
 * - Michael Herf, http://stereopsis.com/stopping/
 */
function pulse_(x) {
    var val, start, expx;
    // test
    x = x * pulseScale;
    if (x < 1) { // acceleartion
        val = x - (1 - Math.exp(-x));
    } else {     // tail
        // the previous animation ended here:
        start = Math.exp(-1);
        // simple viscous drag
        x -= 1;
        expx = 1 - Math.exp(-x);
        val = start + (expx * (1 - start));
    }
    return val * pulseNormalize;
}

function computePulseScale() {
    pulseNormalize = 1 / pulse_(1);
}

/**
 * Viscous fluid with a pulse for part and decay for the rest
 */
function pulse(x) {
    if (x >= 1) return 1;
    if (x <= 0) return 0;

    if (pulseNormalize == 1) {
        computePulseScale();
    }
    return pulse_(x);
}

/************************************************
 * SCROLLING
 ************************************************/

/**
 * Scrolls an element by a given amount.
 */
function scrollElement(el, delta, amount) {
    if (delta > 0) {
        el.scrollTop -= amount;
    } else {
        el.scrollTop += amount;
    }
}

/**
 * Pushes scroll actions to a given direction Array.
 */
function scrollArray(dir, multiplyX, multiplyY, delay) {
    delay || (delay = 1000);
    clearTimeouts(dir === up ? down : up);
    function step() {
        var scale = scrolls[i++]; // linear or pulse
        window.scrollBy( multiplyX * scale, multiplyY * scale );
    }
    for (var i = scrolls.length; i--;) {
        dir.push(setTimeout(step, i * delay / framerate + 1));
    }
    i = 0; // reset so that step() can increment again
}

window.addEventListener("mousewheel", wheel, false);
window.addEventListener("DOMContentLoaded", init, false);

})(window);
<commit_msg>simplified delta normalization<commit_after>
// SmoothScroll v0.9.1
// Licensed under the terms of the MIT license.

// People involved
// - Balazs Galambosi: maintainer (CHANGELOG.txt)
// - Patrick Brunner (patrickb1991@gmail.com)
// - Michael Herf: Pulse Algorithm
// - Frank Yan: Direction Switching Fixes
// - Ismael Barros: Keyboard Fixes

(function(window){

// Frame Variables
var frame         = false;
var noscrollframe = false;
var lastYOffset   = 0;
var lastXOffset   = 0;

// Scroll Variables (tweakable)
var framerate = 50;  // [Hz]
var animtime  = 400; // [px]
var stepsize  = 120; // [px]

// Pulse (less tweakable)
// ratio of 'tail' to 'acceleration'
var pulseAlgorithm = true;
var pulseScale     = 8;
var pulseNormalize = 1;

// Keyboard Settings
var keyboardsupport = true;
var disableKeyboard = false;
var arrowscroll     = 50; // [px]

// Excluded pages
var exclude = "";

// Arrays of timeouts
var up   = [];
var down = [];

// Other Variables
var scrolls;
var deltaX = 0;
var deltaY = 0;
var initdone = false;
var lastScrollTop  = 1337; // ad-hoc
var lastScrollLeft = 1337; // ad-hoc

var key = { up: 38, down: 40, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36 };

/***********************************************
 * SETTINGS
 ***********************************************/

var port = chrome.extension.connect({
    name: "smoothscroll"
});
port.onMessage.addListener(function (settings) {

    // NOTE: + converts to {Number}
    framerate = +settings.framerate;
    animtime  = +settings.animtime;
    stepsize  = +settings.scrollsz;
    exclude   = settings.exclude;
    pulseAlgorithm  = (settings.pulseAlgorithm == "true");
    pulseScale      = +settings.pulseScale;
    keyboardsupport = (settings.keyboardsupport == "true");
    arrowscroll     = +settings.arrscroll;

    scrolls = setupScrolls();
    computePulseScale();

    // it seems that sometimes settings come late
    // and we need to test again for excluded pages
    initTest();

    if (keyboardsupport && !disableKeyboard) {
        document.onkeydown = keydown;
    }

    // If extension settings were deleted somehow
    if (!framerate) {
        alert("SmoothScroll: Please restart Chrome");
    }
});

/***********************************************
 * INITIALIZE
 ***********************************************/

/**
 * Tests if smooth scrolling is allowed. Shuts down everything if not.
 */
function initTest() {

    // disable everything by default for built-in PDF Reader,
    // and keyboard support for google reader (spacebar conflict)
    var embed = document.getElementsByTagName('embed')[0];
    if (embed && embed.type === "application/pdf") {
        window.removeEventListener("mousewheel", wheel, false);
        disableKeyboard = true;
    } else if (document.URL.indexOf("google.com/reader/view") > -1) {
        disableKeyboard = true;
    }

    // disable everything if the page is blacklisted
    if (exclude) {
        var domains = exclude.split(/[,\n] ?/);
        for (var i = domains.length; i--;) {
            if (document.URL.indexOf(domains[i]) > -1) {
                window.removeEventListener("mousewheel", wheel, false);
                disableKeyboard = true;
                break;
            }
        }
    }

    // disable keyboard support if anything above requested it
    if (disableKeyboard) {
        document.onkeydown = null;
    }
}

/**
 * Fills up the scrolls array with easing values.
 * Uses pulse to make it smooth.
 */
function setupScrolls() {

    scrolls = [];

    var last = 0;
    var frames = Math.floor(framerate * animtime / 1000);

    for (var i = 0; i < frames; i++) {
        // scroll is [0, 1]
        var scroll = (i + 1) / frames;
        // transform [0, 1] -> [0, 1]:
        if (pulseAlgorithm) {
           scroll = pulse(scroll);
        }

        // scale and quantize to int so our pixel difference works:
        var iscroll = Math.floor(stepsize * scroll + 0.99);
        scrolls.push(iscroll - last);
        last = iscroll;
    }

    return scrolls;
}

/**
 * Sets up scrolls array, determines if frames are involved.
 */
function init() {

    var body = document.body;

    initTest();

    if (!scrolls) {
      scrolls = setupScrolls();
    }

    // Checks if this script is running in a frame
    if (top != self) {
        frame = true;
        // document.documentElement ?
        if (body.scrollHeight <= body.clientHeight + 10) {
            noscrollframe = true;
        }
    }

    /**
     * This fixes a bug where the areas left and right
     * to the content does not trigger the onmousewheel
     * event on some pages. Acid3 test is excluded.
     */
    if (document.URL !== "http://acid3.acidtests.org/") {
        var underlay = document.createElement('div');
        underlay.setAttribute( "style",
            "z-index: -1; position:absolute; top:0; left: 0; " +
            "width: 100%; height: " + body.scrollHeight + "px;" );
        body.appendChild(underlay);
    }
    initdone = true;
}

/***********************************************
 * EVENTS
 ***********************************************/

/**
 * Mouse wheel handler.
 * NOTE: function logic should be decomposed.
 * @param {Object} event
 */
function wheel(event) {

    if (!initdone) {
        init();
    }

    var scroll = true;
    var prevent = false;
    var scrollup = true;
    var scrolldown = true;
    var lastdeltaY = deltaY;
    var i, overflow, delay, elem, dirX, dirY;

    deltaX = event.wheelDeltaX || 0;
    deltaY = event.wheelDeltaY || 0;

    // normalize deltas
    deltaX /= 120;
    deltaY /= 120;
    // synaptics seems to send 1 sometimes,
    // and 120 other times (fix)
    if (Math.abs(deltaX) < 0.01) {
        deltaX *= 120;
    }
    if (Math.abs(deltaY) < 0.01) {
        deltaY *= 120;
    }

    dir = (deltaY > 0) ? up : down;
    elem = overflowingAncestor(event.target);

    if (!elem) {
        return true;
    }

    if (elem === document.body) {
        scroll = true;
    } else { // other overflowing element
        prevent = true;
        if (elem.scrollTop === lastScrollTop) {
            if (elem.scrollTop === 0) {
                scrollup   = true;
                scrolldown = false;
            } else {
                scrolldown = true;
                scrollup   = false;
            }
        } else {
            scroll = false;
        }
        lastScrollTop = elem.scrollTop;
        scrollElement(elem, deltaY, 1); // Fixes a bug
        clearTimeouts(dir === up ? down : up);
        for (i = 0; i < 10; i++) {
            delay = i * 1000 / framerate + 1;
            dir.push(setTimeout(function() {
                scrollElement(elem, deltaY, 10);
            }, delay));
        }
    }

    if (frame) {
        if (noscrollframe) {
            scroll = false;
        } else {
            // the last scroll did nothing
            if (lastYOffset === window.pageYOffset) {
                // scrolling downwards did nothing
                if (lastdeltaY < 0) {
                    scrollup   = true;
                    scrolldown = false;
                }
                // scrolling upwards did nothing
                else if (lastdeltaY > 0) {
                    scrollup   = false;
                    scrolldown = true;
                }
            }
            lastYOffset = window.pageYOffset;
        }
    }
    if (scroll) {
        if ((scrolldown && deltaY < 0) || (scrollup && deltaY > 0)) {
            scrollArray(dir, -deltaX, -deltaY);
            event.preventDefault();
        }
    }
    // Prevention for scrollable html elements
    if (prevent) {
        event.preventDefault();
    }
}

/**
 * Keydown event handler.
 * @param {Object} event
 */
function keydown(event) {

    var target   = event.target;
    var modifier = event.ctrlKey || event.altKey || event.metaKey;

    // do nothing if user is editing text
    // or using a modifier key (except shift)
    if ( isNodeName(target, "input")    ||
         isNodeName(target, "textarea") ||
         target.isContentEditable       ||
         modifier ) {
      return true;
    }
    // spacebar should trigger button press
    if (isNodeName(target, "button") &&
        event.keyCode === key.spacebar) {
      return true;
    }

    var scale, dir, shift;

    switch (event.keyCode) {
        case key.up:
            scale = -arrowscroll;
            dir = up;
            break;
        case key.down:
            scale = arrowscroll;
            dir = down;
            break;
        case key.spacebar: // (+ shift)
            shift = event.shiftKey ? 1 : -1;
            scale = -shift * window.innerHeight * 0.9;
            dir = (shift > 0) ? up : down;
            break;
        case key.pageup:
            scale = -window.innerHeight * 0.9;
            dir = up;
            break;
        case key.pagedown:
            scale = window.innerHeight * 0.9;
            dir = down;
            break;
        case key.home:
            scale = -target.scrollTop;
            dir = up;
            break;
        case key.end:
            var damt = target.scrollHeight - target.scrollTop - window.innerHeight;
            scale = (damt > 0) ? damt : 0;
            dir = down;
            break;
        default:
            return true; // a key we don't care about
    }
    scale /= stepsize;
    scale = (scale > 0) ? Math.ceil(scale) : Math.floor(scale);
    scrollArray(dir, 0, scale, 1000);
    event.preventDefault();
}

/***********************************************
 * HELPERS
 ***********************************************/

function isNodeName(el, tag) {
    return el.nodeName.toLowerCase() === tag.toLowerCase();
}

function clearTimeouts(array) {
    while (array.length) {
        clearTimeout(array.pop());
    }
}

function overflowingAncestor(el) {
    var bodyScrollHeight = document.body.scrollHeight;
    do {
        if (bodyScrollHeight === el.scrollHeight) {
            return document.body;
        }
        else if (el.clientHeight + 10 < el.scrollHeight) {
            overflow = getComputedStyle(el, "").getPropertyValue("overflow");
            if (overflow === "scroll" || overflow === "auto") {
                return el;
            }
        }
    } while (el = el.parentNode);
}

/***********************************************
 * PULSE
 ***********************************************/

/**
 * Viscous fluid with a pulse for part and decay for the rest.
 * - Applies a fixed force over an interval (a damped acceleration), and
 * - Lets the exponential bleed away the velocity over a longer interval
 * - Michael Herf, http://stereopsis.com/stopping/
 */
function pulse_(x) {
    var val, start, expx;
    // test
    x = x * pulseScale;
    if (x < 1) { // acceleartion
        val = x - (1 - Math.exp(-x));
    } else {     // tail
        // the previous animation ended here:
        start = Math.exp(-1);
        // simple viscous drag
        x -= 1;
        expx = 1 - Math.exp(-x);
        val = start + (expx * (1 - start));
    }
    return val * pulseNormalize;
}

function computePulseScale() {
    pulseNormalize = 1 / pulse_(1);
}

/**
 * Viscous fluid with a pulse for part and decay for the rest
 */
function pulse(x) {
    if (x >= 1) return 1;
    if (x <= 0) return 0;

    if (pulseNormalize == 1) {
        computePulseScale();
    }
    return pulse_(x);
}

/************************************************
 * SCROLLING
 ************************************************/

/**
 * Scrolls an element by a given amount.
 */
function scrollElement(el, delta, amount) {
    if (delta > 0) {
        el.scrollTop -= amount;
    } else {
        el.scrollTop += amount;
    }
}

/**
 * Pushes scroll actions to a given direction Array.
 */
function scrollArray(dir, multiplyX, multiplyY, delay) {
    delay || (delay = 1000);
    clearTimeouts(dir === up ? down : up);
    function step() {
        var scale = scrolls[i++]; // linear or pulse
        window.scrollBy( multiplyX * scale, multiplyY * scale );
    }
    for (var i = scrolls.length; i--;) {
        dir.push(setTimeout(step, i * delay / framerate + 1));
    }
    i = 0; // reset so that step() can increment again
}

window.addEventListener("mousewheel", wheel, false);
window.addEventListener("DOMContentLoaded", init, false);

})(window);
<|endoftext|>


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

    angular.module('chaise.record')

    .controller('RecordController', ['AlertsService', '$cookies', '$log', 'UriUtils', 'DataUtils', 'ErrorService', 'MathUtils', 'messageMap', '$rootScope', '$window', '$scope', '$uibModal', function RecordController(AlertsService, $cookies, $log, UriUtils, DataUtils, ErrorService, MathUtils, messageMap, $rootScope, $window, $scope, $uibModal) {
        var vm = this;
        var addRecordRequests = {}; // <generated unique id : reference of related table>
        var editRecordRequests = {}; // generated id: {schemaName, tableName}
        var updated = {};
        var completed = {};
        vm.alerts = AlertsService.alerts;
        vm.makeSafeIdAttr = DataUtils.makeSafeIdAttr;

        vm.canCreate = function() {
            return ($rootScope.reference && $rootScope.reference.canCreate && $rootScope.modifyRecord);
        };

        vm.createRecord = function() {
            $window.location.href = $rootScope.reference.table.reference.contextualize.entryCreate.appLink;
        };

        vm.canEdit = function() {
            var canEdit = ($rootScope.reference && $rootScope.reference.canUpdate && $rootScope.modifyRecord);
            // If user can edit this record (canEdit === true), then change showEmptyRelatedTables.
            // Otherwise, canEdit will be undefined, so no need to change anything b/c showEmptyRelatedTables is already false.

            return canEdit;
        };

        vm.editRecord = function() {
            $window.location.href = $rootScope.reference.contextualize.entryEdit.appLink;
        };

        vm.copyRecord = function() {
            $window.location.href = $rootScope.reference.contextualize.entryCreate.appLink + "?copy=true&limit=1";
        };

        vm.canDelete = function() {
            return ($rootScope.reference && $rootScope.reference.canDelete && $rootScope.modifyRecord && $rootScope.showDeleteButton);
        };

        vm.deleteRecord = function() {
            $rootScope.reference.delete([$rootScope.tuple]).then(function deleteSuccess() {
                // Get an appLink from a reference to the table that the existing reference came from
                var unfilteredRefAppLink = $rootScope.reference.table.reference.contextualize.compact.appLink;
                $window.location.href = unfilteredRefAppLink;
            }, function deleteFail(error) {
                throw error;
            });
        };

        vm.permalink = function getPermalink() {
            return $window.location.href;
        };

        vm.toRecordSet = function(ref) {
            return $window.location.href = ref.appLink;
        };

        vm.showRelatedTable = function(i) {
            var isFirst = false, prevTableHasLoaded = false;
            if ($rootScope.tableModels && $rootScope.tableModels[i]) {
                if (i === 0) {
                    $rootScope.lastRendered = 0;
                    isFirst = true;
                } else if ($rootScope.tableModels[i-1]) {
                    prevTableHasLoaded = $rootScope.tableModels[i-1].hasLoaded;
                    if ($rootScope.lastRendered == (i-1)) {
                        $rootScope.lastRendered = i;
                    }
                }

                if ($rootScope.lastRendered == $rootScope.relatedReferences.length-1) {
                    $rootScope.loading = false;
                }

                if ($rootScope.showEmptyRelatedTables) {
                    return isFirst || prevTableHasLoaded;
                }

                if ((isFirst || prevTableHasLoaded) && $rootScope.tableModels[i].rowValues && $rootScope.tableModels[i].rowValues.length > 0) {
                    return (i == $rootScope.lastRendered);
                }
                return false;
            }
        };

        vm.toggleRelatedTableDisplayType = function(dataModel) {
            if (dataModel.displayType == 'markdown') {
                dataModel.displayType = 'table';
            } else {
                dataModel.displayType = 'markdown';
            }
        };

        vm.toggleRelatedTables = function() {
            $rootScope.showEmptyRelatedTables = !$rootScope.showEmptyRelatedTables;
        };

        vm.canEditRelated = function(ref) {
            if(angular.isUndefined(ref))
            return false;
           return (ref.canUpdate && $rootScope.modifyRecord);
        };

        vm.canCreateRelated = function(relatedRef) {
            if(angular.isUndefined(relatedRef))
            return false;
           var ref = (relatedRef.derivedAssociationReference ? relatedRef.derivedAssociationReference : relatedRef);
           return (ref.canCreate && $rootScope.modifyRecord);
        };

        // Send user to RecordEdit to create a new row in this related table
        vm.addRelatedRecord = function(ref) {
            // 1. Pluck required values from the ref into cookie obj by getting the values of the keys that form this FK relationship
            var cookie = {
                rowname: $rootScope.recordDisplayname,
                constraintName: ref.origColumnName
            };
            var mapping = ref.contextualize.entryCreate.origFKR.mapping;

            // Get the column pair that form this FKR between this related table and the main entity
            cookie.keys = {};
            mapping._from.forEach(function(fromColumn, i) {
                var toColumn = mapping._to[i];
                // Assign the column value into cookie
                cookie.keys[fromColumn.name] = $rootScope.tuple.data[toColumn.name];
            });
            // 2. Generate a unique cookie name and set it to expire after 24hrs.
            var COOKIE_NAME = 'recordedit-' + MathUtils.getRandomInt(0, Number.MAX_SAFE_INTEGER);
            $cookies.putObject(COOKIE_NAME, cookie, {
                expires: new Date(Date.now() + (60 * 60 * 24 * 1000))
            });

            // Generate a unique id for this request
            // append it to the URL
            var referrer_id = 'recordedit-' + MathUtils.getRandomInt(0, Number.MAX_SAFE_INTEGER);
            addRecordRequests[referrer_id] = ref.uri;

            // 3. Get appLink, append ?prefill=[COOKIE_NAME]&referrer=[referrer_id]
            var appLink = (ref.derivedAssociationReference ? ref.derivedAssociationReference.contextualize.entryCreate.appLink : ref.contextualize.entryCreate.appLink);
            appLink = appLink + (appLink.indexOf("?") === -1? "?" : "&") +
                'prefill=' + UriUtils.fixedEncodeURIComponent(COOKIE_NAME) +
                '&invalidate=' + UriUtils.fixedEncodeURIComponent(referrer_id);

            // 4. Redirect to the url in a new tab
            $window.open(appLink, '_blank');
        };

        $scope.$on("edit-request", function(event, args) {
            editRecordRequests[args.id] = {"schema": args.schema, "table": args.table};
        });

        /**
        * readUpdatedTable(refObj, dataModel, idx) returns model object with all updated component values
        * @param {object} refObj Reference object with component details
        * @param {object} dataModel Contains value that is bind to the table columns
        * @param {int} idx Index of each reference
        */
        function readUpdatedTable(refObj, dataModel, idx){
            if (completed[refObj.uri] || updated[refObj.location.schemaName + ":" + refObj.location.tableName]) {
                delete updated[refObj.location.schemaName + ":" + refObj.location.tableName];
                (function (i) {
                    refObj.read(dataModel.pageLimit).then(function (page) {
                        dataModel.page = page;
                        dataModel.rowValues = DataUtils.getRowValuesFromPage(page);
                    }, function (error) {
                        console.log(error);
                        throw error;
                    }).catch(function (error) {
                        console.log(error);
                        throw error;
                    });
                })(i);
            }
        }

        // When page gets focus, check cookie for completed requests
        // re-read the records for that table
        $window.onfocus = function() {
            if ($rootScope.loading === false) {
                var idxInbFk;
                completed = { };
                for (var id in addRecordRequests) {
                    var cookie = $cookies.getObject(id);
                    if (cookie) { // add request has been completed
                        console.log('Cookie found', cookie);
                        completed[addRecordRequests[id]] = true;

                        // remove cookie and request
                        $cookies.remove(id);
                        delete addRecordRequests[id];
                    } else {
                        console.log('Could not find cookie', cookie);
                    }
                }
                // read updated tables
                if (Object.keys(completed).length > 0 || updated !== {}) {
                    for (var i = 0; i < $rootScope.inboundFKCols.length; i++) {
                        idxInbFk = $rootScope.inboundFKColsIdx[i];
                        readUpdatedTable($rootScope.inboundFKCols[i].reference, $rootScope.colTableModels[idxInbFk],idxInbFk);
                    }
                    for (var i = 0; i < $rootScope.relatedReferences.length; i++) {
                        readUpdatedTable($rootScope.relatedReferences[i], $rootScope.tableModels[i],i);
                    }
                }
            }

        };

        // function called from form.controller.js to notify record that an entity was just updated
        window.updated = function(id) {
            updated[editRecordRequests[id].schema + ":" + editRecordRequests[id].table] = true;
            delete editRecordRequests[id];
        }
    }]);
})();
<commit_msg>Travis test<commit_after>(function() {
    'use strict';

    angular.module('chaise.record')

    .controller('RecordController', ['AlertsService', '$cookies', '$log', 'UriUtils', 'DataUtils', 'ErrorService', 'MathUtils', 'messageMap', '$rootScope', '$window', '$scope', '$uibModal', function RecordController(AlertsService, $cookies, $log, UriUtils, DataUtils, ErrorService, MathUtils, messageMap, $rootScope, $window, $scope, $uibModal) {
        var vm = this;
        var addRecordRequests = {}; // <generated unique id : reference of related table>
        var editRecordRequests = {}; // generated id: {schemaName, tableName}
        var updated = {};
        var completed = {};
        vm.alerts = AlertsService.alerts;
        vm.makeSafeIdAttr = DataUtils.makeSafeIdAttr;

        vm.canCreate = function() {
            return ($rootScope.reference && $rootScope.reference.canCreate && $rootScope.modifyRecord);
        };

        vm.createRecord = function() {
            $window.location.href = $rootScope.reference.table.reference.contextualize.entryCreate.appLink;
        };

        vm.canEdit = function() {
            var canEdit = ($rootScope.reference && $rootScope.reference.canUpdate && $rootScope.modifyRecord);
            // If user can edit this record (canEdit === true), then change showEmptyRelatedTables.
            // Otherwise, canEdit will be undefined, so no need to change anything b/c showEmptyRelatedTables is already false.

            return canEdit;
        };

        vm.editRecord = function() {
            $window.location.href = $rootScope.reference.contextualize.entryEdit.appLink;
        };

        vm.copyRecord = function() {
            $window.location.href = $rootScope.reference.contextualize.entryCreate.appLink + "?copy=true&limit=1";
        };

        vm.canDelete = function() {
            return ($rootScope.reference && $rootScope.reference.canDelete && $rootScope.modifyRecord && $rootScope.showDeleteButton);
        };

        vm.deleteRecord = function() {
            $rootScope.reference.delete([$rootScope.tuple]).then(function deleteSuccess() {
                // Get an appLink from a reference to the table that the existing reference came from
                var unfilteredRefAppLink = $rootScope.reference.table.reference.contextualize.compact.appLink;
                $window.location.href = unfilteredRefAppLink;
            }, function deleteFail(error) {
                throw error;
            });
        };

        vm.permalink = function getPermalink() {
            return $window.location.href;
        };

        vm.toRecordSet = function(ref) {
            return $window.location.href = ref.appLink;
        };

        vm.showRelatedTable = function(i) {
            var isFirst = false, prevTableHasLoaded = false;
            if ($rootScope.tableModels && $rootScope.tableModels[i]) {
                if (i === 0) {
                    $rootScope.lastRendered = 0;
                    isFirst = true;
                } else if ($rootScope.tableModels[i-1]) {
                    prevTableHasLoaded = $rootScope.tableModels[i-1].hasLoaded;
                    if ($rootScope.lastRendered == (i-1)) {
                        $rootScope.lastRendered = i;
                    }
                }

                if ($rootScope.lastRendered == $rootScope.relatedReferences.length-1) {
                    $rootScope.loading = false;
                }

                if ($rootScope.showEmptyRelatedTables) {
                    return isFirst || prevTableHasLoaded;
                }

                if ((isFirst || prevTableHasLoaded) && $rootScope.tableModels[i].rowValues && $rootScope.tableModels[i].rowValues.length > 0) {
                    return (i == $rootScope.lastRendered);
                }
                return false;
            }
        };

        vm.toggleRelatedTableDisplayType = function(dataModel) {
            if (dataModel.displayType == 'markdown') {
                dataModel.displayType = 'table';
            } else {
                dataModel.displayType = 'markdown';
            }
        };

        vm.toggleRelatedTables = function() {
            $rootScope.showEmptyRelatedTables = !$rootScope.showEmptyRelatedTables;
        };

        vm.canEditRelated = function(ref) {
            if(angular.isUndefined(ref))
            return false;
           return (ref.canUpdate && $rootScope.modifyRecord);
        };

        vm.canCreateRelated = function(relatedRef) {
            if(angular.isUndefined(relatedRef))
            return false;
           var ref = (relatedRef.derivedAssociationReference ? relatedRef.derivedAssociationReference : relatedRef);
           return (ref.canCreate && $rootScope.modifyRecord);
        };

        // Send user to RecordEdit to create a new row in this related table
        vm.addRelatedRecord = function(ref) {
            // 1. Pluck required values from the ref into cookie obj by getting the values of the keys that form this FK relationship
            var cookie = {
                rowname: $rootScope.recordDisplayname,
                constraintName: ref.origColumnName
            };
            var mapping = ref.contextualize.entryCreate.origFKR.mapping;

            // Get the column pair that form this FKR between this related table and the main entity
            cookie.keys = {};
            mapping._from.forEach(function(fromColumn, i) {
                var toColumn = mapping._to[i];
                // Assign the column value into cookie
                cookie.keys[fromColumn.name] = $rootScope.tuple.data[toColumn.name];
            });
            // 2. Generate a unique cookie name and set it to expire after 24hrs.
            var COOKIE_NAME = 'recordedit-' + MathUtils.getRandomInt(0, Number.MAX_SAFE_INTEGER);
            $cookies.putObject(COOKIE_NAME, cookie, {
                expires: new Date(Date.now() + (60 * 60 * 24 * 1000))
            });

            // Generate a unique id for this request
            // append it to the URL
            var referrer_id = 'recordedit-' + MathUtils.getRandomInt(0, Number.MAX_SAFE_INTEGER);
            addRecordRequests[referrer_id] = ref.uri;

            // 3. Get appLink, append ?prefill=[COOKIE_NAME]&referrer=[referrer_id]
            var appLink = (ref.derivedAssociationReference ? ref.derivedAssociationReference.contextualize.entryCreate.appLink : ref.contextualize.entryCreate.appLink);
            appLink = appLink + (appLink.indexOf("?") === -1? "?" : "&") +
                'prefill=' + UriUtils.fixedEncodeURIComponent(COOKIE_NAME) +
                '&invalidate=' + UriUtils.fixedEncodeURIComponent(referrer_id);

            // 4. Redirect to the url in a new tab
            $window.open(appLink, '_blank');
        };

        $scope.$on("edit-request", function(event, args) {
            editRecordRequests[args.id] = {"schema": args.schema, "table": args.table};
        });

        /**
        * readUpdatedTable(refObj, dataModel, idx) returns model object with all updated component values
        * @param {object} refObj Reference object with component details
        * @param {object} dataModel Contains value that is bind to the table columns
        * @param {int} idx Index of each reference
        */
        function readUpdatedTable(refObj, dataModel, idx){
            if (completed[refObj.uri] || updated[refObj.location.schemaName + ":" + refObj.location.tableName]) {
                delete updated[refObj.location.schemaName + ":" + refObj.location.tableName];
                (function (i) {
                    refObj.read(dataModel.pageLimit).then(function (page) {
                        dataModel.page = page;
                        dataModel.rowValues = DataUtils.getRowValuesFromPage(page);
                    }, function (error) {
                        console.log(error);
                        throw error;
                    }).catch(function (error) {
                        console.log(error);
                        throw error;
                    });
                })(i);
            }
        }

        // When page gets focus, check cookie for completed requests
        // re-read the records for that table
        $window.onfocus = function() {
            if ($rootScope.loading === false) {
                var idxInbFk;
                completed = { };
                for (var id in addRecordRequests) {
                    var cookie = $cookies.getObject(id);
                    if (cookie) { // add request has been completed
                        console.log('Cookie found', cookie);
                        completed[addRecordRequests[id]] = true;

                        // remove cookie and request
                        $cookies.remove(id);
                        delete addRecordRequests[id];
                    } else {
                        console.log('Could not find cookie', cookie);
                    }
                }
                // read updated tables
                if (Object.keys(completed).length > 0 || updated !== {}) {
                    for (var i = 0; i < $rootScope.inboundFKCols.length; i++) {
                        idxInbFk = $rootScope.inboundFKColsIdx[i];
                        readUpdatedTable($rootScope.inboundFKCols[i].reference, $rootScope.colTableModels[idxInbFk],idxInbFk);
                    }
                    for (var i = 0; i < $rootScope.relatedReferences.length; i++) {
                        readUpdatedTable($rootScope.relatedReferences[i], $rootScope.tableModels[i],i);
                    }
                }
            }

        };
        // function called from form.controller.js to notify record that an entity was just updated
        window.updated = function(id) {
            updated[editRecordRequests[id].schema + ":" + editRecordRequests[id].table] = true;
            delete editRecordRequests[id];
        }
    }]);
})();
<|endoftext|>