<commit_before>/*
 * cli.js: Handlers for the forever CLI commands.
 *
 * (C) 2010 Nodejitsu Inc.
 * MIT LICENCE
 *
 */

var fs = require('fs'),
    util = require('util'),
    path = require('path'),
    flatiron = require('flatiron'),
    cliff = require('cliff'),
    daemon = require('daemon'),
    forever = require('../forever');

var cli = exports;

var help = [
  'usage: forever [action] [options] SCRIPT [script-options]',
  '',
  'Monitors the script specified in the current process or as a daemon',
  '',
  'actions:',
  '  start               Start SCRIPT as a daemon',
  '  stop                Stop the daemon SCRIPT',
  '  stopall             Stop all running forever scripts',
  '  restart             Restart the daemon SCRIPT',
  '  list                List all running forever scripts',
  '  config              Lists all forever user configuration',
  '  set <key> <val>     Sets the specified forever config <key>',
  '  clear <key>         Clears the specified forever config <key>',
  '  logs                Lists log files for all forever processes',
  '  logs <script|index> Tails the logs for <script|index>',
  '  columns add <col>   Adds the specified column to the output in `forever list`',
  '  columns rm <col>    Removed the specified column from the output in `forever list`',
  '  columns set <cols>  Set all columns for the output in `forever list`',
  '  cleanlogs           [CAREFUL] Deletes all historical forever log files',
  '',
  'options:',
  '  -m  MAX          Only run the specified script MAX times',
  '  -l  LOGFILE      Logs the forever output to LOGFILE',
  '  -o  OUTFILE      Logs stdout from child script to OUTFILE',
  '  -e  ERRFILE      Logs stderr from child script to ERRFILE',
  '  -p  PATH         Base path for all forever related files (pid files, etc.)',
  '  -c  COMMAND      COMMAND to execute (defaults to node)',
  '  -a, --append     Append logs',
  '  --pidfile        The pid file',
  '  --sourceDir      The source directory for which SCRIPT is relative to',
  '  --minUptime      Minimum uptime (millis) for a script to not be considered "spinning"',
  '  --spinSleepTime  Time to wait (millis) between launches of a spinning script.',
  '  --plain          Disable command line colors',
  '  -d, --debug      Forces forever to log debug output',
  '  -v, --verbose    Turns on the verbose messages from Forever',
  '  -s, --silent     Run the child script silencing stdout and stderr',
  '  -w, --watch      Watch for file changes',
  '  -h, --help       You\'re staring at it',
  '',
  '[Long Running Process]',
  '  The forever process will continue to run outputting log messages to the console.',
  '  ex. forever -o out.log -e err.log my-script.js',
  '',
  '[Daemon]',
  '  The forever process will run as a daemon which will make the target process start',
  '  in the background. This is extremely useful for remote starting simple node.js scripts',
  '  without using nohup. It is recommended to run start with -o -l, & -e.',
  '  ex. forever start -l forever.log -o out.log -e err.log my-daemon.js',
  '      forever stop my-daemon.js',
  ''
];

var app = flatiron.app;

app.use(flatiron.plugins.cli, {
  usage: help
});

var argvOptions = {
  'command':   {alias: 'c'},
  'errFile':   {alias: 'e'},
  'logFile':   {alias: 'l'},
  'appendLog': {alias: ['a', 'append'], boolean: true},
  'max':       {alias: 'm'},
  'outFile':   {alias: 'o'},
  'path':      {alias: 'p'},
  'help':      {alias: 'h'},
  'silent':    {alias: 's', boolean: true},
  'verbose':   {alias: 'v', boolean: true},
  'watch':     {alias: 'w', boolean: true},
  'debug':     {alias: 'd', boolean: true},
  'plain':     {boolean: true}
};

app.use(flatiron.plugins.config, {argv: argvOptions});
app.config.argv().env();

var reserved = ['root', 'pidPath'];

//
// ### @private function (file, options, callback)
// #### @file {string} Target script to start
// #### @options {Object} Options to start the script with
// #### @callback {function} Continuation to respond to when complete.
// Helper function that sets up the pathing for the specified `file`
// then stats the appropriate files and responds.
//
function tryStart(file, options, callback) {
  var fullLog, fullScript;

  fullLog = forever.logFilePath(options.logFile, options.uid);
  fullScript = path.join(options.sourceDir, file);

  forever.stat(fullLog, fullScript, options.appendLog, function (err) {
    if (err) {
      forever.log.error('Cannot start forever');
      forever.log.error(err.message);
      process.exit(-1);
    }

    callback();
  });
}

//
// ### @private function updateConfig (updater)
// #### @updater {function} Function which updates the forever config
// Helper which runs the specified `updater` and then saves the forever
// config to `forever.config.get('root')`.
//
function updateConfig(updater) {
  updater();
  forever.config.save(function (err) {
    if (err) {
      return forever.log.error('Error saving config: ' + err.message);
    }

    cli.config();
    var configFile = path.join(forever.config.get('root'), 'config.json');
    forever.log.info('Forever config saved: ' + configFile.yellow);
  });
}

//
// ### @private function checkColumn (name)
// #### @name {string} Column to check
// Checks if column `name` exists
//
function checkColumn(name) {
  if (!forever.columns[name]) {
    forever.log.error('Unknown column: ' + name.magenta);
    return false;
  }
  return true;
}

//
// ### @private function getOptions (file)
// #### @file {string} File to run. **Optional**
// Returns `options` object for use with `forever.start` and
// `forever.startDaemon`
//
function getOptions(file) {
  var options = {};
  //
  // First isolate options which should be passed to file
  //
  options.options = process.argv.splice(process.argv.indexOf(file) + 1);

  //
  // Now we have to force optimist to reparse command line options because
  // we've removed some before.
  //
  app.config.stores.argv.store = {};
  app.config.stores.argv.loadSync();

  [
    'pidFile', 'logFile', 'errFile', 'watch', 'minUptime', 'appendLog',
    'silent', 'max', 'command', 'path', 'spinSleepTime', 'sourceDir',
    'uid'
  ].forEach(function (key) {
    options[key] = app.config.get(key);
  });

  options.sourceDir = file && file[0] !== '/' ? process.cwd() : '/';
  if (options.sourceDir) {
    options.spawnWith = {cwd: options.sourceDir};
  }

  return options;
}

//
// ### function cleanLogs
// Deletes all historical forever log files
//
app.cmd('cleanlogs', cli.cleanLogs = function () {
  forever.log.silly('Tidying ' + forever.config.get('root'));
  forever.cleanUp(true).on('cleanUp', function () {
    forever.log.silly(forever.config.get('root') + ' tidied.');
  });
});

//
// ### function start (file)
// #### @file {string} Location of the script to spawn with forever
// Starts a forever process for the script located at `file` as daemon
// process.
//
app.cmd(/start (.+)/, cli.startDaemon = function (file) {
  forever.log.info('Forever processing file: ' + file.grey);
  var options = getOptions(file);
  tryStart(file, options, function () {
    var monitor = forever.startDaemon(file, options);
    monitor.on('start', function () {
      forever.startServer(monitor);
    });
  });
});

//
// ### function stop (file)
// #### @file {string} Target forever process to stop
// Stops the forever process specified by `file`.
//
app.cmd(/stop (.+)/, cli.stop = function (file) {
  var runner = forever.stop(file, true);

  runner.on('stop', function (process) {
    forever.log.info('Forever stopped process:');
    forever.log.data(process);
  });

  runner.on('error', function (err) {
    forever.log.error('Forever cannot find process with index: ' + file);
  });
});

//
// ### function stopall ()
// Stops all currently running forever processes.
//
app.cmd('stopall', cli.stopall = function () {
  var runner = forever.stopAll(true);
  runner.on('stopAll', function (processes) {
    if (processes) {
      forever.log.info('Forever stopped processes:');
      processes.split('\n').forEach(function (line) {
        forever.log.data(line);
      });
    }
    else {
      forever.log.info('No forever processes running');
    }
  });
});

//
// ### function restart (file)
// #### @file {string} Target process to restart
// Restarts the forever process specified by `file`.
//
app.cmd(/restart (.+)/, cli.restart = function (file) {
  var runner = forever.restart(file, true);
  runner.on('restart', function (processes) {
    if (processes) {
      forever.log.info('Forever restarted process(es):');
      processes.split('\n').forEach(function (line) {
        forever.log.data(line);
      });
    }
    else {
      forever.log.info('No forever processes running');
    }
  });

  runner.on('error', function (err) {
    forever.log.error('Error restarting process: ' + file.grey);
    forever.log.error(err.message);
  });
});

//
// ### function list ()
// Lists all currently running forever processes.
//
app.cmd('list', cli.list = function () {
  forever.list(true, function (err, processes) {
    if (processes) {
      forever.log.info('Forever processes running');
      processes.split('\n').forEach(function (line) {
        forever.log.data(line);
      });
    }
    else {
      forever.log.info('No forever processes running');
    }
  });
});

//
// ### function config ()
// Lists all of the configuration in `~/.forever/config.json`.
//
app.cmd('config', cli.config = function () {
  var keys = Object.keys(forever.config.store),
      conf = cliff.inspect(forever.config.store);

  if (keys.length <= 2) {
    conf = conf.replace(/\{\s/, '{ \n')
               .replace(/\}/, '\n}')
               .replace('\\033[90m', '  \\033[90m')
               .replace(/, /ig, ',\n  ');
  }
  else {
    conf = conf.replace(/\n\s{4}/ig, '\n  ');
  }

  conf.split('\n').forEach(function (line) {
    forever.log.data(line);
  });
});

//
// ### function set (key, value)
// #### @key {string} Key to set in forever config
// #### @value {string} Value to set for `key`
// Sets the specified `key` / `value` pair in the
// forever user config.
//
app.cmd('set :key :value', cli.set = function (key, value) {
  updateConfig(function () {
    forever.log.info('Setting forever config: ' + key.grey);
    forever.config.set(key, value);
  });
});

//
// ### function clear (key)
// #### @key {string} Key to remove from `~/.forever/config.json`
// Removes the specified `key` from the forever user config.
//
app.cmd('clear :key', cli.clear = function (key) {
  if (reserved.indexOf(key) !== -1) {
    forever.log.warn('Cannot clear reserved config: ' + key.grey);
    forever.log.warn('Use `forever set ' + key + '` instead');
    return;
  }

  updateConfig(function () {
    forever.log.info('Clearing forever config: ' + key.grey);
    forever.config.clear(key);
  });
});

