<commit_before>/*jslint es5: true, indent: 2, nomen: true, maxlen: 120, evil: true */
/*global exports, require */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues workers
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var db = require('org/arangodb').db,
  flatten = require('internal').flatten,
  exponentialBackOff = require('internal').exponentialBackOff,
  console = require('console'),
  queues = require('org/arangodb/foxx').queues,
  getBackOffDelay;

getBackOffDelay = function (job, cfg) {
  'use strict';
  var n = job.failures.length - 1;
  if (typeof job.backOff === 'string') {
    try {
      return eval('(' + job.backOff + ')(' + n + ')');
    } catch (jobStrErr) {
      console.error('Failed to call backOff function of job ' + job._key, jobStrErr);
    }
  } else if (typeof job.backOff === 'number' && job.backOff >= 0 && job.backOff < Infinity) {
    return exponentialBackOff(n, job.backOff);
  }
  if (typeof cfg.backOff === 'function') {
    try {
      return cfg.backOff(n);
    } catch (cfgFnErr) {
      console.error('Failed to call backOff function of job type ' + job.type, cfgFnErr);
    }
  } else if (typeof cfg.backOff === 'string') {
    try {
      return eval('(' + cfg.backOff + ')(' + n + ')');
    } catch (cfgStrErr) {
      console.error('Failed to call backOff function of job type ' + job.type, cfgStrErr);
    }
  }
  if (typeof cfg.backOff === 'number' && cfg.backOff >= 0 && cfg.backOff < Infinity) {
    return exponentialBackOff(n, cfg.backOff);
  }
  return exponentialBackOff(n, 1000);
};

exports.work = function (job) {
  'use strict';
  var cfg = queues._jobTypes[job.type],
    success = true,
    callback = null,
    now = Date.now(),
    maxFailures,
    result;

  if (!cfg) {
    console.warn('Unknown job type for job ' + job._key + ':', job.type);
    db._jobs.update(job._key, {status: 'pending'});
    return;
  }

  maxFailures = (
    typeof job.maxFailures === 'number'
      ? (job.maxFailures < 0 ? Infinity : job.maxFailures)
      : (cfg.maxFailures < 0 ? Infinity : cfg.maxFailures)
  ) || 0;

  try {
    result = cfg.execute(job.data, job._id);
  } catch (executeErr) {
    console.error('Job ' + job._key + ' failed:', executeErr);
    job.failures.push(flatten(executeErr));
    success = false;
  }
  if (success) {
    // mark complete
    callback = job.onSuccess;
    db._jobs.update(job._key, {modified: Date.now(), status: 'complete'});
  } else if (job.failures.length > maxFailures) {
    // mark failed
    callback = job.onFailure;
    db._jobs.update(job._key, {
      modified: now,
      failures: job.failures,
      status: 'failed'
    });
  } else {
    // queue for retry
    db._jobs.update(job._key, {
      modified: now,
      delayUntil: now + getBackOffDelay(job, cfg),
      failures: job.failures,
      status: 'pending'
    });
  }

  if (callback) {
    try {
      eval('(' + callback + ')(' + [
        JSON.stringify(job._id),
        JSON.stringify(job.data),
        JSON.stringify(result),
        JSON.stringify(job.failures)
      ].join(', ') + ')');
    } catch (callbackErr) {
      console.error(
        'Failed to execute '
          + (success ? 'success' : 'failure')
          + ' callback for job ' + job._key + ':',
        callbackErr
      );
    }
  }
};<commit_msg>Fixed the success callback for Queues<commit_after>/*jslint es5: true, indent: 2, nomen: true, maxlen: 120, evil: true */
/*global exports, require */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues workers
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var db = require('org/arangodb').db,
  flatten = require('internal').flatten,
  exponentialBackOff = require('internal').exponentialBackOff,
  console = require('console'),
  queues = require('org/arangodb/foxx').queues,
  getBackOffDelay;

getBackOffDelay = function (job, cfg) {
  'use strict';
  var n = job.failures.length - 1;
  if (typeof job.backOff === 'string') {
    try {
      return eval('(' + job.backOff + ')(' + n + ')');
    } catch (jobStrErr) {
      console.error('Failed to call backOff function of job ' + job._key, jobStrErr);
    }
  } else if (typeof job.backOff === 'number' && job.backOff >= 0 && job.backOff < Infinity) {
    return exponentialBackOff(n, job.backOff);
  }
  if (typeof cfg.backOff === 'function') {
    try {
      return cfg.backOff(n);
    } catch (cfgFnErr) {
      console.error('Failed to call backOff function of job type ' + job.type, cfgFnErr);
    }
  } else if (typeof cfg.backOff === 'string') {
    try {
      return eval('(' + cfg.backOff + ')(' + n + ')');
    } catch (cfgStrErr) {
      console.error('Failed to call backOff function of job type ' + job.type, cfgStrErr);
    }
  }
  if (typeof cfg.backOff === 'number' && cfg.backOff >= 0 && cfg.backOff < Infinity) {
    return exponentialBackOff(n, cfg.backOff);
  }
  return exponentialBackOff(n, 1000);
};

exports.work = function (job) {
  'use strict';
  var cfg = queues._jobTypes[job.type],
    success = true,
    callback = null,
    now = Date.now(),
    maxFailures,
    result;

  if (!cfg) {
    console.warn('Unknown job type for job ' + job._key + ':', job.type);
    db._jobs.update(job._key, {status: 'pending'});
    return;
  }

  maxFailures = (
    typeof job.maxFailures === 'number'
      ? (job.maxFailures < 0 ? Infinity : job.maxFailures)
      : (cfg.maxFailures < 0 ? Infinity : cfg.maxFailures)
  ) || 0;

  try {
    result = cfg.execute(job.data, job._id);
  } catch (executeErr) {
    console.error('Job ' + job._key + ' failed:', executeErr);
    job.failures.push(flatten(executeErr));
    success = false;
  }
  if (success) {
    // mark complete
    callback = job.onSuccess;
    db._jobs.update(job._key, {modified: Date.now(), status: 'complete'});
  } else if (job.failures.length > maxFailures) {
    // mark failed
    callback = job.onFailure;
    db._jobs.update(job._key, {
      modified: now,
      failures: job.failures,
      status: 'failed'
    });
  } else {
    // queue for retry
    db._jobs.update(job._key, {
      modified: now,
      delayUntil: now + getBackOffDelay(job, cfg),
      failures: job.failures,
      status: 'pending'
    });
  }

  if (callback) {
    try {
      eval('(' + callback + ')(' + [
        JSON.stringify(job._id) || 'null',
        JSON.stringify(job.data) || 'null',
        JSON.stringify(result) || 'null',
        JSON.stringify(job.failures) || 'null'
      ].join(', ') + ')');
    } catch (callbackErr) {
      console.error(
        'Failed to execute '
          + (success ? 'success' : 'failure')
          + ' callback for job ' + job._key + ':',
        callbackErr
      );
    }
  }
};
<|endoftext|>


<commit_before>import clonedeep from 'lodash.clonedeep';
import {ASSET_TYPES} from 'js/constants';

/**
 * Asset type could be either the loaded asset type (editing an existing form)
 * or the desired asset type (creating a new form)
 *
 * @returns {object|null} one of ASSET_TYPES
 */
export function getFormBuilderAssetType(assetType, desiredAssetType) {
  if (assetType && ASSET_TYPES[assetType]) {
    return ASSET_TYPES[assetType];
  } else if (desiredAssetType && ASSET_TYPES[desiredAssetType]) {
    return ASSET_TYPES[desiredAssetType];
  }
  return null;
}

export function surveyToValidJson(survey) {
  // HACK: This is done as a fix for https://github.com/kobotoolbox/kpi/pull/735
  // I'm not entirely sure what this is about but definitely BAD CODE™!
  //
  // skip logic references only preserved after initial call
  // to "survey.toFlatJSON()"
  survey.toFlatJSON();
  // returning the result of the second call to "toFlatJSON()"
  return survey.toFlatJSON();
}

/**
 * This function reverses what `nullifyTranslations` did to the form data.
 * @param {string} surveyDataJSON
 * @param {object} assetContent
 * @return {string} fixed surveyDataJSON
 */
export function unnullifyTranslations(surveyDataJSON, assetContent) {
  let surveyData = JSON.parse(surveyDataJSON);

  let translatedProps = [];
  if (assetContent.translated) {
     translatedProps = assetContent.translated;
  }

  // TRANSLATIONS HACK (Part 2/2):
  // set default_language
  let defaultLang = assetContent.translations_0;
  if (!defaultLang) {
    defaultLang = null;
  }
  if (!surveyData.settings[0].default_language && defaultLang !== null) {
    surveyData.settings[0].default_language = defaultLang;
  }

  if (defaultLang !== null) {
    // replace every "translatedProp" with "translatedProp::defaultLang"
    if (surveyData.choices) {
      surveyData.choices.forEach((choice) => {
        translatedProps.forEach((translatedProp) => {
          if (typeof choice[translatedProp] !== 'undefined') {
            choice[`${translatedProp}::${defaultLang}`] = choice[translatedProp];
            delete choice[translatedProp];
          }
        });
      });
    }
    if (surveyData.survey) {
      surveyData.survey.forEach((surveyRow) => {
        translatedProps.forEach((translatedProp) => {
          if (typeof surveyRow[translatedProp] !== 'undefined') {
            if (typeof surveyData.settings[0] !== 'undefined'
                && typeof surveyData.settings[0].style === 'string'
                && surveyData.settings[0].style.includes('theme-grid')
                && surveyRow.type === 'begin_group'
                && (surveyRow[translatedProp] === null || surveyRow[translatedProp] === '')) {
              delete surveyRow[translatedProp];
            }
            surveyRow[`${translatedProp}::${defaultLang}`] = surveyRow[translatedProp];
            delete surveyRow[translatedProp];
          }
        });
      });
    }
  }

  return JSON.stringify(surveyData);
}

/**
 * @typedef NullifiedTranslations
 * @property {object} survey - Modified survey.
 * @property {Array<string|null>} translations - Modified translations.
 * @property {Array<string|null>} translations_0 - The original default language name.
 */

/**
 * A function that adjust the translations data to the Form Builder code.
 * Requires the sibling `unnullifyTranslations` function to be called before
 * saving the form.
 * @param {Array<string|null>} [translations]
 * @param {Array<string>} translatedProps
 * @param {Array<object>} survey
 * @param {object} baseSurvey
 * @return {NullifiedTranslations}
 */
