<commit_before>/* @flow */

import {ConsoleReporter, JSONReporter} from '../reporters/index.js';
import {registries, registryNames} from '../registries/index.js';
import * as commands from './commands/index.js';
import * as constants from '../constants.js';
import * as network from '../util/network.js';
import {MessageError} from '../errors.js';
import aliases from './aliases.js';
import Config from '../config.js';
import {camelCase} from '../util/misc.js';

const chalk = require('chalk');
const commander = require('commander');
const fs = require('fs');
const invariant = require('invariant');
const lockfile = require('proper-lockfile');
const loudRejection = require('loud-rejection');
const net = require('net');
const onDeath = require('death');
const path = require('path');
const pkg = require('../../package.json');

loudRejection();

//
const startArgs = process.argv.slice(0, 2);
let args = process.argv.slice(2);

// ignore all arguments after a --
let endArgs = [];
for (let i = 0; i < args.length; i++) {
  const arg = args[i];
  if (arg === '--') {
    endArgs = args.slice(i + 1);
    args = args.slice(0, i);
  }
}

// NOTE: Pending resolution of https://github.com/tj/commander.js/issues/346
// Remove this (and subsequent use in the logic below) after bug is resolved and issue is closed
const ARGS_THAT_SHARE_NAMES_WITH_OPTIONS = [
  'version',
];

// set global options
commander.version(pkg.version);
commander.usage('[command] [flags]');
commander.option('--verbose', 'output verbose messages on internal operations');
commander.option('--offline', 'trigger an error if any required dependencies are not available in local cache');
commander.option('--prefer-offline', 'use network only if dependencies are not available in local cache');
commander.option('--strict-semver');
commander.option('--json', '');
commander.option('--ignore-scripts', "don't run lifecycle scripts");
commander.option('--har', 'save HAR output of network traffic');
commander.option('--ignore-platform', 'ignore platform checks');
commander.option('--ignore-engines', 'ignore engines check');
commander.option('--ignore-optional', 'ignore optional dependencies');
commander.option('--force', 'install and build packages even if they were built before, overwrite lockfile');
commander.option('--skip-integrity-check', 'run install without checking if node_modules is installed');
commander.option('--check-files', 'install will verify file tree of packages for consistency');
commander.option('--no-bin-links', "don't generate bin links when setting up packages");
commander.option('--flat', 'only allow one version of a package');
commander.option('--prod, --production [prod]', '');
commander.option('--no-lockfile', "don't read or generate a lockfile");
commander.option('--pure-lockfile', "don't generate a lockfile");
commander.option('--frozen-lockfile', "don't generate a lockfile and fail if an update is needed");
commander.option('--link-duplicates', 'create hardlinks to the repeated modules in node_modules');
commander.option('--global-folder <path>', '');
commander.option(
  '--modules-folder <path>',
  'rather than installing modules into the node_modules folder relative to the cwd, output them here',
);
commander.option(
  '--cache-folder <path>',
  'specify a custom folder to store the yarn cache',
);
commander.option(
  '--mutex <type>[:specifier]',
  'use a mutex to ensure only one yarn instance is executing',
);
commander.option(
  '--no-emoji',
  'disable emoji in output',
);
commander.option(
  '-s, --silent',
  'raw script output',
);
commander.option('--proxy <host>', '');
commander.option('--https-proxy <host>', '');
commander.option(
  '--no-progress',
  'disable progress bar',
);
commander.option('--network-concurrency <number>', 'maximum number of concurrent network requests', parseInt);
commander.option('--network-timeout <milliseconds>', 'TCP timeout for network requests', parseInt);
commander.option('--non-interactive', 'do not show interactive prompts');

// get command name
let commandName: ?string = args.shift() || '';
let command;

//
const getDocsLink = (name) => `${constants.YARN_DOCS}${name || ''}`;
const getDocsInfo = (name) => 'Visit ' + chalk.bold(getDocsLink(name)) + ' for documentation about this command.';

//
if (commandName === '--help' || commandName === '-h') {
  commandName = 'help';
}

// if no args or command name looks like a flag then default to `install`
if (!commandName || commandName[0] === '-') {
  if (commandName) {
    args.unshift(commandName);
  }
  commandName = 'install';
}

// aliases: i -> install
if (commandName && typeof aliases[commandName] === 'string') {
  const alias = aliases[commandName];
  command = {
    run(config: Config, reporter: ConsoleReporter | JSONReporter): Promise<void> {
      throw new MessageError(`Did you mean \`yarn ${alias}\`?`);
    },
  };
}

//
if (commandName === 'help' && args.length) {
  commandName = camelCase(args.shift());
  args.push('--help');
}

//
invariant(commandName, 'Missing command name');
if (!command) {
  const camelised = camelCase(commandName);
  if (camelised) {
    command = commands[camelised];
  }
}

//
if (command && typeof command.setFlags === 'function') {
  command.setFlags(commander);
}

if (args.indexOf('--help') >= 0 || args.indexOf('-h') >= 0) {
  const examples: Array<string> = (command && command.examples) || [];
  if (examples.length) {
    commander.on('--help', () => {
      console.log('  Examples:\n');
      for (const example of examples) {
        console.log(`    $ yarn ${example}`);
      }
      console.log();
    });
  }
  commander.on('--help', () => console.log('  ' + getDocsInfo(commandName) + '\n'));

  commander.parse(startArgs.concat(args));
  commander.help();
  process.exit(1);
}

// parse flags
args.unshift(commandName);

if (ARGS_THAT_SHARE_NAMES_WITH_OPTIONS.indexOf(commandName) >= 0 && args[0] === commandName) {
  args.shift();
}

commander.parse(startArgs.concat(args));
commander.args = commander.args.concat(endArgs);

if (command) {
  commander.args.shift();
} else {
  command = commands.run;
}
invariant(command, 'missing command');

//
let Reporter = ConsoleReporter;
if (commander.json) {
  Reporter = JSONReporter;
}
const reporter = new Reporter({
  emoji: commander.emoji && process.stdout.isTTY && process.platform === 'darwin',
  verbose: commander.verbose,
  noProgress: !commander.progress,
  isSilent: commander.silent,
});

reporter.initPeakMemoryCounter();

const config = new Config(reporter);

// print header
let outputWrapper = true;
if (typeof command.hasWrapper === 'function') {
  outputWrapper = command.hasWrapper(commander, commander.args);
}
if (commander.json) {
  outputWrapper = false;
}
if (outputWrapper && commandName !== 'help') {
  reporter.header(commandName, pkg);
}

if (command.noArguments && commander.args.length) {
  reporter.error(reporter.lang('noArguments'));
  reporter.info(getDocsInfo(commandName));
  process.exit(1);
}

//
if (commander.yes) {
  reporter.warn(reporter.lang('yesWarning'));
}

//
if (!commander.offline && network.isOffline()) {
  reporter.warn(reporter.lang('networkWarning'));
}

//
if (command.requireLockfile && !fs.existsSync(path.join(config.cwd, constants.LOCKFILE_FILENAME))) {
  reporter.error(reporter.lang('noRequiredLockfile'));
  process.exit(1);
}

//
const run = (): Promise<void> => {
  invariant(command, 'missing command');
  return command.run(config, reporter, commander, commander.args).then(() => {
    reporter.close();
    if (outputWrapper) {
      reporter.footer(false);
    }
  });
};

//
const runEventuallyWithFile = (mutexFilename: ?string, isFirstTime?: boolean): Promise<void> => {
  return new Promise((ok) => {
    const lockFilename = mutexFilename || path.join(config.cwd, constants.SINGLE_INSTANCE_FILENAME);
    lockfile.lock(lockFilename, {realpath: false}, (err: mixed, release: () => void) => {
      if (err) {
        if (isFirstTime) {
          reporter.warn(reporter.lang('waitingInstance'));
        }
        setTimeout(() => {
          ok(runEventuallyWithFile(mutexFilename, isFirstTime));
        }, 200); // do not starve the CPU
      } else {
        onDeath(() => {
          process.exit(1);
        });
        ok(run().then(release));
      }
    });
  });
};

//
const runEventuallyWithNetwork = (mutexPort: ?string): Promise<void> => {
  return new Promise((ok) => {
    const connectionOptions = {
      port: +mutexPort || constants.SINGLE_INSTANCE_PORT,
    };

    const server = net.createServer();

    server.on('error', () => {
      // another Yarn instance exists, let's connect to it to know when it dies.
      reporter.warn(reporter.lang('waitingInstance'));
      const socket = net.createConnection(connectionOptions);

      socket
        .on('connect', () => {
          // Allow the program to exit if this is the only active server in the event system.
          socket.unref();
        })
        .on('close', (hadError?: boolean) => {
          // the `close` event gets always called after the `error` event
          if (!hadError) {
            process.nextTick(() => {
              ok(runEventuallyWithNetwork(mutexPort));
            });
          }
        })
        .on('error', () => {
          // No server to listen to ? Let's retry to become the next server then.
          process.nextTick(() => {
            ok(runEventuallyWithNetwork(mutexPort));
          });
        });
    });

    const onServerEnd = (): Promise<void> => {
      server.close();
      return Promise.resolve();
    };

    // open the server and continue only if succeed.
    server.listen(connectionOptions, () => {
      // ensure the server gets closed properly on SIGNALS.
      onDeath(onServerEnd);

      ok(run().then(onServerEnd));
    });
  });
};