//
// ### function logs (target)
// #### @target {string} **Optional** Target script or index to list logs for
// Displays the logs using `tail` for the specified `target`. If no `target` is
// specified then it will display log files for all running forever processes.
//
app.cmd('logs :index', cli.logs = function (index) {
  //
  // Helper function for listing all log files
  //
  function listFiles() {
    var index = 0,
        rows = [['   ', 'script', 'logfile']];

    forever.list(false, function (err, processes) {
      forever.log.info('Logs for running Forever processes');
      rows = rows.concat(processes.map(function (proc) {
        return ['[' + index++ + ']', proc.file.grey, proc.logFile.magenta];
      }));

      cliff.putRows('data', rows, ['white', 'grey', 'magenta']);
    });
  }

  if (typeof index === 'undefined') {
    return listFiles();
  }

  forever.tail(index, function (err, logs) {
    if (err) {
      return forever.log.error(err.message);
    }

    logs.forEach(function (proc) {
      forever.log.info('Showing logs for ' + proc.file.magenta);
      proc.logs.forEach(function (line) {
        forever.log.data(line);
      });
    });
  });
});

app.cmd('columns add :name', cli.addColumn = function (name) {
  if (checkColumn(name)) {
    var columns = forever.config.get('columns');

    if (~columns.indexOf(name)) {
      return forever.log.warn(name.magenta + ' already exists in forever');
    }

    forever.log.info('Adding column: ' + name.magenta);
    columns.push(name);

    forever.config.set('columns', columns);
    forever.config.saveSync();
  }
});

app.cmd('columns rm :name', cli.rmColumn = function (name) {
  if (checkColumn(name)) {
    var columns = forever.config.get('columns');

    if (!~columns.indexOf(name)) {
      return forever.log.warn(name.magenta + ' doesn\'t exist in forever');
    }

    forever.log.info('Removing column: ' + name.magenta);
    columns.splice(columns.indexOf(name), 1);

    forever.config.set('columns', columns);
    forever.config.saveSync();
  }
});

app.cmd('columns set :value', cli.setColumns = function (columns) {
  forever.log.info('Setting columns: ' + columns.join(' ').magenta);

  forever.config.set('columns', columns);
  forever.config.saveSync();
});

//
// ### function help ()
// Shows help
//
app.cmd('help', cli.help = function () {
  util.puts(help.join('\n'));
});

//
// ### function start (file)
// #### @file {string} Location of the script to spawn with forever
// Starts a forever process for the script located at `file` as non-daemon
// process.
//
// Remark: this regex matches everything. It has to be added at the end to
// make executing other commands possible.
//
app.cmd(/(.*)/, cli.start = function (file) {
  var options = getOptions(file);
  tryStart(file, options, function () {
    var monitor = forever.start(file, options);
    monitor.on('start', function () {
      forever.startServer(monitor);
    });
  });
});

cli.startCLI = function () {
  app.init(function () {
    if (app.config.get('help')) {
      return util.puts(help.join('\n'));
    }

    app.start();
  });
};

<commit_msg>[fix] Make option aliases work again<commit_after>/*
 * cli.js: Handlers for the forever CLI commands.
 *
 * (C) 2010 Nodejitsu Inc.
 * MIT LICENCE
 *
 */

var fs = require('fs'),
    util = require('util'),
    path = require('path'),
    flatiron = require('flatiron'),
    cliff = require('cliff'),
    daemon = require('daemon'),
    forever = require('../forever');

var cli = exports;

var help = [
  'usage: forever [action] [options] SCRIPT [script-options]',
  '',
  'Monitors the script specified in the current process or as a daemon',
  '',
  'actions:',
  '  start               Start SCRIPT as a daemon',
  '  stop                Stop the daemon SCRIPT',
  '  stopall             Stop all running forever scripts',
  '  restart             Restart the daemon SCRIPT',
  '  list                List all running forever scripts',
  '  config              Lists all forever user configuration',
  '  set <key> <val>     Sets the specified forever config <key>',
  '  clear <key>         Clears the specified forever config <key>',
  '  logs                Lists log files for all forever processes',
  '  logs <script|index> Tails the logs for <script|index>',
  '  columns add <col>   Adds the specified column to the output in `forever list`',
  '  columns rm <col>    Removed the specified column from the output in `forever list`',
  '  columns set <cols>  Set all columns for the output in `forever list`',
  '  cleanlogs           [CAREFUL] Deletes all historical forever log files',
  '',
  'options:',
  '  -m  MAX          Only run the specified script MAX times',
  '  -l  LOGFILE      Logs the forever output to LOGFILE',
  '  -o  OUTFILE      Logs stdout from child script to OUTFILE',
  '  -e  ERRFILE      Logs stderr from child script to ERRFILE',
  '  -p  PATH         Base path for all forever related files (pid files, etc.)',
  '  -c  COMMAND      COMMAND to execute (defaults to node)',
  '  -a, --append     Append logs',
  '  --pidfile        The pid file',
  '  --sourceDir      The source directory for which SCRIPT is relative to',
  '  --minUptime      Minimum uptime (millis) for a script to not be considered "spinning"',
  '  --spinSleepTime  Time to wait (millis) between launches of a spinning script.',
  '  --plain          Disable command line colors',
  '  -d, --debug      Forces forever to log debug output',
  '  -v, --verbose    Turns on the verbose messages from Forever',
  '  -s, --silent     Run the child script silencing stdout and stderr',
  '  -w, --watch      Watch for file changes',
  '  -h, --help       You\'re staring at it',
  '',
  '[Long Running Process]',
  '  The forever process will continue to run outputting log messages to the console.',
  '  ex. forever -o out.log -e err.log my-script.js',
  '',
  '[Daemon]',
  '  The forever process will run as a daemon which will make the target process start',
  '  in the background. This is extremely useful for remote starting simple node.js scripts',
  '  without using nohup. It is recommended to run start with -o -l, & -e.',
  '  ex. forever start -l forever.log -o out.log -e err.log my-daemon.js',
  '      forever stop my-daemon.js',
  ''
];

var app = flatiron.app;

app.use(flatiron.plugins.cli, {
  usage: help
});

var argvOptions = {
  'command':   {alias: 'c'},
  'errFile':   {alias: 'e'},
  'logFile':   {alias: 'l'},
  'appendLog': {alias: ['a', 'append'], boolean: true},
  'max':       {alias: 'm'},
  'outFile':   {alias: 'o'},
  'path':      {alias: 'p'},
  'help':      {alias: 'h'},
  'silent':    {alias: 's', boolean: true},
  'verbose':   {alias: 'v', boolean: true},
  'watch':     {alias: 'w', boolean: true},
  'debug':     {alias: 'd', boolean: true},
  'plain':     {boolean: true}
};

app.use(flatiron.plugins.config);
app.config.argv(argvOptions).env();

var reserved = ['root', 'pidPath'];

//
// ### @private function (file, options, callback)
// #### @file {string} Target script to start
// #### @options {Object} Options to start the script with
// #### @callback {function} Continuation to respond to when complete.
// Helper function that sets up the pathing for the specified `file`
// then stats the appropriate files and responds.
//
function tryStart(file, options, callback) {
  var fullLog, fullScript;

  fullLog = forever.logFilePath(options.logFile, options.uid);
  fullScript = path.join(options.sourceDir, file);

  forever.stat(fullLog, fullScript, options.appendLog, function (err) {
    if (err) {
      forever.log.error('Cannot start forever');
      forever.log.error(err.message);
      process.exit(-1);
    }

    callback();
  });
}

//
// ### @private function updateConfig (updater)
// #### @updater {function} Function which updates the forever config
// Helper which runs the specified `updater` and then saves the forever
// config to `forever.config.get('root')`.
//
function updateConfig(updater) {
  updater();
  forever.config.save(function (err) {
    if (err) {
      return forever.log.error('Error saving config: ' + err.message);
    }

    cli.config();
    var configFile = path.join(forever.config.get('root'), 'config.json');
    forever.log.info('Forever config saved: ' + configFile.yellow);
  });
}

//
// ### @private function checkColumn (name)
// #### @name {string} Column to check
// Checks if column `name` exists
//
function checkColumn(name) {
  if (!forever.columns[name]) {
    forever.log.error('Unknown column: ' + name.magenta);
    return false;
  }
  return true;
}

//
// ### @private function getOptions (file)
// #### @file {string} File to run. **Optional**
// Returns `options` object for use with `forever.start` and
// `forever.startDaemon`
//
function getOptions(file) {
  var options = {};
  //
  // First isolate options which should be passed to file
  //
  options.options = process.argv.splice(process.argv.indexOf(file) + 1);

  //
  // Now we have to force optimist to reparse command line options because
  // we've removed some before.
  //
  app.config.stores.argv.store = {};
  app.config.stores.argv.loadSync();

  [
    'pidFile', 'logFile', 'errFile', 'watch', 'minUptime', 'appendLog',
    'silent', 'max', 'command', 'path', 'spinSleepTime', 'sourceDir',
    'uid'
  ].forEach(function (key) {
    options[key] = app.config.get(key);
  });

  options.sourceDir = file && file[0] !== '/' ? process.cwd() : '/';
  if (options.sourceDir) {
    options.spawnWith = {cwd: options.sourceDir};
  }

  return options;
}

//
// ### function cleanLogs
// Deletes all historical forever log files
//
app.cmd('cleanlogs', cli.cleanLogs = function () {
  forever.log.silly('Tidying ' + forever.config.get('root'));
  forever.cleanUp(true).on('cleanUp', function () {
    forever.log.silly(forever.config.get('root') + ' tidied.');
  });
});

//
// ### function start (file)
// #### @file {string} Location of the script to spawn with forever
// Starts a forever process for the script located at `file` as daemon
// process.
//
app.cmd(/start (.+)/, cli.startDaemon = function (file) {
  forever.log.info('Forever processing file: ' + file.grey);
  var options = getOptions(file);
  tryStart(file, options, function () {
    var monitor = forever.startDaemon(file, options);
    monitor.on('start', function () {
      forever.startServer(monitor);
    });
  });
});

//
// ### function stop (file)
// #### @file {string} Target forever process to stop
// Stops the forever process specified by `file`.
//
app.cmd(/stop (.+)/, cli.stop = function (file) {
  var runner = forever.stop(file, true);

  runner.on('stop', function (process) {
    forever.log.info('Forever stopped process:');
    forever.log.data(process);
  });

  runner.on('error', function (err) {
    forever.log.error('Forever cannot find process with index: ' + file);
  });
});

//
// ### function stopall ()
// Stops all currently running forever processes.
//
app.cmd('stopall', cli.stopall = function () {
  var runner = forever.stopAll(true);
  runner.on('stopAll', function (processes) {
    if (processes) {
      forever.log.info('Forever stopped processes:');
      processes.split('\n').forEach(function (line) {
        forever.log.data(line);
      });
    }
    else {
      forever.log.info('No forever processes running');
    }
  });
});

//
// ### function restart (file)
// #### @file {string} Target process to restart
// Restarts the forever process specified by `file`.
//
app.cmd(/restart (.+)/, cli.restart = function (file) {
  var runner = forever.restart(file, true);
  runner.on('restart', function (processes) {
    if (processes) {
      forever.log.info('Forever restarted process(es):');
      processes.split('\n').forEach(function (line) {
        forever.log.data(line);
      });
    }
    else {
      forever.log.info('No forever processes running');
    }
  });

  runner.on('error', function (err) {
    forever.log.error('Error restarting process: ' + file.grey);
    forever.log.error(err.message);
  });
});