export function nullifyTranslations(translations, translatedProps, survey, baseSurvey) {
  const data = {
    survey: clonedeep(survey),
    translations: clonedeep(translations)
  };

  if (typeof translations === 'undefined') {
    data.translations = [null];
    return data;
  }

  if (data.translations.length > 1 && data.translations.indexOf(null) !== -1) {
    throw new Error('There is an unnamed translation in your form definition.\nPlease give a name to all translations in your form.\nUse "Manage Translations" option from form landing page.');
  }

  /*
  TRANSLATIONS HACK (Part 1/2):
  all the coffee code assumes first language to be null, and we don't want
  to introduce potential code-breaking refactor in old code, so we store
  first language, then replace with null and reverse this just before saving
  NOTE: when importing assets from Library into form, we need to make sure
  the default language is the same (or force baseSurvey default language)
  */
  if (baseSurvey) {
    const formDefaultLang = baseSurvey._initialParams.translations_0 || null;
    if (data.translations[0] === formDefaultLang) {
      // case 1: nothing to do - same default language in both
    } else if (data.translations.includes(formDefaultLang)) {
      // case 2: imported asset has form default language but not as first, so
      // we need to reorder things
      const defaultLangIndex = data.translations.indexOf(formDefaultLang);
      data.translations.unshift(data.translations.pop(defaultLangIndex));
      data.survey.forEach((row) => {
        translatedProps.forEach((translatedProp) => {
          const transletedPropArr = row[translatedProp];
          if (transletedPropArr) {
            transletedPropArr.unshift(transletedPropArr.pop(defaultLangIndex));
          }
        });
      });
    }

    if (!data.translations.includes(formDefaultLang)) {
      // case 3: imported asset doesn't have form default language, so we
      // force it onto the asset as the first language and try setting some
      // meaningful property value
      data.translations.unshift(formDefaultLang);
      data.survey.forEach((row) => {
        translatedProps.forEach((translatedProp) => {
          if (row[translatedProp]) {
            let propVal = null;
            if (row.name) {
              propVal = row.name;
            } else if (row.$autoname) {
              propVal = row.$autoname;
            }
            row[translatedProp].unshift(propVal);
          }
        });
      });
    }
  }

  // no need to nullify null
  if (data.translations[0] !== null) {
    data.translations_0 = data.translations[0];
    data.translations[0] = null;
  }

  return data;
}

export function koboMatrixParser(params) {
  let content = {};
  if (params.content)
    content = JSON.parse(params.content);
  if (params.source)
    content = JSON.parse(params.source);

  if (!content.survey)
    return params;

  var hasMatrix = false;
  var surveyLength = content.survey.length;

  // add open/close tags for kobomatrix groups
  for (var i = 0; i < surveyLength; i++) {
    if (content.survey[i].type === 'kobomatrix') {
      content.survey[i].type = 'begin_kobomatrix';
      content.survey[i].appearance = 'field-list';
      surveyLength++;
      content.survey.splice(i + 1, 0, {type: 'end_kobomatrix', '$kuid': `/${content.survey[i].$kuid}`});
    }
  }

  // add columns as items in the group
  for (i = 0; i < surveyLength; i++) {
    if (content.survey[i].type === 'begin_kobomatrix') {
      var j = i;
      hasMatrix = true;
      var matrix = localStorage.getItem(`koboMatrix.${content.survey[i].$kuid}`);

      if (matrix != null) {
        matrix = JSON.parse(matrix);
        for (var kuid of matrix.cols) {
          i++;
          surveyLength++;
          content.survey.splice(i, 0, matrix[kuid]);
        }

        for (var k of Object.keys(matrix.choices)) {
          content.choices.push(matrix.choices[k]);
        }
      }
      // TODO: handle corrupt matrix data
    }
  }

  if (hasMatrix) {
    if (params.content)
      params.content = JSON.stringify(content);
    if (params.source)
      params.source = JSON.stringify(content);
  }
  return params;
}

export function readParameters(str) {
  if (typeof str !== 'string') {
    return null;
  }

  const params = {};

  let separator = ' ';
  if (str.includes(';')) {
    separator = ';';
  } else if (str.includes(',')) {
    separator = ',';
  }
  const otherSeparators = ';, '.replace(separator, '');
  const cleanStr = str.replace(new RegExp(' *= *', 'g'), '=');
  const parts = cleanStr.split(new RegExp(`[${otherSeparators}]*${separator}[${otherSeparators}]*`, 'g'));

  parts.forEach((part) => {
    if (part.includes('=')) {
      const key = part.slice(0, part.indexOf('='));
      const value = part.slice(key.length + 1);
      params[key] = value;
    }
  });

  if (Object.keys(params).length < 1) {
    return null;
  }
  return params;
}

export function writeParameters(obj) {
  let params = [];
  Object.keys(obj).forEach((key) => {
    if (obj[key] !== undefined && obj[key] !== null) {
      let value = obj[key];
      if (typeof value === 'object') {
        value = JSON.stringify(value);
      }
      params.push(`${key}=${value}`);
    }
  });
  return params.join(';');
}
<commit_msg>unfinished fix from 6bb852c277<commit_after>import clonedeep from 'lodash.clonedeep';
import {ASSET_TYPES} from 'js/constants';

/**
 * Asset type could be either the loaded asset type (editing an existing form)
 * or the desired asset type (creating a new form)
 *
 * @returns {object|null} one of ASSET_TYPES
 */
export function getFormBuilderAssetType(assetType, desiredAssetType) {
  if (assetType && ASSET_TYPES[assetType]) {
    return ASSET_TYPES[assetType];
  } else if (desiredAssetType && ASSET_TYPES[desiredAssetType]) {
    return ASSET_TYPES[desiredAssetType];
  }
  return null;
}

export function surveyToValidJson(survey) {
  // HACK: This is done as a fix for https://github.com/kobotoolbox/kpi/pull/735
  // I'm not entirely sure what this is about but definitely BAD CODE™!
  //
  // skip logic references only preserved after initial call
  // to "survey.toFlatJSON()"
  survey.toFlatJSON();
  // returning the result of the second call to "toFlatJSON()"
  return JSON.stringify(survey.toFlatJSON());
}

/**
 * This function reverses what `nullifyTranslations` did to the form data.
 * @param {string} surveyDataJSON
 * @param {object} assetContent
 * @return {string} fixed surveyDataJSON
 */
export function unnullifyTranslations(surveyDataJSON, assetContent) {
  let surveyData = JSON.parse(surveyDataJSON);

  let translatedProps = [];
  if (assetContent.translated) {
     translatedProps = assetContent.translated;
  }

  // TRANSLATIONS HACK (Part 2/2):
  // set default_language
  let defaultLang = assetContent.translations_0;
  if (!defaultLang) {
    defaultLang = null;
  }
  if (!surveyData.settings[0].default_language && defaultLang !== null) {
    surveyData.settings[0].default_language = defaultLang;
  }

  if (defaultLang !== null) {
    // replace every "translatedProp" with "translatedProp::defaultLang"
    if (surveyData.choices) {
      surveyData.choices.forEach((choice) => {
        translatedProps.forEach((translatedProp) => {
          if (typeof choice[translatedProp] !== 'undefined') {
            choice[`${translatedProp}::${defaultLang}`] = choice[translatedProp];
            delete choice[translatedProp];
          }
        });
      });
    }
    if (surveyData.survey) {
      surveyData.survey.forEach((surveyRow) => {
        translatedProps.forEach((translatedProp) => {
          if (typeof surveyRow[translatedProp] !== 'undefined') {
            if (typeof surveyData.settings[0] !== 'undefined'
                && typeof surveyData.settings[0].style === 'string'
                && surveyData.settings[0].style.includes('theme-grid')
                && surveyRow.type === 'begin_group'
                && (surveyRow[translatedProp] === null || surveyRow[translatedProp] === '')) {
              delete surveyRow[translatedProp];
            }
            surveyRow[`${translatedProp}::${defaultLang}`] = surveyRow[translatedProp];
            delete surveyRow[translatedProp];
          }
        });
      });
    }
  }

  return JSON.stringify(surveyData);
}

/**
 * @typedef NullifiedTranslations
 * @property {object} survey - Modified survey.
 * @property {Array<string|null>} translations - Modified translations.
 * @property {Array<string|null>} translations_0 - The original default language name.
 */

/**
 * A function that adjust the translations data to the Form Builder code.
 * Requires the sibling `unnullifyTranslations` function to be called before
 * saving the form.
 * @param {Array<string|null>} [translations]
 * @param {Array<string>} translatedProps
 * @param {Array<object>} survey
 * @param {object} baseSurvey
 * @return {NullifiedTranslations}
 */
export function nullifyTranslations(translations, translatedProps, survey, baseSurvey) {
  const data = {
    survey: clonedeep(survey),
    translations: clonedeep(translations)
  };

  if (typeof translations === 'undefined') {
    data.translations = [null];
    return data;
  }

  if (data.translations.length > 1 && data.translations.indexOf(null) !== -1) {
    throw new Error('There is an unnamed translation in your form definition.\nPlease give a name to all translations in your form.\nUse "Manage Translations" option from form landing page.');
  }

  /*
  TRANSLATIONS HACK (Part 1/2):
  all the coffee code assumes first language to be null, and we don't want
  to introduce potential code-breaking refactor in old code, so we store
  first language, then replace with null and reverse this just before saving
  NOTE: when importing assets from Library into form, we need to make sure
  the default language is the same (or force baseSurvey default language)
  */
  if (baseSurvey) {
    const formDefaultLang = baseSurvey._initialParams.translations_0 || null;
    if (data.translations[0] === formDefaultLang) {
      // case 1: nothing to do - same default language in both
    } else if (data.translations.includes(formDefaultLang)) {
      // case 2: imported asset has form default language but not as first, so
      // we need to reorder things
      const defaultLangIndex = data.translations.indexOf(formDefaultLang);
      data.translations.unshift(data.translations.pop(defaultLangIndex));
      data.survey.forEach((row) => {
        translatedProps.forEach((translatedProp) => {
          const transletedPropArr = row[translatedProp];
          if (transletedPropArr) {
            transletedPropArr.unshift(transletedPropArr.pop(defaultLangIndex));
          }
        });
      });
    }

    if (!data.translations.includes(formDefaultLang)) {
      // case 3: imported asset doesn't have form default language, so we
      // force it onto the asset as the first language and try setting some
      // meaningful property value
      data.translations.unshift(formDefaultLang);
      data.survey.forEach((row) => {
        translatedProps.forEach((translatedProp) => {
          if (row[translatedProp]) {
            let propVal = null;
            if (row.name) {
              propVal = row.name;
            } else if (row.$autoname) {
              propVal = row.$autoname;
            }
            row[translatedProp].unshift(propVal);
          }
        });
      });
    }
  }

  // no need to nullify null
  if (data.translations[0] !== null) {
    data.translations_0 = data.translations[0];
    data.translations[0] = null;
  }

  return data;
}