function onUnexpectedError(err: Error) {
  function indent(str: string): string {
    return '\n  ' + str.trim().split('\n').join('\n  ');
  }

  const log = [];
  log.push(`Arguments: ${indent(process.argv.join(' '))}`);
  log.push(`PATH: ${indent(process.env.PATH || 'undefined')}`);
  log.push(`Yarn version: ${indent(pkg.version)}`);
  log.push(`Node version: ${indent(process.versions.node)}`);
  log.push(`Platform: ${indent(process.platform + ' ' + process.arch)}`);

  // add manifests
  for (const registryName of registryNames) {
    const possibleLoc = path.join(config.cwd, registries[registryName].filename);
    const manifest = fs.existsSync(possibleLoc) ? fs.readFileSync(possibleLoc, 'utf8') : 'No manifest';
    log.push(`${registryName} manifest: ${indent(manifest)}`);
  }

  // lockfile
  const lockLoc = path.join(config.cwd, constants.LOCKFILE_FILENAME);
  const lockfile = fs.existsSync(lockLoc) ? fs.readFileSync(lockLoc, 'utf8') : 'No lockfile';
  log.push(`Lockfile: ${indent(lockfile)}`);

  log.push(`Trace: ${indent(err.stack)}`);

  const errorReportLoc = writeErrorReport(log);

  reporter.error(reporter.lang('unexpectedError', err.message));

  if (errorReportLoc) {
    reporter.info(reporter.lang('bugReport', errorReportLoc));
  }
}

function writeErrorReport(log) : ?string {
  const errorReportLoc = path.join(config.cwd, 'yarn-error.log');

  try {
    fs.writeFileSync(errorReportLoc, log.join('\n\n') + '\n');
  } catch (err) {
    reporter.error(reporter.lang('fileWriteError', errorReportLoc, err.message));
    return undefined;
  }

  return errorReportLoc;
}

//
config.init({
  binLinks: commander.binLinks,
  modulesFolder: commander.modulesFolder,
  globalFolder: commander.globalFolder,
  cacheRootFolder: commander.cacheFolder,
  preferOffline: commander.preferOffline,
  captureHar: commander.har,
  ignorePlatform: commander.ignorePlatform,
  ignoreEngines: commander.ignoreEngines,
  ignoreScripts: commander.ignoreScripts,
  offline: commander.preferOffline || commander.offline,
  looseSemver: !commander.strictSemver,
  production: commander.production,
  httpProxy: commander.proxy,
  httpsProxy: commander.httpsProxy,
  networkConcurrency: commander.networkConcurrency,
  nonInteractive: commander.nonInteractive,
  commandName,
}).then(() => {

  // option "no-progress" stored in yarn config
  const noProgressConfig = config.registries.yarn.getOption('no-progress');

  if (noProgressConfig) {
    reporter.disableProgress();
  }

  const exit = () => {
    process.exit(0);
  };
  // verbose logs outputs process.uptime() with this line we can sync uptime to absolute time on the computer
  reporter.verbose(`current time: ${new Date().toISOString()}`);

  const mutex: mixed = commander.mutex;
  if (mutex && typeof mutex === 'string') {
    const parts = mutex.split(':');
    const mutexType = parts.shift();
    const mutexSpecifier = parts.join(':');

    if (mutexType === 'file') {
      return runEventuallyWithFile(mutexSpecifier, true).then(exit);
    } else if (mutexType === 'network') {
      return runEventuallyWithNetwork(mutexSpecifier).then(exit);
    } else {
      throw new MessageError(`Unknown single instance type ${mutexType}`);
    }
  } else {
    return run().then(exit);
  }
}).catch((err: Error) => {
  reporter.verbose(err.stack);

  if (err instanceof MessageError) {
    reporter.error(err.message);
  } else {
    onUnexpectedError(err);
  }

  if (commandName) {
    const actualCommandForHelp = commands[commandName] ? commandName : aliases[commandName];
    if (command && actualCommandForHelp) {
      reporter.info(getDocsInfo(actualCommandForHelp));
    }
  }

  process.exit(1);
});
<commit_msg>CLI command quick description fix<commit_after>/* @flow */

import {ConsoleReporter, JSONReporter} from '../reporters/index.js';
import {registries, registryNames} from '../registries/index.js';
import * as commands from './commands/index.js';
import * as constants from '../constants.js';
import * as network from '../util/network.js';
import {MessageError} from '../errors.js';
import aliases from './aliases.js';
import Config from '../config.js';
import {camelCase} from '../util/misc.js';

const chalk = require('chalk');
const commander = require('commander');
const fs = require('fs');
const invariant = require('invariant');
const lockfile = require('proper-lockfile');
const loudRejection = require('loud-rejection');
const net = require('net');
const onDeath = require('death');
const path = require('path');
const pkg = require('../../package.json');

loudRejection();

//
const startArgs = process.argv.slice(0, 2);
let args = process.argv.slice(2);

// ignore all arguments after a --
let endArgs = [];
for (let i = 0; i < args.length; i++) {
  const arg = args[i];
  if (arg === '--') {
    endArgs = args.slice(i + 1);
    args = args.slice(0, i);
  }
}

// NOTE: Pending resolution of https://github.com/tj/commander.js/issues/346
// Remove this (and subsequent use in the logic below) after bug is resolved and issue is closed
const ARGS_THAT_SHARE_NAMES_WITH_OPTIONS = [
  'version',
];

// set global options
commander.version(pkg.version);
commander.usage('[command] [flags]');
commander.option('--verbose', 'output verbose messages on internal operations');
commander.option('--offline', 'trigger an error if any required dependencies are not available in local cache');
commander.option('--prefer-offline', 'use network only if dependencies are not available in local cache');
commander.option('--strict-semver');
commander.option('--json', '');
commander.option('--ignore-scripts', "don't run lifecycle scripts");
commander.option('--har', 'save HAR output of network traffic');
commander.option('--ignore-platform', 'ignore platform checks');
commander.option('--ignore-engines', 'ignore engines check');
commander.option('--ignore-optional', 'ignore optional dependencies');
commander.option('--force', 'install and build packages even if they were built before, overwrite lockfile');
commander.option('--skip-integrity-check', 'run install without checking if node_modules is installed');
commander.option('--check-files', 'install will verify file tree of packages for consistency');
commander.option('--no-bin-links', "don't generate bin links when setting up packages");
commander.option('--flat', 'only allow one version of a package');
commander.option('--prod, --production [prod]', '');
commander.option('--no-lockfile', "don't read or generate a lockfile");
commander.option('--pure-lockfile', "don't generate a lockfile");
commander.option('--frozen-lockfile', "don't generate a lockfile and fail if an update is needed");
commander.option('--link-duplicates', 'create hardlinks to the repeated modules in node_modules');
commander.option('--global-folder <path>', '');
commander.option(
  '--modules-folder <path>',
  'rather than installing modules into the node_modules folder relative to the cwd, output them here',
);
commander.option(
  '--cache-folder <path>',
  'specify a custom folder to store the yarn cache',
);
commander.option(
  '--mutex <type>[:specifier]',
  'use a mutex to ensure only one yarn instance is executing',
);
commander.option(
  '--no-emoji',
  'disable emoji in output',
);
commander.option(
  '-s, --silent',
  'skip Yarn console logs, other types of logs (script output) will be printed',
);
commander.option('--proxy <host>', '');
commander.option('--https-proxy <host>', '');
commander.option(
  '--no-progress',
  'disable progress bar',
);
commander.option('--network-concurrency <number>', 'maximum number of concurrent network requests', parseInt);
commander.option('--network-timeout <milliseconds>', 'TCP timeout for network requests', parseInt);
commander.option('--non-interactive', 'do not show interactive prompts');

// get command name
let commandName: ?string = args.shift() || '';
let command;

//
const getDocsLink = (name) => `${constants.YARN_DOCS}${name || ''}`;
const getDocsInfo = (name) => 'Visit ' + chalk.bold(getDocsLink(name)) + ' for documentation about this command.';

//
if (commandName === '--help' || commandName === '-h') {
  commandName = 'help';
}

// if no args or command name looks like a flag then default to `install`
if (!commandName || commandName[0] === '-') {
  if (commandName) {
    args.unshift(commandName);
  }
  commandName = 'install';
}

// aliases: i -> install
if (commandName && typeof aliases[commandName] === 'string') {
  const alias = aliases[commandName];
  command = {
    run(config: Config, reporter: ConsoleReporter | JSONReporter): Promise<void> {
      throw new MessageError(`Did you mean \`yarn ${alias}\`?`);
    },
  };
}

//
if (commandName === 'help' && args.length) {
  commandName = camelCase(args.shift());
  args.push('--help');
}

//
invariant(commandName, 'Missing command name');
if (!command) {
  const camelised = camelCase(commandName);
  if (camelised) {
    command = commands[camelised];
  }
}

//
if (command && typeof command.setFlags === 'function') {
  command.setFlags(commander);
}

if (args.indexOf('--help') >= 0 || args.indexOf('-h') >= 0) {
  const examples: Array<string> = (command && command.examples) || [];
  if (examples.length) {
    commander.on('--help', () => {
      console.log('  Examples:\n');
      for (const example of examples) {
        console.log(`    $ yarn ${example}`);
      }
      console.log();
    });
  }
  commander.on('--help', () => console.log('  ' + getDocsInfo(commandName) + '\n'));

  commander.parse(startArgs.concat(args));
  commander.help();
  process.exit(1);
}

// parse flags
args.unshift(commandName);

if (ARGS_THAT_SHARE_NAMES_WITH_OPTIONS.indexOf(commandName) >= 0 && args[0] === commandName) {
  args.shift();
}

