<commit_before>// initial version, doesn't bundle vfs_fonts yet

var gulp = require('gulp');
var webpack = require('webpack-stream');
var gutil = require('gulp-util');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var sourcemaps = require('gulp-sourcemaps');
var replace = require('gulp-replace');
var mocha = require('gulp-spawn-mocha');
var jshint = require('gulp-jshint');
var DEBUG = process.env.NODE_ENV === 'debug',
  		 CI = process.env.CI === 'true';

var uglifyOptions = {
	preserveComments: 'some',
	// source_map: 'pdfmake.min.js.map',
	compress: {
		drop_console: true
	},
	mangle: {
		except: ['HeadTable', 'NameTable', 'CmapTable', 'HheaTable', 'MaxpTable', 'HmtxTable', 'PostTable', 'OS2Table', 'LocaTable', 'GlyfTable']
	}
};

gulp.task('default', [/*'lint',*/ 'test', 'build']);
gulp.task('build', function() {
	return gulp.src('src/browser-extensions/pdfMake.js')
		.pipe(webpack(require('./webpack.config.js'), null, reportWebPackErrors))
		.pipe(gulp.dest('build'))
		.pipe(sourcemaps.init())
		.pipe(uglify(uglifyOptions))
		.pipe(rename({ extname: '.min.js' }))
		.pipe(sourcemaps.write('./'))
		.pipe(gulp.dest('build'));
});

function reportWebPackErrors(err, stats) {
  if(err) throw new gutil.PluginError("webpack", err);
  gutil.log("[webpack]", stats.toString({
  }));
}

gulp.task('test', ['prepareTestEnv'], function(cb) {
	return gulp.src(['test-env/tests/**/*.js'])
		.pipe(mocha({
			debugBrk: DEBUG,
			R: CI ? 'spec' : 'nyan'
		}));
});

gulp.task('prepareTestEnv', ['copy-src-with-exposed-test-methods', 'copy-tests']);

gulp.task('copy-src-with-exposed-test-methods', function() {
	return gulp.src(['src/**/*.js'], { base: './' })
		.pipe(replace(/^(\/(\*)*TESTS.*$)/gm, '/$1'))
		.pipe(gulp.dest('test-env'));
});

gulp.task('copy-tests', function() {
	return gulp.src('tests/**/*.*', { base: './' })
		.pipe(gulp.dest('test-env'))
});

gulp.task('lint', function() {
  return gulp.src(['./src/**/*.js'])
    .pipe(jshint())
		.pipe(jshint.reporter());
});
<commit_msg>reformat<commit_after>// initial version, doesn't bundle vfs_fonts yet

var gulp = require('gulp');
var webpack = require('webpack-stream');
var gutil = require('gulp-util');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var sourcemaps = require('gulp-sourcemaps');
var replace = require('gulp-replace');
var mocha = require('gulp-spawn-mocha');
var jshint = require('gulp-jshint');
var DEBUG = process.env.NODE_ENV === 'debug',
  		 CI = process.env.CI === 'true';

var uglifyOptions = {
	preserveComments: 'some',
	// source_map: 'pdfmake.min.js.map',
	compress: {
		drop_console: true
	},
	mangle: {
		except: ['HeadTable', 'NameTable', 'CmapTable', 'HheaTable', 'MaxpTable', 'HmtxTable', 'PostTable', 'OS2Table', 'LocaTable', 'GlyfTable']
	}
};

gulp.task('default', [/*'lint',*/ 'test', 'build']);
gulp.task('build', function () {
	return gulp.src('src/browser-extensions/pdfMake.js')
		.pipe(webpack(require('./webpack.config.js'), null, reportWebPackErrors))
		.pipe(gulp.dest('build'))
		.pipe(sourcemaps.init())
		.pipe(uglify(uglifyOptions))
		.pipe(rename({extname: '.min.js'}))
		.pipe(sourcemaps.write('./'))
		.pipe(gulp.dest('build'));
});

function reportWebPackErrors(err, stats) {
	if (err) {
		throw new gutil.PluginError("webpack", err);
	}
	gutil.log("[webpack]", stats.toString({
	}));
}

gulp.task('test', ['prepareTestEnv'], function (cb) {
	return gulp.src(['test-env/tests/**/*.js'])
		.pipe(mocha({
			debugBrk: DEBUG,
			R: CI ? 'spec' : 'nyan'
		}));
});

gulp.task('prepareTestEnv', ['copy-src-with-exposed-test-methods', 'copy-tests']);

gulp.task('copy-src-with-exposed-test-methods', function () {
	return gulp.src(['src/**/*.js'], {base: './'})
		.pipe(replace(/^(\/(\*)*TESTS.*$)/gm, '/$1'))
		.pipe(gulp.dest('test-env'));
});

gulp.task('copy-tests', function () {
	return gulp.src('tests/**/*.*', {base: './'})
		.pipe(gulp.dest('test-env'))
});

gulp.task('lint', function () {
	return gulp.src(['./src/**/*.js'])
		.pipe(jshint())
		.pipe(jshint.reporter());
});
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc filter
 * @name limitTo
 * @kind function
 *
 * @description
 * Creates a new array or string containing only a specified number of elements. The elements
 * are taken from either the beginning or the end of the source array or string, as specified by
 * the value and sign (positive or negative) of `limit`.
 *
 * @param {Array|string} input Source array or string to be limited.
 * @param {string|number} limit The length of the returned array or string. If the `limit` number
 *     is positive, `limit` number of items from the beginning of the source array/string are copied.
 *     If the number is negative, `limit` number  of items from the end of the source array/string
 *     are copied. The `limit` will be trimmed if it exceeds `array.length`
 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
 *     had less than `limit` elements.
 *
 * @example
   <example>
     <file name="index.html">

         Limit {{numbers}} to: <input type="integer" ng-model="numLimit">

Output numbers: {{ numbers | limitTo:numLimit }}

         Limit {{letters}} to: <input type="integer" ng-model="letterLimit">

Output letters: {{ letters | limitTo:letterLimit }}

     </file>
     <file name="protractor.js" type="protractor">
       var numLimitInput = element(by.model('numLimit'));
       var letterLimitInput = element(by.model('letterLimit'));
       var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
       var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));

       it('should limit the number array to first three items', function() {
         expect(numLimitInput.getAttribute('value')).toBe('3');
         expect(letterLimitInput.getAttribute('value')).toBe('3');
         expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
         expect(limitedLetters.getText()).toEqual('Output letters: abc');
       });

       it('should update the output when -3 is entered', function() {
         numLimitInput.clear();
         numLimitInput.sendKeys('-3');
         letterLimitInput.clear();
         letterLimitInput.sendKeys('-3');
         expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
         expect(limitedLetters.getText()).toEqual('Output letters: ghi');
       });

       it('should not exceed the maximum size of input array', function() {
         numLimitInput.clear();
         numLimitInput.sendKeys('100');
         letterLimitInput.clear();
         letterLimitInput.sendKeys('100');
         expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
         expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
       });
     </file>
   </example>
 */
function limitToFilter(){
  return function(input, limit) {
    if (!isArray(input) && !isString(input)) return input;

    if (Math.abs(Number(limit)) === Infinity) {
      limit = Number(limit);
    } else {
      limit = int(limit);
    }

    if (isString(input)) {
      //NaN check on limit
      if (limit) {
        return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
      } else {
        return "";
      }
    }

    var out = [],
      i, n;

    // if abs(limit) exceeds maximum length, trim it
    if (limit > input.length)
      limit = input.length;
    else if (limit < -input.length)
      limit = -input.length;

    if (limit > 0) {
      i = 0;
      n = limit;
    } else {
      i = input.length + limit;
      n = input.length;
    }

    for (; i<n; i++) {
      out.push(input[i]);
    }

    return out;
  };
}
<commit_msg>docs(limitTo): update example to use a module<commit_after>'use strict';

/**
 * @ngdoc filter
 * @name limitTo
 * @kind function
 *
 * @description
 * Creates a new array or string containing only a specified number of elements. The elements
 * are taken from either the beginning or the end of the source array or string, as specified by
 * the value and sign (positive or negative) of `limit`.
 *
 * @param {Array|string} input Source array or string to be limited.
 * @param {string|number} limit The length of the returned array or string. If the `limit` number
 *     is positive, `limit` number of items from the beginning of the source array/string are copied.
 *     If the number is negative, `limit` number  of items from the end of the source array/string
 *     are copied. The `limit` will be trimmed if it exceeds `array.length`
 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
 *     had less than `limit` elements.
 *
 * @example
   <example module="limitToExample">
     <file name="index.html">

         Limit {{numbers}} to: <input type="integer" ng-model="numLimit">

Output numbers: {{ numbers | limitTo:numLimit }}

         Limit {{letters}} to: <input type="integer" ng-model="letterLimit">

Output letters: {{ letters | limitTo:letterLimit }}

     </file>
     <file name="protractor.js" type="protractor">
       var numLimitInput = element(by.model('numLimit'));
       var letterLimitInput = element(by.model('letterLimit'));
       var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
       var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));

       it('should limit the number array to first three items', function() {
         expect(numLimitInput.getAttribute('value')).toBe('3');
         expect(letterLimitInput.getAttribute('value')).toBe('3');
         expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
         expect(limitedLetters.getText()).toEqual('Output letters: abc');
       });

       it('should update the output when -3 is entered', function() {
         numLimitInput.clear();
         numLimitInput.sendKeys('-3');
         letterLimitInput.clear();
         letterLimitInput.sendKeys('-3');
         expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
         expect(limitedLetters.getText()).toEqual('Output letters: ghi');
       });

       it('should not exceed the maximum size of input array', function() {
         numLimitInput.clear();
         numLimitInput.sendKeys('100');
         letterLimitInput.clear();
         letterLimitInput.sendKeys('100');
         expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
         expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
       });
     </file>
   </example>
 */