export function koboMatrixParser(params) {
  let content = {};
  if (params.content)
    content = JSON.parse(params.content);
  if (params.source)
    content = JSON.parse(params.source);

  if (!content.survey)
    return params;

  var hasMatrix = false;
  var surveyLength = content.survey.length;

  // add open/close tags for kobomatrix groups
  for (var i = 0; i < surveyLength; i++) {
    if (content.survey[i].type === 'kobomatrix') {
      content.survey[i].type = 'begin_kobomatrix';
      content.survey[i].appearance = 'field-list';
      surveyLength++;
      content.survey.splice(i + 1, 0, {type: 'end_kobomatrix', '$kuid': `/${content.survey[i].$kuid}`});
    }
  }

  // add columns as items in the group
  for (i = 0; i < surveyLength; i++) {
    if (content.survey[i].type === 'begin_kobomatrix') {
      var j = i;
      hasMatrix = true;
      var matrix = localStorage.getItem(`koboMatrix.${content.survey[i].$kuid}`);

      if (matrix != null) {
        matrix = JSON.parse(matrix);
        for (var kuid of matrix.cols) {
          i++;
          surveyLength++;
          content.survey.splice(i, 0, matrix[kuid]);
        }

        for (var k of Object.keys(matrix.choices)) {
          content.choices.push(matrix.choices[k]);
        }
      }
      // TODO: handle corrupt matrix data
    }
  }

  if (hasMatrix) {
    if (params.content)
      params.content = JSON.stringify(content);
    if (params.source)
      params.source = JSON.stringify(content);
  }
  return params;
}

export function readParameters(str) {
  if (typeof str !== 'string') {
    return null;
  }

  const params = {};

  let separator = ' ';
  if (str.includes(';')) {
    separator = ';';
  } else if (str.includes(',')) {
    separator = ',';
  }
  const otherSeparators = ';, '.replace(separator, '');
  const cleanStr = str.replace(new RegExp(' *= *', 'g'), '=');
  const parts = cleanStr.split(new RegExp(`[${otherSeparators}]*${separator}[${otherSeparators}]*`, 'g'));

  parts.forEach((part) => {
    if (part.includes('=')) {
      const key = part.slice(0, part.indexOf('='));
      const value = part.slice(key.length + 1);
      params[key] = value;
    }
  });

  if (Object.keys(params).length < 1) {
    return null;
  }
  return params;
}

export function writeParameters(obj) {
  let params = [];
  Object.keys(obj).forEach((key) => {
    if (obj[key] !== undefined && obj[key] !== null) {
      let value = obj[key];
      if (typeof value === 'object') {
        value = JSON.stringify(value);
      }
      params.push(`${key}=${value}`);
    }
  });
  return params.join(';');
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import reactMixin from 'react-mixin';
import autoBind from 'react-autobind';
import Reflux from 'reflux';
import Dropzone from 'react-dropzone';
import Select from 'react-select';
import {bem} from 'js/bem';
import {LoadingSpinner} from 'js/ui';
import {stores} from 'js/stores';
import mixins from 'js/mixins';
import {renderBackButton} from './modalHelpers';
import {validFileTypes} from 'utils';
import {ASSET_TYPES} from 'js/constants';

const DESIRED_TYPES = [
  {
    value: ASSET_TYPES.block.id,
    label: ASSET_TYPES.block.label,
  },
  {
    value: ASSET_TYPES.template.id,
    label: ASSET_TYPES.template.label,
  },
];

/**
 * @prop {function} onSetModalTitle
 * @prop {file} [file] optional preloaded file
 */
class LibraryUploadForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isSessionLoaded: !!stores.session.currentAccount,
      isPending: false,
      // default is block
      desiredType: DESIRED_TYPES[0],
      currentFile: this.props.file || null,
    };

    autoBind(this);
  }

  componentDidMount() {
    this.listenTo(stores.session, () => {
      this.setState({isSessionLoaded: true});
    });
  }

  isSubmitEnabled() {
    return (
      !this.state.isPending &&
      this.state.currentFile !== null
    );
  }

  onFileDrop(files) {
    if (files[0]) {
      this.setState({currentFile: files[0]});
    }
  }

  onDesiredTypeChange(newValue) {
    this.setState({desiredType: newValue});
  }

  onSubmit(evt) {
    evt.preventDefault();
    this.setState({isPending: true});
    this.dropFiles(
      [this.state.currentFile],
      [],
      evt,
      {desired_type: this.state.desiredType.value}
    );
  }

  render() {
    if (!this.state.isSessionLoaded) {
      return (<LoadingSpinner/>);
    }

    return (
      <bem.FormModal__form className='project-settings project-settings--upload-file'>
        <bem.Modal__subheader>
          {t('Import an XLSForm from your computer.')}
        </bem.Modal__subheader>

        {!this.state.isPending &&
          <React.Fragment>
            <bem.FormModal__item>

                {t('Choose desired type')}

              <Select
                id='desired-type'
                value={this.state.desiredType}
                onChange={this.onDesiredTypeChange}
                options={DESIRED_TYPES}
                className='kobo-select'
                classNamePrefix='kobo-select'
                menuPlacement='auto'
              />
            </bem.FormModal__item>

            <bem.FormModal__item>
              <Dropzone
                onDrop={this.onFileDrop.bind(this)}
                multiple={false}
                className='dropzone'
                activeClassName='dropzone-active'
                rejectClassName='dropzone-reject'
                accept={validFileTypes()}
              >

                {this.state.currentFile &&
                  this.state.currentFile.name
                }
                {!this.state.currentFile &&
                  t(' Drag and drop the XLSForm file here or click to browse')
                }
              </Dropzone>
            </bem.FormModal__item>
          </React.Fragment>
        }
        {this.state.isPending &&

            <LoadingSpinner message={t('Uploading file…')}/>

        }

        <bem.Modal__footer>
          {renderBackButton(this.state.isPending)}

          <bem.KoboButton
            m='blue'
            type='submit'
            onClick={this.onSubmit}
            disabled={!this.isSubmitEnabled()}
            className='mdl-js-button'
          >
            {t('Upload')}
          </bem.KoboButton>
        </bem.Modal__footer>
      </bem.FormModal__form>
    );
  }
}

reactMixin(LibraryUploadForm.prototype, Reflux.ListenerMixin);
reactMixin(LibraryUploadForm.prototype, mixins.droppable);
LibraryUploadForm.contextTypes = {router: PropTypes.object};

export default LibraryUploadForm;
<commit_msg>change modal children order<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import reactMixin from 'react-mixin';
import autoBind from 'react-autobind';
import Reflux from 'reflux';
import Dropzone from 'react-dropzone';
import Select from 'react-select';
import {bem} from 'js/bem';
import {LoadingSpinner} from 'js/ui';
import {stores} from 'js/stores';
import mixins from 'js/mixins';
import {renderBackButton} from './modalHelpers';
import {validFileTypes} from 'utils';
import {ASSET_TYPES} from 'js/constants';

const DESIRED_TYPES = [
  {
    value: ASSET_TYPES.block.id,
    label: ASSET_TYPES.block.label,
  },
  {
    value: ASSET_TYPES.template.id,
    label: ASSET_TYPES.template.label,
  },
];

/**
 * @prop {function} onSetModalTitle
 * @prop {file} [file] optional preloaded file
 */
class LibraryUploadForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isSessionLoaded: !!stores.session.currentAccount,
      isPending: false,
      // default is block
      desiredType: DESIRED_TYPES[0],
      currentFile: this.props.file || null,
    };

    autoBind(this);
  }

  componentDidMount() {
    this.listenTo(stores.session, () => {
      this.setState({isSessionLoaded: true});
    });
  }

  isSubmitEnabled() {
    return (
      !this.state.isPending &&
      this.state.currentFile !== null
    );
  }

  onFileDrop(files) {
    if (files[0]) {
      this.setState({currentFile: files[0]});
    }
  }

  onDesiredTypeChange(newValue) {
    this.setState({desiredType: newValue});
  }

  onSubmit(evt) {
    evt.preventDefault();
    this.setState({isPending: true});
    this.dropFiles(
      [this.state.currentFile],
      [],
      evt,
      {desired_type: this.state.desiredType.value}
    );
  }

  render() {
    if (!this.state.isSessionLoaded) {
      return (<LoadingSpinner/>);
    }

    return (
      <bem.FormModal__form className='project-settings project-settings--upload-file'>
        <bem.Modal__subheader>
          {t('Import an XLSForm from your computer.')}
        </bem.Modal__subheader>

        {!this.state.isPending &&
          <React.Fragment>
            <bem.FormModal__item>
              <Dropzone
                onDrop={this.onFileDrop.bind(this)}
                multiple={false}
                className='dropzone'
                activeClassName='dropzone-active'
                rejectClassName='dropzone-reject'
                accept={validFileTypes()}
              >

                {this.state.currentFile &&
                  this.state.currentFile.name
                }
                {!this.state.currentFile &&
                  t(' Drag and drop the XLSForm file here or click to browse')
                }
              </Dropzone>
            </bem.FormModal__item>

            <bem.FormModal__item>

                {t('Choose desired type')}

              <Select
                id='desired-type'
                value={this.state.desiredType}
                onChange={this.onDesiredTypeChange}
                options={DESIRED_TYPES}
                className='kobo-select'
                classNamePrefix='kobo-select'
                menuPlacement='auto'
              />
            </bem.FormModal__item>
          </React.Fragment>
        }
        {this.state.isPending &&

            <LoadingSpinner message={t('Uploading file…')}/>

        }

        <bem.Modal__footer>
          {renderBackButton(this.state.isPending)}

          <bem.KoboButton
            m='blue'
            type='submit'
            onClick={this.onSubmit}
            disabled={!this.isSubmitEnabled()}
            className='mdl-js-button'
          >
            {t('Upload')}
          </bem.KoboButton>
        </bem.Modal__footer>
      </bem.FormModal__form>
    );
  }
}

reactMixin(LibraryUploadForm.prototype, Reflux.ListenerMixin);
reactMixin(LibraryUploadForm.prototype, mixins.droppable);
LibraryUploadForm.contextTypes = {router: PropTypes.object};

export default LibraryUploadForm;
<|endoftext|>