commander.parse(startArgs.concat(args));
commander.args = commander.args.concat(endArgs);

if (command) {
  commander.args.shift();
} else {
  command = commands.run;
}
invariant(command, 'missing command');

//
let Reporter = ConsoleReporter;
if (commander.json) {
  Reporter = JSONReporter;
}
const reporter = new Reporter({
  emoji: commander.emoji && process.stdout.isTTY && process.platform === 'darwin',
  verbose: commander.verbose,
  noProgress: !commander.progress,
  isSilent: commander.silent,
});

reporter.initPeakMemoryCounter();

const config = new Config(reporter);

// print header
let outputWrapper = true;
if (typeof command.hasWrapper === 'function') {
  outputWrapper = command.hasWrapper(commander, commander.args);
}
if (commander.json) {
  outputWrapper = false;
}
if (outputWrapper && commandName !== 'help') {
  reporter.header(commandName, pkg);
}

if (command.noArguments && commander.args.length) {
  reporter.error(reporter.lang('noArguments'));
  reporter.info(getDocsInfo(commandName));
  process.exit(1);
}

//
if (commander.yes) {
  reporter.warn(reporter.lang('yesWarning'));
}

//
if (!commander.offline && network.isOffline()) {
  reporter.warn(reporter.lang('networkWarning'));
}

//
if (command.requireLockfile && !fs.existsSync(path.join(config.cwd, constants.LOCKFILE_FILENAME))) {
  reporter.error(reporter.lang('noRequiredLockfile'));
  process.exit(1);
}

//
const run = (): Promise<void> => {
  invariant(command, 'missing command');
  return command.run(config, reporter, commander, commander.args).then(() => {
    reporter.close();
    if (outputWrapper) {
      reporter.footer(false);
    }
  });
};

//
const runEventuallyWithFile = (mutexFilename: ?string, isFirstTime?: boolean): Promise<void> => {
  return new Promise((ok) => {
    const lockFilename = mutexFilename || path.join(config.cwd, constants.SINGLE_INSTANCE_FILENAME);
    lockfile.lock(lockFilename, {realpath: false}, (err: mixed, release: () => void) => {
      if (err) {
        if (isFirstTime) {
          reporter.warn(reporter.lang('waitingInstance'));
        }
        setTimeout(() => {
          ok(runEventuallyWithFile(mutexFilename, isFirstTime));
        }, 200); // do not starve the CPU
      } else {
        onDeath(() => {
          process.exit(1);
        });
        ok(run().then(release));
      }
    });
  });
};

//
const runEventuallyWithNetwork = (mutexPort: ?string): Promise<void> => {
  return new Promise((ok) => {
    const connectionOptions = {
      port: +mutexPort || constants.SINGLE_INSTANCE_PORT,
    };

    const server = net.createServer();

    server.on('error', () => {
      // another Yarn instance exists, let's connect to it to know when it dies.
      reporter.warn(reporter.lang('waitingInstance'));
      const socket = net.createConnection(connectionOptions);

      socket
        .on('connect', () => {
          // Allow the program to exit if this is the only active server in the event system.
          socket.unref();
        })
        .on('close', (hadError?: boolean) => {
          // the `close` event gets always called after the `error` event
          if (!hadError) {
            process.nextTick(() => {
              ok(runEventuallyWithNetwork(mutexPort));
            });
          }
        })
        .on('error', () => {
          // No server to listen to ? Let's retry to become the next server then.
          process.nextTick(() => {
            ok(runEventuallyWithNetwork(mutexPort));
          });
        });
    });

    const onServerEnd = (): Promise<void> => {
      server.close();
      return Promise.resolve();
    };

    // open the server and continue only if succeed.
    server.listen(connectionOptions, () => {
      // ensure the server gets closed properly on SIGNALS.
      onDeath(onServerEnd);

      ok(run().then(onServerEnd));
    });
  });
};

function onUnexpectedError(err: Error) {
  function indent(str: string): string {
    return '\n  ' + str.trim().split('\n').join('\n  ');
  }

  const log = [];
  log.push(`Arguments: ${indent(process.argv.join(' '))}`);
  log.push(`PATH: ${indent(process.env.PATH || 'undefined')}`);
  log.push(`Yarn version: ${indent(pkg.version)}`);
  log.push(`Node version: ${indent(process.versions.node)}`);
  log.push(`Platform: ${indent(process.platform + ' ' + process.arch)}`);

  // add manifests
  for (const registryName of registryNames) {
    const possibleLoc = path.join(config.cwd, registries[registryName].filename);
    const manifest = fs.existsSync(possibleLoc) ? fs.readFileSync(possibleLoc, 'utf8') : 'No manifest';
    log.push(`${registryName} manifest: ${indent(manifest)}`);
  }

  // lockfile
  const lockLoc = path.join(config.cwd, constants.LOCKFILE_FILENAME);
  const lockfile = fs.existsSync(lockLoc) ? fs.readFileSync(lockLoc, 'utf8') : 'No lockfile';
  log.push(`Lockfile: ${indent(lockfile)}`);

  log.push(`Trace: ${indent(err.stack)}`);

  const errorReportLoc = writeErrorReport(log);

  reporter.error(reporter.lang('unexpectedError', err.message));

  if (errorReportLoc) {
    reporter.info(reporter.lang('bugReport', errorReportLoc));
  }
}

function writeErrorReport(log) : ?string {
  const errorReportLoc = path.join(config.cwd, 'yarn-error.log');

  try {
    fs.writeFileSync(errorReportLoc, log.join('\n\n') + '\n');
  } catch (err) {
    reporter.error(reporter.lang('fileWriteError', errorReportLoc, err.message));
    return undefined;
  }

  return errorReportLoc;
}

//
config.init({
  binLinks: commander.binLinks,
  modulesFolder: commander.modulesFolder,
  globalFolder: commander.globalFolder,
  cacheRootFolder: commander.cacheFolder,
  preferOffline: commander.preferOffline,
  captureHar: commander.har,
  ignorePlatform: commander.ignorePlatform,
  ignoreEngines: commander.ignoreEngines,
  ignoreScripts: commander.ignoreScripts,
  offline: commander.preferOffline || commander.offline,
  looseSemver: !commander.strictSemver,
  production: commander.production,
  httpProxy: commander.proxy,
  httpsProxy: commander.httpsProxy,
  networkConcurrency: commander.networkConcurrency,
  nonInteractive: commander.nonInteractive,
  commandName,
}).then(() => {

  // option "no-progress" stored in yarn config
  const noProgressConfig = config.registries.yarn.getOption('no-progress');

  if (noProgressConfig) {
    reporter.disableProgress();
  }

  const exit = () => {
    process.exit(0);
  };
  // verbose logs outputs process.uptime() with this line we can sync uptime to absolute time on the computer
  reporter.verbose(`current time: ${new Date().toISOString()}`);

  const mutex: mixed = commander.mutex;
  if (mutex && typeof mutex === 'string') {
    const parts = mutex.split(':');
    const mutexType = parts.shift();
    const mutexSpecifier = parts.join(':');

    if (mutexType === 'file') {
      return runEventuallyWithFile(mutexSpecifier, true).then(exit);
    } else if (mutexType === 'network') {
      return runEventuallyWithNetwork(mutexSpecifier).then(exit);
    } else {
      throw new MessageError(`Unknown single instance type ${mutexType}`);
    }
  } else {
    return run().then(exit);
  }
}).catch((err: Error) => {
  reporter.verbose(err.stack);

  if (err instanceof MessageError) {
    reporter.error(err.message);
  } else {
    onUnexpectedError(err);
  }

  if (commandName) {
    const actualCommandForHelp = commands[commandName] ? commandName : aliases[commandName];
    if (command && actualCommandForHelp) {
      reporter.info(getDocsInfo(actualCommandForHelp));
    }
  }

  process.exit(1);
});
<|endoftext|>


<commit_before>$(window).scroll(function() {
    if ($('.navbar').offset().top > 50) {
        $('.navbar-fixed-top').addClass('top-nav-collapse');
    } else {
        $('.navbar-fixed-top').removeClass('top-nav-collapse');
    }
});

$(function() {
    if(navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(function(position) {
            var jqxhr = $.get('/school/find', {
                latitude: position.coords.latitude,
                longitude: position.coords.longitude
            });

            jqxhr.done(function(response) {
                loadMap(position, response);
                // update header with closest school and information about it.
            });

            jqxhr.fail(function(response) {
                console.log(response);
            });
        }, function() {
            console.log('Geolocation service failed.');
        });
    } else {
        console.log('Browser does not support Geolocation');
    }
});