function limitToFilter(){
  return function(input, limit) {
    if (!isArray(input) && !isString(input)) return input;

    if (Math.abs(Number(limit)) === Infinity) {
      limit = Number(limit);
    } else {
      limit = int(limit);
    }

    if (isString(input)) {
      //NaN check on limit
      if (limit) {
        return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
      } else {
        return "";
      }
    }

    var out = [],
      i, n;

    // if abs(limit) exceeds maximum length, trim it
    if (limit > input.length)
      limit = input.length;
    else if (limit < -input.length)
      limit = -input.length;

    if (limit > 0) {
      i = 0;
      n = limit;
    } else {
      i = input.length + limit;
      n = input.length;
    }

    for (; i<n; i++) {
      out.push(input[i]);
    }

    return out;
  };
}
<|endoftext|>


<commit_before>var gulp    = require("gulp");
var becklyn = require("becklyn-gulp");
var isDebug = !!require("yargs").argv.debug || !!require("yargs").argv.dev;

gulp.task("css", becklyn.scss("layout/assets/**/*.scss", isDebug));
gulp.task("js",  becklyn.js("layout/assets/js/**/*.js", isDebug));

gulp.task("default", ["css", "js"]);
<commit_msg>Added gulp task to link the assets<commit_after>var gulp    = require("gulp");
var becklyn = require("becklyn-gulp");
var isDebug = !!require("yargs").argv.debug || !!require("yargs").argv.dev;

gulp.task("css", becklyn.scss("layout/assets/**/*.scss", isDebug));
gulp.task("js",  becklyn.js("layout/assets/js/**/*.js", isDebug));

gulp.task("assets", ["css", "js"],
    function ()
    {
        if (fs.existsSync("web/assets"))
        {
            fs.unlinkSync("web/assets");
        }

        fs.symlinkSync("../layout/public", "web/assets");
    }
);

gulp.task("default", ["css", "js", "assets"]);
<|endoftext|>


<commit_before>var gulp = require("gulp"),
  rename = require("gulp-rename"),
  concat = require("gulp-concat"),
  del = require("del"),
  browserify = require("browserify"),
  source = require("vinyl-source-stream"),
  buffer = require("vinyl-buffer"),
  uglify = require("gulp-uglify"),
  sourcemaps = require("gulp-sourcemaps"),
  babelify = require("babelify"),
  inline = require("gulp-inline-source"),
  fs = require("fs"),
  path = require("path");

gulp.task("default", function (done) {
  gulp.series(["styles", "scripts", "html"], "inline", "copy")();
  done();
});

gulp.task(
  "watch",
  gulp.series("default", function () {
    gulp.watch(["frontend/src/**/*"], ["default"]);
  })
);

gulp.task("styles", function () {
  return gulp
    .src("frontend/src/styles/**/*.css")
    .pipe(concat("style.css"))
    .pipe(gulp.dest("frontend/dist/styles"));
});

gulp.task("scripts", function () {
  var b = browserify({
    entries: "frontend/src/scripts/main.js",
    debug: true,
  });

  return b
    .transform("babelify", { presets: ["env"] })
    .bundle()
    .pipe(source("main.js"))
    .pipe(buffer())
    .pipe(sourcemaps.init({ loadmaps: true }))
    .pipe(uglify())
    .pipe(sourcemaps.write("./"))
    .pipe(gulp.dest("./frontend/dist/scripts"));
});

gulp.task("html", function () {
  return gulp.src("frontend/src/index.html").pipe(gulp.dest("./frontend/dist"));
});

gulp.task("inline", function (done) {
  htmlpath = path.resolve("frontend/dist/index.html");
  inline(
    htmlpath,
    {
      compress: true,
      rootpath: path.resolve("dist"),
    },
    function (err, html) {
      fs.writeFileSync("frontend/dist/index.html", html);
    }
  );
  done();
});

gulp.task('copy', function (done) {
  gulp.src('./frontend/src/favicon.ico')
      .pipe(gulp.dest('./frontend/dist/'));
  done();
});
<commit_msg>gulpfile.js: Fixed html inlining<commit_after>var gulp = require("gulp"),
  rename = require("gulp-rename"),
  concat = require("gulp-concat"),
  del = require("del"),
  browserify = require("browserify"),
  source = require("vinyl-source-stream"),
  buffer = require("vinyl-buffer"),
  uglify = require("gulp-uglify"),
  sourcemaps = require("gulp-sourcemaps"),
  babelify = require("babelify"),
  inlinesource = require("gulp-inline-source"),
  fs = require("fs"),
  path = require("path");

gulp.task("default", function (done) {
  gulp.series(["styles", "scripts", "copy"], "html")();
  done();
});

gulp.task(
  "watch",
  gulp.series("default", function () {
    gulp.watch(["frontend/src/**/*"], ["default"]);
  })
);

gulp.task("styles", function () {
  return gulp
    .src("frontend/src/styles/**/*.css")
    .pipe(concat("style.css"))
    .pipe(gulp.dest("frontend/dist/styles"));
});

gulp.task("scripts", function () {
  var b = browserify({
    entries: "frontend/src/scripts/main.js",
    debug: true,
  });

  return b
    .transform("babelify", { presets: ["env"] })
    .bundle()
    .pipe(source("main.js"))
    .pipe(buffer())
    .pipe(sourcemaps.init({ loadmaps: true }))
    .pipe(uglify())
    .pipe(sourcemaps.write("./"))
    .pipe(gulp.dest("./frontend/dist/scripts"));
});

gulp.task("html", function () {
  var options = {
    compress: false,
    rootpath: path.resolve("frontend/dist"),
  };

  return gulp.src('./frontend/src/index.html')
      .pipe(inlinesource(options))
      .pipe(gulp.dest('./frontend/dist'));
});

gulp.task('copy', function (done) {
  gulp.src('./frontend/src/favicon.ico')
      .pipe(gulp.dest('./frontend/dist/'));
  done();
});
<|endoftext|>


<commit_before>'use strict';
const path = require('path');
const _ = require('lodash');
const child_process = require('child_process');
const { app, dialog, ipcMain, nativeImage, Notification } = require('electron');
const Config = require('electron-config');
const notifier = require('node-notifier');

const spawn = child_process.spawn;
const exec = child_process.exec;
const Convert = require('ansi-to-html');
// const Sudoer = require('electron-sudo').default;
// const runCmd = require('./run_cmd');

const convert = new Convert();
const config = new Config();

const isWin = process.platform === 'win32';

const iconSuccessPath = path.join(__dirname, '../images/iconSuccess.png');
const iconErrorPath = path.join(__dirname, '../images/iconError.png');
// const iconSuccess = nativeImage.createFromPath(path.join(__dirname, '../images/iconSuccess.png'));
// const iconError = nativeImage.createFromPath(path.join(__dirname, '../images/iconError.png'));

notifier.on('click', () => {
  console.log('notifier on click');
  global.CP_WIN.show();
});

function guid() {
  return 'xyxyxyxy'.replace(/[xy]/g, (c) => {
    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); // eslint-disable-line
    return v.toString(16);
  });
}

// Ensure immutable, don't know if config.get always returns new data.
const cmds = _.cloneDeep(config.get('cmds') || []);
for (const c of cmds) {
  c.outputs = [];
}

const cmdHash = {};
cmds.forEach(cmd => cmdHash[cmd.id] = cmd); // eslint-disable-line

function stopCmd(cmd) {
  console.log('stopping cmd: ', cmd.id);
  if (!cmd || !cmd.process) return;
  try {
    if (isWin) {
      console.log('pid: ', cmd.process.pid);
      child_process.exec('taskkill /pid ' + cmd.process.pid + ' /T /F');
    } else {
      cmd.process.destroy();
    }
  } catch (e) {
    console.log('failed to kill the process: ', e);
  }
}

function stopAllCmds() {
  console.log('app will quit.');
  for (const cmd of cmds) {
    if (cmd.process) {
      stopCmd(cmd);
    }
  }
}

function getEnvPath() {
  let envPath = config.get('envPath');
  if (envPath) {
    if (isWin) {
      envPath = ';' + envPath;
    } else {
      envPath = ':/usr/local/bin:/usr/local/sbin:/usr/local/share/npm/bin:/usr/local/share/node/bin:' + envPath;
    }
  } else {
    envPath = ':/usr/local/bin:/usr/local/sbin:/usr/local/share/npm/bin:/usr/local/share/node/bin';
  }
  return envPath;
}

module.exports = {
  appWillQuit: stopAllCmds
};

function getOutputRowsLimit() {
  return config.get('outputRowsLimit') || 100;
}
/* ==================== Get Init Data ============================== */
ipcMain.on('GET_INIT_DATA', (evt) => {
  evt.sender.send('SET_INIT_DATA', {
    appVersion: app.getVersion(),
    envPath: config.get('envPath'),
    outputRowsLimit: getOutputRowsLimit(),
    isWin,
    cmds: cmds.map(c => Object.assign(_.pick(c, ['id', 'name', 'cmd', 'cwd', 'sudo', 'outputs', 'url', 'finishPrompt']), { status: c.process ? 'running' : 'stopped' })),
  });
});

/* ==================== Save Command ============================== */
ipcMain.on('SAVE_CMD', (evt, data, cmdId) => {
  console.log('saving cmd');
  const cmd = { id: cmdId || guid() };
  Object.assign(cmd, {
    name: data.name,
    cmd: data.cmd,
    sudo: data.sudo,
    finishPrompt: data.finishPrompt,
    cwd: data.cwd || null,
    url: data.url || null,
  });
  const newCmds = config.get('cmds') || [];
  if (cmdId) {
    const i = newCmds.findIndex(c => c.id === cmdId);
    newCmds.splice(i, 1, cmd);
  } else {
    newCmds.push(cmd);
  }
  config.set('cmds', newCmds);

  // Notify UI
  const curr = cmdId ? cmds.find(c => c.id === cmdId) : { status: 'stopped', outputs: [] };
  Object.assign(curr, cmd);
  if (!cmdId) {
    cmds.push(curr);
  }

  cmdHash[cmd.id] = curr;
  evt.sender.send('SAVE_CMD_SUCCESS', cmd);
});