<commit_before>/*
 Scrollator jQuery Plugin
 Scrollator is a jQuery-based replacement for the browsers scroll bar, which doesn't use any space.
 version 1.0, July 3rd, 2014
 by Ingi P. Jacobsen

 The MIT License (MIT)

 Copyright (c) 2014 Faroe Media

 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
 the Software without restriction, including without limitation the rights to
 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

Scrollator = {
	scrollatorElementsStack: [],
	refreshAll: function () {
		var i = Scrollator.scrollatorElementsStack.length;
		while (i--) {
			if (!Scrollator.scrollatorElementsStack[i].$sourceElement.closest('body').length > 0) {
				Scrollator.scrollatorElementsStack[i].destroy();
			} else {
				Scrollator.scrollatorElementsStack[i].refresh();
			}
		}
	}
};
$(window).load(function () {
	Scrollator.refreshAll();
});
(function($) {
	$.scrollator = function (sourceElement, options) {
		var defaults = {
		};

		var plugin = this;
		plugin.settings = {
			custom_class: '',
			zIndex: ''
		};
		var $sourceElement = $(sourceElement);
		plugin.$sourceElement = $sourceElement;
		var $mainScrollbarsHolder = null;
		var $thisScrollbarLane = null;
		var $thisScrollbarHandle = null;
		var timerVisibility = null;
		var isDraggingHandle = false;
		var dragStartY = 0;
		var dragStartScrollTop = 0;
		var dragHandleOffsetY = 0;

		// INITIALIZE PLUGIN
		plugin.init = function () {
			plugin.settings = $.extend({}, defaults, options);
			$mainScrollbarsHolder = $('#scrollator_holder');
			$sourceElement.addClass('scrollator');
			$thisScrollbarLane = $(document.createElement('div')).addClass('scrollator_lane');
			$thisScrollbarLane.addClass(plugin.settings.custom_class);
			$thisScrollbarLane.css('z-index', $sourceElement.css('z-index'));
			plugin.settings.zIndex !== '' && $thisScrollbarLane.css('z-index', plugin.settings.zIndex);
			$sourceElement.is('body') && $thisScrollbarLane.addClass('scrollator_body_lane');
			$thisScrollbarHandle = $(document.createElement('div')).addClass('scrollator_handle');
			initializeMainScrollbarsHolder();
			$sourceElement.bind('mousewheel DOMMouseScroll', mouseWheelEvent);
			$thisScrollbarLane.bind('mousewheel DOMMouseScroll', mouseWheelEvent);
			$thisScrollbarHandle.bind('mousewheel DOMMouseScroll', mouseWheelEvent);
			$sourceElement.bind('mousemove', mouseMoveEvent);
			$thisScrollbarLane.bind('mousemove', mouseMoveEvent);
			$thisScrollbarHandle.bind('mousemove', mouseMoveEvent);
			$thisScrollbarHandle.bind('mousedown', mouseDownEvent);
			$(window).bind('mouseup', windowMouseUpEvent);
			$(window).bind('mousemove', windowMouseMoveEvent);
			$thisScrollbarLane.append($thisScrollbarHandle);
			$mainScrollbarsHolder.append($thisScrollbarLane);
			refreshScrollbarPosition();
			// refresh/resize/position all scrollbars on window resize
			if (!document.body.hasScrollatorPageResizeEventHandler) {
				document.body.hasScrollatorPageResizeEventHandler = true;
				$(window).bind('resize', function () {
					Scrollator.refreshAll();
				});
			}
		};

		var mouseWheelEvent = function (e) {
			if ($(e.target).css('overflow-y') != 'auto') {
				e.preventDefault();
				e.stopPropagation();
				var scrollTop = ($sourceElement.is('body') ? $(window) : $sourceElement).scrollTop();
				var wheelDelta = e.originalEvent.wheelDelta !== undefined ? e.originalEvent.wheelDelta : (e.originalEvent.detail*-1);
				scrollTop += (wheelDelta > 0) ? -100 : 100;
				($sourceElement.is('body') ? $(window) : $sourceElement).scrollTop(scrollTop);
				Scrollator.refreshAll();
			}
		};
		var mouseMoveEvent = function () {
			clearTimeout(timerVisibility);
			$thisScrollbarLane.css('opacity', 1);
			timerVisibility = setTimeout(function () {
				$thisScrollbarLane.css('opacity', 0);
			}, 1500);
		};
		var mouseDownEvent = function (e) {
			e.preventDefault();
			isDraggingHandle = true;
			dragStartY = e.clientY;
			dragStartScrollTop = ($sourceElement.is('body') ? $(window) : $sourceElement).scrollTop();
			dragHandleOffsetY = e.offsetY;
			$thisScrollbarLane.addClass('hover');
		};
		var windowMouseMoveEvent = function (e) {
			if (isDraggingHandle) {
				var draggedY = e.clientY - dragStartY;
				var multiplier = $sourceElement[0].scrollHeight / (($sourceElement.is('body')) ? $(window).height() : $sourceElement.innerHeight());
				($sourceElement.is('body') ? $(window) : $sourceElement).scrollTop(dragStartScrollTop + (draggedY * multiplier));
				Scrollator.refreshAll();
				mouseMoveEvent();
			}
		};
		var windowMouseUpEvent = function () {
			isDraggingHandle = false;
			$thisScrollbarLane.removeClass('hover');
		};

		plugin.refresh = function () {
			refreshScrollbarPosition();
		};
		var refreshScrollbarPosition = function () {
			var boundingClientRect = $sourceElement[0].getBoundingClientRect();
			var sourceBounds = {
				left:   boundingClientRect.left + $(window).scrollLeft(),
				top:    boundingClientRect.top + $(window).scrollTop(),
				right:  boundingClientRect.right + $(window).scrollLeft(),
				bottom: boundingClientRect.bottom + $(window).scrollTop(),
				width:  boundingClientRect.width,
				height: boundingClientRect.height
			};
			var paddingTop = parseInt($sourceElement.css('border-top-width'), 10) + 3;
			var paddingRight = parseInt($sourceElement.css('border-right-width'), 10) + 3;
			var paddingBottom = parseInt($sourceElement.css('border-bottom-width'), 10) + 3;
			var paddingLeft = parseInt($sourceElement.css('border-left-width'), 10);
			var contentHeight = $sourceElement[0].scrollHeight;
			var laneHeight = ($sourceElement.is('body')) ? $(window).height() : $sourceElement.innerHeight();
			var handleHeight = (laneHeight / contentHeight) * 100;
			var handlePosition = (($sourceElement.is('body') ? $(window) : $sourceElement).scrollTop() / contentHeight) * 100;
			if (!$sourceElement.is('body')) {
				$thisScrollbarLane.css({
					top: sourceBounds.top + paddingTop,
					right: -sourceBounds.right + paddingRight,
					bottom: -sourceBounds.bottom + paddingBottom
				});
			}
			$thisScrollbarHandle.css({
				height: handleHeight + '%',
				top: handlePosition + '%'
			});
		};

		// INITIALIZE SCROLLBARS HOLDER IF NEEDED
		var initializeMainScrollbarsHolder = function () {
			if ($mainScrollbarsHolder.length === 0) {
				$mainScrollbarsHolder = $(document.createElement('div')).attr('id', 'scrollator_holder');
				$('body').append($mainScrollbarsHolder);
			}
		};

		// REMOVE PLUGIN AND REVERT INPUT ELEMENT TO ORIGINAL STATE
		plugin.destroy = function () {
			$sourceElement.removeClass('scrollator');
			$.removeData(sourceElement, 'scrollator');
			$sourceElement.unbind('mousewheel DOMMouseScroll', mouseWheelEvent);
			$sourceElement.unbind('mousemove', mouseMoveEvent);
			$(window).unbind('mouseup', windowMouseUpEvent);
			$(window).unbind('mousemove', windowMouseMoveEvent);
			$thisScrollbarLane.remove();
			var i = Scrollator.scrollatorElementsStack.length;
			while (i--) {
				if (Scrollator.scrollatorElementsStack[i] === plugin) {
					Scrollator.scrollatorElementsStack.splice(i, 1);
				}
			}
			if ($mainScrollbarsHolder.children().length === 0) {
				$mainScrollbarsHolder.remove();
				$mainScrollbarsHolder = null;
			}
		};

		// Initialize plugin
		plugin.init();
	};

	$.fn.scrollator = function(options) {
		options = options !== undefined ? options : {};
		return this.each(function () {
			if ( !/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
				if (typeof(options) === 'object') {
					if (undefined === $(this).data('scrollator')) {
						var plugin = new $.scrollator(this, options);
						Scrollator.scrollatorElementsStack.push(plugin);
						$(this).data('scrollator', plugin);
					}
				} else if ($(this).data('scrollator')[options]) {
					$(this).data('scrollator')[options].apply(this, Array.prototype.slice.call(arguments, 1));
				} else {
					$.error('Method ' + options + ' does not exist in $.scrollator');
				}
			}
		});
	};

}(jQuery));
<commit_msg>Added methods to show/hide scrollbars<commit_after>/*
 Scrollator jQuery Plugin
 Scrollator is a jQuery-based replacement for the browsers scroll bar, which doesn't use any space.
 version 1.0, July 3rd, 2014
 by Ingi P. Jacobsen

 The MIT License (MIT)

 Copyright (c) 2014 Faroe Media

 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
 the Software without restriction, including without limitation the rights to
 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

Scrollator = {
	scrollatorElementsStack: [],
	refreshAll: function () {
		var i = Scrollator.scrollatorElementsStack.length;
		while (i--) {
			if (!Scrollator.scrollatorElementsStack[i].$sourceElement.closest('body').length > 0) {
				Scrollator.scrollatorElementsStack[i].destroy();
			} else {
				Scrollator.scrollatorElementsStack[i].refresh();
			}
		}
	}
};
$(window).load(function () {
	Scrollator.refreshAll();
});
(function($) {
	$.scrollator = function (sourceElement, options) {
		var defaults = {
		};

		var plugin = this;
		plugin.settings = {
			custom_class: '',
			zIndex: ''
		};
		var $sourceElement = $(sourceElement);
		plugin.$sourceElement = $sourceElement;
		var $mainScrollbarsHolder = null;
		var $thisScrollbarLane = null;
		var $thisScrollbarHandle = null;
		var timerVisibility = null;
		var isDraggingHandle = false;
		var dragStartY = 0;
		var dragStartScrollTop = 0;
		var dragHandleOffsetY = 0;

		// INITIALIZE PLUGIN
		plugin.init = function () {
			plugin.settings = $.extend({}, defaults, options);
			$mainScrollbarsHolder = $('#scrollator_holder');
			$sourceElement.addClass('scrollator');
			$thisScrollbarLane = $(document.createElement('div')).addClass('scrollator_lane');
			$thisScrollbarLane.addClass(plugin.settings.custom_class);
			$thisScrollbarLane.css('z-index', $sourceElement.css('z-index'));
			plugin.settings.zIndex !== '' && $thisScrollbarLane.css('z-index', plugin.settings.zIndex);
			$sourceElement.is('body') && $thisScrollbarLane.addClass('scrollator_body_lane');
			$thisScrollbarHandle = $(document.createElement('div')).addClass('scrollator_handle');
			initializeMainScrollbarsHolder();
			$sourceElement.bind('mousewheel DOMMouseScroll', mouseWheelEvent);
			$thisScrollbarLane.bind('mousewheel DOMMouseScroll', mouseWheelEvent);
			$thisScrollbarHandle.bind('mousewheel DOMMouseScroll', mouseWheelEvent);
			$sourceElement.bind('mousemove', mouseMoveEvent);
			$thisScrollbarLane.bind('mousemove', mouseMoveEvent);
			$thisScrollbarHandle.bind('mousemove', mouseMoveEvent);
			$thisScrollbarHandle.bind('mousedown', mouseDownEvent);
			$(window).bind('mouseup', windowMouseUpEvent);
			$(window).bind('mousemove', windowMouseMoveEvent);
			$thisScrollbarLane.append($thisScrollbarHandle);
			$mainScrollbarsHolder.append($thisScrollbarLane);
			refreshScrollbarPosition();
			// refresh/resize/position all scrollbars on window resize
			if (!document.body.hasScrollatorPageResizeEventHandler) {
				document.body.hasScrollatorPageResizeEventHandler = true;
				$(window).bind('resize', function () {
					Scrollator.refreshAll();
				});
			}
		};

		var mouseWheelEvent = function (e) {
			if ($(e.target).css('overflow-y') != 'auto') {
				e.preventDefault();
				e.stopPropagation();
				var scrollTop = ($sourceElement.is('body') ? $(window) : $sourceElement).scrollTop();
				var wheelDelta = e.originalEvent.wheelDelta !== undefined ? e.originalEvent.wheelDelta : (e.originalEvent.detail*-1);
				scrollTop += (wheelDelta > 0) ? -100 : 100;
				($sourceElement.is('body') ? $(window) : $sourceElement).scrollTop(scrollTop);
				Scrollator.refreshAll();
			}
		};
		var mouseMoveEvent = function () {
			clearTimeout(timerVisibility);
			$thisScrollbarLane.css('opacity', 1);
			timerVisibility = setTimeout(function () {
				$thisScrollbarLane.css('opacity', 0);
			}, 1500);
		};
		var mouseDownEvent = function (e) {
			e.preventDefault();
			isDraggingHandle = true;
			dragStartY = e.clientY;
			dragStartScrollTop = ($sourceElement.is('body') ? $(window) : $sourceElement).scrollTop();
			dragHandleOffsetY = e.offsetY;
			$thisScrollbarLane.addClass('hover');
		};
		var windowMouseMoveEvent = function (e) {
			if (isDraggingHandle) {
				var draggedY = e.clientY - dragStartY;
				var multiplier = $sourceElement[0].scrollHeight / (($sourceElement.is('body')) ? $(window).height() : $sourceElement.innerHeight());
				($sourceElement.is('body') ? $(window) : $sourceElement).scrollTop(dragStartScrollTop + (draggedY * multiplier));
				Scrollator.refreshAll();
				mouseMoveEvent();
			}
		};
		var windowMouseUpEvent = function () {
			isDraggingHandle = false;
			$thisScrollbarLane.removeClass('hover');
		};

		plugin.refresh = function () {
			refreshScrollbarPosition();
		};
		var refreshScrollbarPosition = function () {
			var boundingClientRect = $sourceElement[0].getBoundingClientRect();
			var sourceBounds = {
				left:   boundingClientRect.left + $(window).scrollLeft(),
				top:    boundingClientRect.top + $(window).scrollTop(),
				right:  boundingClientRect.right + $(window).scrollLeft(),
				bottom: boundingClientRect.bottom + $(window).scrollTop(),
				width:  boundingClientRect.width,
				height: boundingClientRect.height
			};
			var paddingTop = parseInt($sourceElement.css('border-top-width'), 10) + 3;
			var paddingRight = parseInt($sourceElement.css('border-right-width'), 10) + 3;
			var paddingBottom = parseInt($sourceElement.css('border-bottom-width'), 10) + 3;
			var paddingLeft = parseInt($sourceElement.css('border-left-width'), 10);
			var contentHeight = $sourceElement[0].scrollHeight;
			var laneHeight = ($sourceElement.is('body')) ? $(window).height() : $sourceElement.innerHeight();
			var handleHeight = (laneHeight / contentHeight) * 100;
			var handlePosition = (($sourceElement.is('body') ? $(window) : $sourceElement).scrollTop() / contentHeight) * 100;
			if (!$sourceElement.is('body')) {
				$thisScrollbarLane.css({
					top: sourceBounds.top + paddingTop,
					right: -sourceBounds.right + paddingRight,
					bottom: -sourceBounds.bottom + paddingBottom
				});
			}
			$thisScrollbarHandle.css({
				height: handleHeight + '%',
				top: handlePosition + '%'
			});
		};

		// INITIALIZE SCROLLBARS HOLDER IF NEEDED
		var initializeMainScrollbarsHolder = function () {
			if ($mainScrollbarsHolder.length === 0) {
				$mainScrollbarsHolder = $(document.createElement('div')).attr('id', 'scrollator_holder');
				$('body').append($mainScrollbarsHolder);
			}
		};

		// HIDE SCROLLATOR
		plugin.hide = function () {
			console.log('hide');
			$thisScrollbarLane.hide();
		};

		// SHOW SCROLLATOR
		plugin.show = function () {
			console.log('show');
			$thisScrollbarLane.show();
		};

		// REMOVE PLUGIN AND REVERT INPUT ELEMENT TO ORIGINAL STATE
		plugin.destroy = function () {
			$sourceElement.removeClass('scrollator');
			$.removeData(sourceElement, 'scrollator');
			$sourceElement.unbind('mousewheel DOMMouseScroll', mouseWheelEvent);
			$sourceElement.unbind('mousemove', mouseMoveEvent);
			$(window).unbind('mouseup', windowMouseUpEvent);
			$(window).unbind('mousemove', windowMouseMoveEvent);
			$thisScrollbarLane.remove();
			var i = Scrollator.scrollatorElementsStack.length;
			while (i--) {
				if (Scrollator.scrollatorElementsStack[i] === plugin) {
					Scrollator.scrollatorElementsStack.splice(i, 1);
				}
			}
			if ($mainScrollbarsHolder.children().length === 0) {
				$mainScrollbarsHolder.remove();
				$mainScrollbarsHolder = null;
			}
		};

		// Initialize plugin
		plugin.init();
	};

	$.fn.scrollator = function(options) {
		options = options !== undefined ? options : {};
		return this.each(function () {
			if ( !/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
				if (typeof(options) === 'object') {
					if (undefined === $(this).data('scrollator')) {
						var plugin = new $.scrollator(this, options);
						Scrollator.scrollatorElementsStack.push(plugin);
						$(this).data('scrollator', plugin);
					}
				} else if ($(this).data('scrollator')[options]) {
					$(this).data('scrollator')[options].apply(this, Array.prototype.slice.call(arguments, 1));
				} else {
					$.error('Method ' + options + ' does not exist in $.scrollator');
				}
			}
		});
	};

}(jQuery));
<|endoftext|>


<commit_before>var getopt = require('node-getopt')
    , q = require('q')
    , _ = require('underscore')
    , util = require('util')
    , fs = require('fs')
    , novaform = require('novaform')
    , novastl = require('novastl')
    , AWS = require('aws-sdk')
    , uuid = require('node-uuid')
    , moment = require('moment')
    , stackUtils = require('../stack-utils')
    , assert = require('assert');

var cmdopts = module.exports.opts = getopt.create([
    ['w', 'wait', 'Wait for completion'],
    ['h', 'help', 'Display help']
]);

cmdopts.setHelp('[[OPTIONS]]\n');

function Command(config, commonOptions, args, helpCallback) {
    if (!(this instanceof Command)) {
        return new Command(name, properties);
    }

    this.config = config;
    this.displayHelpAndExit = helpCallback;

    var opts = this.opts = cmdopts.parse(args);
    this.commonOptions = commonOptions;
    this.commandOptions = this.opts.options;

    if (opts.options.help) {
        helpCallback();
        return;
    }

    if (_(opts.argv).isEmpty()) {
        helpCallback('Missing project/component reference');
        return;
    } else if (opts.argv.length !== 1) {
        helpCallback('Too many project/component references specified');
        return;
    }

    var ref = opts.argv[0];
    ref = this.ref = Ref.parse(ref);
    if (!this.ref) {
        helpCallback('Invalid project ref');
        return;
    }
    if (!this.ref.component) {
        helpCallback('Component was not specified')
        return;
    }

    this.project = Project.load(this.ref.project, function(e) {
        helpCallback(util.format('Failed to load project "%s": %s', ref.project, e.message));
    });
    if (!this.project) {
        helpCallback(util.format('Could not find project "%s"', this.ref.project));
        return;
    }

    this.component = this.project.findComponent(this.ref.component);
    if (!this.component) {
        helpCallback(util.format('Component "%s" does not exist', this.ref.component));
        return;
    }
}

Command.prototype._waitForStack = function(options, shouldStopCallback) {
    var cfn = options.cfn;
    var stackName = options.stackName;
    var maxWaitSeconds = options.maxWaitSeconds || 15 * 60;
    var waitSeconds = options.waitSeconds || 1;

    var start = moment();
    var maxEnd = moment(start);
    maxEnd.add(maxWaitSeconds, 'seconds');

    var getStackStatus = q.nbind(stackUtils.getStackStatus, stackUtils);

    var f = function() {
        return getStackStatus(cfn, stackName).then(function(status) {
            var callbackResult = shouldStopCallback(status);
            if (callbackResult) {
                return callbackResult;
            }
            if (moment().isAfter(maxEnd)) {
                throw new Error('Timeout');
            }
            return q.delay(waitSeconds * 1000).then(f);
        });
    };

    return f();
};

Command.prototype.execute = function() {
    var that = this;
    return q().then(function() {
        // TODO: validate project's components, make sure dependencies exist
        var deplist = [];

        function walkDeps(result, componentName, walked) {
            if (!walked) {
                walked = [];
            }
            if (walked.indexOf(componentName) !== -1) {
                throw new Error('recursive dependency');
            }

            walked.push(componentName);

            var component = that.project.findComponent(componentName);
            if (!component) {
                throw new Error(util.format('Could not find dependent component "%s"', componentName));
            }
            var deps = component.dependencies.map(function(depname) {
                var w = walked ? walked.slice() : [];
                return walkDeps([depname], depname, w);
            });

            deps.sort(function(a, b) { return a.length - b.length; });

            deps.forEach(function(deps) {
                deps.reverse();
                deps.forEach(function(d) {
                    var idx = result.indexOf(d);
                    if (idx !== -1) {
                        result.splice(idx, 1);
                    }
                    result.unshift(d);
                });
            });

            return result;
        }

        var deplist = walkDeps([], that.component.name);
        return deplist;
    }).then(function(dependencies) {
        // fetch output for each dependent component

        var getStackOutput = q.nbind(stackUtils.getStackOutput, stackUtils);
        if (that.commonOptions.verbose) {
            console.log('Fetching outputs of dependent stacks...');
        }

        var region = that.component.region;
        var cfn = that.cfn = new AWS.CloudFormation({ region : region });

        var outputsPromises = dependencies.map(function(depname) {
            return Ref(that.ref.project, depname).makeStackName();
        }).map(function(stackName) {
            return getStackOutput(cfn, stackName);
        });

        return q.all(outputsPromises).then(function(results) {
            return _.object(_.zip(dependencies, results));
        }).catch(function(e) {
            if (e === stackUtils.Status.DOES_NOT_EXIST) {
                throw new Error('One of the dependent stacks is not yet deployed!');
            }
            throw e;
        });
    }).then(function(dependencies) {
        // TODO: inject dependencies into the component
        // var dependencies = {
            // infrastructure: { vpc: 'vpc-123' }
        // };

        return dependencies;
    }).then(function(dependencies) {
        // build cloudformation resources
        if (that.commonOptions.verbose) {
            console.log('Generating cloudformation template...');
        }

        var stackName = that.ref.makeStackName();
        var deploymentDate = moment.utc();
        var deploymentId = uuid.v4();

        var result = that.component.build(dependencies);
        var stack = novaform.Stack(stackName);
        stack.add(result.resourceGroups);
        if (result.outputs) {
            stack.add(result.outputs);
        }

        var templateBody = stack.toJson();
        return {
            projectName: that.ref.project,
            componentName: that.ref.component,
            deploymentDate: deploymentDate,
            deploymentId: deploymentId,
            stackName: stackName,
            templateBody: templateBody,
        };
    }).then(function(deploymentConfig) {
        if (that.commonOptions.verbose) {
            console.log('Uploading cloudformation template to S3...');
        }

        var bucketname = that.config.s3.bucket;
        var region = that.config.s3.region;
        var datestring = deploymentConfig.deploymentDate.format();
        var keypath = util.format('%s%s/%s-%s',
            that.config.s3.keyPrefix, that.ref.project,
            deploymentConfig.stackName, datestring);

        var params = {
            Bucket: bucketname,
            Key: keypath,
            Body: deploymentConfig.templateBody,
        };

        function s3_endpoint(region) {
            if (!region) {
                return 'https://s3.amazonaws.com';
            }
            return util.format('https://s3-%s.amazonaws.com', region);
        }

        var s3 = new AWS.S3({ region : region });
        var s3upload = q.nbind(s3.upload, s3);
        return s3upload(params).then(function() {
            var s3endpoint = s3_endpoint(that.config.s3.region);
            var url = util.format('%s/%s/%s', s3endpoint, bucketname, keypath);
            return _.extend(deploymentConfig, {
                templateUrl: url,
            });
        }).catch(function(e) {
            throw new Error(util.format('Failed to upload to S3: %s', JSON.stringify(e)));
        });
    }).then(function(deploymentConfig) {
        // initiate cloudformation deployment
        var cfn = that.cfn;
        var getStackStatus = q.nbind(stackUtils.getStackStatus, stackUtils);
        if (that.commonOptions.verbose) {
            console.log('Checking cloudformation stack status...');
        }
        return getStackStatus(cfn, deploymentConfig.stackName).then(function(status) {
            if (status !== stackUtils.Status.ROLLBACK_COMPLETE) {
                // all good, nothing to do here.
                return status;
            }

            if (that.commonOptions.verbose) {
                console.log('Stack was stuck in a rollback state, deleting it before deploying...');
            }

            // oh, previous stack creation failed and we cannot update failed stack
            // the only option is to delete the stack and create it again.
            var deleteStack = q.nbind(cfn.deleteStack, cfn);
            return deleteStack({
                StackName: deploymentConfig.stackName
            }).then(function(data) {
                return that._waitForStack({
                    cfn: cfn,
                    stackName: deploymentConfig.stackName,
                }, function(status) {
                    if (status === stackUtils.Status.DOES_NOT_EXIST) {
                        return status;
                    }
                    console.log('Still waiting...');
                    return null;
                });
            }).catch(function(err) {
                throw new Error(util.format('Failed to delete stack "%s": %j', deploymentConfig.stackName, err));
            });
        }).then(function(status) {
            if (that.commonOptions.verbose) {
                console.log('Deploying cloudformation stack...');
            }
            if (status === stackUtils.Status.DOES_NOT_EXIST) {
                // create a new stack
                var createStack = q.nbind(cfn.createStack, cfn);
                return createStack({
                    Capabilities: [ 'CAPABILITY_IAM' ], // TODO: this is only needed for some stacks that create iam roles, hm.
                    StackName: deploymentConfig.stackName,
                    TemplateURL: deploymentConfig.templateUrl,
                    Tags: [
                        { Key: 'nova-project', Value: deploymentConfig.projectName },
                        { Key: 'nova-component', Value: deploymentConfig.componentName },
                    ],
                }).then(function(data) {
                    return _.extend(deploymentConfig, {
                        cfn: cfn,
                        stackId: data.StackId,
                    });
                }).catch(function(err) {
                    throw new Error(util.format('Failed to initiate stack creation:\n%j', err));
                });
            } else {
                if (!stackUtils.isStatusComplete(status)) {
                    // already in progress?
                    throw new Error(util.format('Stack is not in a valid state for deployment (%s)', status));
                }

                // update an existing stack
                var updateStack = q.nbind(cfn.updateStack, cfn);
                return updateStack({
                    Capabilities: [ 'CAPABILITY_IAM' ], // TODO: this is only needed for some stacks that create iam roles, hm.
                    StackName: deploymentConfig.stackName,
                    TemplateURL: deploymentConfig.templateUrl,
                }).then(function(data) {
                    return _.extend(deploymentConfig, {
                        cfn: cfn,
                        stackId: data.StackId,
                    });
                }).catch(function(err) {
                    throw new Error(util.format('Failed to initiate stack creation:\n%j', err));
                });
            }
        });
    }).then(function(deploymentConfig) {
        // wait for completion
        if (that.commandOptions.wait) {
            if (that.commonOptions.verbose) {
                console.log('Waiting for deployment to complete...');
            }

            return that._waitForStack({
                cfn: deploymentConfig.cfn,
                stackName: deploymentConfig.stackName,
            }, function(status) {
                if (stackUtils.isStatusFailed(status)
                    || stackUtils.isStatusRolledBack(status)
                    || stackUtils.isStatusRollingback(status)) {
                    throw new Error('Stack deployment failed');
                }
                if (!stackUtils.isStatusComplete(status)) {
                    if (that.commonOptions.verbose) {
                        console.log('Still waiting...');
                    }
                    return null;
                }
                return deploymentConfig;
            });
        }
        return deploymentConfig;
    }).then(function(deploymentConfig) {
        // TODO: fetch stack output and print it
        var output = {
            project: deploymentConfig.projectName,
            component: deploymentConfig.componentName,
            deploymentId: deploymentConfig.deploymentId,
            stackId: deploymentConfig.stackId,
        };

        if (that.commonOptions['output-format'] == 'json') {
            console.log(output);
        } else if (that.commonOptions['output-format'] == 'text') {
            console.log('Output:\n')
            for (var key in output) {
                var value = output[key];
                console.log(util.format('%s: %s', key, value));
            }
        }
    }).catch(function(e) {
        // TODO: differentiate between internal errors and valid exits like timeout or stack deployment failed
        console.error(util.format('Internal error: %s', e.stack));
    });
}

Command.usageText = '[options] <project>/<component>'
Command.descriptionText = 'Deploys project component';
Command.optionsText = cmdopts.getHelp();

module.exports = Command;

// helpers
// TODO: this should probably be shared in lib/shared ?

function Ref(project, component) {
    if (!(this instanceof Ref)) {
        return new Ref(project, component);
    }

    this.project = project;
    this.component = component;
}

Ref.parse = function(ref) {
    var project;
    var component;

    if (typeof ref !== 'string') {
        return undefined;
    }
    var l = ref.split('/');
    if (l.length > 2) {
        return undefined;
    }
    project = l[0];
    component = l[1];

    return new Ref(project, component);
}

Ref.prototype.makeStackName = function() {
    assert(this.project);
    assert(this.component);

    var project = this.project[0].toUpperCase() + this.project.substr(1).toLowerCase();
    var component = this.component[0].toUpperCase() + this.component.substr(1).toLowerCase();

    return project + component;
};

function Project(config) {
    this.config = config;
}
Project.searchPaths = [process.cwd()];
Project.load = function(name, callback) {
    for (var i = 0; i < Project.searchPaths.length; ++i) {
        var searchPath = Project.searchPaths[i];
        var filepath = searchPath + '/' + name;

        if (fs.existsSync(filepath) || fs.existsSync(filepath + '.js')) {
            try {
                var module = require(filepath);
                var config = module(novaform, novastl);
                return new Project(config);
            } catch (e) {
                if (callback) {
                    callback(e);
                }
            }
            return null;
        }
    }
    return null;
};
Project.prototype.findComponent = function(name) {
    var component = _.findWhere(this.config.components, { name : name })
    return component;
}
<commit_msg>A temporary hack: print the full error message including stack trace<commit_after>var getopt = require('node-getopt')
    , q = require('q')
    , _ = require('underscore')
    , util = require('util')
    , fs = require('fs')
    , novaform = require('novaform')
    , novastl = require('novastl')
    , AWS = require('aws-sdk')
    , uuid = require('node-uuid')
    , moment = require('moment')
    , stackUtils = require('../stack-utils')
    , assert = require('assert');

var cmdopts = module.exports.opts = getopt.create([
    ['w', 'wait', 'Wait for completion'],
    ['h', 'help', 'Display help']
]);

cmdopts.setHelp('[[OPTIONS]]\n');

function Command(config, commonOptions, args, helpCallback) {
    if (!(this instanceof Command)) {
        return new Command(name, properties);
    }

    this.config = config;
    this.displayHelpAndExit = helpCallback;

    var opts = this.opts = cmdopts.parse(args);
    this.commonOptions = commonOptions;
    this.commandOptions = this.opts.options;

    if (opts.options.help) {
        helpCallback();
        return;
    }

    if (_(opts.argv).isEmpty()) {
        helpCallback('Missing project/component reference');
        return;
    } else if (opts.argv.length !== 1) {
        helpCallback('Too many project/component references specified');
        return;
    }

    var ref = opts.argv[0];
    ref = this.ref = Ref.parse(ref);
    if (!this.ref) {
        helpCallback('Invalid project ref');
        return;
    }
    if (!this.ref.component) {
        helpCallback('Component was not specified')
        return;
    }

    this.project = Project.load(this.ref.project, function(e) {
        helpCallback(util.format('Failed to load project "%s": %s', ref.project, e.message));
    });
    if (!this.project) {
        helpCallback(util.format('Could not find project "%s"', this.ref.project));
        return;
    }

    this.component = this.project.findComponent(this.ref.component);
    if (!this.component) {
        helpCallback(util.format('Component "%s" does not exist', this.ref.component));
        return;
    }
}

Command.prototype._waitForStack = function(options, shouldStopCallback) {
    var cfn = options.cfn;
    var stackName = options.stackName;
    var maxWaitSeconds = options.maxWaitSeconds || 15 * 60;
    var waitSeconds = options.waitSeconds || 1;

    var start = moment();
    var maxEnd = moment(start);
    maxEnd.add(maxWaitSeconds, 'seconds');

    var getStackStatus = q.nbind(stackUtils.getStackStatus, stackUtils);

    var f = function() {
        return getStackStatus(cfn, stackName).then(function(status) {
            var callbackResult = shouldStopCallback(status);
            if (callbackResult) {
                return callbackResult;
            }
            if (moment().isAfter(maxEnd)) {
                throw new Error('Timeout');
            }
            return q.delay(waitSeconds * 1000).then(f);
        });
    };

    return f();
};

Command.prototype.execute = function() {
    var that = this;
    return q().then(function() {
        // TODO: validate project's components, make sure dependencies exist
        var deplist = [];

        function walkDeps(result, componentName, walked) {
            if (!walked) {
                walked = [];
            }
            if (walked.indexOf(componentName) !== -1) {
                throw new Error('recursive dependency');
            }

            walked.push(componentName);

            var component = that.project.findComponent(componentName);
            if (!component) {
                throw new Error(util.format('Could not find dependent component "%s"', componentName));
            }
            var deps = component.dependencies.map(function(depname) {
                var w = walked ? walked.slice() : [];
                return walkDeps([depname], depname, w);
            });

            deps.sort(function(a, b) { return a.length - b.length; });

            deps.forEach(function(deps) {
                deps.reverse();
                deps.forEach(function(d) {
                    var idx = result.indexOf(d);
                    if (idx !== -1) {
                        result.splice(idx, 1);
                    }
                    result.unshift(d);
                });
            });

            return result;
        }

        var deplist = walkDeps([], that.component.name);
        return deplist;
    }).then(function(dependencies) {
        // fetch output for each dependent component

        var getStackOutput = q.nbind(stackUtils.getStackOutput, stackUtils);
        if (that.commonOptions.verbose) {
            console.log('Fetching outputs of dependent stacks...');
        }

        var region = that.component.region;
        var cfn = that.cfn = new AWS.CloudFormation({ region : region });

        var outputsPromises = dependencies.map(function(depname) {
            return Ref(that.ref.project, depname).makeStackName();
        }).map(function(stackName) {
            return getStackOutput(cfn, stackName);
        });

        return q.all(outputsPromises).then(function(results) {
            return _.object(_.zip(dependencies, results));
        }).catch(function(e) {
            if (e === stackUtils.Status.DOES_NOT_EXIST) {
                throw new Error('One of the dependent stacks is not yet deployed!');
            }
            throw e;
        });
    }).then(function(dependencies) {
        // TODO: inject dependencies into the component
        // var dependencies = {
            // infrastructure: { vpc: 'vpc-123' }
        // };

        return dependencies;
    }).then(function(dependencies) {
        // build cloudformation resources
        if (that.commonOptions.verbose) {
            console.log('Generating cloudformation template...');
        }

        var stackName = that.ref.makeStackName();
        var deploymentDate = moment.utc();
        var deploymentId = uuid.v4();

        var result = that.component.build(dependencies);
        var stack = novaform.Stack(stackName);
        stack.add(result.resourceGroups);
        if (result.outputs) {
            stack.add(result.outputs);
        }

        var templateBody = stack.toJson();
        return {
            projectName: that.ref.project,
            componentName: that.ref.component,
            deploymentDate: deploymentDate,
            deploymentId: deploymentId,
            stackName: stackName,
            templateBody: templateBody,
        };
    }).then(function(deploymentConfig) {
        if (that.commonOptions.verbose) {
            console.log('Uploading cloudformation template to S3...');
        }

        var bucketname = that.config.s3.bucket;
        var region = that.config.s3.region;
        var datestring = deploymentConfig.deploymentDate.format();
        var keypath = util.format('%s%s/%s-%s',
            that.config.s3.keyPrefix, that.ref.project,
            deploymentConfig.stackName, datestring);

        var params = {
            Bucket: bucketname,
            Key: keypath,
            Body: deploymentConfig.templateBody,
        };

        function s3_endpoint(region) {
            if (!region) {
                return 'https://s3.amazonaws.com';
            }
            return util.format('https://s3-%s.amazonaws.com', region);
        }

        var s3 = new AWS.S3({ region : region });
        var s3upload = q.nbind(s3.upload, s3);
        return s3upload(params).then(function() {
            var s3endpoint = s3_endpoint(that.config.s3.region);
            var url = util.format('%s/%s/%s', s3endpoint, bucketname, keypath);
            return _.extend(deploymentConfig, {
                templateUrl: url,
            });
        }).catch(function(e) {
            throw new Error(util.format('Failed to upload to S3: %s', JSON.stringify(e)));
        });
    }).then(function(deploymentConfig) {
        // initiate cloudformation deployment
        var cfn = that.cfn;
        var getStackStatus = q.nbind(stackUtils.getStackStatus, stackUtils);
        if (that.commonOptions.verbose) {
            console.log('Checking cloudformation stack status...');
        }
        return getStackStatus(cfn, deploymentConfig.stackName).then(function(status) {
            if (status !== stackUtils.Status.ROLLBACK_COMPLETE) {
                // all good, nothing to do here.
                return status;
            }

            if (that.commonOptions.verbose) {
                console.log('Stack was stuck in a rollback state, deleting it before deploying...');
            }

            // oh, previous stack creation failed and we cannot update failed stack
            // the only option is to delete the stack and create it again.
            var deleteStack = q.nbind(cfn.deleteStack, cfn);
            return deleteStack({
                StackName: deploymentConfig.stackName
            }).then(function(data) {
                return that._waitForStack({
                    cfn: cfn,
                    stackName: deploymentConfig.stackName,
                }, function(status) {
                    if (status === stackUtils.Status.DOES_NOT_EXIST) {
                        return status;
                    }
                    console.log('Still waiting...');
                    return null;
                });
            }).catch(function(err) {
                throw new Error(util.format('Failed to delete stack "%s": %j', deploymentConfig.stackName, err));
            });
        }).then(function(status) {
            if (that.commonOptions.verbose) {
                console.log('Deploying cloudformation stack...');
            }
            if (status === stackUtils.Status.DOES_NOT_EXIST) {
                // create a new stack
                var createStack = q.nbind(cfn.createStack, cfn);
                return createStack({
                    Capabilities: [ 'CAPABILITY_IAM' ], // TODO: this is only needed for some stacks that create iam roles, hm.
                    StackName: deploymentConfig.stackName,
                    TemplateURL: deploymentConfig.templateUrl,
                    Tags: [
                        { Key: 'nova-project', Value: deploymentConfig.projectName },
                        { Key: 'nova-component', Value: deploymentConfig.componentName },
                    ],
                }).then(function(data) {
                    return _.extend(deploymentConfig, {
                        cfn: cfn,
                        stackId: data.StackId,
                    });
                }).catch(function(err) {
                    throw new Error(util.format('Failed to initiate stack creation:\n%j', err));
                });
            } else {
                if (!stackUtils.isStatusComplete(status)) {
                    // already in progress?
                    throw new Error(util.format('Stack is not in a valid state for deployment (%s)', status));
                }

                // update an existing stack
                var updateStack = q.nbind(cfn.updateStack, cfn);
                return updateStack({
                    Capabilities: [ 'CAPABILITY_IAM' ], // TODO: this is only needed for some stacks that create iam roles, hm.
                    StackName: deploymentConfig.stackName,
                    TemplateURL: deploymentConfig.templateUrl,
                }).then(function(data) {
                    return _.extend(deploymentConfig, {
                        cfn: cfn,
                        stackId: data.StackId,
                    });
                }).catch(function(err) {
                    throw new Error(util.format('Failed to initiate stack creation:\n%j', err));
                });
            }
        });
    }).then(function(deploymentConfig) {
        // wait for completion
        if (that.commandOptions.wait) {
            if (that.commonOptions.verbose) {
                console.log('Waiting for deployment to complete...');
            }

            return that._waitForStack({
                cfn: deploymentConfig.cfn,
                stackName: deploymentConfig.stackName,
            }, function(status) {
                if (stackUtils.isStatusFailed(status)
                    || stackUtils.isStatusRolledBack(status)
                    || stackUtils.isStatusRollingback(status)) {
                    throw new Error('Stack deployment failed');
                }
                if (!stackUtils.isStatusComplete(status)) {
                    if (that.commonOptions.verbose) {
                        console.log('Still waiting...');
                    }
                    return null;
                }
                return deploymentConfig;
            });
        }
        return deploymentConfig;
    }).then(function(deploymentConfig) {
        // TODO: fetch stack output and print it
        var output = {
            project: deploymentConfig.projectName,
            component: deploymentConfig.componentName,
            deploymentId: deploymentConfig.deploymentId,
            stackId: deploymentConfig.stackId,
        };

        if (that.commonOptions['output-format'] == 'json') {
            console.log(output);
        } else if (that.commonOptions['output-format'] == 'text') {
            console.log('Output:\n')
            for (var key in output) {
                var value = output[key];
                console.log(util.format('%s: %s', key, value));
            }
        }
    }).catch(function(e) {
        // TODO: differentiate between internal errors and valid exits like timeout or stack deployment failed
        console.error(util.format('Internal error: %s', e.stack));
        console.log(e);
    });
}

Command.usageText = '[options] <project>/<component>'
Command.descriptionText = 'Deploys project component';
Command.optionsText = cmdopts.getHelp();

module.exports = Command;

// helpers
// TODO: this should probably be shared in lib/shared ?

function Ref(project, component) {
    if (!(this instanceof Ref)) {
        return new Ref(project, component);
    }

    this.project = project;
    this.component = component;
}

Ref.parse = function(ref) {
    var project;
    var component;

    if (typeof ref !== 'string') {
        return undefined;
    }
    var l = ref.split('/');
    if (l.length > 2) {
        return undefined;
    }
    project = l[0];
    component = l[1];

    return new Ref(project, component);
}

Ref.prototype.makeStackName = function() {
    assert(this.project);
    assert(this.component);

    var project = this.project[0].toUpperCase() + this.project.substr(1).toLowerCase();
    var component = this.component[0].toUpperCase() + this.component.substr(1).toLowerCase();

    return project + component;
};

function Project(config) {
    this.config = config;
}
Project.searchPaths = [process.cwd()];
Project.load = function(name, callback) {
    for (var i = 0; i < Project.searchPaths.length; ++i) {
        var searchPath = Project.searchPaths[i];
        var filepath = searchPath + '/' + name;

        if (fs.existsSync(filepath) || fs.existsSync(filepath + '.js')) {
            try {
                var module = require(filepath);
                var config = module(novaform, novastl);
                return new Project(config);
            } catch (e) {
                if (callback) {
                    callback(e);
                }
            }
            return null;
        }
    }
    return null;
};
Project.prototype.findComponent = function(name) {
    var component = _.findWhere(this.config.components, { name : name })
    return component;
}
<|endoftext|>


<commit_before>import React, { StyleSheet, Text, View, Animated, Easing, PropTypes, Platform } from 'react-native';
import NavButton from './NavButton';
import * as Styles from '../styles';

const propTypes = {
  backButtonComponent: PropTypes.func,
  borderBottomWidth: PropTypes.number,
  borderColor: PropTypes.string,
  customAction: PropTypes.func,
  goBack: PropTypes.func,
  goForward: PropTypes.func,
  leftProps: PropTypes.object,
  rightCorner: PropTypes.func,
  rightProps: PropTypes.object,
  route: PropTypes.object.isRequired,
  titleProps: PropTypes.object,
  titleStyle: Text.propTypes.style,
  willDisappear: PropTypes.bool,
};

const styles = StyleSheet.create({
  navbar: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    height: Styles.NAV_BAR_HEIGHT,
    justifyContent: 'center',
    alignItems: 'center',
    flexDirection: 'row',
    paddingTop: Styles.NAV_BAR_PADDING_TOP,
  },
  navbarText: {
    color: 'white',
    fontSize: 17,
    margin: 10,
    marginTop: Styles.NAV_BAR_TEXT_MARGIN_TOP,
    marginBottom: Styles.NAV_BAR_TEXT_MARGIN_TOP,
    fontWeight: '600',
    textAlign: Styles.NAV_BAR_TEXT_ALIGN,
    alignItems: 'center',
  },
  corner: {
    flex: 1,
    justifyContent: 'center',
  },

  alignLeft: {
    alignItems: 'flex-start',
  },
  alignRight: {
    alignItems: 'flex-end',
  },
  buttonTextLeft: {
    marginLeft: 10,
  },
  buttonTextRight: {
    marginRight: 10,
  },
});

class NavBarContent extends React.Component {
  constructor(props) {
    super(props);

    this.goBack = this.goBack.bind(this);
    this.goForward = this.goForward.bind(this);
    this.customAction = this.customAction.bind(this);

    this.state = {
      opacity: this.props.willDisappear ? new Animated.Value(1) : new Animated.Value(0),
    };
  }

  componentWillReceiveProps(newProps) {
    if (newProps.route !== this.props.route) {
      this.state.opacity.setValue(this.props.willDisappear ? 1 : 0);

      setTimeout(() => {
        Animated.timing(
          this.state.opacity,
          {
            fromValue: this.props.willDisappear ? 1 : 0,
            toValue: this.props.willDisappear ? 0 : 1,
            duration: 300,
            easing: Easing.easeOutQuad,
          }
        ).start();
      }, 0);
    } else if (newProps.route === this.props.route) {
      this.state.opacity.setValue(1);
    }
  }

  goBack() {
    if (!this.props.willDisappear) {
      this.props.goBack();
    }
  }

  goForward(route) {
    this.props.goForward(route);
  }

  customAction(opts) {
    this.props.customAction(opts);
  }

  render() {
    const transitionStyle = {
      opacity: this.state.opacity,
    };

    let leftCorner;
    let LeftCorner;
    let rightCorner;
    let RightCorner;
    let titleComponent;
    let leftCornerContent;
    let rightCornerContent;
    let titleContent;
    let TitleComponent;
    let trans;
    let width;
    let color;

    /**
     * Set leftCorner
     * (defaults to "Back"-button for routes with index > 0)
     */

    if (this.props.route.leftCorner) {
      LeftCorner = this.props.route.leftCorner;
      leftCornerContent = (
        <LeftCorner
          toRoute={this.goForward}
          customAction={this.customAction}
          {...this.props.leftProps}
          {...this.props.route.leftCornerProps}
        />
      );
    } else if (this.props.route.index > 0) {
      leftCornerContent = (
        <NavButton onPress={this.goBack} backButtonComponent={this.props.backButtonComponent} />
      );
    }

    if (Platform.OS === 'ios' || this.props.route.leftCorner || this.props.route.index > 0) {
      leftCorner = (
        <View style={[styles.corner, styles.alignLeft]}>
          {leftCornerContent}
        </View>
      );
    }

    /**
     * Set rightCorner
     */

    if (this.props.route.rightCorner || this.props.rightCorner) {
      RightCorner = this.props.route.rightCorner || this.props.rightCorner;
      rightCornerContent = (
        <RightCorner
          toRoute={this.goForward}
          customAction={this.customAction}
          {...this.props.rightProps}
          {...this.props.route.rightCornerProps}
        />
      );
    }

    if (Platform.OS === 'ios' || this.props.route.rightCorner || this.props.rightCorner) {
      rightCorner = (
        <View style={[styles.corner, styles.alignRight]}>
          {rightCornerContent}
        </View>
      );
    }

    /**
     * Set title message
     */

    if (this.props.route.titleComponent) {
      TitleComponent = this.props.route.titleComponent;
      titleContent = <TitleComponent {...this.props.titleProps} />;
    } else {
      titleContent = (
        <Text style={[styles.navbarText, this.props.titleStyle]} numberOfLines={1}>
          {this.props.route.name}
        </Text>
      );
    }

    titleComponent = (
      <View style={{ flex: 6 }}>
        {titleContent}
      </View>
    );

    if (this.props.route.trans === true) {
      trans = { backgroundColor: 'transparent', borderBottomWidth: 0 };
    } else {
      trans = {};
    }

    width = this.props.borderBottomWidth ? this.props.borderBottomWidth : 0;
    color = this.props.borderColor ? this.props.borderColor : null;

    return (
      <Animated.View
        style={
          [
            styles.navbar,
            transitionStyle,
            this.props.route.headerStyle,
            { borderBottomWidth: width, borderColor: color },
            trans,
          ]
        }
      >
        {leftCorner}
        {titleComponent}
        {rightCorner}
      </Animated.View>
    );
  }
}