function loadMap(position, school) {
    var mapOptions = {
        zoom: 13,
        center: new google.maps.LatLng(position.coords.latitude, position.coords.longitude),
        disableDefaultUI: true,
        scrollwheel: false,
        draggable: true,
        //styles: [{"featureType":"water","elementType":"geometry","stylers":[{"color":"#a2daf2"}]},{"featureType":"landscape.man_made","elementType":"geometry","stylers":[{"color":"#f7f1df"}]},{"featureType":"landscape.natural","elementType":"geometry","stylers":[{"color":"#d0e3b4"}]},{"featureType":"landscape.natural.terrain","elementType":"geometry","stylers":[{"visibility":"off"}]},{"featureType":"poi.park","elementType":"geometry","stylers":[{"color":"#bde6ab"}]},{"featureType":"poi","elementType":"labels","stylers":[{"visibility":"off"}]},{"featureType":"poi.medical","elementType":"geometry","stylers":[{"color":"#fbd3da"}]},{"featureType":"poi.business","stylers":[{"visibility":"off"}]},{"featureType":"road","elementType":"geometry.stroke","stylers":[{"visibility":"off"}]},{"featureType":"road","elementType":"labels","stylers":[{"visibility":"off"}]},{"featureType":"road.highway","elementType":"geometry.fill","stylers":[{"color":"#ffe15f"}]},{"featureType":"road.highway","elementType":"geometry.stroke","stylers":[{"color":"#efd151"}]},{"featureType":"road.arterial","elementType":"geometry.fill","stylers":[{"color":"#ffffff"}]},{"featureType":"road.local","elementType":"geometry.fill","stylers":[{"color":"black"}]},{"featureType":"transit.station.airport","elementType":"geometry.fill","stylers":[{"color":"#cfb2db"}]}]
    };

    var mapElement = document.getElementById('map');
    var map = new google.maps.Map(mapElement, mapOptions);
    var origin = position.coords.latitude + ',' + position.coords.longitude;
    var destination = school.latitude + ',' + school.longitude;
    var service = new google.maps.DirectionsService();

    var request = {
        origin: origin,
        destination: destination,
        travelMode: google.maps.DirectionsTravelMode.DRIVING
    };

    service.route(request, function(response, status) {
        if (status == google.maps.DirectionsStatus.OK) {
            new google.maps.DirectionsRenderer({
                map: map,
                directions: response,
                suppressMarkers: true
            });

            var route = response.routes[0].legs[0];
            var startMarker = new google.maps.Marker({
                position: route.start_location,
                map: map,
                animation: google.maps.Animation.DROP,
                title: 'Start Point'
            });

            var startingMarkerContent = '
You are Here
';
            var startInfoWindow = new google.maps.InfoWindow();
            startInfoWindow.setContent(startingMarkerContent);
            startInfoWindow.open(map, startMarker);

            google.maps.event.addListener(startMarker, 'click', function() {
                startInfoWindow.open(map, startMarker);
            });

            var endMarker = new google.maps.Marker({
                position: route.end_location,
                map: map,
                animation: google.maps.Animation.DROP,
                title: 'End Point'
            });

            var endingMarkerContent = '
'+
                    '
'+school.name+'
'+
                    '
'+school.address+'
'+
                    school.city+' '+school.state+' '+school.zip;

            if(school.url !== undefined) {
                endingMarkerContent += '
'+school.url+'';
            }

            endingMarkerContent += '

';

            var endInfowindow = new google.maps.InfoWindow();
            endInfowindow.setContent(endingMarkerContent);
            endInfowindow.open(map, endMarker);

            google.maps.event.addListener(endMarker, 'click', function() {
                endInfoWindow.open(map, endMarker);
            });
        } else {
            console.log("Unable to retrieve your route");
        }
    });
}<commit_msg>Adding in proper error handling for the main ajax call<commit_after>$(window).scroll(function() {
    if ($('.navbar').offset().top > 50) {
        $('.navbar-fixed-top').addClass('top-nav-collapse');
    } else {
        $('.navbar-fixed-top').removeClass('top-nav-collapse');
    }
});

$(function() {
    if(navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(function(position) {
            var jqxhr = $.get('/school/find', {
                latitude: position.coords.latitude,
                longitude: position.coords.longitude
            });

            jqxhr.done(function(response) {
                loadMap(position, response);
                // update header with closest school and information about it.
            });

            jqxhr.fail(function(response) {
                if(response.status == '404') {
                    $('#map').html(response.responseText);
                }
            });
        }, function() {
            console.log('Geolocation service failed.');
        });
    } else {
        console.log('Browser does not support Geolocation');
    }
});

function loadMap(position, school) {
    var mapOptions = {
        zoom: 13,
        center: new google.maps.LatLng(position.coords.latitude, position.coords.longitude),
        disableDefaultUI: true,
        scrollwheel: false,
        draggable: true,
        //styles: [{"featureType":"water","elementType":"geometry","stylers":[{"color":"#a2daf2"}]},{"featureType":"landscape.man_made","elementType":"geometry","stylers":[{"color":"#f7f1df"}]},{"featureType":"landscape.natural","elementType":"geometry","stylers":[{"color":"#d0e3b4"}]},{"featureType":"landscape.natural.terrain","elementType":"geometry","stylers":[{"visibility":"off"}]},{"featureType":"poi.park","elementType":"geometry","stylers":[{"color":"#bde6ab"}]},{"featureType":"poi","elementType":"labels","stylers":[{"visibility":"off"}]},{"featureType":"poi.medical","elementType":"geometry","stylers":[{"color":"#fbd3da"}]},{"featureType":"poi.business","stylers":[{"visibility":"off"}]},{"featureType":"road","elementType":"geometry.stroke","stylers":[{"visibility":"off"}]},{"featureType":"road","elementType":"labels","stylers":[{"visibility":"off"}]},{"featureType":"road.highway","elementType":"geometry.fill","stylers":[{"color":"#ffe15f"}]},{"featureType":"road.highway","elementType":"geometry.stroke","stylers":[{"color":"#efd151"}]},{"featureType":"road.arterial","elementType":"geometry.fill","stylers":[{"color":"#ffffff"}]},{"featureType":"road.local","elementType":"geometry.fill","stylers":[{"color":"black"}]},{"featureType":"transit.station.airport","elementType":"geometry.fill","stylers":[{"color":"#cfb2db"}]}]
    };

    var mapElement = document.getElementById('map');
    var map = new google.maps.Map(mapElement, mapOptions);
    var origin = position.coords.latitude + ',' + position.coords.longitude;
    var destination = school.latitude + ',' + school.longitude;
    var service = new google.maps.DirectionsService();

    var request = {
        origin: origin,
        destination: destination,
        travelMode: google.maps.DirectionsTravelMode.DRIVING
    };

    service.route(request, function(response, status) {
        if (status == google.maps.DirectionsStatus.OK) {
            new google.maps.DirectionsRenderer({
                map: map,
                directions: response,
                suppressMarkers: true
            });

            var route = response.routes[0].legs[0];
            var startMarker = new google.maps.Marker({
                position: route.start_location,
                map: map,
                animation: google.maps.Animation.DROP,
                title: 'Start Point'
            });

            var startingMarkerContent = '
You are Here
';
            var startInfoWindow = new google.maps.InfoWindow();
            startInfoWindow.setContent(startingMarkerContent);
            startInfoWindow.open(map, startMarker);

            google.maps.event.addListener(startMarker, 'click', function() {
                startInfoWindow.open(map, startMarker);
            });

            var endMarker = new google.maps.Marker({
                position: route.end_location,
                map: map,
                animation: google.maps.Animation.DROP,
                title: 'End Point'
            });

            var endingMarkerContent = '
'+
                    '
'+school.name+'
'+
                    '
'+school.address+'
'+
                    school.city+' '+school.state+' '+school.zip;

            if(school.url !== undefined) {
                endingMarkerContent += '
'+school.url+'';
            }

            endingMarkerContent += '

';

            var endInfowindow = new google.maps.InfoWindow();
            endInfowindow.setContent(endingMarkerContent);
            endInfowindow.open(map, endMarker);

            google.maps.event.addListener(endMarker, 'click', function() {
                endInfoWindow.open(map, endMarker);
            });
        } else {
            console.log("Unable to retrieve your route");
        }
    });
}<|endoftext|>