/* ==================== Delete Command ============================== */
ipcMain.on('DELETE_CMD', (evt, cmdId) => {
  console.log('deleting cmd');
  // Stop the process if needed
  const cmd = cmdHash[cmdId];
  if (cmd.process) {
    stopCmd(cmd);
  }
  // Save to config
  const newCmds = config.get('cmds') || [];
  _.remove(newCmds, c => c.id === cmdId);
  config.set('cmds', newCmds);

  // Remove from store
  _.remove(cmds, c => c.id === cmdId);
  delete cmdHash[cmdId];

  evt.sender.send('DELETE_CMD_SUCCESS', cmdId);
});

/* ==================== Reorder Commands ============================== */
ipcMain.on('REORDER_CMDS', (evt, cmdIds) => {
  console.log('reordering cmds');

  // Save to config
  let newCmds = config.get('cmds') || [];
  const groups = _.groupBy(newCmds, 'id');
  newCmds = cmdIds.map(id => groups[id][0]);
  config.set('cmds', newCmds);

  // Update store order
  for (let i = 0; i < cmds.length; i++) {
    cmds[i] = cmdHash[cmdIds[i]];
  }

  evt.sender.send('REORDER_CMDS_SUCCESS', cmdIds);
});

/* ==================== Run Command ============================== */
ipcMain.on('RUN_CMD', (evt, cmdId, password) => { // eslint-disable-line
  const cmd = cmdHash[cmdId];
  console.log('running cmd: ', cmd.cmd);

  // notify the UI to show command is running. TODO: maybe use sync action is enough
  evt.sender.send('RUN_CMD_SUCCESS', cmdId);
  if (cmd.process) {
    // prevent from running multiple times.
    return;
  }

  let lineId = 0;
  cmd.outputs.length = 0;
  function onData(chunk, term) {
    let out = chunk.toString('utf8');

    if (!isWin) {
      if (/sorry.*try.*again.*/i.test(out)) {
        cmd.outputs.push({
          id: `${cmdId}_${lineId++}`, //eslint-disable-line
          text: 'Password is incorrect.',
        });
        evt.sender.send('CMD_OUTPUT', cmdId, [].concat(cmd.outputs));
        term.destroy();
        return;
      }

      if (cmd.sudo && /password/i.test(out)) {
        term.write(`${password}\r`);
        return;
      }
    }

    out = out.replace(/[\n\r]$/, '');
    const lines = out.split('\n');
    const rowsLimit = getOutputRowsLimit();
    for (const line of lines) {
      let text = line.replace(/>/g, '>').replace(/</g, '<');
      if (!isWin) text = convert.toHtml(text);
      cmd.outputs.push({
        id: `${cmdId}_${lineId++}`, //eslint-disable-line
        text,
      });
      if (cmd.outputs.length >= rowsLimit) {
        cmd.outputs.splice(0, cmd.outputs.length - rowsLimit);
      }
    }
    evt.sender.send('CMD_OUTPUT', cmdId, [].concat(cmd.outputs));
  }

  function onExit(code) {
    delete cmd.process;
    delete cmd._manualStop;
    if (!cmd._manualStop && cmd.finishPrompt) {
      notifier.notify({ // eslint-disable-line
        title: 'Command finished',
        message: `Command ${code > 0 ? 'failed' : 'finished'}: ${cmd.name} .`,
        icon: code > 0 ? iconErrorPath : iconSuccessPath,
        wait: true,
      });
    }
    evt.sender.send('CMD_FINISHED', cmdId, code);
  }

  let arr = cmd.cmd.match(/'[^']*'|"[^"]*"|[^ ]+/g) || [];
  arr = arr.map(m => m.replace(/^['"]|['"]/g, ''));

  const envPath = getEnvPath();
  if (isWin) {
    // use cmd.exe to execute command
    arr = ['/s', '/c'].concat(arr);
    const child = spawn('cmd', arr, {
      cwd: cmd.cwd || process.env.HOME,
      stdio: 'pipe',
      env: Object.assign({}, process.env, { Path: `${process.env.Path}${envPath}` }),
    });
    child.stdout.pipe(process.stdout);
    cmd.process = child;
    child.stdout.on('data', onData);
    child.stderr.on('data', onData);
    child.on('exit', onExit);
    child.on('error', () => onExit(99));
  } else {
    // use ptyw.js to execute command
    let target;
    if (cmd.sudo) {
      target = 'sudo';
    } else {
      target = arr.shift();
    }
    const ptyw = require('ptyw.js');

    const term = ptyw.spawn(target, arr, {
      name: 'xterm-color',
      cols: 80,
      rows: 30,
      cwd: cmd.cwd || process.env.HOME,
      detached: true,
      env: Object.assign({}, process.env, { PATH: `${process.env.PATH}${envPath}` }),
    });
    term.on('data', onData);
    term.on('exit', onExit);
  }
});

/* ==================== Stop Command ============================== */
ipcMain.on('STOP_CMD', (evt, cmdId) => {
  console.log('stopping cmd: ', cmdId);
  const cmd = cmdHash[cmdId];

  if (cmd.process) {
    cmd._manualStop = true;
    stopCmd(cmd);
  }
});

/* ==================== Save Settings ============================== */
ipcMain.on('SAVE_SETTINGS', (evt, data) => {
  console.log('save settings');
  config.set('envPath', data.envPath);
  config.set('outputRowsLimit', parseInt(data.outputRowsLimit, 10) || 100);
  evt.sender.send('SAVE_SETTINGS_SUCCESS');
});

/* ==================== Save Settings ============================== */
ipcMain.on('CLEAR_OUTPUT', (evt, cmdId) => {
  console.log('clear output');
  const cmd = cmdHash[cmdId];
  cmd.outputs.length = 0;
  evt.sender.send('CLEAR_OUTPUT_SUCCESS');
});

<commit_msg>Code clean.<commit_after>'use strict';
const path = require('path');
const _ = require('lodash');
const childProcess = require('child_process');
const { app, ipcMain } = require('electron');
const Config = require('electron-config');
const notifier = require('node-notifier');

const spawn = childProcess.spawn;
const exec = childProcess.exec;
const Convert = require('ansi-to-html');
// const Sudoer = require('electron-sudo').default;
// const runCmd = require('./run_cmd');

const convert = new Convert();
const config = new Config();

const isWin = process.platform === 'win32';

const iconSuccessPath = path.join(__dirname, '../images/iconSuccess.png');
const iconErrorPath = path.join(__dirname, '../images/iconError.png');
// const iconSuccess = nativeImage.createFromPath(path.join(__dirname, '../images/iconSuccess.png'));
// const iconError = nativeImage.createFromPath(path.join(__dirname, '../images/iconError.png'));

notifier.on('click', () => {
  console.log('notifier on click');
  global.CP_WIN.show();
});

function guid() {
  return 'xyxyxyxy'.replace(/[xy]/g, (c) => {
    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); // eslint-disable-line
    return v.toString(16);
  });
}

// Ensure immutable, don't know if config.get always returns new data.
const cmds = _.cloneDeep(config.get('cmds') || []);
for (const c of cmds) {
  c.outputs = [];
}

const cmdHash = {};
cmds.forEach(cmd => cmdHash[cmd.id] = cmd); // eslint-disable-line

function stopCmd(cmd) {
  console.log('stopping cmd: ', cmd.id);
  if (!cmd || !cmd.process) return;
  try {
    if (isWin) {
      exec('taskkill /pid ' + cmd.process.pid + ' /T /F');
    } else {
      cmd.process.destroy();
    }
  } catch (e) {
    console.log('failed to kill the process: ', e);
  }
}

function stopAllCmds() {
  console.log('app will quit.');
  for (const cmd of cmds) {
    if (cmd.process) {
      stopCmd(cmd);
    }
  }
}

function getEnvPath() {
  let envPath = config.get('envPath');
  if (envPath) {
    if (isWin) {
      envPath = ';' + envPath;
    } else {
      envPath = ':/usr/local/bin:/usr/local/sbin:/usr/local/share/npm/bin:/usr/local/share/node/bin:' + envPath;
    }
  } else {
    envPath = ':/usr/local/bin:/usr/local/sbin:/usr/local/share/npm/bin:/usr/local/share/node/bin';
  }
  return envPath;
}

function getOutputRowsLimit() {
  return config.get('outputRowsLimit') || 100;
}
/* ==================== Get Init Data ============================== */
ipcMain.on('GET_INIT_DATA', (evt) => {
  evt.sender.send('SET_INIT_DATA', {
    appVersion: app.getVersion(),
    envPath: config.get('envPath'),
    outputRowsLimit: getOutputRowsLimit(),
    isWin,
    cmds: cmds.map(c => Object.assign(_.pick(c, ['id', 'name', 'cmd', 'cwd', 'sudo', 'outputs', 'url', 'finishPrompt']), { status: c.process ? 'running' : 'stopped' })),
  });
});

/* ==================== Save Command ============================== */
ipcMain.on('SAVE_CMD', (evt, data, cmdId) => {
  console.log('saving cmd');
  const cmd = { id: cmdId || guid() };
  Object.assign(cmd, {
    name: data.name,
    cmd: data.cmd,
    sudo: data.sudo,
    finishPrompt: data.finishPrompt,
    cwd: data.cwd || null,
    url: data.url || null,
  });
  const newCmds = config.get('cmds') || [];
  if (cmdId) {
    const i = newCmds.findIndex(c => c.id === cmdId);
    newCmds.splice(i, 1, cmd);
  } else {
    newCmds.push(cmd);
  }
  config.set('cmds', newCmds);

  // Notify UI
  const curr = cmdId ? cmds.find(c => c.id === cmdId) : { status: 'stopped', outputs: [] };
  Object.assign(curr, cmd);
  if (!cmdId) {
    cmds.push(curr);
  }

  cmdHash[cmd.id] = curr;
  evt.sender.send('SAVE_CMD_SUCCESS', cmd);
});

/* ==================== Delete Command ============================== */
ipcMain.on('DELETE_CMD', (evt, cmdId) => {
  console.log('deleting cmd');
  // Stop the process if needed
  const cmd = cmdHash[cmdId];
  if (cmd.process) {
    stopCmd(cmd);
  }
  // Save to config
  const newCmds = config.get('cmds') || [];
  _.remove(newCmds, c => c.id === cmdId);
  config.set('cmds', newCmds);

  // Remove from store
  _.remove(cmds, c => c.id === cmdId);
  delete cmdHash[cmdId];

  evt.sender.send('DELETE_CMD_SUCCESS', cmdId);
});

/* ==================== Reorder Commands ============================== */
ipcMain.on('REORDER_CMDS', (evt, cmdIds) => {
  console.log('reordering cmds');

  // Save to config
  let newCmds = config.get('cmds') || [];
  const groups = _.groupBy(newCmds, 'id');
  newCmds = cmdIds.map(id => groups[id][0]);
  config.set('cmds', newCmds);

  // Update store order
  for (let i = 0; i < cmds.length; i++) {
    cmds[i] = cmdHash[cmdIds[i]];
  }

  evt.sender.send('REORDER_CMDS_SUCCESS', cmdIds);
});

/* ==================== Run Command ============================== */
ipcMain.on('RUN_CMD', (evt, cmdId, password) => { // eslint-disable-line
  const cmd = cmdHash[cmdId];
  console.log('running cmd: ', cmd.cmd);

  // notify the UI to show command is running. TODO: maybe use sync action is enough
  evt.sender.send('RUN_CMD_SUCCESS', cmdId);
  if (cmd.process) {
    // prevent from running multiple times.
    return;
  }

  let lineId = 0;
  cmd.outputs.length = 0;
  function onData(chunk, term) {
    let out = chunk.toString('utf8');

    if (!isWin) {
      if (/sorry.*try.*again.*/i.test(out)) {
        cmd.outputs.push({
          id: `${cmdId}_${lineId++}`, //eslint-disable-line
          text: 'Password is incorrect.',
        });
        evt.sender.send('CMD_OUTPUT', cmdId, [].concat(cmd.outputs));
        term.destroy();
        return;
      }

      if (cmd.sudo && /password/i.test(out)) {
        term.write(`${password}\r`);
        return;
      }
    }

    out = out.replace(/[\n\r]$/, '');
    const lines = out.split('\n');
    const rowsLimit = getOutputRowsLimit();
    for (const line of lines) {
      let text = line.replace(/>/g, '>').replace(/</g, '<');
      if (!isWin) text = convert.toHtml(text);
      cmd.outputs.push({
        id: `${cmdId}_${lineId++}`, //eslint-disable-line
        text,
      });
      if (cmd.outputs.length >= rowsLimit) {
        cmd.outputs.splice(0, cmd.outputs.length - rowsLimit);
      }
    }
    evt.sender.send('CMD_OUTPUT', cmdId, [].concat(cmd.outputs));
  }

  function onExit(code) {
    delete cmd.process;
    delete cmd._manualStop;
    if (!cmd._manualStop && cmd.finishPrompt) {
      notifier.notify({ // eslint-disable-line
        title: 'Command Pad',
        message: `Command ${code > 0 ? 'failed' : 'finished'}: ${cmd.name} .`,
        icon: code > 0 ? iconErrorPath : iconSuccessPath,
        wait: true,
      });
    }
    evt.sender.send('CMD_FINISHED', cmdId, code);
  }

  let arr = cmd.cmd.match(/'[^']*'|"[^"]*"|[^ ]+/g) || [];
  arr = arr.map(m => m.replace(/^['"]|['"]/g, ''));

  const envPath = getEnvPath();
  if (isWin) {
    // use cmd.exe to execute command
    arr = ['/s', '/c'].concat(arr);
    const child = spawn('cmd', arr, {
      cwd: cmd.cwd || process.env.HOME,
      stdio: 'pipe',
      env: Object.assign({}, process.env, { Path: `${process.env.Path}${envPath}` }),
    });
    child.stdout.pipe(process.stdout);
    cmd.process = child;
    child.stdout.on('data', onData);
    child.stderr.on('data', onData);
    child.on('exit', onExit);
    child.on('error', () => onExit(99));
  } else {
    // use ptyw.js to execute command
    let target;
    if (cmd.sudo) {
      target = 'sudo';
    } else {
      target = arr.shift();
    }
    const ptyw = require('ptyw.js');

    const term = ptyw.spawn(target, arr, {
      name: 'xterm-color',
      cols: 80,
      rows: 30,
      cwd: cmd.cwd || process.env.HOME,
      detached: true,
      env: Object.assign({}, process.env, { PATH: `${process.env.PATH}${envPath}` }),
    });
    term.on('data', onData);
    term.on('exit', onExit);
  }
});

/* ==================== Stop Command ============================== */
ipcMain.on('STOP_CMD', (evt, cmdId) => {
  console.log('stopping cmd: ', cmdId);
  const cmd = cmdHash[cmdId];

  if (cmd.process) {
    cmd._manualStop = true;
    stopCmd(cmd);
  }
});

/* ==================== Save Settings ============================== */
ipcMain.on('SAVE_SETTINGS', (evt, data) => {
  console.log('save settings');
  config.set('envPath', data.envPath);
  config.set('outputRowsLimit', parseInt(data.outputRowsLimit, 10) || 100);
  evt.sender.send('SAVE_SETTINGS_SUCCESS');
});

/* ==================== Save Settings ============================== */
ipcMain.on('CLEAR_OUTPUT', (evt, cmdId) => {
  console.log('clear output');
  const cmd = cmdHash[cmdId];
  cmd.outputs.length = 0;
  evt.sender.send('CLEAR_OUTPUT_SUCCESS');
});

module.exports = {
  appWillQuit: stopAllCmds
};

<|endoftext|>


<commit_before>var gulp    = require('gulp');
var uglify  = require('gulp-uglify');
var umd     = require('gulp-wrap-umd');
var rename  = require('gulp-rename');
var browser = require('browser-sync').create();

gulp.task( 'dist', function() {
  gulp.src('scrollreveal.js')
    .pipe( umd({ namespace: 'ScrollReveal', exports: 'ScrollReveal' }) )
    .pipe( gulp.dest('dist') )
});

gulp.task( 'dev', function() {
  gulp.src('scrollreveal.js')
    .pipe( gulp.dest('dev') );
});

gulp.task( 'dist:minify', function() {
  gulp.src('scrollreveal.js')
    .pipe( umd({ namespace: 'ScrollReveal', exports: 'ScrollReveal' }) )
    .pipe( uglify() )
    .pipe( rename('scrollreveal.min.js') )
    .pipe( gulp.dest('dist') )
})

gulp.task( 'default', function() {
  browser.init({ server: { baseDir: "./dev" } });
  gulp.watch([ 'scrollreveal.js '], [ 'dev' ])
  gulp.watch([ 'dev/*.*' ]).on( "change", browser.reload );
});

gulp.task( 'build', [ 'dist', 'dist:minify' ]);
<commit_msg>gulp(development): update default task<commit_after>var gulp    = require('gulp');
var uglify  = require('gulp-uglify');
var umd     = require('gulp-wrap-umd');
var rename  = require('gulp-rename');
var bower   = require('bower-json');
var browser = require('browser-sync').create();

gulp.task( 'dist', function() {
  gulp.src('scrollreveal.js')
    .pipe( umd({ namespace: 'ScrollReveal', exports: 'ScrollReveal' }) )
    .pipe( gulp.dest('dist') )
});

gulp.task( 'dev', function() {
  gulp.src('scrollreveal.js')
    .pipe( gulp.dest('dev') );
});

gulp.task( 'validate', function() {
  bower.read('./bower.json', function( err, json ) {
    if ( err ) {
      console.error('There was an error reading the file');
      console.error( err.message );
      return;
    }
  });
});

gulp.task( 'dist:minify', function() {
  gulp.src('scrollreveal.js')
    .pipe( umd({ namespace: 'ScrollReveal', exports: 'ScrollReveal' }) )
    .pipe( uglify() )
    .pipe( rename('scrollreveal.min.js') )
    .pipe( gulp.dest('dist') )
})

gulp.task( 'default', function() {
  browser.init({ server: { baseDir: "./dev" } });
  gulp.start(['dev']);
  gulp.watch([ 'scrollreveal.js '], [ 'dev' ])
  gulp.watch([ 'dev/*.*' ]).on( "change", browser.reload );
});

gulp.task( 'build', [ 'validate','dist', 'dist:minify' ]);
<|endoftext|>


<commit_before>var cacheAssetKey = '0.0.0',
  cacheApiKey = '1.0.0';

// app shell
var filesToCache = [
  '/',
  '/index.html',
  '/src/css/main.css',
  '/src/js/entity/Entity.js',
  '/src/js/static/Resource.js',
  '/src/js/static/Util.js',
  '/src/js/main.js',
  '/src/js/engine/Engine.js',
];

// cache / load asset (app shell)
self.addEventListener('install', function(e) {
  console.log('serviceWorker: install');

  e.waitUntil(caches.open(cacheAssetKey).then(function(cache) {
    console.log('serviceWorker: caching app shell');
    return cache.addAll(filesToCache);
  }));
});

// cache / load API
self.addEventListener('fetch', function(e) {
  console.log('serviceWorker: fetch', e.request.url);

  var apiRoot = 'https://';
  if (e.request.url.indexOf(apiRoot) > -1) {
    // "Cache then network" strategy
    // https://jakearchibald.com/2014/offline-cookbook/#cache-then-network
    e.respondWith(caches.open(cacheApiKey).then(function(cache) {
      return fetch(e.request).then(function(res) {
        cache.put(e.request.url, res.clone());
        return res;
      });
    }));

  } else {
    // app shell
    // "Cache, falling back to the network" offline strategy:
    // https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network
    e.respondWith(
      caches.match(e.request).then(function(response) {
        return response || fetch(e.request);
      })
    );
  }
});

// remove old cache
self.addEventListener('activate', function(e) {
  console.log('serviceWorker: activate');

  e.waitUntil(caches.keys().then(function(keyList) {
    return Promise.all(keyList.map(function(key) {
      if (key !== cacheAssetKey && key !== cacheApiKey) {
        console.log('serviceWorker: removing old cache', key);
        return caches.delete(key);
      }
    }));
  }));

  // fix a corner case
  // https://github.com/googlecodelabs/your-first-pwapp/blob/master/final/service-worker.js#L69
  // https://davidwalsh.name/service-worker-claim
  // http://stackoverflow.com/questions/41009167/what-is-the-use-of-self-clients-claim
  return self.clients.claim();
});
<commit_msg>fix(serviceWorker): Fix cache files<commit_after>var cacheAssetKey = '0.0.0',
  cacheApiKey = '1.0.0';

// app shell
var filesToCache = [
  '/',
  '/index.html',
  '/dist/css/main.min.css',
  '/dist/js/main.min.js',
];

// cache / load asset (app shell)
self.addEventListener('install', function(e) {
  console.log('serviceWorker: install');

  e.waitUntil(caches.open(cacheAssetKey).then(function(cache) {
    console.log('serviceWorker: caching app shell');
    return cache.addAll(filesToCache);
  }));
});

// cache / load API
self.addEventListener('fetch', function(e) {
  console.log('serviceWorker: fetch', e.request.url);

  var apiRoot = 'https://';
  if (e.request.url.indexOf(apiRoot) > -1) {
    // "Cache then network" strategy
    // https://jakearchibald.com/2014/offline-cookbook/#cache-then-network
    e.respondWith(caches.open(cacheApiKey).then(function(cache) {
      return fetch(e.request).then(function(res) {
        cache.put(e.request.url, res.clone());
        return res;
      });
    }));

  } else {
    // app shell
    // "Cache, falling back to the network" offline strategy:
    // https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network
    e.respondWith(
      caches.match(e.request).then(function(response) {
        return response || fetch(e.request);
      })
    );
  }
});

// remove old cache
self.addEventListener('activate', function(e) {
  console.log('serviceWorker: activate');

  e.waitUntil(caches.keys().then(function(keyList) {
    return Promise.all(keyList.map(function(key) {
      if (key !== cacheAssetKey && key !== cacheApiKey) {
        console.log('serviceWorker: removing old cache', key);
        return caches.delete(key);
      }
    }));
  }));

  // fix a corner case
  // https://github.com/googlecodelabs/your-first-pwapp/blob/master/final/service-worker.js#L69
  // https://davidwalsh.name/service-worker-claim
  // http://stackoverflow.com/questions/41009167/what-is-the-use-of-self-clients-claim
  return self.clients.claim();
});
<|endoftext|>


<commit_before>import { createSelector } from "reselect";
import queryString from "query-string";

export default createSelector(
  (location) => location.search,
  (search) => queryString.parse(search)
);
<commit_msg>Removed useless module<commit_after><|endoftext|>


<commit_before>
/* jshint node: true */
/* eslint vars-on-top:0 */
/* eslint-env node, browser */

/**
 * Flocks gulpfile.
 *
 * @module gulpfile
 * @main   gulp
 * @class  gulpfile
 */

"use strict";

/**
 * This unbound blockless step installs the node-jsx loader to convert statics.
 * This is not actually a method and cannot be called.
 *
 * @method requireNodeJsx
 */

require("node-jsx").install({
  "extension" : ".jsx",
  "harmony"   : true
});

var gulp            = require("gulp"),
    git             = require("gulp-git"),
    bump            = require("gulp-bump"),
    lint            = require("sc-eslint/estask.js"),
    shell           = require("gulp-shell"),
    clean           = require("gulp-clean"),
    yuidoc          = require("gulp-yuidoc"),
    stripDomComment = require("gulp-strip-react-dom-comment"),
    jsx             = require("gulp-jsx"),
    closureCompiler = require("gulp-closure-compiler"),
    sloc            = require("gulp-sloc"),
    rename          = require("gulp-rename"),
    flocks          = require("./lib/flocks.jsx"),
    argv            = require("yargs").argv;

/**
 * <tt>gulp clean</tt> will remove the <tt>./doc</tt>, <tt>./dist</tt>,
 * and <tt>./reports</tt> directories, in the expectation that they will be
 * remade by the gulp process.
 *
 * <tt>clean</tt> has no dependencies.
 *
 * @method clean
 */

gulp.task("clean", function() {
  return gulp.src(["./doc", "./dist", "./reports"], {"read" : false}).pipe(clean());
});

/**
 * <tt>gulp docs</tt> will generate the documentation through <tt>yuidoc</tt>.
 *
 * <tt>docs</tt> will invoke <tt>clean</tt> and <tt>bump</tt>.
 *
 * @method docs
 */

gulp.task("docs", ["clean", "bump"], function() {
  gulp.src(["./lib/*.js", "./lib/*.jsx", "./gulpfile.js"])
    .pipe(stripDomComment())
    .pipe(yuidoc())
    .pipe(gulp.dest("./doc"));
});

/**
 * <tt>gulp lint</tt> will run the linting engine on the <tt>gulpfile</tt>,
 * on <tt>lib/flocks.jsx</tt>, and on <tt>dist/flocks.js</tt>.  Obviously the
 * minified version is not linted.  <tt>:D</tt>
 *
 * <tt>lint</tt> has no dependencies.
 *
 * @method lint
 */

lint.gulpreg(gulp, {
  "targets" : "gulpfile.js lib/flocks.jsx dist/flocks.js"
});

/**
 * <tt>gulp minify</tt> will run post-transpiled <tt>flocks.js</tt>
 * through <tt>closure compiler</tt> to produce <tt>flocks.min.js</tt>.
 *
 * <tt>minify</tt> will invoke <tt>clean</tt> and <tt>transpile</tt>.
 *
 * @method minify
 */

gulp.task("minify", ["clean","transpile"], function() {
  return gulp.src("dist/flocks.js")
    .pipe(closureCompiler({
      "compilerPath" : "bower_components/closure-compiler/compiler.jar",
      "fileName"     : "./dist/flocks.min.js"
    }))
    .pipe(gulp.dest("."));
});

/**
 * <tt>gulp transpile</tt> will use <tt>gulp-jsx</tt> to convert the flocks
 * library <tt>flocks.jsx</tt> file to its <tt>flocks.js</tt> form for
 * publishing on the CDN.
 *
 * <tt>transpile</tt> will invoke <tt>clean</tt>.
 *
 * @method transpile
 */

gulp.task("transpile", ["clean"], function() {

  return gulp.src("lib/flocks.jsx")
    .pipe(jsx())
    .pipe(rename("flocks.js"))
    .pipe(gulp.dest("dist"));

});

/**
 * <tt>gulp vows</tt> will uses <tt>gulp-shell</tt> to issue the
 * command <tt>npm test</tt> to run the <tt>vows</tt> tests on gulp.  This is
 * done with an <tt>npm</tt> script so that other tooling will pick it up
 * automatically.
 *
 * This is a support task; you should <tt>gulp test</tt> instead.
 *
 * <tt>vows</tt> has no dependencies.
 *
 * @method vows
 */

gulp.task("vows", shell.task("npm test"));

/**
 * <tt>gulp test</tt> invokes <tt>build</tt>, <tt>vows</tt>, and <tt>lint</tt>.
 *
 * @method test
 */

gulp.task("test", ["build", "vows", "lint"]);

/**
 * <tt>gulp build</tt> invokes <tt>minify</tt>.  Other build steps will be here
 * soon.
 *
 * @method build
 */

gulp.task("build",  ["minify"]);

/**
 * <tt>gulp bump</tt> uses <tt>gulp-bump</tt> to set the version number of
 * the <tt>package.json</tt> and <tt>bower.json</tt> files to match the version
 * number emitted by the library.
 *
 * @method bump
 */

gulp.task("bump", function() {

  gulp.src(["./bower.json", "./package.json"])
    .pipe(bump({
      "version" : flocks.version
    }))
    .pipe(gulp.dest("./"));

});

gulp.task("sloc", ["build"], function() {

  gulp.src(["gulpfile.js", "bower.json", "package.json", "lib/flocks.jsx", "test/specs/*.js"])
    .pipe(sloc({"tolerant" : true}));

  gulp.src(["gulpfile.js", "bower.json", "package.json", "lib/flocks.jsx", "test/specs/*.js"])
    .pipe(sloc({
      "tolerant"   : true,
      "reportType" : "json"
    }))
    .pipe(gulp.dest("./reports"));

});

gulp.task("default", ["test", "docs", "sloc"]);

gulp.task("publish", ["default", "tag", "push"], function() {

  shell("npm publish");

});

gulp.task("add", ["default"], function() {

  return gulp.src([
      // can't just glob exclude because of node's broken depth nonsense
      "./gulpfile.js",
      "./bower.json",
      "./package.json",
      "./README.md",
      "./LICENSE",
      "./dist/**/*",
      "./lib/**/*",
      "./reports/**/*",
      "./test/**/*"
    ], {
      "read" : false
    })
    .pipe(git.add({"args" : "-A"}));

});

gulp.task("tag", ["default", "add"], function() {

  var version = flocks.version,
      message = argv.m;

  return gulp.src('./git-test/*')
    .pipe(git.commit("Version " + version + ": " + message))
    .pipe(git.tag(version, version + ": " + message, function(error) { if (error) { throw error; } }));

});

gulp.task("push", ["tag"], function() {

  git.push("origin", "master", {"args" : "--tags"}, function(error) { if (error) { throw error; } });

});
<commit_msg>document sloc, touch up older<commit_after>
/* jshint node: true */
/* eslint vars-on-top:0 */
/* eslint-env node, browser */

/**
 * Flocks gulpfile.
 *
 * @module gulpfile
 * @main   gulp
 * @class  gulpfile
 */

"use strict";

/**
 * This unbound blockless step installs the node-jsx loader to convert statics.
 * This is not actually a method and cannot be called.
 *
 * @method requireNodeJsx
 */

require("node-jsx").install({
  "extension" : ".jsx",
  "harmony"   : true
});

var gulp            = require("gulp"),
    git             = require("gulp-git"),
    bump            = require("gulp-bump"),
    lint            = require("sc-eslint/estask.js"),
    shell           = require("gulp-shell"),
    clean           = require("gulp-clean"),
    yuidoc          = require("gulp-yuidoc"),
    stripDomComment = require("gulp-strip-react-dom-comment"),
    jsx             = require("gulp-jsx"),
    closureCompiler = require("gulp-closure-compiler"),
    sloc            = require("gulp-sloc"),
    rename          = require("gulp-rename"),
    flocks          = require("./lib/flocks.jsx"),
    argv            = require("yargs").argv;

/**
 * <tt>gulp clean</tt> will remove the <tt>./doc</tt>, <tt>./dist</tt>,
 * and <tt>./reports</tt> directories, in the expectation that they will be
 * remade by the gulp process.
 *
 * <tt>clean</tt> has no dependencies.
 *
 * @method clean
 */

gulp.task("clean", function() {
  return gulp.src(["./doc", "./dist", "./reports"], {"read" : false}).pipe(clean());
});

/**
 * <tt>gulp docs</tt> will generate the documentation through <tt>yuidoc</tt>.
 *
 * <tt>docs</tt> will invoke <tt>clean</tt> and <tt>bump</tt>.
 *
 * @method docs
 */

gulp.task("docs", ["clean", "bump"], function() {
  gulp.src(["./lib/*.js", "./lib/*.jsx", "./gulpfile.js"])
    .pipe(stripDomComment())
    .pipe(yuidoc())
    .pipe(gulp.dest("./doc"));
});

/**
 * <tt>gulp lint</tt> will run the linting engine on the <tt>gulpfile</tt>,
 * on <tt>lib/flocks.jsx</tt>, and on <tt>dist/flocks.js</tt>.  Obviously the
 * minified version is not linted.  <tt>:D</tt>
 *
 * <tt>lint</tt> has no dependencies.
 *
 * @method lint
 */

lint.gulpreg(gulp, {
  "targets" : "gulpfile.js lib/flocks.jsx dist/flocks.js"
});

/**
 * <tt>gulp minify</tt> will run post-transpiled <tt>flocks.js</tt>
 * through <tt>closure compiler</tt> to produce <tt>flocks.min.js</tt>.
 *
 * <tt>minify</tt> will invoke <tt>clean</tt> and <tt>transpile</tt>.
 *
 * @method minify
 */

gulp.task("minify", ["clean","transpile"], function() {
  return gulp.src("dist/flocks.js")
    .pipe(closureCompiler({
      "compilerPath" : "bower_components/closure-compiler/compiler.jar",
      "fileName"     : "./dist/flocks.min.js"
    }))
    .pipe(gulp.dest("."));
});

/**
 * <tt>gulp transpile</tt> will use <tt>gulp-jsx</tt> to convert the flocks
 * library <tt>flocks.jsx</tt> file to its <tt>flocks.js</tt> form for
 * publishing on the CDN.
 *
 * <tt>transpile</tt> will invoke <tt>clean</tt>.
 *
 * @method transpile
 */

gulp.task("transpile", ["clean"], function() {

  return gulp.src("lib/flocks.jsx")
    .pipe(jsx())
    .pipe(rename("flocks.js"))
    .pipe(gulp.dest("dist"));

});

/**
 * <tt>gulp vows</tt> will uses <tt>gulp-shell</tt> to issue the
 * command <tt>npm test</tt> to run the <tt>vows</tt> tests on gulp.  This is
 * done with an <tt>npm</tt> script so that other tooling will pick it up
 * automatically.
 *
 * This is a support task; you should <tt>gulp test</tt> instead.
 *
 * <tt>vows</tt> has no dependencies.
 *
 * @method vows
 */

gulp.task("vows", shell.task("npm test"));

/**
 * <tt>gulp test</tt> invokes <tt>build</tt>, <tt>vows</tt>, and <tt>lint</tt>.
 *
 * @method test
 */

gulp.task("test", ["build", "vows", "lint"]);

/**
 * <tt>gulp build</tt> invokes <tt>minify</tt>.  Other build steps will be here
 * soon.
 *
 * @method build
 */

gulp.task("build",  ["minify"]);

/**
 * <tt>gulp bump</tt> uses <tt>gulp-bump</tt> to set the version number of
 * the <tt>package.json</tt> and <tt>bower.json</tt> files to match the version
 * number emitted by the library.
 *
 * <tt>bump</tt> has no dependencies.
 *
 * @method bump
 */

gulp.task("bump", function() {

  gulp.src(["./bower.json", "./package.json"])
    .pipe(bump({
      "version" : flocks.version
    }))
    .pipe(gulp.dest("./"));

});

/**
 * <tt>gulp sloc</tt> runs the <tt>gulp-sloc</tt> plugin twice - once to show
 * the sloc report, and once to dump it to disk in <tt>./reports</tt>.
 *
 * <tt>sloc</tt> will invoke <tt>build</tt>.
 *
 * @method sloc
 */

gulp.task("sloc", ["build"], function() {

  gulp.src(["gulpfile.js", "bower.json", "package.json", "lib/flocks.jsx", "test/specs/*.js"])
    .pipe(sloc({"tolerant" : true}));

  gulp.src(["gulpfile.js", "bower.json", "package.json", "lib/flocks.jsx", "test/specs/*.js"])
    .pipe(sloc({
      "tolerant"   : true,
      "reportType" : "json"
    }))
    .pipe(gulp.dest("./reports"));

});

gulp.task("default", ["test", "docs", "sloc"]);

gulp.task("publish", ["default", "tag", "push"], function() {

  shell("npm publish");

});

gulp.task("add", ["default"], function() {

  return gulp.src([
      // can't just glob exclude because of node's broken depth nonsense
      "./gulpfile.js",
      "./bower.json",
      "./package.json",
      "./README.md",
      "./LICENSE",
      "./dist/**/*",
      "./lib/**/*",
      "./reports/**/*",
      "./test/**/*"
    ], {
      "read" : false
    })
    .pipe(git.add({"args" : "-A"}));

});

gulp.task("tag", ["default", "add"], function() {

  var version = flocks.version,
      message = argv.m;

  return gulp.src('./git-test/*')
    .pipe(git.commit("Version " + version + ": " + message))
    .pipe(git.tag(version, version + ": " + message, function(error) { if (error) { throw error; } }));

});

gulp.task("push", ["tag"], function() {

  git.push("origin", "master", {"args" : "--tags"}, function(error) { if (error) { throw error; } });

});
<|endoftext|>


<commit_before>var util = require('util'),
    webutil = require('../util/web'),
    Tab = require('../client/tab').Tab,
    Amount = ripple.Amount,
    Base = ripple.Base,
    Currency = ripple.Currency;

var ExchangeTab = function ()
{
  Tab.call(this);
};

util.inherits(ExchangeTab, Tab);

ExchangeTab.prototype.tabName = 'exchange';
ExchangeTab.prototype.mainMenu = 'exchange';

ExchangeTab.prototype.generateHtml = function ()
{
  return require('../../jade/tabs/exchange.jade')();
};

ExchangeTab.prototype.angular = function (module)
{
  module.controller('ExchangeCtrl', ['$scope', '$timeout', '$routeParams',
    'rpId', 'rpNetwork', 'rpTracker', 'rpKeychain', '$rootScope',
    function ($scope, $timeout, $routeParams, $id, $network, $rpTracker, keychain, $rootScope)
    {
      if (!$id.loginStatus) return $id.goId();

      var timer, pf;

      // Remember user preference on Convert vs. Trade
      $rootScope.ripple_exchange_selection_trade = false;

      var xrpCurrency = Currency.from_json("XRP");

      $scope.xrp = {
        name: xrpCurrency.to_human({full_name:$scope.currencies_all_keyed["XRP"].name}),
        code: xrpCurrency.get_iso(),
        currency: xrpCurrency
      };

      $scope.$watch('exchange.amount', function () {
        $scope.update_exchange();
      }, true);

      $scope.$watch('exchange.currency_name', function () {
        var exchange = $scope.exchange;
        var currency = Currency.from_human($scope.exchange.currency_name ? $scope.exchange.currency_name : "XRP");
        exchange.currency_obj = currency;
        exchange.currency_code = currency.get_iso();
        exchange.currency_name = currency.to_human({full_name:$scope.currencies_all_keyed[currency.get_iso()].name});
        $scope.update_exchange();
      }, true);

      $scope.reset_paths = function () {
        var exchange = $scope.exchange;

        exchange.alternatives = [];
      };

      var pathUpdateTimeout;
      $scope.update_exchange = function () {
        var exchange = $scope.exchange;
        var currency = ripple.Currency.from_human(exchange.currency_name);

        $scope.reset_paths();

        // if formatted or money to exchange is 0 then don't calculate paths or offer to exchange
        if (parseFloat(exchange.amount) === 0 || !exchange.currency_name)
        {
          $scope.error_type = 'required';
          return false;
        }

        else {
          $scope.error_type = '';
        }

        var matchedCurrency = currency.has_interest() ? currency.to_hex() : currency.get_iso();
        var match = /^([a-zA-Z0-9]{3}|[A-Fa-f0-9]{40})\b/.exec(matchedCurrency);

        if (!match) {
          // Currency code not recognized, should have been caught by
          // form validator.
          return;
        }

        // Demurrage: Get a reference date five minutes in the future
        //
        // Normally, when using demurrage currencies, we would immediately round
        // down (e.g. 0.99999 instead of 1) as demurrage occurs continuously. Not
        // a good user experience.
        //
        // By choosing a date in the future, this gives us a time window before
        // this rounding down occurs. Note that for positive interest currencies
        // this actually *causes* the same odd rounding problem, so in the future
        // we'll want a better solution, but for right now this does what we need.
        var refDate = new Date(new Date().getTime() + 5 * 60000);

        exchange.amount_feedback = Amount.from_human('' + exchange.amount + ' ' + matchedCurrency, { reference_date: refDate });
        exchange.amount_feedback.set_issuer($id.account);

        if (exchange.amount_feedback.is_valid()) {
          exchange.path_status = 'pending';
          exchange.alt = null;

          if (pathUpdateTimeout) clearTimeout(pathUpdateTimeout);
          pathUpdateTimeout = $timeout($scope.update_paths, 500);
        } else {
          exchange.path_status = 'waiting';
        }
      };

      $scope.update_paths = function () {
        $scope.$apply(function () {
          $scope.exchange.path_status = 'pending';
          var amount = $scope.exchange.amount_feedback;

          if (amount.is_zero()) return;

          // Start path find
          pf = $network.remote.path_find($id.account,
              $id.account,
              amount);

          var lastUpdate;

          pf.on('update', function (upd) {
            $scope.$apply(function () {
              lastUpdate = new Date();

              clearInterval(timer);
              timer = setInterval(function(){
                $scope.$apply(function(){
                  var seconds = Math.round((new Date() - lastUpdate)/1000);
                  $scope.lastUpdate = seconds ? seconds : 0;
                })
              }, 1000);

              if (!upd.alternatives || !upd.alternatives.length) {
                $scope.exchange.path_status  = "no-path";
                $scope.exchange.alternatives = [];
              } else {
                $scope.exchange.path_status  = "done";
                $scope.exchange.alternatives = _.map(upd.alternatives, function (raw) {
                  var alt = {};
                  alt.amount   = Amount.from_json(raw.source_amount);
                  alt.rate     = alt.amount.ratio_human(amount);
                  alt.send_max = alt.amount.product_human(Amount.from_json('1.01'));
                  alt.paths    = raw.paths_computed
                      ? raw.paths_computed
                      : raw.paths_canonical;

                  return alt;
                });
              }
            });
          });
        });
      };

      var updateCurrencyOptions = function(){
        // create a list of currency codes from the trust line objects
        var currencies = _.uniq(_.map($scope.lines, function (line) {
          return line.currency;
        }));

        // add XRP
        currencies.unshift('XRP');

        // create a currency object for each of the currency codes
        for (var i=0; i < currencies.length; i++) {
          currencies[i] = ripple.Currency.from_json(currencies[i]);
        }

        // create the display version of the currencies
        currencies = _.map(currencies, function (currency) {
          if ($scope.currencies_all_keyed[currency.get_iso()]) {
            return currency.to_human({full_name:$scope.currencies_all_keyed[currency.get_iso()].name});
          }
        });

        $scope.currency_choices = currencies;
      };

      $scope.$on('$balancesUpdate', updateCurrencyOptions);

      $scope.reset = function () {
        $scope.mode = "form";

        // XXX Most of these variables should be properties of $scope.exchange.
        //     The Angular devs recommend that models be objects due to the way
        //     scope inheritance works.
        $scope.exchange = {
          amount: '',
          currency_name: $scope.xrp.name,
          currency_code: $scope.xrp.code,
          currency_obj: $scope.xrp.currency,
          path_status: 'waiting',
          fund_status: 'none'
        };
        $scope.nickname = '';
        $scope.error_type = '';
        if ($scope.exchangeForm) $scope.exchangeForm.$setPristine(true);
      };

      $scope.cancelConfirm = function () {
        $scope.mode = "form";
        $scope.exchange.alt = null;
      };

      $scope.reset_goto = function (tabName) {
        $scope.reset();

        // TODO do something clever instead of document.location
        // because goToTab does $scope.$digest() which we don't need
        document.location = '#' + tabName;
      };

      /**
       * N3. Confirmation page
       */
      $scope.exchange_prepared = function () {
        $scope.confirm_wait = true;
        $timeout(function () {
          $scope.confirm_wait = false;
        }, 1000, true);

        $scope.mode = "confirm";
      };

      /**
       * N4. Waiting for transaction result page
       */
      $scope.exchange_confirmed = function () {

        // parse the currency name and extract the iso
        var currencyHex = Currency.from_human($scope.exchange.currency_name).to_hex();
        var amount = Amount.from_human(""+$scope.exchange.amount+" "+currencyHex);

        amount.set_issuer($id.account);

        var tx = $network.remote.transaction();

        // Destination tag
        tx.destination_tag(webutil.getDestTagFromAddress($id.account));
        tx.payment($id.account, $id.account, amount.to_json());
        tx.send_max($scope.exchange.alt.send_max);
        tx.paths($scope.exchange.alt.paths);

        if ($scope.exchange.secret) {
          tx.secret($scope.exchange.secret);
        } else {
          // Get secret asynchronously
          keychain.requestSecret($id.account, $id.username,
            function (err, secret) {
              if (err) {
                console.log("client: exchange tab: error while " +
                  "unlocking wallet: ", err);
                $scope.mode = "error";
                $scope.error_type = "unlockFailed";

                return;
              }

              $scope.exchange.secret = secret;
              $scope.exchange_confirmed();
            });
          return;
        }

        tx.on('proposed', function (res) {
          $scope.$apply(function () {
            setEngineStatus(res, false);
            $scope.exchanged(tx.hash);

            // Remember currency and increase order
            var found;

            for (var i = 0; i < $scope.currencies_all.length; i++) {
              if ($scope.currencies_all[i].value.toLowerCase() === $scope.exchange.amount_feedback.currency().get_iso().toLowerCase()) {
                $scope.currencies_all[i].order++;
                found = true;
                break;
              }
            }

            // // Removed feature until a permanent fix
            // if (!found) {
            //   $scope.currencies_all.push({
            //     "name": $scope.exchange.amount_feedback.currency().to_human().toUpperCase(),
            //     "value": $scope.exchange.amount_feedback.currency().to_human().toUpperCase(),
            //     "order": 1
            //   });
            // }
          });
        });
        tx.on('success',function(res){
          setEngineStatus(res, true);
        });
        tx.on('error', function (res) {
          setImmediate(function () {
            $scope.$apply(function () {
              $scope.mode = "error";

              if (res.result === "tejMaxFeeExceeded") {
                $scope.error_type = "maxFeeExceeded";
              }

              if (res.error === 'remoteError' &&
                  res.remote.error === 'noPath') {
                $scope.mode = "status";
                $scope.tx_result = "noPath";
              }
            });
          });
        });
        tx.submit();

        $scope.mode = "sending";
      };

      /**
       * N6. exchanged page
       */
      $scope.exchanged = function (hash) {
        $scope.mode = "status";
        $network.remote.on('transaction', handleAccountEvent);

        function handleAccountEvent(e) {
          $scope.$apply(function () {
            if (e.transaction.hash === hash) {
              setEngineStatus(e, true);
              $network.remote.removeListener('transaction', handleAccountEvent);
            }
          });
        }
      };

      function setEngineStatus(res, accepted) {
        $scope.engine_result = res.engine_result;
        $scope.engine_result_message = res.engine_result_message;
        switch (res.engine_result.slice(0, 3)) {
          case 'tes':
            $scope.tx_result = accepted ? "cleared" : "pending";
            break;
          case 'tem':
            $scope.tx_result = "malformed";
            break;
          case 'ter':
            $scope.tx_result = "failed";
            break;
          case 'tep':
            $scope.tx_result = "partial";
            break;
          case 'tec':
            $scope.tx_result = "claim";
            break;
          case 'tef':
            $scope.tx_result = "failure";
            break;
          case 'tel':
            $scope.tx_result = "local";
            break;
          default:
            console.warn("Unhandled engine status encountered!");
        }
      }

      $scope.reset();

      updateCurrencyOptions();

      // Stop the pathfinding when leaving the page
      $scope.$on('$destroy', function(){
        if (pf && "function" === typeof pf.close) {
          pf.close();
        }
      });
    }]);

  /**
   * Contact name and address uniqueness validator
   */
    // TODO move to global directives
  module.directive('unique', function() {
    return {
      restrict: 'A',
      require: '?ngModel',
      link: function ($scope, elm, attr, ctrl) {
        if (!ctrl) return;

        var validator = function(value) {
          var unique = !webutil.getContact($scope.userBlob.data.contacts,value);
          ctrl.$setValidity('unique', unique);
          if (unique) return value;
        };

        ctrl.$formatters.push(validator);
        ctrl.$parsers.unshift(validator);

        attr.$observe('unique', function() {
          validator(ctrl.$viewValue);
        });
      }
    };
  });
};

module.exports = ExchangeTab;
<commit_msg>[FIX] Exchange: Make it work for currencies without interest<commit_after>var util = require('util'),
    webutil = require('../util/web'),
    Tab = require('../client/tab').Tab,
    Amount = ripple.Amount,
    Base = ripple.Base,
    Currency = ripple.Currency;

var ExchangeTab = function ()
{
  Tab.call(this);
};

util.inherits(ExchangeTab, Tab);

ExchangeTab.prototype.tabName = 'exchange';
ExchangeTab.prototype.mainMenu = 'exchange';

ExchangeTab.prototype.generateHtml = function ()
{
  return require('../../jade/tabs/exchange.jade')();
};

ExchangeTab.prototype.angular = function (module)
{
  module.controller('ExchangeCtrl', ['$scope', '$timeout', '$routeParams',
    'rpId', 'rpNetwork', 'rpTracker', 'rpKeychain', '$rootScope',
    function ($scope, $timeout, $routeParams, $id, $network, $rpTracker, keychain, $rootScope)
    {
      if (!$id.loginStatus) return $id.goId();

      var timer, pf;

      // Remember user preference on Convert vs. Trade
      $rootScope.ripple_exchange_selection_trade = false;

      var xrpCurrency = Currency.from_json("XRP");

      $scope.xrp = {
        name: xrpCurrency.to_human({full_name:$scope.currencies_all_keyed["XRP"].name}),
        code: xrpCurrency.get_iso(),
        currency: xrpCurrency
      };

      $scope.$watch('exchange.amount', function () {
        $scope.update_exchange();
      }, true);

      $scope.$watch('exchange.currency_name', function () {
        var exchange = $scope.exchange;
        var currency = Currency.from_human($scope.exchange.currency_name ? $scope.exchange.currency_name : "XRP");
        exchange.currency_obj = currency;
        exchange.currency_code = currency.get_iso();
        exchange.currency_name = currency.to_human({full_name:$scope.currencies_all_keyed[currency.get_iso()].name});
        $scope.update_exchange();
      }, true);

      $scope.reset_paths = function () {
        var exchange = $scope.exchange;

        exchange.alternatives = [];
      };

      var pathUpdateTimeout;
      $scope.update_exchange = function () {
        var exchange = $scope.exchange;
        var currency = ripple.Currency.from_human(exchange.currency_name);

        $scope.reset_paths();

        // if formatted or money to exchange is 0 then don't calculate paths or offer to exchange
        if (parseFloat(exchange.amount) === 0 || !exchange.currency_name)
        {
          $scope.error_type = 'required';
          return false;
        }

        else {
          $scope.error_type = '';
        }

        var matchedCurrency = currency.has_interest() ? currency.to_hex() : currency.get_iso();
        var match = /^([a-zA-Z0-9]{3}|[A-Fa-f0-9]{40})\b/.exec(matchedCurrency);

        if (!match) {
          // Currency code not recognized, should have been caught by
          // form validator.
          return;
        }

        // Demurrage: Get a reference date five minutes in the future
        //
        // Normally, when using demurrage currencies, we would immediately round
        // down (e.g. 0.99999 instead of 1) as demurrage occurs continuously. Not
        // a good user experience.
        //
        // By choosing a date in the future, this gives us a time window before
        // this rounding down occurs. Note that for positive interest currencies
        // this actually *causes* the same odd rounding problem, so in the future
        // we'll want a better solution, but for right now this does what we need.
        var refDate = new Date(new Date().getTime() + 5 * 60000);

        exchange.amount_feedback = Amount.from_human('' + exchange.amount + ' ' + matchedCurrency, { reference_date: refDate });
        exchange.amount_feedback.set_issuer($id.account);

        if (exchange.amount_feedback.is_valid()) {
          exchange.path_status = 'pending';
          exchange.alt = null;

          if (pathUpdateTimeout) clearTimeout(pathUpdateTimeout);
          pathUpdateTimeout = $timeout($scope.update_paths, 500);
        } else {
          exchange.path_status = 'waiting';
        }
      };

      $scope.update_paths = function () {
        $scope.$apply(function () {
          $scope.exchange.path_status = 'pending';
          var amount = $scope.exchange.amount_feedback;

          if (amount.is_zero()) return;

          // Start path find
          pf = $network.remote.path_find($id.account,
              $id.account,
              amount);

          var lastUpdate;

          pf.on('update', function (upd) {
            $scope.$apply(function () {
              lastUpdate = new Date();

              clearInterval(timer);
              timer = setInterval(function(){
                $scope.$apply(function(){
                  var seconds = Math.round((new Date() - lastUpdate)/1000);
                  $scope.lastUpdate = seconds ? seconds : 0;
                })
              }, 1000);

              if (!upd.alternatives || !upd.alternatives.length) {
                $scope.exchange.path_status  = "no-path";
                $scope.exchange.alternatives = [];
              } else {
                $scope.exchange.path_status  = "done";
                $scope.exchange.alternatives = _.map(upd.alternatives, function (raw) {
                  var alt = {};
                  alt.amount   = Amount.from_json(raw.source_amount);
                  alt.rate     = alt.amount.ratio_human(amount);
                  alt.send_max = alt.amount.product_human(Amount.from_json('1.01'));
                  alt.paths    = raw.paths_computed
                      ? raw.paths_computed
                      : raw.paths_canonical;

                  return alt;
                });
              }
            });
          });
        });
      };

      var updateCurrencyOptions = function(){
        // create a list of currency codes from the trust line objects
        var currencies = _.uniq(_.map($scope.lines, function (line) {
          return line.currency;
        }));

        // add XRP
        currencies.unshift('XRP');

        // create a currency object for each of the currency codes
        for (var i=0; i < currencies.length; i++) {
          currencies[i] = ripple.Currency.from_json(currencies[i]);
        }

        // create the display version of the currencies
        currencies = _.map(currencies, function (currency) {
          if ($scope.currencies_all_keyed[currency.get_iso()]) {
            return currency.to_human({full_name:$scope.currencies_all_keyed[currency.get_iso()].name});
          }
        });

        $scope.currency_choices = currencies;
      };

      $scope.$on('$balancesUpdate', updateCurrencyOptions);

      $scope.reset = function () {
        $scope.mode = "form";

        // XXX Most of these variables should be properties of $scope.exchange.
        //     The Angular devs recommend that models be objects due to the way
        //     scope inheritance works.
        $scope.exchange = {
          amount: '',
          currency_name: $scope.xrp.name,
          currency_code: $scope.xrp.code,
          currency_obj: $scope.xrp.currency,
          path_status: 'waiting',
          fund_status: 'none'
        };
        $scope.nickname = '';
        $scope.error_type = '';
        if ($scope.exchangeForm) $scope.exchangeForm.$setPristine(true);
      };

      $scope.cancelConfirm = function () {
        $scope.mode = "form";
        $scope.exchange.alt = null;
      };

      $scope.reset_goto = function (tabName) {
        $scope.reset();

        // TODO do something clever instead of document.location
        // because goToTab does $scope.$digest() which we don't need
        document.location = '#' + tabName;
      };

      /**
       * N3. Confirmation page
       */
      $scope.exchange_prepared = function () {
        $scope.confirm_wait = true;
        $timeout(function () {
          $scope.confirm_wait = false;
        }, 1000, true);

        $scope.mode = "confirm";
      };

      /**
       * N4. Waiting for transaction result page
       */
      $scope.exchange_confirmed = function () {

        // parse the currency name and extract the iso
        var currency = Currency.from_human($scope.exchange.currency_name);
        currency = currency.has_interest() ? currency.to_hex() : currency.get_iso();
        var amount = Amount.from_human(""+$scope.exchange.amount+" "+currency);

        amount.set_issuer($id.account);

        var tx = $network.remote.transaction();

        // Destination tag
        tx.destination_tag(webutil.getDestTagFromAddress($id.account));
        tx.payment($id.account, $id.account, amount.to_json());
        tx.send_max($scope.exchange.alt.send_max);
        tx.paths($scope.exchange.alt.paths);

        if ($scope.exchange.secret) {
          tx.secret($scope.exchange.secret);
        } else {
          // Get secret asynchronously
          keychain.requestSecret($id.account, $id.username,
            function (err, secret) {
              if (err) {
                console.log("client: exchange tab: error while " +
                  "unlocking wallet: ", err);
                $scope.mode = "error";
                $scope.error_type = "unlockFailed";

                return;
              }

              $scope.exchange.secret = secret;
              $scope.exchange_confirmed();
            });
          return;
        }

        tx.on('proposed', function (res) {
          $scope.$apply(function () {
            setEngineStatus(res, false);
            $scope.exchanged(tx.hash);

            // Remember currency and increase order
            var found;

            for (var i = 0; i < $scope.currencies_all.length; i++) {
              if ($scope.currencies_all[i].value.toLowerCase() === $scope.exchange.amount_feedback.currency().get_iso().toLowerCase()) {
                $scope.currencies_all[i].order++;
                found = true;
                break;
              }
            }

            // // Removed feature until a permanent fix
            // if (!found) {
            //   $scope.currencies_all.push({
            //     "name": $scope.exchange.amount_feedback.currency().to_human().toUpperCase(),
            //     "value": $scope.exchange.amount_feedback.currency().to_human().toUpperCase(),
            //     "order": 1
            //   });
            // }
          });
        });
        tx.on('success',function(res){
          setEngineStatus(res, true);
        });
        tx.on('error', function (res) {
          setImmediate(function () {
            $scope.$apply(function () {
              $scope.mode = "error";

              if (res.result === "tejMaxFeeExceeded") {
                $scope.error_type = "maxFeeExceeded";
              }

              if (res.error === 'remoteError' &&
                  res.remote.error === 'noPath') {
                $scope.mode = "status";
                $scope.tx_result = "noPath";
              }
            });
          });
        });
        tx.submit();

        $scope.mode = "sending";
      };

      /**
       * N6. exchanged page
       */
      $scope.exchanged = function (hash) {
        $scope.mode = "status";
        $network.remote.on('transaction', handleAccountEvent);

        function handleAccountEvent(e) {
          $scope.$apply(function () {
            if (e.transaction.hash === hash) {
              setEngineStatus(e, true);
              $network.remote.removeListener('transaction', handleAccountEvent);
            }
          });
        }
      };

      function setEngineStatus(res, accepted) {
        $scope.engine_result = res.engine_result;
        $scope.engine_result_message = res.engine_result_message;
        switch (res.engine_result.slice(0, 3)) {
          case 'tes':
            $scope.tx_result = accepted ? "cleared" : "pending";
            break;
          case 'tem':
            $scope.tx_result = "malformed";
            break;
          case 'ter':
            $scope.tx_result = "failed";
            break;
          case 'tep':
            $scope.tx_result = "partial";
            break;
          case 'tec':
            $scope.tx_result = "claim";
            break;
          case 'tef':
            $scope.tx_result = "failure";
            break;
          case 'tel':
            $scope.tx_result = "local";
            break;
          default:
            console.warn("Unhandled engine status encountered!");
        }
      }

      $scope.reset();

      updateCurrencyOptions();

      // Stop the pathfinding when leaving the page
      $scope.$on('$destroy', function(){
        if (pf && "function" === typeof pf.close) {
          pf.close();
        }
      });
    }]);

  /**
   * Contact name and address uniqueness validator
   */
    // TODO move to global directives
  module.directive('unique', function() {
    return {
      restrict: 'A',
      require: '?ngModel',
      link: function ($scope, elm, attr, ctrl) {
        if (!ctrl) return;

        var validator = function(value) {
          var unique = !webutil.getContact($scope.userBlob.data.contacts,value);
          ctrl.$setValidity('unique', unique);
          if (unique) return value;
        };

        ctrl.$formatters.push(validator);
        ctrl.$parsers.unshift(validator);

        attr.$observe('unique', function() {
          validator(ctrl.$viewValue);
        });
      }
    };
  });
};

module.exports = ExchangeTab;
<|endoftext|>