NavBarContent.propTypes = propTypes;

export default NavBarContent;
<commit_msg>Ensure animation is called on initial render<commit_after>import React, { StyleSheet, Text, View, Animated, Easing, PropTypes, Platform } from 'react-native';
import NavButton from './NavButton';
import * as Styles from '../styles';

const propTypes = {
  backButtonComponent: PropTypes.func,
  borderBottomWidth: PropTypes.number,
  borderColor: PropTypes.string,
  customAction: PropTypes.func,
  goBack: PropTypes.func,
  goForward: PropTypes.func,
  leftProps: PropTypes.object,
  rightCorner: PropTypes.func,
  rightProps: PropTypes.object,
  route: PropTypes.object.isRequired,
  titleProps: PropTypes.object,
  titleStyle: Text.propTypes.style,
  willDisappear: PropTypes.bool,
};

const styles = StyleSheet.create({
  navbar: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    height: Styles.NAV_BAR_HEIGHT,
    justifyContent: 'center',
    alignItems: 'center',
    flexDirection: 'row',
    paddingTop: Styles.NAV_BAR_PADDING_TOP,
  },
  navbarText: {
    color: 'white',
    fontSize: 17,
    margin: 10,
    marginTop: Styles.NAV_BAR_TEXT_MARGIN_TOP,
    marginBottom: Styles.NAV_BAR_TEXT_MARGIN_TOP,
    fontWeight: '600',
    textAlign: Styles.NAV_BAR_TEXT_ALIGN,
    alignItems: 'center',
  },
  corner: {
    flex: 1,
    justifyContent: 'center',
  },

  alignLeft: {
    alignItems: 'flex-start',
  },
  alignRight: {
    alignItems: 'flex-end',
  },
  buttonTextLeft: {
    marginLeft: 10,
  },
  buttonTextRight: {
    marginRight: 10,
  },
});