<commit_before>/**
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { UPDATE_FEED_URL } from 'common/config';
import { app, autoUpdater, BrowserWindow, ipcMain, Menu, nativeTheme, systemPreferences, Tray } from 'electron';
import electronPositioner from 'electron-positioner';
import EventEmitter from 'events';
import fs from 'fs';
import path from 'path';
import { loadConfig } from './config-helper.js';
import { EDIT_MENU, HELP_MENU, SEPARATOR_MENU_ITEM, WINDOW_MENU } from './standard-menus.js';

const DEV_MODE = process.env.NODE_ENV === 'development';
const IS_MAC = process.platform == 'darwin';

const config = loadConfig();

let colorverse = require('@the-colors');
if (config.extraColors) {
  colorverse = {
    ...config.extraColors,
    ...colorverse,
  };
};

const UI_MODES = {
  TRAY: 'tray',
  TRAY_ATTACHED: 'tray-attached',
  NORMAL: 'normal'
};

let uiMode = null;
let mainWindow;
let mainWindowPositioner;
let trayIcon;
let trayMenu;
let openAtLogin;

const eventBus = new EventEmitter();

if (!app.requestSingleInstanceLock()) {
  app.quit();
} else {
  app.on('second-instance', () => {
    mainWindow.show();
    mainWindow.focus();
    eventBus.emit('show-hide');
  });
}

app.on('ready', () => {
  // this setting doesn't seem to change instantly, so don't rely on it
  // for showing the checkbox state in the menu
  openAtLogin = !!app.getLoginItemSettings().openAtLogin;

  readPrefs();
  setupUiMode(uiMode, { firstRun: true });
  if (!DEV_MODE) {
    try {
      checkForAppUpdates();
    } catch (e) {
      console.error(e);
    }
  }
});

app.on('window-all-closed', () => app.quit());

app.on('activate', () => {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (!mainWindow) {
    setupUiMode(uiMode);
  }
  if (mainWindow && !mainWindow.isVisible()) {
    mainWindow.show();
    mainWindow.focus();
  }
});

systemPreferences.subscribeNotification(
  'AppleInterfaceThemeChangedNotification',
  () => updateMainWindowDarkMode());

ipcMain.on('on-hide', () => eventBus.emit('show-hide'));
ipcMain.on('show-overflow-menu', () => trayMenu.popup());
ipcMain.on('get-config', event => event.returnValue = config);
ipcMain.on('get-colorverse', event => event.returnValue = colorverse);
ipcMain.on('set-height', (event, height) => {
  mainWindow && mainWindow.setContentSize(mainWindow.getContentSize()[0], height, false);
  event.returnValue = true;
});

eventBus.on('show-hide', () => {
  setupMenus();

  if (trayIcon && uiMode == UI_MODES.TRAY_ATTACHED) {
    // trayIcon.setHighlightMode(isVisible() ? 'always' : 'never');
  }
});

function updateMainWindowDarkMode() {
  if (!mainWindow) {
    return;
  }

  let darkMode = nativeTheme.shouldUseDarkColors;
  mainWindow.webContents.send('dark-mode-updated', darkMode);
  mainWindow.setBackgroundColor(darkMode ? '#3c3c3c' : '#fff');
}

function setupUiMode(newUiMode, { fromUser, firstRun } = {}) {
  uiMode = newUiMode;

  setupMainWindow({ firstRun });
  setupTray();
  setupDock();
  setupMenus();
  setupAbout();

  if (fromUser) {
    writePrefs();
  }
}

function setupMainWindow({ firstRun }) {
  if (!mainWindow) {
    mainWindow = new BrowserWindow({
      transparent: true,
      width: 200,
      height: 400,
      resizable: false,
      skipTaskbar: true,
      maximizable: false,
      fullscreenable: false,
      frame: false,
      webPreferences: {
        contextIsolation: false,
        enableRemoteModule: true,
        nodeIntegration: true,
      }
    });

    updateMainWindowDarkMode();

    if (uiMode == UI_MODES.NORMAL) {
      mainWindow.show();
    } else {
      // when starting in tray mode, don't show it
      if (firstRun) {
        mainWindow.hide();
      }
    }

    mainWindow.on('show', () => eventBus.emit('show-hide'));
    mainWindow.on('hide', () => eventBus.emit('show-hide'));
    mainWindow.on('blur', () => {
      if (uiMode == UI_MODES.TRAY_ATTACHED) {
        toggleVisibility(false)
      }
    });
    mainWindow.on('closed', () => app.quit());

    mainWindowPositioner = new electronPositioner(mainWindow);

    if (DEV_MODE) {
      mainWindow.webContents.openDevTools({ mode: 'detach' });
      // triggers blur, which hides the window in tray-attached mode
    }
  }

  let darkMode = nativeTheme.shouldUseDarkColors;
  mainWindow.setAlwaysOnTop(uiMode == UI_MODES.TRAY || uiMode == UI_MODES.TRAY_ATTACHED);
  mainWindow.setMovable(uiMode != UI_MODES.TRAY_ATTACHED);
  let qs = `?uiMode=${uiMode}&darkMode=${darkMode}`;
  if (DEV_MODE) {
    mainWindow.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}?${qs}`);
  } else {
    mainWindow.loadURL(`file://${__dirname}/index.html?${qs}`);
  }
}

function setupTray() {
  // first tear it down
  if (trayIcon) {
    trayIcon.destroy();
    trayIcon = null;
  }

  if (uiMode != UI_MODES.TRAY && uiMode != UI_MODES.TRAY_ATTACHED) {
    return;
  }

  trayIcon = new Tray(path.join(__static, 'TrayIconTemplate.png'));
  trayIcon.setToolTip(app.getName());
  if (uiMode == UI_MODES.TRAY_ATTACHED) {
    trayIcon.on('click', (evt, trayBounds) => {
      toggleVisibility();
      mainWindowPositioner.move('trayCenter', trayBounds);
    });
    trayIcon.on('right-click', () => {
      trayIcon.popUpContextMenu(trayMenu);
    });

    // position main window to bounds
    let trayBounds = trayIcon.getBounds();
    mainWindowPositioner.move('trayCenter', trayBounds);
  } else if (uiMode == UI_MODES.TRAY) {
    trayIcon.on('click', (evt, trayBounds) => {
      trayIcon.popUpContextMenu(trayMenu);
    });
    trayIcon.on('right-click', () => {
      toggleVisibility();
    });
  }
}

function setupDock() {
  if (!IS_MAC || !app.dock) {
    return;
  }

  if (uiMode == UI_MODES.NORMAL) {
    app.dock.show();
  } else {
    app.dock.hide();
  }
}

function setupMenus() {
  if (!mainWindow) {
    return;
  }

  const showHideMenuItem = {
    label: isVisible() ? 'Hide Colors' : 'Show Colors',
    accelerator: 'Command+H',
    click: () => {
      toggleVisibility();
    }
  };

  const switchModeMacMenuItem = {
    label: 'App Mode',
    submenu: [
      {
        label: 'Normal',
        type: 'checkbox',
        checked: uiMode == UI_MODES.NORMAL,
        click: () => setupUiMode(UI_MODES.NORMAL, { fromUser: true }),
      },
      {
        label: 'Menu Bar',
        type: 'checkbox',
        checked: uiMode == UI_MODES.TRAY,
        click: () => setupUiMode(UI_MODES.TRAY, { fromUser: true }),
      },
      {
        label: 'Menu Bar (Attached)',
        type: 'checkbox',
        checked: uiMode == UI_MODES.TRAY_ATTACHED,
        click: () => setupUiMode(UI_MODES.TRAY_ATTACHED, { fromUser: true }),
      },
    ]
  };

  const openAtLoginMenuItem = {
    label: 'Open at Login',
    type: 'checkbox',
    checked: openAtLogin,
    click: () => {
      openAtLogin = !openAtLogin;

      // TODO: if the user chooses Options > Open at Login in the dock
      // we don't have a chance to update the custom app menus :-/
      app.setLoginItemSettings({
        openAtLogin,
        openAsHidden: true
      });
      setupMenus();
    },
  };

  const aboutMacMenuItem = {
    label: 'About ' + app.getName(),
    role: 'about'
  };

  const quitMenuItem = {
    label: 'Quit',
    accelerator: 'Command+Q',
    click: () => app.quit(),
  };

  if (uiMode == UI_MODES.TRAY || uiMode == UI_MODES.TRAY_ATTACHED) {
    trayMenu = Menu.buildFromTemplate([
      showHideMenuItem,
      ...(IS_MAC ? [switchModeMacMenuItem] : []),
      SEPARATOR_MENU_ITEM,
      openAtLoginMenuItem,
      ...(IS_MAC ? [aboutMacMenuItem] : []),
      quitMenuItem]);
    // if (uiMode == UI_MODES.TRAY) {
    //   trayIcon.setContextMenu(trayMenu);
    // }
  }

  if (IS_MAC) {
    app.dock.setMenu(Menu.buildFromTemplate([
      switchModeMacMenuItem,
      aboutMacMenuItem
    ]));

    // build the app menu
    Menu.setApplicationMenu(Menu.buildFromTemplate([
      {
        label: 'app', // automatically set to title
        submenu: [
          showHideMenuItem,
          switchModeMacMenuItem,
          SEPARATOR_MENU_ITEM,
          openAtLoginMenuItem,
          aboutMacMenuItem,
          quitMenuItem
        ]
      },
      EDIT_MENU,
      WINDOW_MENU,
      HELP_MENU]));
  }
}

function setupAbout() {
  app.setAboutPanelOptions({
    copyright: 'Copyright 2021 Google LLC',
    credits: 'by Roman Nurik',
  });
}

function readPrefs() {
  uiMode = UI_MODES.NORMAL;
  try {
    let prefStr = fs.readFileSync(app.getPath('userData') + '/prefs.json');
    if (prefStr) {
      let prefs = JSON.parse(prefStr);
      uiMode = prefs.uiMode;
      if (!uiMode && 'isTrayMode' in prefs) {
        uiMode = prefs.isTrayMode ? UI_MODES.TRAY : UI_MODES.NORMAL;
      }
    }
  } catch (e) { }
}

function writePrefs() {
  fs.writeFileSync(app.getPath('userData') + '/prefs.json', JSON.stringify({
    uiMode
  }));
}

function checkForAppUpdates() {
  let packageInfo = require('@/package.json');

  let query = {
    version: app.getVersion(),
    bundleId: packageInfo.appBundleId
  };

  let qs = Object.keys(query)
    .map(k => k + '=' + encodeURIComponent(query[k]))
    .join('&');

  autoUpdater.setFeedURL(UPDATE_FEED_URL + '?' + qs);
  autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName, releaseDate, updateURL) => {
    mainWindow.webContents.send('update-downloaded', releaseName);
    ipcMain.on('install-update', () => autoUpdater.quitAndInstall());
  });
  autoUpdater.on('error', error => {
    console.error('Error updating: ' + error);
    // electron.dialog.showErrorBox('error', error.toString());
  });
  autoUpdater.checkForUpdates();
}

function isVisible() {
  return mainWindow && mainWindow.isVisible();
}

function toggleVisibility(makeVisible) {
  if (makeVisible === undefined) {
    makeVisible = !isVisible();
  }

  if (!makeVisible && mainWindow) {
    mainWindow.hide();
  }
  if (makeVisible) {
    if (!mainWindow) {
      // TODO: do we need to handle this?
    }

    mainWindow.show();
  }
}
<commit_msg>Newer dark mode change detection<commit_after>/**
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { UPDATE_FEED_URL } from 'common/config';
import { app, autoUpdater, BrowserWindow, ipcMain, Menu, nativeTheme, Tray } from 'electron';
import electronPositioner from 'electron-positioner';
import EventEmitter from 'events';
import fs from 'fs';
import path from 'path';
import { loadConfig } from './config-helper.js';
import { EDIT_MENU, HELP_MENU, SEPARATOR_MENU_ITEM, WINDOW_MENU } from './standard-menus.js';

const DEV_MODE = process.env.NODE_ENV === 'development';
const IS_MAC = process.platform == 'darwin';

const config = loadConfig();

let colorverse = require('@the-colors');
if (config.extraColors) {
  colorverse = {
    ...config.extraColors,
    ...colorverse,
  };
};

const UI_MODES = {
  TRAY: 'tray',
  TRAY_ATTACHED: 'tray-attached',
  NORMAL: 'normal'
};

let uiMode = null;
let mainWindow;
let mainWindowPositioner;
let trayIcon;
let trayMenu;
let openAtLogin;

const eventBus = new EventEmitter();

if (!app.requestSingleInstanceLock()) {
  app.quit();
} else {
  app.on('second-instance', () => {
    mainWindow.show();
    mainWindow.focus();
    eventBus.emit('show-hide');
  });
}

app.on('ready', () => {
  // this setting doesn't seem to change instantly, so don't rely on it
  // for showing the checkbox state in the menu
  openAtLogin = !!app.getLoginItemSettings().openAtLogin;

  readPrefs();
  setupUiMode(uiMode, { firstRun: true });
  if (!DEV_MODE) {
    try {
      checkForAppUpdates();
    } catch (e) {
      console.error(e);
    }
  }
});

app.on('window-all-closed', () => app.quit());

app.on('activate', () => {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (!mainWindow) {
    setupUiMode(uiMode);
  }
  if (mainWindow && !mainWindow.isVisible()) {
    mainWindow.show();
    mainWindow.focus();
  }
});

nativeTheme.on('updated', () => updateMainWindowDarkMode());

ipcMain.on('on-hide', () => eventBus.emit('show-hide'));
ipcMain.on('show-overflow-menu', () => trayMenu.popup());
ipcMain.on('get-config', event => event.returnValue = config);
ipcMain.on('get-colorverse', event => event.returnValue = colorverse);
ipcMain.on('set-height', (event, height) => {
  mainWindow && mainWindow.setContentSize(mainWindow.getContentSize()[0], height, false);
  event.returnValue = true;
});

eventBus.on('show-hide', () => {
  setupMenus();

  if (trayIcon && uiMode == UI_MODES.TRAY_ATTACHED) {
    // trayIcon.setHighlightMode(isVisible() ? 'always' : 'never');
  }
});

function updateMainWindowDarkMode() {
  if (!mainWindow) {
    return;
  }

  let darkMode = nativeTheme.shouldUseDarkColors;
  mainWindow.webContents.send('dark-mode-updated', darkMode);
  mainWindow.setBackgroundColor(darkMode ? '#3c3c3c' : '#fff');
}

function setupUiMode(newUiMode, { fromUser, firstRun } = {}) {
  uiMode = newUiMode;

  setupMainWindow({ firstRun });
  setupTray();
  setupDock();
  setupMenus();
  setupAbout();

  if (fromUser) {
    writePrefs();
  }
}

function setupMainWindow({ firstRun }) {
  if (!mainWindow) {
    mainWindow = new BrowserWindow({
      transparent: true,
      width: 200,
      height: 400,
      resizable: false,
      skipTaskbar: true,
      maximizable: false,
      fullscreenable: false,
      frame: false,
      webPreferences: {
        contextIsolation: false,
        enableRemoteModule: true,
        nodeIntegration: true,
      }
    });

    updateMainWindowDarkMode();

    if (uiMode == UI_MODES.NORMAL) {
      mainWindow.show();
    } else {
      // when starting in tray mode, don't show it
      if (firstRun) {
        mainWindow.hide();
      }
    }

    mainWindow.on('show', () => eventBus.emit('show-hide'));
    mainWindow.on('hide', () => eventBus.emit('show-hide'));
    mainWindow.on('blur', () => {
      if (uiMode == UI_MODES.TRAY_ATTACHED) {
        toggleVisibility(false)
      }
    });
    mainWindow.on('closed', () => app.quit());

    mainWindowPositioner = new electronPositioner(mainWindow);

    if (DEV_MODE) {
      mainWindow.webContents.openDevTools({ mode: 'detach' });
      // triggers blur, which hides the window in tray-attached mode
    }
  }

  let darkMode = nativeTheme.shouldUseDarkColors;
  mainWindow.setAlwaysOnTop(uiMode == UI_MODES.TRAY || uiMode == UI_MODES.TRAY_ATTACHED);
  mainWindow.setMovable(uiMode != UI_MODES.TRAY_ATTACHED);
  let qs = `?uiMode=${uiMode}&darkMode=${darkMode}`;
  if (DEV_MODE) {
    mainWindow.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}?${qs}`);
  } else {
    mainWindow.loadURL(`file://${__dirname}/index.html?${qs}`);
  }
}

function setupTray() {
  // first tear it down
  if (trayIcon) {
    trayIcon.destroy();
    trayIcon = null;
  }

  if (uiMode != UI_MODES.TRAY && uiMode != UI_MODES.TRAY_ATTACHED) {
    return;
  }

  trayIcon = new Tray(path.join(__static, 'TrayIconTemplate.png'));
  trayIcon.setToolTip(app.getName());
  if (uiMode == UI_MODES.TRAY_ATTACHED) {
    trayIcon.on('click', (evt, trayBounds) => {
      toggleVisibility();
      mainWindowPositioner.move('trayCenter', trayBounds);
    });
    trayIcon.on('right-click', () => {
      trayIcon.popUpContextMenu(trayMenu);
    });

    // position main window to bounds
    let trayBounds = trayIcon.getBounds();
    mainWindowPositioner.move('trayCenter', trayBounds);
  } else if (uiMode == UI_MODES.TRAY) {
    trayIcon.on('click', (evt, trayBounds) => {
      trayIcon.popUpContextMenu(trayMenu);
    });
    trayIcon.on('right-click', () => {
      toggleVisibility();
    });
  }
}

function setupDock() {
  if (!IS_MAC || !app.dock) {
    return;
  }

  if (uiMode == UI_MODES.NORMAL) {
    app.dock.show();
  } else {
    app.dock.hide();
  }
}

function setupMenus() {
  if (!mainWindow) {
    return;
  }

  const showHideMenuItem = {
    label: isVisible() ? 'Hide Colors' : 'Show Colors',
    accelerator: 'Command+H',
    click: () => {
      toggleVisibility();
    }
  };

  const switchModeMacMenuItem = {
    label: 'App Mode',
    submenu: [
      {
        label: 'Normal',
        type: 'checkbox',
        checked: uiMode == UI_MODES.NORMAL,
        click: () => setupUiMode(UI_MODES.NORMAL, { fromUser: true }),
      },
      {
        label: 'Menu Bar',
        type: 'checkbox',
        checked: uiMode == UI_MODES.TRAY,
        click: () => setupUiMode(UI_MODES.TRAY, { fromUser: true }),
      },
      {
        label: 'Menu Bar (Attached)',
        type: 'checkbox',
        checked: uiMode == UI_MODES.TRAY_ATTACHED,
        click: () => setupUiMode(UI_MODES.TRAY_ATTACHED, { fromUser: true }),
      },
    ]
  };

  const openAtLoginMenuItem = {
    label: 'Open at Login',
    type: 'checkbox',
    checked: openAtLogin,
    click: () => {
      openAtLogin = !openAtLogin;

      // TODO: if the user chooses Options > Open at Login in the dock
      // we don't have a chance to update the custom app menus :-/
      app.setLoginItemSettings({
        openAtLogin,
        openAsHidden: true
      });
      setupMenus();
    },
  };

  const aboutMacMenuItem = {
    label: 'About ' + app.getName(),
    role: 'about'
  };

  const quitMenuItem = {
    label: 'Quit',
    accelerator: 'Command+Q',
    click: () => app.quit(),
  };

  if (uiMode == UI_MODES.TRAY || uiMode == UI_MODES.TRAY_ATTACHED) {
    trayMenu = Menu.buildFromTemplate([
      showHideMenuItem,
      ...(IS_MAC ? [switchModeMacMenuItem] : []),
      SEPARATOR_MENU_ITEM,
      openAtLoginMenuItem,
      ...(IS_MAC ? [aboutMacMenuItem] : []),
      quitMenuItem]);
    // if (uiMode == UI_MODES.TRAY) {
    //   trayIcon.setContextMenu(trayMenu);
    // }
  }

  if (IS_MAC) {
    app.dock.setMenu(Menu.buildFromTemplate([
      switchModeMacMenuItem,
      aboutMacMenuItem
    ]));

    // build the app menu
    Menu.setApplicationMenu(Menu.buildFromTemplate([
      {
        label: 'app', // automatically set to title
        submenu: [
          showHideMenuItem,
          switchModeMacMenuItem,
          SEPARATOR_MENU_ITEM,
          openAtLoginMenuItem,
          aboutMacMenuItem,
          quitMenuItem
        ]
      },
      EDIT_MENU,
      WINDOW_MENU,
      HELP_MENU]));
  }
}

function setupAbout() {
  app.setAboutPanelOptions({
    copyright: 'Copyright 2021 Google LLC',
    credits: 'by Roman Nurik',
  });
}

function readPrefs() {
  uiMode = UI_MODES.NORMAL;
  try {
    let prefStr = fs.readFileSync(app.getPath('userData') + '/prefs.json');
    if (prefStr) {
      let prefs = JSON.parse(prefStr);
      uiMode = prefs.uiMode;
      if (!uiMode && 'isTrayMode' in prefs) {
        uiMode = prefs.isTrayMode ? UI_MODES.TRAY : UI_MODES.NORMAL;
      }
    }
  } catch (e) { }
}

function writePrefs() {
  fs.writeFileSync(app.getPath('userData') + '/prefs.json', JSON.stringify({
    uiMode
  }));
}

function checkForAppUpdates() {
  let packageInfo = require('@/package.json');

  let query = {
    version: app.getVersion(),
    bundleId: packageInfo.appBundleId
  };

  let qs = Object.keys(query)
    .map(k => k + '=' + encodeURIComponent(query[k]))
    .join('&');

  autoUpdater.setFeedURL(UPDATE_FEED_URL + '?' + qs);
  autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName, releaseDate, updateURL) => {
    mainWindow.webContents.send('update-downloaded', releaseName);
    ipcMain.on('install-update', () => autoUpdater.quitAndInstall());
  });
  autoUpdater.on('error', error => {
    console.error('Error updating: ' + error);
    // electron.dialog.showErrorBox('error', error.toString());
  });
  autoUpdater.checkForUpdates();
}

function isVisible() {
  return mainWindow && mainWindow.isVisible();
}

function toggleVisibility(makeVisible) {
  if (makeVisible === undefined) {
    makeVisible = !isVisible();
  }

  if (!makeVisible && mainWindow) {
    mainWindow.hide();
  }
  if (makeVisible) {
    if (!mainWindow) {
      // TODO: do we need to handle this?
    }

    mainWindow.show();
  }
}
<|endoftext|>


<commit_before>EsriLeaflet.Layers.FeatureLayer = EsriLeaflet.Layers.FeatureManager.extend({

  statics: {
    EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
  },

  options: {
    cacheLayers: true
  },

  /**
   * Constructor
   */

  initialize: function (url, options) {
    EsriLeaflet.Layers.FeatureManager.prototype.initialize.call(this, url, options);

    options = L.setOptions(this, options);

    this._layers = {};
    this._leafletIds = {};
    this._key = 'c'+(Math.random() * 1e9).toString(36).replace('.', '_');
  },

  /**
   * Layer Interface
   */

  onAdd: function(map){
    map.on('zoomstart zoomend', function(e){
      this._zooming = (e.type === 'zoomstart');
    }, this);
    return EsriLeaflet.Layers.FeatureManager.prototype.onAdd.call(this, map);
  },

  onRemove: function(map){
    for (var i in this._layers) {
      map.removeLayer(this._layers[i]);
    }

    return EsriLeaflet.Layers.FeatureManager.prototype.onRemove.call(this, map);
  },

  createNewLayer: function(geojson){
    // @TODO Leaflet 0.8
    //newLayer = L.GeoJSON.geometryToLayer(geojson, this.options);
    return L.GeoJSON.geometryToLayer(geojson, this.options.pointToLayer, L.GeoJSON.coordsToLatLng, this.options);
  },

  /**
   * Feature Managment Methods
   */

  createLayers: function(features){
    for (var i = features.length - 1; i >= 0; i--) {

      var geojson = features[i];

      var layer = this._layers[geojson.id];
      var newLayer;

      if(layer && !this._map.hasLayer(layer)){
        this._map.addLayer(layer);
      }

      if (layer && layer.setLatLngs) {
        // @TODO Leaflet 0.8
        //newLayer = L.GeoJSON.geometryToLayer(geojson, this.options);

        var updateGeo = this.createNewLayer(geojson);
        layer.setLatLngs(updateGeo.getLatLngs());
      }

      if(!layer){
        // @TODO Leaflet 0.8
        //newLayer = L.GeoJSON.geometryToLayer(geojson, this.options);

        newLayer =  this.createNewLayer(geojson);
        newLayer.feature = geojson;
        newLayer.defaultOptions = newLayer.options;
        newLayer._leaflet_id = this._key + '_' + geojson.id;

        this._leafletIds[newLayer._leaflet_id] = geojson.id;

        // bubble events from layers to this
        // @TODO Leaflet 0.8
        // newLayer.addEventParent(this);

        newLayer.on(EsriLeaflet.Layers.FeatureLayer.EVENTS, this._propagateEvent, this);

        // bind a popup if we have one
        if(this._popup && newLayer.bindPopup){
          newLayer.bindPopup(this._popup(newLayer.feature, newLayer), this._popupOptions);
        }

        if(this.options.onEachFeature){
          this.options.onEachFeature(newLayer.feature, newLayer);
        }

        // cache the layer
        this._layers[newLayer.feature.id] = newLayer;

        // style the layer
        this.resetStyle(newLayer.feature.id);

        this.fire('createfeature', {
          feature: newLayer.feature
        });

        // add the layer if it is within the time bounds or our layer is not time enabled
        if(!this.options.timeField || (this.options.timeField && this._featureWithinTimeRange(geojson)) ){
          this._map.addLayer(newLayer);
        }
      }
    }
  },

  addLayers: function(ids){
    for (var i = ids.length - 1; i >= 0; i--) {
      var layer = this._layers[ids[i]];
      if(layer){
        this.fire('addfeature', {
          feature: layer.feature
        });
        this._map.addLayer(layer);
      }
    }
  },

  removeLayers: function(ids, permanent){
    for (var i = ids.length - 1; i >= 0; i--) {
      var id = ids[i];
      var layer = this._layers[id];
      if(layer){
        this.fire('removefeature', {
          feature: layer.feature,
          permanent: permanent
        });
        this._map.removeLayer(layer);
      }
      if(layer && permanent){
        delete this._layers[id];
      }
    }
  },

  cellEnter: function(bounds, coords){
    if(!this._zooming){
      EsriLeaflet.Util.requestAnimationFrame(L.Util.bind(function(){
        var cacheKey = this._cacheKey(coords);
        var cellKey = this._cellCoordsToKey(coords);
        var layers = this._cache[cacheKey];
        if(this._activeCells[cellKey] && layers){
          this.addLayers(layers);
        }
      }, this));
    }
  },

  cellLeave: function(bounds, coords){
    if(!this._zooming){
      EsriLeaflet.Util.requestAnimationFrame(L.Util.bind(function(){
        var cacheKey = this._cacheKey(coords);
        var cellKey = this._cellCoordsToKey(coords);
        var layers = this._cache[cacheKey];
        var mapBounds = this._map.getBounds();
        if(!this._activeCells[cellKey] && layers){
          var removable = true;

          for (var i = 0; i < layers.length; i++) {
            var layer = this._layers[layers[i]];
            if(layer && layer.getBounds && mapBounds.intersects(layer.getBounds())){
              removable = false;
            }
          }

          if(removable){
            this.removeLayers(layers, !this.options.cacheLayers);
          }

          if(!this.options.cacheLayers && removable){
            delete this._cache[cacheKey];
            delete this._cells[cellKey];
            delete this._activeCells[cellKey];
          }
        }
      }, this));
    }
  },

  /**
   * Styling Methods
   */

  resetStyle: function (id) {
    var layer = this._layers[id];

    if(layer){
      this.setFeatureStyle(layer.feature.id, layer.defaultOptions.style);
    }

    return this;
  },

  setStyle: function (style) {
    this.options.style = style;
    this.eachFeature(function (layer) {
      this.setFeatureStyle(layer.feature.id, style);
    }, this);
    return this;
  },

  setFeatureStyle: function (id, style) {
    var layer = this._layers[id];

    if (typeof style === 'function') {
      style = style(layer.feature);
    }

    //trap inability to access default style options from MultiLine/MultiPolygon please revisit at Leaflet 1.0
    else if (!style && !layer.defaultOptions) {
      var dummyPath = new L.Path();
      style = L.Path.prototype.options;
      style.fill = true; //not set by default
    }

    if (layer.setStyle) {
      layer.setStyle(style);
    }
  },

  /**
   * Popup Methods
   */

  bindPopup: function (fn, options) {
    this._popup = fn;
    this._popupOptions = options;
    for (var i in this._layers) {
      var layer = this._layers[i];
      var popupContent = this._popup(layer.feature, layer);
      layer.bindPopup(popupContent, options);
    }
    return this;
  },

  unbindPopup: function () {
    this._popup =  false;
    for (var i in this._layers) {
      var layer = this._layers[i];
      if (layer.unbindPopup) {
        layer.unbindPopup();
      } else if (layer.getLayers) {
        var groupLayers = layer.getLayers();
        for (var j in groupLayers) {
          var gLayer = groupLayers[j];
          gLayer.unbindPopup();
        }
      }
    }
    return this;
  },

  /**
   * Utility Methods
   */

  eachFeature: function (fn, context) {
    for (var i in this._layers) {
      fn.call(context, this._layers[i]);
    }
    return this;
  },

  getFeature: function (id) {
    return this._layers[id];
  },

  // from https://github.com/Leaflet/Leaflet/blob/v0.7.2/src/layer/FeatureGroup.js
  // @TODO remove at Leaflet 0.8
  _propagateEvent: function (e) {
    e.layer = this._layers[this._leafletIds[e.target._leaflet_id]];
    e.target = this;
    this.fire(e.type, e);
  }
});