//
// ### function list ()
// Lists all currently running forever processes.
//
app.cmd('list', cli.list = function () {
  forever.list(true, function (err, processes) {
    if (processes) {
      forever.log.info('Forever processes running');
      processes.split('\n').forEach(function (line) {
        forever.log.data(line);
      });
    }
    else {
      forever.log.info('No forever processes running');
    }
  });
});

//
// ### function config ()
// Lists all of the configuration in `~/.forever/config.json`.
//
app.cmd('config', cli.config = function () {
  var keys = Object.keys(forever.config.store),
      conf = cliff.inspect(forever.config.store);

  if (keys.length <= 2) {
    conf = conf.replace(/\{\s/, '{ \n')
               .replace(/\}/, '\n}')
               .replace('\\033[90m', '  \\033[90m')
               .replace(/, /ig, ',\n  ');
  }
  else {
    conf = conf.replace(/\n\s{4}/ig, '\n  ');
  }

  conf.split('\n').forEach(function (line) {
    forever.log.data(line);
  });
});

//
// ### function set (key, value)
// #### @key {string} Key to set in forever config
// #### @value {string} Value to set for `key`
// Sets the specified `key` / `value` pair in the
// forever user config.
//
app.cmd('set :key :value', cli.set = function (key, value) {
  updateConfig(function () {
    forever.log.info('Setting forever config: ' + key.grey);
    forever.config.set(key, value);
  });
});

//
// ### function clear (key)
// #### @key {string} Key to remove from `~/.forever/config.json`
// Removes the specified `key` from the forever user config.
//
app.cmd('clear :key', cli.clear = function (key) {
  if (reserved.indexOf(key) !== -1) {
    forever.log.warn('Cannot clear reserved config: ' + key.grey);
    forever.log.warn('Use `forever set ' + key + '` instead');
    return;
  }

  updateConfig(function () {
    forever.log.info('Clearing forever config: ' + key.grey);
    forever.config.clear(key);
  });
});

//
// ### function logs (target)
// #### @target {string} **Optional** Target script or index to list logs for
// Displays the logs using `tail` for the specified `target`. If no `target` is
// specified then it will display log files for all running forever processes.
//
app.cmd('logs :index', cli.logs = function (index) {
  //
  // Helper function for listing all log files
  //
  function listFiles() {
    var index = 0,
        rows = [['   ', 'script', 'logfile']];

    forever.list(false, function (err, processes) {
      forever.log.info('Logs for running Forever processes');
      rows = rows.concat(processes.map(function (proc) {
        return ['[' + index++ + ']', proc.file.grey, proc.logFile.magenta];
      }));

      cliff.putRows('data', rows, ['white', 'grey', 'magenta']);
    });
  }

  if (typeof index === 'undefined') {
    return listFiles();
  }

  forever.tail(index, function (err, logs) {
    if (err) {
      return forever.log.error(err.message);
    }

    logs.forEach(function (proc) {
      forever.log.info('Showing logs for ' + proc.file.magenta);
      proc.logs.forEach(function (line) {
        forever.log.data(line);
      });
    });
  });
});

app.cmd('columns add :name', cli.addColumn = function (name) {
  if (checkColumn(name)) {
    var columns = forever.config.get('columns');

    if (~columns.indexOf(name)) {
      return forever.log.warn(name.magenta + ' already exists in forever');
    }

    forever.log.info('Adding column: ' + name.magenta);
    columns.push(name);

    forever.config.set('columns', columns);
    forever.config.saveSync();
  }
});

app.cmd('columns rm :name', cli.rmColumn = function (name) {
  if (checkColumn(name)) {
    var columns = forever.config.get('columns');

    if (!~columns.indexOf(name)) {
      return forever.log.warn(name.magenta + ' doesn\'t exist in forever');
    }

    forever.log.info('Removing column: ' + name.magenta);
    columns.splice(columns.indexOf(name), 1);

    forever.config.set('columns', columns);
    forever.config.saveSync();
  }
});

app.cmd('columns set :value', cli.setColumns = function (columns) {
  forever.log.info('Setting columns: ' + columns.join(' ').magenta);

  forever.config.set('columns', columns);
  forever.config.saveSync();
});

//
// ### function help ()
// Shows help
//
app.cmd('help', cli.help = function () {
  util.puts(help.join('\n'));
});

//
// ### function start (file)
// #### @file {string} Location of the script to spawn with forever
// Starts a forever process for the script located at `file` as non-daemon
// process.
//
// Remark: this regex matches everything. It has to be added at the end to
// make executing other commands possible.
//
app.cmd(/(.*)/, cli.start = function (file) {
  var options = getOptions(file);
  tryStart(file, options, function () {
    var monitor = forever.start(file, options);
    monitor.on('start', function () {
      forever.startServer(monitor);
    });
  });
});

cli.startCLI = function () {
  app.init(function () {
    if (app.config.get('help')) {
      return util.puts(help.join('\n'));
    }

    app.start();
  });
};

<|endoftext|>


<commit_before>var path = require('path');

module.exports = function(grunt) {
  var electronVer = '0.36.5'; // Electron build version

  // Load the plugins...
  grunt.loadNpmTasks('grunt-electron');
  if (process.platform === 'win32') grunt.loadNpmTasks('grunt-electron-installer');
  //if (process.platform === 'darwin') grunt.loadNpmTasks('grunt-appdmg');

  // Load the tasks in '/tasks'
  grunt.loadTasks('tasks');

  // Set all subsequent paths to the relative to the root of the repo
  grunt.file.setBase(path.resolve('..'));

  var appInfo = grunt.file.readJSON('package.json');
  var version = appInfo.version;
  var numericVersion = appInfo.version.split('-')[0];
  var buildIgnore = 'build/dist|build/node_modules|build/tasks|build/package.json|build/Gruntfile.js';

  // Project configuration.
  grunt.initConfig({
    name: appInfo.name,
    pkg: appInfo,
    electron: {
      macbuild: {
        options: {
          name: appInfo.name,
          dir: './',
          out: 'build/dist',
          icon: 'resources/darwin/app.icns',
          version: electronVer,
          platform: 'darwin',
          arch: 'x64',
          ignore: buildIgnore,
          'app-version': version,
          overwrite: true,
          prune: true,
          'app-bundle-id': 'pancakepainter-main',
          'helper-bundle-id': 'pancakepainter-helper'
        }
      },
      winbuild: {
        options: {
          name: appInfo.name,
          dir: './',
          out: 'build/dist',
          icon: 'resources/win32/app.ico',
          version: electronVer,
          platform: 'win32',
          arch: 'x64,ia32',
          ignore: buildIgnore,
          'app-version': version,
          overwrite: true,
          prune: true,
          'version-string': {
            CompanyName: 'PancakeBot Inc.',
            LegalCopyright: 'Copyright (C) PancakeBot Inc., all rights reserved. Code under Apache v2.0 free and open source license.',
            FileDescription: appInfo.name,
            OriginalFilename: appInfo.name + '.exe',
            FileVersion: electronVer,
            ProductVersion: version,
            ProductName: appInfo.name,
            InternalName: appInfo.name
          }
        }
      }
    },
    'create-windows-installer': {
      64: {
        iconUrl: "http://raw.githubusercontent.com/PancakeBot/PancakePainter/master/resources/win32/app.ico",
        appDirectory: 'build/dist/PancakePainter-win32-x64',
        outputDirectory: 'build/dist/winstall64/',
        loadingGif: 'resources/win32/install_anim.gif',
        version: numericVersion,
        authors: 'PancakeBot Inc.'
      },
      32: {
        iconUrl: "http://raw.githubusercontent.com/PancakeBot/PancakePainter/master/resources/win32/app.ico",
        appDirectory: 'build/dist/PancakePainter-win32-ia32',
        outputDirectory: 'build/dist/winstall32/',
        loadingGif: 'resources/win32/install_anim.gif',
        version: numericVersion,
        authors: 'PancakeBot Inc.'
      }
    },
  });

  // Default task(s).
  grunt.registerTask('default', ['pre-build']);
};
<commit_msg>Pull Electron version directly in build script via package<commit_after>var path = require('path');

module.exports = function(grunt) {
  // Load the plugins...
  grunt.loadNpmTasks('grunt-electron');
  if (process.platform === 'win32') grunt.loadNpmTasks('grunt-electron-installer');
  //if (process.platform === 'darwin') grunt.loadNpmTasks('grunt-appdmg');

  // Load the tasks in '/tasks'
  grunt.loadTasks('tasks');

  // Set all subsequent paths to the relative to the root of the repo
  grunt.file.setBase(path.resolve('..'));

  var appInfo = grunt.file.readJSON('package.json');
  var version = appInfo.version;
  var electronVer = appInfo.electronVersion; // Electron build version
  var numericVersion = appInfo.version.split('-')[0];
  var buildIgnore = 'build/dist|build/node_modules|build/tasks|build/package.json|build/Gruntfile.js';

  // Project configuration.
  grunt.initConfig({
    name: appInfo.name,
    pkg: appInfo,
    electron: {
      macbuild: {
        options: {
          name: appInfo.name,
          dir: './',
          out: 'build/dist',
          icon: 'resources/darwin/app.icns',
          version: electronVer,
          platform: 'darwin',
          arch: 'x64',
          ignore: buildIgnore,
          'app-version': version,
          overwrite: true,
          prune: true,
          'app-bundle-id': 'pancakepainter-main',
          'helper-bundle-id': 'pancakepainter-helper'
        }
      },
      winbuild: {
        options: {
          name: appInfo.name,
          dir: './',
          out: 'build/dist',
          icon: 'resources/win32/app.ico',
          version: electronVer,
          platform: 'win32',
          arch: 'x64,ia32',
          ignore: buildIgnore,
          'app-version': version,
          overwrite: true,
          prune: true,
          'version-string': {
            CompanyName: 'PancakeBot Inc.',
            LegalCopyright: 'Copyright (C) PancakeBot Inc., all rights reserved. Code under Apache v2.0 free and open source license.',
            FileDescription: appInfo.name,
            OriginalFilename: appInfo.name + '.exe',
            FileVersion: electronVer,
            ProductVersion: version,
            ProductName: appInfo.name,
            InternalName: appInfo.name
          }
        }
      }
    },
    'create-windows-installer': {
      64: {
        iconUrl: "http://raw.githubusercontent.com/PancakeBot/PancakePainter/master/resources/win32/app.ico",
        appDirectory: 'build/dist/PancakePainter-win32-x64',
        outputDirectory: 'build/dist/winstall64/',
        loadingGif: 'resources/win32/install_anim.gif',
        version: numericVersion,
        authors: 'PancakeBot Inc.'
      },
      32: {
        iconUrl: "http://raw.githubusercontent.com/PancakeBot/PancakePainter/master/resources/win32/app.ico",
        appDirectory: 'build/dist/PancakePainter-win32-ia32',
        outputDirectory: 'build/dist/winstall32/',
        loadingGif: 'resources/win32/install_anim.gif',
        version: numericVersion,
        authors: 'PancakeBot Inc.'
      }
    },
  });

  // Default task(s).
  grunt.registerTask('default', ['pre-build']);
};
<|endoftext|>