class NavBarContent extends React.Component {
  constructor(props) {
    super(props);

    this.goBack = this.goBack.bind(this);
    this.goForward = this.goForward.bind(this);
    this.customAction = this.customAction.bind(this);

    this.state = {
      opacity: this.props.willDisappear ? new Animated.Value(1) : new Animated.Value(0),
    };

    this.doAnimation = this.doAnimation.bind(this);
  }

  componentDidMount() {
    // componentWillReceiveProps is not called on initial render, ensure animation is.
    this.doAnimation();
  }

  componentWillReceiveProps(newProps) {
    if (newProps.route !== this.props.route) {
      this.state.opacity.setValue(this.props.willDisappear ? 1 : 0);
      this.doAnimation();
    } else if (newProps.route === this.props.route) {
      this.state.opacity.setValue(1);
    }
  }

  doAnimation() {
    setTimeout(() => {
      Animated.timing(
        this.state.opacity,
        {
          fromValue: this.props.willDisappear ? 1 : 0,
          toValue: this.props.willDisappear ? 0 : 1,
          duration: 300,
          easing: Easing.easeOutQuad,
        }
      ).start();
    }, 0);
  }

  goBack() {
    if (!this.props.willDisappear) {
      this.props.goBack();
    }
  }

  goForward(route) {
    this.props.goForward(route);
  }