EsriLeaflet.FeatureLayer = EsriLeaflet.Layers.FeatureLayer;

EsriLeaflet.Layers.featureLayer = function(url, options){
  return new EsriLeaflet.Layers.FeatureLayer(url, options);
};

EsriLeaflet.featureLayer = function(url, options){
  return new EsriLeaflet.Layers.FeatureLayer(url, options);
};<commit_msg>fix #320, update using full GeoJSON coords rather then rely on getLatLngs()<commit_after>EsriLeaflet.Layers.FeatureLayer = EsriLeaflet.Layers.FeatureManager.extend({

  statics: {
    EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
  },

  options: {
    cacheLayers: true
  },

  /**
   * Constructor
   */

  initialize: function (url, options) {
    EsriLeaflet.Layers.FeatureManager.prototype.initialize.call(this, url, options);

    options = L.setOptions(this, options);

    this._layers = {};
    this._leafletIds = {};
    this._key = 'c'+(Math.random() * 1e9).toString(36).replace('.', '_');
  },

  /**
   * Layer Interface
   */

  onAdd: function(map){
    map.on('zoomstart zoomend', function(e){
      this._zooming = (e.type === 'zoomstart');
    }, this);
    return EsriLeaflet.Layers.FeatureManager.prototype.onAdd.call(this, map);
  },

  onRemove: function(map){
    for (var i in this._layers) {
      map.removeLayer(this._layers[i]);
    }

    return EsriLeaflet.Layers.FeatureManager.prototype.onRemove.call(this, map);
  },

  createNewLayer: function(geojson){
    // @TODO Leaflet 0.8
    //newLayer = L.GeoJSON.geometryToLayer(geojson, this.options);
    return L.GeoJSON.geometryToLayer(geojson, this.options.pointToLayer, L.GeoJSON.coordsToLatLng, this.options);
  },

  _updateLayerGeometry: function(layer, geojson){
    // convert the geojson coordinates into a Leaflet LatLng array/nested arrays
    // pass it to setLatLngs to update layer geometries
    var latlngs = [];
    var coordsToLatLng = this.options.coordsToLatLng || L.GeoJSON.coordsToLatLng;

    switch(geojson.geometry.type){
      case "LineString":
        latlngs = L.GeoJSON.coordsToLatLngs(geojson.geometry.coordinates, 0, coordsToLatLng);
        layer.setLatLngs(latlngs);
        break;
      case "MultiLineString":
        latlngs = L.GeoJSON.coordsToLatLngs(geojson.geometry.coordinates, 1, coordsToLatLng);
        layer.setLatLngs(latlngs);
        break;
      case "Polygon":
        latlngs = L.GeoJSON.coordsToLatLngs(geojson.geometry.coordinates, 1, coordsToLatLng);
        layer.setLatLngs(latlngs);
        break;
      case "MultiPolygon":
        latlngs = L.GeoJSON.coordsToLatLngs(geojson.geometry.coordinates, 2, coordsToLatLng);
        layer.setLatLngs(latlngs);
        break;
    }
  },

  /**
   * Feature Managment Methods
   */

  createLayers: function(features){
    for (var i = features.length - 1; i >= 0; i--) {

      var geojson = features[i];

      var layer = this._layers[geojson.id];
      var newLayer;

      if(layer && !this._map.hasLayer(layer)){
        this._map.addLayer(layer);
        return;
      }

      if (layer && layer.setLatLngs) {
        this._updateLayerGeometry(layer, geojson);
        return;
      }

      if(!layer){
        // @TODO Leaflet 0.8
        //newLayer = L.GeoJSON.geometryToLayer(geojson, this.options);

        newLayer =  this.createNewLayer(geojson);
        newLayer.feature = geojson;
        newLayer.defaultOptions = newLayer.options;
        newLayer._leaflet_id = this._key + '_' + geojson.id;

        this._leafletIds[newLayer._leaflet_id] = geojson.id;

        // bubble events from layers to this
        // @TODO Leaflet 0.8
        // newLayer.addEventParent(this);

        newLayer.on(EsriLeaflet.Layers.FeatureLayer.EVENTS, this._propagateEvent, this);

        // bind a popup if we have one
        if(this._popup && newLayer.bindPopup){
          newLayer.bindPopup(this._popup(newLayer.feature, newLayer), this._popupOptions);
        }

        if(this.options.onEachFeature){
          this.options.onEachFeature(newLayer.feature, newLayer);
        }

        // cache the layer
        this._layers[newLayer.feature.id] = newLayer;

        // style the layer
        this.resetStyle(newLayer.feature.id);

        this.fire('createfeature', {
          feature: newLayer.feature
        });

        // add the layer if it is within the time bounds or our layer is not time enabled
        if(!this.options.timeField || (this.options.timeField && this._featureWithinTimeRange(geojson)) ){
          this._map.addLayer(newLayer);
        }
      }
    }
  },

  addLayers: function(ids){
    for (var i = ids.length - 1; i >= 0; i--) {
      var layer = this._layers[ids[i]];
      if(layer){
        this.fire('addfeature', {
          feature: layer.feature
        });
        this._map.addLayer(layer);
      }
    }
  },

  removeLayers: function(ids, permanent){
    for (var i = ids.length - 1; i >= 0; i--) {
      var id = ids[i];
      var layer = this._layers[id];
      if(layer){
        this.fire('removefeature', {
          feature: layer.feature,
          permanent: permanent
        });
        this._map.removeLayer(layer);
      }
      if(layer && permanent){
        delete this._layers[id];
      }
    }
  },

  cellEnter: function(bounds, coords){
    if(!this._zooming){
      EsriLeaflet.Util.requestAnimationFrame(L.Util.bind(function(){
        var cacheKey = this._cacheKey(coords);
        var cellKey = this._cellCoordsToKey(coords);
        var layers = this._cache[cacheKey];
        if(this._activeCells[cellKey] && layers){
          this.addLayers(layers);
        }
      }, this));
    }
  },

  cellLeave: function(bounds, coords){
    if(!this._zooming){
      EsriLeaflet.Util.requestAnimationFrame(L.Util.bind(function(){
        var cacheKey = this._cacheKey(coords);
        var cellKey = this._cellCoordsToKey(coords);
        var layers = this._cache[cacheKey];
        var mapBounds = this._map.getBounds();
        if(!this._activeCells[cellKey] && layers){
          var removable = true;

          for (var i = 0; i < layers.length; i++) {
            var layer = this._layers[layers[i]];
            if(layer && layer.getBounds && mapBounds.intersects(layer.getBounds())){
              removable = false;
            }
          }

          if(removable){
            this.removeLayers(layers, !this.options.cacheLayers);
          }

          if(!this.options.cacheLayers && removable){
            delete this._cache[cacheKey];
            delete this._cells[cellKey];
            delete this._activeCells[cellKey];
          }
        }
      }, this));
    }
  },

  /**
   * Styling Methods
   */

  resetStyle: function (id) {
    var layer = this._layers[id];

    if(layer){
      this.setFeatureStyle(layer.feature.id, layer.defaultOptions.style);
    }

    return this;
  },

  setStyle: function (style) {
    this.options.style = style;
    this.eachFeature(function (layer) {
      this.setFeatureStyle(layer.feature.id, style);
    }, this);
    return this;
  },

  setFeatureStyle: function (id, style) {
    var layer = this._layers[id];

    if (typeof style === 'function') {
      style = style(layer.feature);
    }

    //trap inability to access default style options from MultiLine/MultiPolygon please revisit at Leaflet 1.0
    else if (!style && !layer.defaultOptions) {
      var dummyPath = new L.Path();
      style = L.Path.prototype.options;
      style.fill = true; //not set by default
    }

    if (layer.setStyle) {
      layer.setStyle(style);
    }
  },

  /**
   * Popup Methods
   */

  bindPopup: function (fn, options) {
    this._popup = fn;
    this._popupOptions = options;
    for (var i in this._layers) {
      var layer = this._layers[i];
      var popupContent = this._popup(layer.feature, layer);
      layer.bindPopup(popupContent, options);
    }
    return this;
  },

  unbindPopup: function () {
    this._popup =  false;
    for (var i in this._layers) {
      var layer = this._layers[i];
      if (layer.unbindPopup) {
        layer.unbindPopup();
      } else if (layer.getLayers) {
        var groupLayers = layer.getLayers();
        for (var j in groupLayers) {
          var gLayer = groupLayers[j];
          gLayer.unbindPopup();
        }
      }
    }
    return this;
  },

  /**
   * Utility Methods
   */

  eachFeature: function (fn, context) {
    for (var i in this._layers) {
      fn.call(context, this._layers[i]);
    }
    return this;
  },

  getFeature: function (id) {
    return this._layers[id];
  },

  // from https://github.com/Leaflet/Leaflet/blob/v0.7.2/src/layer/FeatureGroup.js
  // @TODO remove at Leaflet 0.8
  _propagateEvent: function (e) {
    e.layer = this._layers[this._leafletIds[e.target._leaflet_id]];
    e.target = this;
    this.fire(e.type, e);
  }
});

EsriLeaflet.FeatureLayer = EsriLeaflet.Layers.FeatureLayer;

EsriLeaflet.Layers.featureLayer = function(url, options){
  return new EsriLeaflet.Layers.FeatureLayer(url, options);
};

EsriLeaflet.featureLayer = function(url, options){
  return new EsriLeaflet.Layers.FeatureLayer(url, options);
};<|endoftext|>