<commit_before>import {
  Animator,
  BaseClass,
  Font,
  GraphComponent,
  HierarchicNestingPolicy,
  IAnimation,
  INode,
  IPoint,
  IVisualCreator,
  Point,
  Rect,
  ShapeNodeShape,
  ShapeNodeStyle,
  SvgExport,
  SvgVisual,
  TextRenderSupport,
  TimeSpan,
  VoidNodeStyle
} from 'yfiles' //from 'yfiles/typings/yfiles-api-npm'
import {
  createAggregationStyle,
  createAssociationStyle,
  createCompositionStyle,
  createDirectedAssociationStyle,
  createGeneralizationStyle
} from '../edges/styles/UMLEdgeStyleFactory.js'

/**
 * Provides the visuals of the edge creation buttons.
 */
export default class ButtonVisualCreator extends BaseClass(IVisualCreator) {
  /**
   * The provided edge creation buttons.
   * @returns {IEdgeStyle[]}
   */
  static get edgeCreationButtons() {
    return [
      // createRealizationStyle(),
      createGeneralizationStyle(),
      createCompositionStyle(),
      createAggregationStyle(),
      // createDependencyStyle(),
      // createDirectedAssociationStyle(),
      createAssociationStyle()
    ]
  }

  /**
   * Creates the visual creator for the edge creation buttons.
   * @param {INode} node The node for which the buttons should be created.
   * @param {.GraphComponent} graphComponent The graph component in which the node resides.
   */
  constructor(node, graphComponent) {
    super()
    this.renderer = new ButtonIconRenderer()
    this.node = node
    this.animator = new Animator(graphComponent)
    this.animator.autoInvalidation = false
    this.animator.allowUserInteraction = true
    ButtonVisualCreator.buttons = []
  }

  /**
   * @param {.IRenderContext} ctx The context that describes where the visual will be used.
   * @returns {.Visual}
   */
  createVisual(ctx) {
    // save the button elements to conveniently use them for hit testing
    ButtonVisualCreator.buttons = []

    // the context button container
    const container = document.createElementNS('http://www.w3.org/2000/svg', 'g')
    container.setAttribute('class', 'context-button')

    // create the edge creation buttons
    let first = -60
    const step = 40
    const animations = []
    for (let i = 0; i < ButtonVisualCreator.edgeCreationButtons.length; i++) {
      const child = this.renderer.renderButton(ButtonVisualCreator.edgeCreationButtons[i])
      const childg1 = document.createElementNS('http://www.w3.org/2000/svg', 'g')
      const childg2 = document.createElementNS('http://www.w3.org/2000/svg', 'g')
      const childg3 = document.createElementNS('http://www.w3.org/2000/svg', 'g')
      childg1.setAttribute('transform', 'translate(-15 -15)')
      ButtonVisualCreator.buttons.push(childg1)
      childg2.setAttribute('transform', 'translate(0 0)')
      childg3.setAttribute('transform', `rotate(${-first})`)
      animations.push(new ButtonAnimation(childg1, first, childg2))
      childg1.appendChild(child)
      childg2.appendChild(childg1)
      childg3.appendChild(childg2)
      container.appendChild(childg3)
      first += step
    }
    const layout = this.node.layout
    const topRight = layout.topRight
    SvgVisual.setTranslate(container, topRight.x, topRight.y)

    // add interface/abstract toggle buttons
    const interfaceButton = this.renderer.renderTextButton('I')
    SvgVisual.setTranslate(interfaceButton, layout.x - topRight.x, layout.y - topRight.y - 25)
    const abstractButton = this.renderer.renderTextButton('A')
    SvgVisual.setTranslate(abstractButton, layout.x - topRight.x + 25, layout.y - topRight.y - 25)
    container.appendChild(interfaceButton)
    container.appendChild(abstractButton)

    // visualize the button state
    if (this.node.tag.stereotype === 'interface') {
      interfaceButton.setAttribute('class', 'interface-toggle toggled')
    }
    if (this.node.tag.constraint === 'abstract') {
      abstractButton.setAttribute('class', 'abstract-toggle toggled')
    }

    // we fade the buttons via CSS
    container.setAttribute('opacity', '0')
    setTimeout(() => {
      container.setAttribute('opacity', '1')
    }, 0)

    // we animate the position 'manually' because doing it via CSS causes animation artifacts
    animations.forEach(animation => {
      this.animator.animate(animation)
    })

    // store the button state to update them if needed
    container['data-renderDataCache'] = {
      width: layout.width,
      height: layout.height,
      interfaceToggle: this.node.tag.stereotype,
      constraintToggle: this.node.tag.constraint
    }

    return new SvgVisual(container)
  }

  /**
   * @param {.IRenderContext} ctx The context that describes where the visual will be used in.
   * @param {.Visual} oldVisual The visual instance that had been returned the last time the
   *   {@link .IVisualCreator#createVisual} method was called on this instance.
   * @returns {.Visual}
   */
  updateVisual(ctx, oldVisual) {
    const layout = this.node.layout
    const topRight = layout.topRight
    const svgElement = oldVisual.svgElement
    const cache = svgElement['data-renderDataCache']

    // update the container layout
    SvgVisual.setTranslate(svgElement, topRight.x, topRight.y)

    // maybe update the toggle buttons
    const interfaceButton = svgElement.childNodes[svgElement.childNodes.length - 2]
    const abstractButton = svgElement.childNodes[svgElement.childNodes.length - 1]
    if (!interfaceButton || !abstractButton) {
      this.createVisual(ctx)
    }

    if (cache.width !== layout.width || cache.height !== layout.height) {
      SvgVisual.setTranslate(interfaceButton, layout.x - topRight.x, layout.y - topRight.y - 25)
      SvgVisual.setTranslate(abstractButton, layout.x - topRight.x + 25, layout.y - topRight.y - 25)
      cache.width = layout.width
      cache.height = layout.height
    }

    // update the button state if they have changed
    if (cache.interfaceToggle !== this.node.tag.stereotype) {
      interfaceButton.setAttribute(
        'class',
        this.node.style.model.stereotype.length > 0
          ? 'interface-toggle toggled'
          : 'interface-toggle'
      )
      cache.interfaceToggle = this.node.style.model.stereotype
    }
    if (cache.constraintToggle !== this.node.tag.constraint) {
      abstractButton.setAttribute(
        'class',
          this.node.tag.constraint.length > 0 ? 'abstract-toggle toggled' : 'abstract-toggle'
      )
      cache.constraintToggle = this.node.tag.constraint
    }

    return oldVisual
  }

  /**
   * Helper method to get the edge style of the edge creation button if there is a button at the given location.
   * @param {.CanvasComponent} canvasComponent The canvas component in which the node resides.
   * @param {INode} node The node who should be checked for a button.
   * @param {IPoint} location The world location to check for a button.
   * @returns {IEdgeStyle} The edge style if there is a button at the given location, otherwise null.
   */
  static getStyleButtonAt(canvasComponent, node, location) {
    for (let i = 0; i < ButtonVisualCreator.buttons.length; i++) {
      const boundingRect = ButtonVisualCreator.buttons[i].getBoundingClientRect()
      const worldTopLeft = canvasComponent.toWorldFromPage(
        new Point(boundingRect.left, boundingRect.top)
      )
      if (
        location.x >= worldTopLeft.x &&
        location.x <= worldTopLeft.x + 30 &&
        location.y >= worldTopLeft.y &&
        location.y <= worldTopLeft.y + 30
      ) {
        return ButtonVisualCreator.edgeCreationButtons[i]
      }
    }
    return null
  }

  /**
   * Helper method to get the context button at the given location.
   * @param {INode} node The node who should be checked for a button.
   * @param {IPoint} location The world location to check for a button.
   * @returns {string|object} The context button at the given or null.
   */
  static getContextButtonAt(node, location) {
    const layout = node.layout
    if (
      location.x >= layout.x &&
      location.x <= layout.x + 20 &&
      location.y <= layout.y - 5 &&
      location.y >= layout.y - 25
    ) {
      return 'interface'
    }
    if (
      location.x >= layout.x + 25 &&
      location.x <= layout.x + 45 &&
      location.y <= layout.y - 5 &&
      location.y >= layout.y - 25
    ) {
      return 'abstract'
    }
    return null
  }
}

/**
 * Executes the button fan out animation.
 */
class ButtonAnimation extends BaseClass(IAnimation) {
  constructor(rotationElement, finishAngle, translationElement) {
    super()
    this.rotationElement = rotationElement
    this.translationElement = translationElement
    this.finishAngle = finishAngle
  }

  get preferredDuration() {
    return TimeSpan.fromMilliseconds(200)
  }

  /**
   * @param {number} time - the animation time [0,1]
   */
  animate(time) {
    this.rotationElement.setAttribute(
      'transform',
      `rotate(${time * this.finishAngle}) translate(-15 -15)`
    )
    this.translationElement.setAttribute('transform', `translate(${time * 50} 0)`)
  }

  cleanUp() {}

  initialize() {}
}

/**
 * Helper class that creates a round button visual containing a given edge style visualization.
 */
class ButtonIconRenderer {
  constructor() {
    this.gc = new GraphComponent()
    this.gc.graphModelManager.hierarchicNestingPolicy = HierarchicNestingPolicy.NONE
    this.gc.graphModelManager.edgeGroup.above(this.gc.graphModelManager.nodeGroup)
  }

  renderTextButton(text) {
    const textSize = TextRenderSupport.measureText(
      text,
      new Font({
        fontFamily: 'monospace',
        fontSize: 18
      })
    )
    const container = document.createElementNS('http://www.w3.org/2000/svg', 'g')
    const background = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
    background.setAttribute('width', '20')
    background.setAttribute('height', '20')
    background.setAttribute('fill', '#FFF')
    background.setAttribute('stroke', '#607D8B')
    const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text')
    textElement.setAttribute('font-family', 'monospace')
    textElement.setAttribute('font-size', '18')
    textElement.setAttribute('x', `${(20 - textSize.width) / 2}`)
    textElement.setAttribute('y', '16')
    textElement.textContent = text
    container.appendChild(background)
    container.appendChild(textElement)
    container.setAttribute('class', text === 'I' ? 'interface-toggle' : 'abstract-toggle')
    return container
  }
}
<commit_msg>Fix deleted too much code<commit_after>import {
  Animator,
  BaseClass,
  Font,
  GraphComponent,
  HierarchicNestingPolicy,
  IAnimation,
  INode,
  IPoint,
  IVisualCreator,
  Point,
  Rect,
  ShapeNodeShape,
  ShapeNodeStyle,
  SvgExport,
  SvgVisual,
  TextRenderSupport,
  TimeSpan,
  VoidNodeStyle
} from 'yfiles' //from 'yfiles/typings/yfiles-api-npm'
import {
  createAggregationStyle,
  createAssociationStyle,
  createCompositionStyle,
  createDirectedAssociationStyle,
  createGeneralizationStyle
} from '../edges/styles/UMLEdgeStyleFactory.js'