  customAction(opts) {
    this.props.customAction(opts);
  }

  render() {
    const transitionStyle = {
      opacity: this.state.opacity,
    };

    let leftCorner;
    let LeftCorner;
    let rightCorner;
    let RightCorner;
    let titleComponent;
    let leftCornerContent;
    let rightCornerContent;
    let titleContent;
    let TitleComponent;
    let trans;
    let width;
    let color;

    /**
     * Set leftCorner
     * (defaults to "Back"-button for routes with index > 0)
     */

    if (this.props.route.leftCorner) {
      LeftCorner = this.props.route.leftCorner;
      leftCornerContent = (
        <LeftCorner
          toRoute={this.goForward}
          customAction={this.customAction}
          {...this.props.leftProps}
          {...this.props.route.leftCornerProps}
        />
      );
    } else if (this.props.route.index > 0) {
      leftCornerContent = (
        <NavButton onPress={this.goBack} backButtonComponent={this.props.backButtonComponent} />
      );
    }

    if (Platform.OS === 'ios' || this.props.route.leftCorner || this.props.route.index > 0) {
      leftCorner = (
        <View style={[styles.corner, styles.alignLeft]}>
          {leftCornerContent}
        </View>
      );
    }

    /**
     * Set rightCorner
     */

    if (this.props.route.rightCorner || this.props.rightCorner) {
      RightCorner = this.props.route.rightCorner || this.props.rightCorner;
      rightCornerContent = (
        <RightCorner
          toRoute={this.goForward}
          customAction={this.customAction}
          {...this.props.rightProps}
          {...this.props.route.rightCornerProps}
        />
      );
    }

    if (Platform.OS === 'ios' || this.props.route.rightCorner || this.props.rightCorner) {
      rightCorner = (
        <View style={[styles.corner, styles.alignRight]}>
          {rightCornerContent}
        </View>
      );
    }

    /**
     * Set title message
     */

    if (this.props.route.titleComponent) {
      TitleComponent = this.props.route.titleComponent;
      titleContent = <TitleComponent {...this.props.titleProps} />;
    } else {
      titleContent = (
        <Text style={[styles.navbarText, this.props.titleStyle]} numberOfLines={1}>
          {this.props.route.name}
        </Text>
      );
    }

    titleComponent = (
      <View style={{ flex: 6 }}>
        {titleContent}
      </View>
    );

    if (this.props.route.trans === true) {
      trans = { backgroundColor: 'transparent', borderBottomWidth: 0 };
    } else {
      trans = {};
    }

    width = this.props.borderBottomWidth ? this.props.borderBottomWidth : 0;
    color = this.props.borderColor ? this.props.borderColor : null;

    return (
      <Animated.View
        style={
          [
            styles.navbar,
            transitionStyle,
            this.props.route.headerStyle,
            { borderBottomWidth: width, borderColor: color },
            trans,
          ]
        }
      >
        {leftCorner}
        {titleComponent}
        {rightCorner}
      </Animated.View>
    );
  }
}

NavBarContent.propTypes = propTypes;

export default NavBarContent;
<|endoftext|>