/**
 * Provides the visuals of the edge creation buttons.
 */
export default class ButtonVisualCreator extends BaseClass(IVisualCreator) {
  /**
   * The provided edge creation buttons.
   * @returns {IEdgeStyle[]}
   */
  static get edgeCreationButtons() {
    return [
      // createRealizationStyle(),
      createGeneralizationStyle(),
      createCompositionStyle(),
      createAggregationStyle(),
      // createDependencyStyle(),
      // createDirectedAssociationStyle(),
      createAssociationStyle()
    ]
  }

  /**
   * Creates the visual creator for the edge creation buttons.
   * @param {INode} node The node for which the buttons should be created.
   * @param {.GraphComponent} graphComponent The graph component in which the node resides.
   */
  constructor(node, graphComponent) {
    super()
    this.renderer = new ButtonIconRenderer()
    this.node = node
    this.animator = new Animator(graphComponent)
    this.animator.autoInvalidation = false
    this.animator.allowUserInteraction = true
    ButtonVisualCreator.buttons = []
  }

  /**
   * @param {.IRenderContext} ctx The context that describes where the visual will be used.
   * @returns {.Visual}
   */
  createVisual(ctx) {
    // save the button elements to conveniently use them for hit testing
    ButtonVisualCreator.buttons = []

    // the context button container
    const container = document.createElementNS('http://www.w3.org/2000/svg', 'g')
    container.setAttribute('class', 'context-button')

    // create the edge creation buttons
    let first = -60
    const step = 40
    const animations = []
    for (let i = 0; i < ButtonVisualCreator.edgeCreationButtons.length; i++) {
      const child = this.renderer.renderButton(ButtonVisualCreator.edgeCreationButtons[i])
      const childg1 = document.createElementNS('http://www.w3.org/2000/svg', 'g')
      const childg2 = document.createElementNS('http://www.w3.org/2000/svg', 'g')
      const childg3 = document.createElementNS('http://www.w3.org/2000/svg', 'g')
      childg1.setAttribute('transform', 'translate(-15 -15)')
      ButtonVisualCreator.buttons.push(childg1)
      childg2.setAttribute('transform', 'translate(0 0)')
      childg3.setAttribute('transform', `rotate(${-first})`)
      animations.push(new ButtonAnimation(childg1, first, childg2))
      childg1.appendChild(child)
      childg2.appendChild(childg1)
      childg3.appendChild(childg2)
      container.appendChild(childg3)
      first += step
    }
    const layout = this.node.layout
    const topRight = layout.topRight
    SvgVisual.setTranslate(container, topRight.x, topRight.y)

    // add interface/abstract toggle buttons
    const interfaceButton = this.renderer.renderTextButton('I')
    SvgVisual.setTranslate(interfaceButton, layout.x - topRight.x, layout.y - topRight.y - 25)
    const abstractButton = this.renderer.renderTextButton('A')
    SvgVisual.setTranslate(abstractButton, layout.x - topRight.x + 25, layout.y - topRight.y - 25)
    container.appendChild(interfaceButton)
    container.appendChild(abstractButton)

    // visualize the button state
    if (this.node.tag.stereotype === 'interface') {
      interfaceButton.setAttribute('class', 'interface-toggle toggled')
    }
    if (this.node.tag.constraint === 'abstract') {
      abstractButton.setAttribute('class', 'abstract-toggle toggled')
    }

    // we fade the buttons via CSS
    container.setAttribute('opacity', '0')
    setTimeout(() => {
      container.setAttribute('opacity', '1')
    }, 0)

    // we animate the position 'manually' because doing it via CSS causes animation artifacts
    animations.forEach(animation => {
      this.animator.animate(animation)
    })

    // store the button state to update them if needed
    container['data-renderDataCache'] = {
      width: layout.width,
      height: layout.height,
      interfaceToggle: this.node.tag.stereotype,
      constraintToggle: this.node.tag.constraint
    }

    return new SvgVisual(container)
  }

  /**
   * @param {.IRenderContext} ctx The context that describes where the visual will be used in.
   * @param {.Visual} oldVisual The visual instance that had been returned the last time the
   *   {@link .IVisualCreator#createVisual} method was called on this instance.
   * @returns {.Visual}
   */
  updateVisual(ctx, oldVisual) {
    const layout = this.node.layout
    const topRight = layout.topRight
    const svgElement = oldVisual.svgElement
    const cache = svgElement['data-renderDataCache']

    // update the container layout
    SvgVisual.setTranslate(svgElement, topRight.x, topRight.y)

    // maybe update the toggle buttons
    const interfaceButton = svgElement.childNodes[svgElement.childNodes.length - 2]
    const abstractButton = svgElement.childNodes[svgElement.childNodes.length - 1]
    if (!interfaceButton || !abstractButton) {
      this.createVisual(ctx)
    }

    if (cache.width !== layout.width || cache.height !== layout.height) {
      SvgVisual.setTranslate(interfaceButton, layout.x - topRight.x, layout.y - topRight.y - 25)
      SvgVisual.setTranslate(abstractButton, layout.x - topRight.x + 25, layout.y - topRight.y - 25)
      cache.width = layout.width
      cache.height = layout.height
    }

    // update the button state if they have changed
    if (cache.interfaceToggle !== this.node.tag.stereotype) {
      interfaceButton.setAttribute(
        'class',
        this.node.style.model.stereotype.length > 0
          ? 'interface-toggle toggled'
          : 'interface-toggle'
      )
      cache.interfaceToggle = this.node.style.model.stereotype
    }
    if (cache.constraintToggle !== this.node.tag.constraint) {
      abstractButton.setAttribute(
        'class',
          this.node.tag.constraint.length > 0 ? 'abstract-toggle toggled' : 'abstract-toggle'
      )
      cache.constraintToggle = this.node.tag.constraint
    }

    return oldVisual
  }

  /**
   * Helper method to get the edge style of the edge creation button if there is a button at the given location.
   * @param {.CanvasComponent} canvasComponent The canvas component in which the node resides.
   * @param {INode} node The node who should be checked for a button.
   * @param {IPoint} location The world location to check for a button.
   * @returns {IEdgeStyle} The edge style if there is a button at the given location, otherwise null.
   */
  static getStyleButtonAt(canvasComponent, node, location) {
    for (let i = 0; i < ButtonVisualCreator.buttons.length; i++) {
      const boundingRect = ButtonVisualCreator.buttons[i].getBoundingClientRect()
      const worldTopLeft = canvasComponent.toWorldFromPage(
        new Point(boundingRect.left, boundingRect.top)
      )
      if (
        location.x >= worldTopLeft.x &&
        location.x <= worldTopLeft.x + 30 &&
        location.y >= worldTopLeft.y &&
        location.y <= worldTopLeft.y + 30
      ) {
        return ButtonVisualCreator.edgeCreationButtons[i]
      }
    }
    return null
  }

  /**
   * Helper method to get the context button at the given location.
   * @param {INode} node The node who should be checked for a button.
   * @param {IPoint} location The world location to check for a button.
   * @returns {string|object} The context button at the given or null.
   */
  static getContextButtonAt(node, location) {
    const layout = node.layout
    if (
      location.x >= layout.x &&
      location.x <= layout.x + 20 &&
      location.y <= layout.y - 5 &&
      location.y >= layout.y - 25
    ) {
      return 'interface'
    }
    if (
      location.x >= layout.x + 25 &&
      location.x <= layout.x + 45 &&
      location.y <= layout.y - 5 &&
      location.y >= layout.y - 25
    ) {
      return 'abstract'
    }
    return null
  }
}

/**
 * Executes the button fan out animation.
 */
class ButtonAnimation extends BaseClass(IAnimation) {
  constructor(rotationElement, finishAngle, translationElement) {
    super()
    this.rotationElement = rotationElement
    this.translationElement = translationElement
    this.finishAngle = finishAngle
  }

  get preferredDuration() {
    return TimeSpan.fromMilliseconds(200)
  }

  /**
   * @param {number} time - the animation time [0,1]
   */
  animate(time) {
    this.rotationElement.setAttribute(
      'transform',
      `rotate(${time * this.finishAngle}) translate(-15 -15)`
    )
    this.translationElement.setAttribute('transform', `translate(${time * 50} 0)`)
  }

  cleanUp() {}

  initialize() {}
}

/**
 * Helper class that creates a round button visual containing a given edge style visualization.
 */
class ButtonIconRenderer {
  constructor() {
    this.gc = new GraphComponent()
    this.gc.graphModelManager.hierarchicNestingPolicy = HierarchicNestingPolicy.NONE
    this.gc.graphModelManager.edgeGroup.above(this.gc.graphModelManager.nodeGroup)
  }

  renderButton(edgeStyle) {
    const graph = this.gc.graph
    graph.clear()
    const style = new ShapeNodeStyle({
      fill: 'white',
      stroke: '#607D8B',
      shape: ShapeNodeShape.ELLIPSE
    })
    graph.createNode(new Rect(-15, -15, 30, 30), style)
    const src = graph.createNode(new Rect(-10, 0, 1, 1), VoidNodeStyle.INSTANCE)
    const tgt = graph.createNode(new Rect(10, 0, 1, 1), VoidNodeStyle.INSTANCE)
    graph.createEdge({
      source: src,
      target: tgt,
      style: edgeStyle
    })
    const svgExport = new SvgExport(new Rect(-18, -18, 36, 36))
    return svgExport.exportSvg(this.gc)
  }

  renderTextButton(text) {
    const textSize = TextRenderSupport.measureText(
      text,
      new Font({
        fontFamily: 'monospace',
        fontSize: 18
      })
    )
    const container = document.createElementNS('http://www.w3.org/2000/svg', 'g')
    const background = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
    background.setAttribute('width', '20')
    background.setAttribute('height', '20')
    background.setAttribute('fill', '#FFF')
    background.setAttribute('stroke', '#607D8B')
    const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text')
    textElement.setAttribute('font-family', 'monospace')
    textElement.setAttribute('font-size', '18')
    textElement.setAttribute('x', `${(20 - textSize.width) / 2}`)
    textElement.setAttribute('y', '16')
    textElement.textContent = text
    container.appendChild(background)
    container.appendChild(textElement)
    container.setAttribute('class', text === 'I' ? 'interface-toggle' : 'abstract-toggle')
    return container
  }
}<|endoftext|>


<commit_before>"use strict"
/**
 * Angular Tag Cloud (https://github.com/angular-tag-cloud)
 * Author: Zeeshan Hyder (https://github.com/zeeshanhyder)
 * Ported from: lucaong jQCloud (https://github.com/lucaong/jQCloud)
 *
 * Description:
 * Hello. This is the tag cloud library based in Angular and pure Javascript. It has no external dependencies. The library is ported from
 * lucaong's  jQCloud(github link above). His library is based in jQuery and is a core dependency, where as this library is independent and needs  * no external dependency. Please feel free to use this code and modify it according to your own wish.
 *
 *
 * Thankyou.
 */

var ngTagCloud = angular.module("ngTagCloud",[]);

ngTagCloud.directive("ngTagCloud",["$timeout","$log",function($timeout,$log){
    return {
        restrict: 'EA',
        scope: {

            cloudWidth: '=?',
            cloudHeight: '=?',
            cloudOverflow: '=?',
            cloudData: '=',
            cloudClick:'=',
            delayedMode:'=?',
            onRendered: '&',
            cloudColors:'=?'
        },
        template: "

",
        link: function($scope,element,attrs){

            if($scope.cloudData === "" || $scope.cloudData === undefined){
                $log.debug("ng-tag-cloud: No data passed. Please pass tags data as json. <ng-tag-cloud cloud-data='tagsJSON'></ng-tag-cloud\nFor more info see here: https://github.com/zeeshanhyder/angular-tag-cloud");
                return;
            }
            if($scope.cloudClick === "" || $scope.cloudClick === undefined){
                $log.error("ng-tag-cloud: No click function passed. Please pass a function to handle click over words");
                return;
            }
            $scope.$watchCollection('[cloudData]', function () {
                $timeout(function(){
                    buildOptions();
                    drawWordCloud();
                }, 50);
            });

            //default options
            var options = {
                width: $scope.cloudWidth?$scope.cloudWidth:"300",
                height: $scope.cloudHeight?$scope.cloudHeight:"300",
                delayedMode: ($scope.delayedMode!==undefined)?$scope.delayedMode:($scope.cloudData.length > 50)
            };

            //Enable to execute function after cloud rendered
            options.afterCloudRender = function() {
                $scope.onRendered();
            };

            // Reference to the container element
            var $this = angular.element(element)[0];
            // Namespace word ids to avoid collisions between multiple clouds
            var cloud_namespace = $this.getAttribute('id') || Math.floor((Math.random()*1000000)).toString(36);

            var word_array = [];
            var buildOptions = function () {
                word_array = $scope.cloudData;

                $this.style.width = options.width+"px";
                $this.style.height = options.height+"px";
                // Default options value
                var default_options = {
                    width: $this.offsetWidth,
                    height: $this.offsetHeight,
                    center: {
                        x: (options.width / 2.0),
                        y: (options.height / 2.0)
                    },
                    delayedMode: options.delayedMode,
                    shape: false, // It defaults to elliptic shape
                    encodeURI: true,
                    removeOverflowing: $scope.cloudOverflow?false:true //TRUE by default. I know this is confusing, will be changed in next versions.
                };
                options = angular.extend(default_options, options || {});
            };

            buildOptions();

            // Container's CSS position cannot be 'static'
            if ($this.style.position === "static" || $this.style.position === "") {
                $this.style.position = "relative";
            }

            var drawWordCloud = function() {
                element.empty();
                // Helper function to test if an element overlaps others
                var hitTest = function(elem, other_elems) {
                    // Pairwise overlap detection
                    var overlapping = function(a, b) {
                        if (Math.abs(2.0*a.offsetLeft + a.offsetWidth - 2.0*b.offsetLeft - b.offsetWidth) < a.offsetWidth + b.offsetWidth) {
                            if (Math.abs(2.0*a.offsetTop + a.offsetHeight - 2.0*b.offsetTop - b.offsetHeight) < a.offsetHeight + b.offsetHeight) {
                                return true;
                            }
                        }
                        return false;
                    };
                    var i = 0;
                    // Check elements for overlap one by one, stop and return false as soon as an overlap is found
                    for(i = 0; i < other_elems.length; i++) {
                        if (overlapping(elem, other_elems[i])) {
                            return true;
                        }
                    }
                    return false;
                };

                // Make sure every weight is a number before sorting
                for (var i = 0; i < word_array.length; i++) {
                    word_array[i].weight = parseFloat(word_array[i].weight, 10);
                }

                // Sort word_array from the word with the highest weight to the one with the lowest
                word_array.sort(function(a, b) { if (a.weight  b.weight) {return -1;} else {return 0;} });

                var step = (options.shape === "rectangular") ? 18.0 : 2.0,
                    already_placed_words = [],
                    aspect_ratio = options.width / options.height;

                // Function to draw a word, by moving it in spiral until it finds a suitable empty place. This will be iterated on each word.
                var drawOneWord = function(index, word) {
                    // Define the ID attribute of the span that will wrap the word, and the associated jQuery selector string
                    var word_id = cloud_namespace + "_word_" + index,
                        word_selector = "#" + word_id,
                        angle = 6.28 * Math.random(),
                        radius = 0.0,

                        // Only used if option.shape == 'rectangular'
                        steps_in_direction = 0.0,
                        quarter_turns = 0.0,

                        weight = 5,
                        custom_class = "",
                        inner_html = "",
                        word_span;

                    // Leave out custom html for now.

                    // Check if min(weight) > max(weight) otherwise use default
                    if (word_array[0].weight > word_array[word_array.length - 1].weight) {
                        // Linearly map the original weight to a discrete scale from 1 to 10
                        weight = Math.round((word.weight - word_array[word_array.length - 1].weight) /
                                            (word_array[0].weight - word_array[word_array.length - 1].weight) * 9.0) + 1;
                    }

                    // Create a new span and insert node.
                    word_span = document.createElement("span");
                    word_span.className = 'w' + weight;
                    var textNode = document.createTextNode(word.text);

                    // Append href if there's a link alongwith the tag
                    if (word.link !== undefined && word.link !== "") {
                        // If link is a string, then use it as the link href
                        if (typeof word.link === "string") {
                            var href = word.link;
                        }

                        // Extend link html options with defaults
                        if ( options.encodeURI ) {
                            href = encodeURI(href).replace(/'/g, "%27");
                        }

                        var word_link = document.createElement("a");
                        word_link.href = href;
                        word_link.appendChild(textNode);
                        word_span.appendChild(word_link);
                    } else {

                        // If there's no link attribute
                        word_span.appendChild(textNode);
                    }

                    // Bind handlers to words (though not really useful in this version!)
                    //                if (!!word.handlers) {
                    //                  for (var prop in word.handlers) {
                    //                    if (word.handlers.hasOwnProperty(prop) && typeof word.handlers[prop] === 'function') {
                    //                        word_span.addEventListener(prop,word.handlers[prop]);
                    //                    }
                    //                  }
                    //                }$scope.cloudClick(word.text)
                    var $ = angular.element;
                    $(word_span).click({text:word.text},$scope.cloudClick);
                    // Every word has the clas cloud-word to apply any predefined style.
                    $(word_span).addClass('cloud-word');
                    $this.appendChild(word_span);

                    var width = word_span.offsetWidth,
                        height = word_span.offsetHeight,
                        left = options.center.x - width / 2.0,
                        top = options.center.y - height / 2.0;

                    // Save a reference to the style property, for better performance
                    var word_style = word_span.style;
                    word_style.position = "absolute";
                    word_style.left = left + "px";
                    word_style.top = top + "px";
                    //                word_style.color="white";
                    //                word_style.cursor="pointer";

                    while(hitTest(word_span, already_placed_words)) {
                        // option shape is 'rectangular' so move the word in a rectangular spiral
                        if (options.shape === "rectangular") { //not enabled in this version.
                            steps_in_direction++;
                            if (steps_in_direction * step > (1 + Math.floor(quarter_turns / 2.0)) * step * ((quarter_turns % 4 % 2) === 0 ? 1 : aspect_ratio)) {
                                steps_in_direction = 0.0;
                                quarter_turns++;
                            }
                            switch(quarter_turns % 4) {
                                case 1:
                                    left += step * aspect_ratio + Math.random() * 2.0;
                                    break;
                                case 2:
                                    top -= step + Math.random() * 2.0;
                                    break;
                                case 3:
                                    left -= step * aspect_ratio + Math.random() * 2.0;
                                    break;
                                case 0:
                                    top += step + Math.random() * 2.0;
                                    break;
                                                    }
                        } else { // Default settings: elliptic spiral shape
                            radius += step;
                            angle += (index % 2 === 0 ? 1 : -1)*step;

                            left = options.center.x - (width / 2.0) + (radius*Math.cos(angle)) * aspect_ratio;
                            top = options.center.y + radius*Math.sin(angle) - (height / 2.0);
                        }
                        word_style.left = left + "px";
                        word_style.top = top + "px";
                    }

                    // Don't render word if part of it would be outside the container
                    if (options.removeOverflowing && (left < 0 || top < 0 || (left + width) > options.width || (top + height) > options.height)) {
                        word_span.remove();
                        return;
                    }

                    already_placed_words.push(word_span);

                    // Invoke callback if existing
                    if (typeof(word.afterWordRender) === "function") {
                        word.afterWordRender.call(word_span);
                    }
                };

                var drawOneWordDelayed = function(index) {
                    index = index || 0;
                    if ($this.offsetWidth<= 0 && $this.offsetHeight <= 0 ) { // if not visible then do not attempt to draw
                        $timeout(function(){drawOneWordDelayed(index);},10);
                        return;
                    }
                    if (index < word_array.length) {
                        drawOneWord(index, word_array[index]);
                        $timeout(function(){drawOneWordDelayed(index + 1);}, 10);
                    } else if(index != 0){
                        if (typeof(options.afterCloudRender) === "function") {
                            options.afterCloudRender.call($this);
                        }
                    }
                };

                // Iterate drawOneWord on every word. The way the iteration is done depends on the drawing mode (delayedMode is true or false)
                if (options.delayedMode){
                    drawOneWordDelayed();
                }
                else {

                    word_array.forEach( function(elem,index){
                        drawOneWord(index, elem);
                    } );
                    if (typeof(options.afterCloudRender) === "function") {
                        options.afterCloudRender.call($this);
                    }
                }
            };
        },
        replace: true
    }
}]);
<commit_msg>custom click<commit_after>/**
 * Angular Tag Cloud (https://github.com/angular-tag-cloud)
 * Author: Zeeshan Hyder (https://github.com/zeeshanhyder)
 * Ported from: lucaong jQCloud (https://github.com/lucaong/jQCloud)
 *
 * Description:
 * Hello. This is the tag cloud library based in Angular and pure Javascript. It has no external dependencies. The library is ported from
 * lucaong's  jQCloud(github link above). His library is based in jQuery and is a core dependency, where as this library is independent and needs  * no external dependency. Please feel free to use this code and modify it according to your own wish.
 *
 *
 * Thankyou.
 */

var ngTagCloud = angular.module("ngTagCloud", []);

ngTagCloud.directive("ngTagCloud", ["$timeout", "$log", function ($timeout, $log) {
    return {
        restrict: 'EA',
        scope: {

            cloudWidth: '=?',
            cloudHeight: '=?',
            cloudOverflow: '=?',
            cloudData: '=',
            delayedMode: '=?',
            onRendered: '&',
            cloudColors: '=?'
        },
        template: "

",
        link: function ($scope, element, attrs) {

            if ($scope.cloudData === "" || $scope.cloudData === undefined) {
                $log.error("ng-tag-cloud: No data passed. Please pass tags data as json. <ng-tag-cloud cloud-data='tagsJSON'></ng-tag-cloud\nFor more info see here: https://github.com/zeeshanhyder/angular-tag-cloud");
                return;
            }
            $scope.$watchCollection('[cloudData]', function () {
                $timeout(function () {
                    buildOptions();
                    drawWordCloud();
                }, 10);
            });

            //default options
            var options = {
                width: $scope.cloudWidth ? $scope.cloudWidth : "300",
                height: $scope.cloudHeight ? $scope.cloudHeight : "300",
                delayedMode: ($scope.delayedMode !== undefined) ? $scope.delayedMode : ($scope.cloudData.length > 50)
            };

            //Enable to execute function after cloud rendered
            options.afterCloudRender = function () {
                $scope.onRendered();
            };

            // Reference to the container element
            var $this = angular.element(element)[0];
            // Namespace word ids to avoid collisions between multiple clouds
            var cloud_namespace = $this.getAttribute('id') || Math.floor((Math.random() * 1000000)).toString(36);

            var word_array = [];
            var buildOptions = function () {
                word_array = $scope.cloudData;

                $this.style.width = options.width + "px";
                $this.style.height = options.height + "px";
                // Default options value
                var default_options = {
                    width: $this.offsetWidth,
                    height: $this.offsetHeight,
                    center: {
                        x: (options.width / 2.0),
                        y: (options.height / 2.0)
                    },
                    delayedMode: options.delayedMode,
                    shape: false, // It defaults to elliptic shape
                    encodeURI: true,
                    removeOverflowing: $scope.cloudOverflow ? false : true //TRUE by default. I know this is confusing, will be changed in next versions.
                };
                options = angular.extend(default_options, options || {});
            }

            buildOptions();

            // Container's CSS position cannot be 'static'
            if ($this.style.position === "static" || $this.style.position === "") {
                $this.style.position === "relative";
            }

            var drawWordCloud = function () {
                element.empty();
                // Helper function to test if an element overlaps others
                var hitTest = function (elem, other_elems) {
                    // Pairwise overlap detection
                    var overlapping = function (a, b) {
                        if (Math.abs(2.0 * a.offsetLeft + a.offsetWidth - 2.0 * b.offsetLeft - b.offsetWidth) < a.offsetWidth + b.offsetWidth) {
                            if (Math.abs(2.0 * a.offsetTop + a.offsetHeight - 2.0 * b.offsetTop - b.offsetHeight) < a.offsetHeight + b.offsetHeight) {
                                return true;
                            }
                        }
                        return false;
                    };
                    var i = 0;
                    // Check elements for overlap one by one, stop and return false as soon as an overlap is found
                    for (i = 0; i < other_elems.length; i++) {
                        if (overlapping(elem, other_elems[i])) {
                            return true;
                        }
                    }
                    return false;
                };

                // Make sure every weight is a number before sorting
                for (var i = 0; i < word_array.length; i++) {
                    word_array[i].weight = parseFloat(word_array[i].weight, 10);
                }

                // Sort word_array from the word with the highest weight to the one with the lowest
                word_array.sort(function (a, b) {
                    if (a.weight  b.weight) {
                        return -1;
                    } else {
                        return 0;
                    }
                });

                var step = (options.shape === "rectangular") ? 18.0 : 2.0,
                    already_placed_words = [],
                    aspect_ratio = options.width / options.height;

                // Function to draw a word, by moving it in spiral until it finds a suitable empty place. This will be iterated on each word.
                var drawOneWord = function (index, word) {
                    // Define the ID attribute of the span that will wrap the word, and the associated jQuery selector string
                    var word_id = cloud_namespace + "_word_" + index,
                        word_selector = "#" + word_id,
                        angle = 6.28 * Math.random(),
                        radius = 0.0,

                        // Only used if option.shape == 'rectangular'
                        steps_in_direction = 0.0,
                        quarter_turns = 0.0,

                        weight = 5,
                        custom_class = "",
                        inner_html = "",
                        word_span;

                    // Leave out custom html for now.

                    // Check if min(weight) > max(weight) otherwise use default
                    if (word_array[0].weight > word_array[word_array.length - 1].weight) {
                        // Linearly map the original weight to a discrete scale from 1 to 10
                        weight = Math.round((word.weight - word_array[word_array.length - 1].weight) /
                            (word_array[0].weight - word_array[word_array.length - 1].weight) * 9.0) + 1;
                    }

                    // Create a new span and insert node.
                    word_span = document.createElement("span");
                    word_span.className = 'w' + weight;
                    var textNode = document.createTextNode(word.text);

                    // Append href if there's a link alongwith the tag
                    if (word.link !== undefined && word.link !== "") {
                        // If link is a string, then use it as the link href
                        if (typeof word.link === "string") {
                            var href = word.link;
                        }

                        // Extend link html options with defaults
                        if (options.encodeURI) {
                            href = encodeURI(href).replace(/'/g, "%27");
                        }

                        var word_link = document.createElement("a");
                        word_link.href = href;
                        word_link.appendChild(textNode);
                        word_span.appendChild(word_link);
                    } else {

                        // If there's no link attribute
                        word_span.appendChild(textNode);
                    }

                    // Bind handlers to words (though not really useful in this version!)
                    //                if (!!word.handlers) {
                    //                  for (var prop in word.handlers) {
                    //                    if (word.handlers.hasOwnProperty(prop) && typeof word.handlers[prop] === 'function') {
                    //                        word_span.addEventListener(prop,word.handlers[prop]);
                    //                    }
                    //                  }
                    //                }$scope.cloudClick(word.text)
                    var $ = angular.element;
                    $(word_span).click({
                        text: word.text
                    }, $scope.cloudClick);
                    $(word_span).addClass('cloud-word');
                    //                word_span.onclick($scope.cloudClick(word.text));
                    $this.appendChild(word_span);

                    var width = word_span.offsetWidth,
                        height = word_span.offsetHeight,
                        left = options.center.x - width / 2.0,
                        top = options.center.y - height / 2.0;

                    // Save a reference to the style property, for better performance
                    var word_style = word_span.style;
                    word_style.position = "absolute";
                    word_style.left = left + "px";
                    word_style.top = top + "px";

                    while (hitTest(word_span, already_placed_words)) {
                        // option shape is 'rectangular' so move the word in a rectangular spiral
                        if (options.shape === "rectangular") { //not enabled in this version.
                            steps_in_direction++;
                            if (steps_in_direction * step > (1 + Math.floor(quarter_turns / 2.0)) * step * ((quarter_turns % 4 % 2) === 0 ? 1 : aspect_ratio)) {
                                steps_in_direction = 0.0;
                                quarter_turns++;
                            }
                            switch (quarter_turns % 4) {
                                case 1:
                                    left += step * aspect_ratio + Math.random() * 2.0;
                                    break;
                                case 2:
                                    top -= step + Math.random() * 2.0;
                                    break;
                                case 3:
                                    left -= step * aspect_ratio + Math.random() * 2.0;
                                    break;
                                case 0:
                                    top += step + Math.random() * 2.0;
                                    break;
                            }
                        } else { // Default settings: elliptic spiral shape
                            radius += step;
                            angle += (index % 2 === 0 ? 1 : -1) * step;

                            left = options.center.x - (width / 2.0) + (radius * Math.cos(angle)) * aspect_ratio;
                            top = options.center.y + radius * Math.sin(angle) - (height / 2.0);
                        }
                        word_style.left = left + "px";
                        word_style.top = top + "px";
                    }

                    // Don't render word if part of it would be outside the container
                    if (options.removeOverflowing && (left < 0 || top < 0 || (left + width) > options.width || (top + height) > options.height)) {
                        word_span.remove()
                        return;
                    }

                    already_placed_words.push(word_span);

                    // Invoke callback if existing
                    if (typeof (word.afterWordRender) === "function") {
                        word.afterWordRender.call(word_span);
                    }
                };

                var drawOneWordDelayed = function (index) {
                    index = index || 0;
                    if ($this.offsetWidth <= 0 && $this.offsetHeight <= 0) { // if not visible then do not attempt to draw
                        $timeout(function () {
                            drawOneWordDelayed(index);
                        }, 10);
                        return;
                    }
                    if (index < word_array.length) {
                        drawOneWord(index, word_array[index]);
                        $timeout(function () {
                            drawOneWordDelayed(index + 1);
                        }, 10);
                    } else if (index != 0) {
                        if (typeof (options.afterCloudRender) === "function") {
                            options.afterCloudRender.call($this);
                        }
                    }
                };

                // Iterate drawOneWord on every word. The way the iteration is done depends on the drawing mode (delayedMode is true or false)
                if (options.delayedMode) {
                    drawOneWordDelayed();
                } else {

                    word_array.forEach(function (elem, index) {
                        drawOneWord(index, elem);
                    });
                    if (typeof (options.afterCloudRender) === "function") {
                        options.afterCloudRender.call($this);
                    }
                }
            };
        },
        replace: true
    }
}]);<|endoftext|>


<commit_before>/**
 *  \details © 2011  Open Ximdex Evolution SL [http://www.ximdex.org]
 *
 *  Ximdex a Semantic Content Management System (CMS)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  See the Affero GNU General Public License for more details.
 *  You should have received a copy of the Affero GNU General Public License
 *  version 3 along with Ximdex (see LICENSE file).
 *
 *  If not, visit http://gnu.org/licenses/agpl-3.0.html.
 *
 *  @author Ximdex DevTeam <dev@ximdex.com>
 *  @version $Revision$
 */

(function(X) {

	var B = X.browser;

	var idCount = 0;

	B.ActionView = Object.xo_create(B.AbstractView, {

		url: null,
		action: null,
		nodes: null,
		forms: null,
		history: null,

		_init: function(options) {

			B.ActionView._construct(this, options);

			this.id = 'browser-action-view-' + (++idCount);
			this.className = 'browser-action-view';
			this.content = $('

')
				.addClass('browser-action-view-content')
				.attr({id: this.id});
			this.forms = {};
			this.history = [];

			this.openAction(options);
		},

		openAction: function(options) {

			this.url = options.url || null;
			this.action = options.action || null;
			this.nodes = options.nodes || [];
			this.nodes = (!Object.isArray(this.nodes) ? [this.nodes] : this.nodes);
			this.label = options.label;

			if (this.url === null && this.action !== null) {
				this._createUrl();
			}

			this.history.push(this.url);

			this.content.load(this.url, function(data, textStatus, xhr) {
				this.processAction();
			}.bind(this));
		},

		getForm: function(name) {
			name = name || null;
			return name === null ? this.forms : (this.forms[name] || null);
		},

		_createUrl: function() {

			var action = '';
			if (this.action['actionid']) {

				action = 'actionid=' + this.action.actionid;
			} else if (this.action['command']) {

				action = 'action=' + this.action.command;
			} else {

				// No defined action
				action = 'action=_NONE_';
			}

			var nodes = [];
			for (var i=0,l=this.nodes.length; i<l; i++) {
				nodes.push('nodes[]=' + this.nodes[i]);
			};

			var actionParams = '';
			if (this.action.params && this.action.params.length > 0) {
				actionParams = this.action.params;
			}

			var qs = [
				nodes.join('&'),
				action,
				'noCacheVar='+(new Date().getTime())
			];

			if (!Object.isEmpty(this.action.method)) qs.push('method='+this.action.method);
			if (!Object.isEmpty(this.action.module)) qs.push('mod='+this.action.module);
			if (!Object.isEmpty(actionParams)) qs.push(actionParams);

			qs = qs.join('&');
			this.url = '%s?%s'.printf(X.restUrl, qs);
		},

		close: function() {
			console.log("Action close method");
			$(this.container).tabs('remove', this.tabId());
		},

		//onLink: function() {
		//},

		redirect: function(url, params) {
			$('form', this.content).attr('action', url);
		},

		setActionContent: function(content) {
			$(this.content).unbind().empty().append(content);
			this.forms = {};
			this.processAction();
		},

		processAction: function() {

			// jQuery plugin in extensions folder
			$(this.content).labelWidth();

			this._loadAssets();
			this._bindFormEvents();
		},

		_bindFormEvents: function() {

			// For each form create a FormsManager instance and register events for assets management
			$('form', this.content).each(function(index, form) {

				var name = !Object.isEmpty($(form).attr('id'))
					? $(form).attr('id')
					: (!Object.isEmpty($(form).attr('name'))
						? $(form).attr('name')
						: 'form_' + X.getUID()
						);

				this.forms[name] = form;

				var fm = new X.FormsManager({
					actionView: this,
					actionContainer: this.content,
					form: form
				});

				$(fm).bind('form-loaded', function(event) {
					this.processAction();
				}.bind(this));

			}.bind(this));

			// Close the action tab when the 'Close' button is clicked
			$('fieldset.buttons-form .close-button', this.content).click(function(e) {
				this.close();
				return false;
			}.bind(this));

			// Controls the logic when the 'Go back' button is clicked
			$('fieldset.buttons-form .goback-button', this.content).click(function(e) {

				var history = $('fieldset.buttons-form .history-value', this.content);
				history = history.html() || 1;

				while (this.history.length > 0 && history > 0) {
					this.history.pop();
					history--;
				}

				var url = this.history[this.history.length-1];
				this.content.load(url, function(data, textStatus, xhr) {
					this.processAction();
				}.bind(this));
				return false;
			}.bind(this));
		},

		_loadAssets: function() {

			var css = $('ul.css_to_include li', this.content).map(function(index, item) {
				return $(item).html();
			});
			css = $.makeArray(css);

			var js = $('ul.js_to_include li', this.content).map(function(index, item) {
				var url = $(item).html();
				url = Object.urldecode(url.replace(/&/g, '&', url));
				return url;
			}.bind(this));

			js = {
				onComplete: this._onAssetsCompleted.bind(this),
				//onLoad: this._onScriptLoaded.bind(this),
				js: js
			};

			Object.loadCss(css);

			if (js.js.length > 0) {
				Object.loadScript(js);
			} else {
				this._onAssetsCompleted();
			}
		},

		_onAssetsCompleted: function() {
			X.triggerActionLoaded({
				actionView: this,
				browser: this.browser,
				context: this.content,
				url: this.url,
				action: this.action,
				nodes: this.nodes,
				tabId: this.tabId()
			});
			//Creates a new angular scope and compile the DOM
			var _this = this;
			console.log("ASSETS COMPLETE THIS", X.browser);
			var $injector = angular.injector(['ng', 'ximdex']);
			$injector.invoke(function($rootScope, $compile) {
			    var scope = $rootScope.$new();
			    X.browser.panels.RightPanel.tabScopes = X.browser.panels.RightPanel.tabScopes || {};
			    X.browser.panels.RightPanel.tabScopes[_this.tabId()] = scope;
			    $compile(_this.content[0])(scope);
			    scope.$digest();
			});
		}

	});

})(com.ximdex);
<commit_msg>Removing debug lines<commit_after>/**
 *  \details © 2011  Open Ximdex Evolution SL [http://www.ximdex.org]
 *
 *  Ximdex a Semantic Content Management System (CMS)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  See the Affero GNU General Public License for more details.
 *  You should have received a copy of the Affero GNU General Public License
 *  version 3 along with Ximdex (see LICENSE file).
 *
 *  If not, visit http://gnu.org/licenses/agpl-3.0.html.
 *
 *  @author Ximdex DevTeam <dev@ximdex.com>
 *  @version $Revision$
 */

(function(X) {

	var B = X.browser;

	var idCount = 0;

	B.ActionView = Object.xo_create(B.AbstractView, {

		url: null,
		action: null,
		nodes: null,
		forms: null,
		history: null,

		_init: function(options) {

			B.ActionView._construct(this, options);

			this.id = 'browser-action-view-' + (++idCount);
			this.className = 'browser-action-view';
			this.content = $('

')
				.addClass('browser-action-view-content')
				.attr({id: this.id});
			this.forms = {};
			this.history = [];

			this.openAction(options);
		},

		openAction: function(options) {

			this.url = options.url || null;
			this.action = options.action || null;
			this.nodes = options.nodes || [];
			this.nodes = (!Object.isArray(this.nodes) ? [this.nodes] : this.nodes);
			this.label = options.label;

			if (this.url === null && this.action !== null) {
				this._createUrl();
			}

			this.history.push(this.url);

			this.content.load(this.url, function(data, textStatus, xhr) {
				this.processAction();
			}.bind(this));
		},

		getForm: function(name) {
			name = name || null;
			return name === null ? this.forms : (this.forms[name] || null);
		},

		_createUrl: function() {

			var action = '';
			if (this.action['actionid']) {

				action = 'actionid=' + this.action.actionid;
			} else if (this.action['command']) {

				action = 'action=' + this.action.command;
			} else {

				// No defined action
				action = 'action=_NONE_';
			}

			var nodes = [];
			for (var i=0,l=this.nodes.length; i<l; i++) {
				nodes.push('nodes[]=' + this.nodes[i]);
			};

			var actionParams = '';
			if (this.action.params && this.action.params.length > 0) {
				actionParams = this.action.params;
			}

			var qs = [
				nodes.join('&'),
				action,
				'noCacheVar='+(new Date().getTime())
			];

			if (!Object.isEmpty(this.action.method)) qs.push('method='+this.action.method);
			if (!Object.isEmpty(this.action.module)) qs.push('mod='+this.action.module);
			if (!Object.isEmpty(actionParams)) qs.push(actionParams);

			qs = qs.join('&');
			this.url = '%s?%s'.printf(X.restUrl, qs);
		},

		close: function() {
			$(this.container).tabs('remove', this.tabId());
		},

		//onLink: function() {
		//},

		redirect: function(url, params) {
			$('form', this.content).attr('action', url);
		},

		setActionContent: function(content) {
			$(this.content).unbind().empty().append(content);
			this.forms = {};
			this.processAction();
		},

		processAction: function() {

			// jQuery plugin in extensions folder
			$(this.content).labelWidth();

			this._loadAssets();
			this._bindFormEvents();
		},

		_bindFormEvents: function() {

			// For each form create a FormsManager instance and register events for assets management
			$('form', this.content).each(function(index, form) {

				var name = !Object.isEmpty($(form).attr('id'))
					? $(form).attr('id')
					: (!Object.isEmpty($(form).attr('name'))
						? $(form).attr('name')
						: 'form_' + X.getUID()
						);

				this.forms[name] = form;

				var fm = new X.FormsManager({
					actionView: this,
					actionContainer: this.content,
					form: form
				});

				$(fm).bind('form-loaded', function(event) {
					this.processAction();
				}.bind(this));

			}.bind(this));

			// Close the action tab when the 'Close' button is clicked
			$('fieldset.buttons-form .close-button', this.content).click(function(e) {
				this.close();
				return false;
			}.bind(this));

			// Controls the logic when the 'Go back' button is clicked
			$('fieldset.buttons-form .goback-button', this.content).click(function(e) {

				var history = $('fieldset.buttons-form .history-value', this.content);
				history = history.html() || 1;

				while (this.history.length > 0 && history > 0) {
					this.history.pop();
					history--;
				}

				var url = this.history[this.history.length-1];
				this.content.load(url, function(data, textStatus, xhr) {
					this.processAction();
				}.bind(this));
				return false;
			}.bind(this));
		},

		_loadAssets: function() {

			var css = $('ul.css_to_include li', this.content).map(function(index, item) {
				return $(item).html();
			});
			css = $.makeArray(css);

			var js = $('ul.js_to_include li', this.content).map(function(index, item) {
				var url = $(item).html();
				url = Object.urldecode(url.replace(/&/g, '&', url));
				return url;
			}.bind(this));

			js = {
				onComplete: this._onAssetsCompleted.bind(this),
				//onLoad: this._onScriptLoaded.bind(this),
				js: js
			};

			Object.loadCss(css);

			if (js.js.length > 0) {
				Object.loadScript(js);
			} else {
				this._onAssetsCompleted();
			}
		},

		_onAssetsCompleted: function() {
			X.triggerActionLoaded({
				actionView: this,
				browser: this.browser,
				context: this.content,
				url: this.url,
				action: this.action,
				nodes: this.nodes,
				tabId: this.tabId()
			});
			//Creates a new angular scope and compile the DOM
			var _this = this;
			var $injector = angular.injector(['ng', 'ximdex']);
			$injector.invoke(function($rootScope, $compile) {
			    var scope = $rootScope.$new();
			    X.browser.panels.RightPanel.tabScopes = X.browser.panels.RightPanel.tabScopes || {};
			    X.browser.panels.RightPanel.tabScopes[_this.tabId()] = scope;
			    $compile(_this.content[0])(scope);
			    scope.$digest();
			});
		}

	});

})(com.ximdex);
<|endoftext|>