<commit_before>// @flow

import {signCodeStream} from '../lib/codeSign';

import {copyFile, mkdirp} from '../lib/fileUtils';

import {findFlowRoot} from '../lib/flowProjectUtils';

import {
  toSemverString as flowVersionToSemver,
  parseFlowSpecificVer,
} from '../lib/flowVersion';
import type {FlowVersion} from '../lib/flowVersion';

import {fs, path} from '../lib/node';

import {
  findNpmLibDef,
  getInstalledNpmLibDefs,
  getNpmLibDefVersionHash,
} from '../lib/npm/npmLibDefs';
import type {NpmLibDef} from '../lib/npm/npmLibDefs';

import {
  findFlowSpecificVer,
  getPackageJsonData,
  getPackageJsonDependencies,
} from '../lib/npm/npmProjectUtils';

import {getCacheRepoDir} from '../lib/cacheRepoUtils';

import {getRangeLowerBound} from '../lib/semver';

import colors from 'colors/safe';

import semver from 'semver';

import {createStub, pkgHasFlowFiles} from '../lib/stubUtils';

import typeof Yargs from 'yargs';

export const name = 'install';
export const description = 'Installs libdefs into the ./flow-typed directory';
export type Args = {
  _: Array<string>,
  flowVersion?: string,
  overwrite: boolean,
  skip: boolean,
  verbose: boolean,
  libdefDir?: string,
  packageDir?: string,
  ignoreDeps?: Array<string>,
};
export function setup(yargs: Yargs) {
  return yargs.usage(`$0 ${name} - ${description}`).options({
    flowVersion: {
      alias: 'f',
      describe:
        'The Flow version that fetched libdefs must be compatible ' + 'with',
      type: 'string',
    },
    verbose: {
      describe: 'Print additional, verbose info while installing libdefs',
      type: 'boolean',
      demand: false,
    },
    skip: {
      alias: 's',
      describe: 'Do not generate stubs for missing libdefs',
      type: 'boolean',
      demand: false,
    },
    libdefDir: {
      alias: 'l',
      describe: 'Use a custom directory to install libdefs',
      type: 'string',
      demand: false,
    },
    packageDir: {
      alias: 'p',
      describe: 'The relative path of package.json where flow-bin is installed',
      type: 'string',
    },
    ignoreDeps: {
      alias: 'i',
      describe: 'Dependency categories to ignore when installing definitions',
      type: 'array',
    },
  });
}
export async function run(args: Args) {
  const cwd = process.cwd();
  const packageDir = args.packageDir ? path.resolve(args.packageDir) : cwd;
  const flowVersion = await determineFlowVersion(packageDir, args.flowVersion);
  const libdefDir = args.libdefDir || 'flow-typed';
  const explicitLibDefs = args._.slice(1);
  const ignoreDeps = args.ignoreDeps || [];

  const coreLibDefResult = await installCoreLibDefs();
  if (coreLibDefResult !== 0) {
    return coreLibDefResult;
  }

  const npmLibDefResult = await installNpmLibDefs({
    cwd,
    flowVersion,
    explicitLibDefs,
    libdefDir: libdefDir,
    verbose: args.verbose,
    overwrite: args.overwrite,
    skip: args.skip,
    ignoreDeps: ignoreDeps,
  });
  if (npmLibDefResult !== 0) {
    return npmLibDefResult;
  }
  return 0;
}

async function determineFlowVersion(cwd: string, flowVersionArg?: string) {
  if (flowVersionArg != null) {
    // Be permissive if the prefix 'v' is left off
    let flowVersionStr =
      flowVersionArg[0] === 'v' ? flowVersionArg : `v${flowVersionArg}`;

    if (/^v[0-9]+\.[0-9]+$/.test(flowVersionStr)) {
      flowVersionStr = `${flowVersionStr}.0`;
    }

    return {
      kind: 'specific',
      ver: parseFlowSpecificVer(flowVersionStr, flowVersionArg),
    };
  } else {
    return {
      kind: 'specific',
      ver: await findFlowSpecificVer(cwd),
    };
  }
}

async function installCoreLibDefs(): Promise<number> {
  // TODO...
  return 0;
}

const FLOW_BUILT_IN_NPM_LIBS = ['react', 'react-dom'];
type installNpmLibDefsArgs = {|
  cwd: string,
  flowVersion: FlowVersion,
  explicitLibDefs: Array<string>,
  libdefDir: string,
  verbose: boolean,
  overwrite: boolean,
  skip: boolean,
  ignoreDeps: Array<string>,
|};
async function installNpmLibDefs({
  cwd,
  flowVersion,
  explicitLibDefs,
  libdefDir,
  verbose,
  overwrite,
  skip,
  ignoreDeps,
}: installNpmLibDefsArgs): Promise<number> {
  const flowProjectRoot = await findFlowRoot(cwd);
  if (flowProjectRoot === null) {
    console.error(
      'Error: Unable to find a flow project in the current dir or any of ' +
        "it's parent dirs!\n" +
        'Please run this command from within a Flow project.',
    );
    return 1;
  }

  const libdefsToSearchFor: Map<string, string> = new Map();

  // If a specific pkg/version was specified, only add those packages.
  // Otherwise, extract dependencies from the package.json
  if (explicitLibDefs.length > 0) {
    for (var i = 0; i < explicitLibDefs.length; i++) {
      const term = explicitLibDefs[i];
      const termMatches = term.match(/(@[^@\/]+\/)?([^@]+)@(.+)/);
      if (termMatches == null) {
        console.error(
          'ERROR: Please specify npm package names in the format of `foo@1.2.3`',
        );
        return 1;
      }

      const [_, npmScope, pkgName, pkgVerStr] = termMatches;
      const scopedPkgName = npmScope != null ? npmScope + pkgName : pkgName;
      libdefsToSearchFor.set(scopedPkgName, pkgVerStr);
    }
    console.log(`• Searching for ${libdefsToSearchFor.size} libdefs...`);
  } else {
    const pkgJsonData = await getPackageJsonData(cwd);
    const pkgJsonDeps = getPackageJsonDependencies(pkgJsonData, ignoreDeps);
    for (const pkgName in pkgJsonDeps) {
      libdefsToSearchFor.set(pkgName, pkgJsonDeps[pkgName]);
    }

    if (libdefsToSearchFor.size === 0) {
      console.error(
        "No dependencies were found in this project's package.json!",
      );
      return 0;
    }

    if (verbose) {
      libdefsToSearchFor.forEach((ver, name) => {
        console.log(`• Found package.json dependency: ${name}@${ver}`);
      });
    } else {
      console.log(
        `• Found ${libdefsToSearchFor.size} dependencies in package.json to ` +
          `install libdefs for. Searching...`,
      );
    }
  }
  const libDefsToSearchForEntries = [...libdefsToSearchFor.entries()];

  // Search for the requested libdefs
  const libDefsToInstall: Map<string, NpmLibDef> = new Map();
  const outdatedLibDefsToInstall: [
    NpmLibDef,
    {name: string, ver: string},
  ][] = [];
  const unavailableLibDefs = [];
  await Promise.all(
    libDefsToSearchForEntries.map(async ([name, ver]) => {
      if (FLOW_BUILT_IN_NPM_LIBS.indexOf(name) !== -1) {
        return;
      }

      const libDef = await findNpmLibDef(name, ver, flowVersion);
      if (libDef === null) {
        unavailableLibDefs.push({name, ver});
      } else {
        libDefsToInstall.set(name, libDef);

        // If the libdef is outdated (but still compatible), note this so we can
        // warn the user
        const libDefLower = getRangeLowerBound(libDef.version);
        const depLower = getRangeLowerBound(ver);
        if (semver.lt(libDefLower, depLower)) {
          outdatedLibDefsToInstall.push([libDef, {name, ver}]);
        }
      }
    }),
  );

  // Scan libdefs that are already installed
  const libDefsToUninstall = new Map();
  const alreadyInstalledLibDefs = await getInstalledNpmLibDefs(
    path.join(flowProjectRoot),
    libdefDir,
  );
  [...alreadyInstalledLibDefs.entries()].forEach(([filePath, npmLibDef]) => {
    const fullFilePath = path.join(flowProjectRoot, filePath);
    switch (npmLibDef.kind) {
      case 'LibDef':
        // If a libdef is already installed for some dependency, we need to
        // uninstall it before installing the new (potentially updated) ver
        const libDef = npmLibDef.libDef;
        //const toInstall = libDefsToInstall.has(libDef.name);
        //console.log(`Found ${libDef.name} already installed. Uninstall? ${toInstall != null ? 'yes' : 'no'}`);
        if (libDefsToInstall.has(libDef.name)) {
          libDefsToUninstall.set(libDef.name, fullFilePath);
        }
        break;

      case 'Stub':
        break;

      default:
        (npmLibDef: empty);
    }
  });

  if (libDefsToInstall.size > 0) {
    console.log(`• Installing ${libDefsToInstall.size} libDefs...`);
    const flowTypedDirPath = path.join(flowProjectRoot, libdefDir, 'npm');
    await mkdirp(flowTypedDirPath);
    const results = await Promise.all(
      [...libDefsToInstall.values()].map(async (def: NpmLibDef) => {
        const toUninstall = libDefsToUninstall.get(def.name);
        if (toUninstall != null) {
          await fs.unlink(toUninstall);
        }
        return installNpmLibDef(def, flowTypedDirPath, overwrite);
      }),
    );

    if (results.some(res => !res)) {
      return 1;
    }
  }

  if (
    (verbose || unavailableLibDefs.length === 0) &&
    outdatedLibDefsToInstall.length > 0
  ) {
    console.log(
      '• The following installed libdefs are compatible with your ' +
        'dependencies, but may not include all minor and patch changes for ' +
        'your specific dependency version:\n',
    );
    outdatedLibDefsToInstall.forEach(
      ([libDef, {name: pkgName, ver: pkgVersion}]) => {
        console.log(
          '  • libdef: %s (satisfies %s)',
          colors.yellow(`${libDef.name}_${libDef.version}`),
          colors.bold(`${pkgName}@${pkgVersion}`),
        );

        const libDefPlural =
          outdatedLibDefsToInstall.length > 1
            ? ['versioned updates', 'these packages']
            : ['a versioned update', 'this package'];
        console.log(
          `\n` +
            `  Consider submitting ${libDefPlural[0]} for ${libDefPlural[1]} to \n` +
            `  https://github.com/flowtype/flow-typed/\n`,
        );
      },
    );
  }

  if (
    unavailableLibDefs.length > 0 &&
    unavailableLibDefs.length === explicitLibDefs.length
  ) {
    // If the user specified an explicit library to be installed, don't generate
    // a stub if no libdef exists -- just inform them that one doesn't exist
    console.log(
      colors.red(
        `!! No libdefs found in flow-typed for the explicitly requested libdefs. !!`,
      ) +
        '\n' +
        '\n' +
        'Consider using `%s` to generate an empty libdef that you can fill in.',
      colors.bold(`flow-typed create-stub ${explicitLibDefs.join(' ')}`),
    );

    return 1;
  } else {
    // If a package that's missing a flow-typed libdef has any .flow files,
    // we'll skip generating a stub for it.
    const untypedMissingLibDefs = [];
    const typedMissingLibDefs = [];
    await Promise.all(
      unavailableLibDefs.map(async ({name: pkgName, ver: pkgVer}) => {
        const hasFlowFiles = await pkgHasFlowFiles(cwd, pkgName);
        if (hasFlowFiles) {
          typedMissingLibDefs.push([pkgName, pkgVer]);
        } else {
          untypedMissingLibDefs.push([pkgName, pkgVer]);
        }
      }),
    );

    if (untypedMissingLibDefs.length > 0 && !skip) {
      console.log('• Generating stubs for untyped dependencies...');
      await Promise.all(
        untypedMissingLibDefs.map(async ([pkgName, pkgVerStr]) => {
          await createStub(
            flowProjectRoot,
            pkgName,
            pkgVerStr,
            overwrite,
            libdefDir,
          );
        }),
      );

      console.log(
        colors.red(
          `\n!! No flow@${flowVersionToSemver(
            flowVersion,
          )}-compatible libdefs ` +
            `found in flow-typed for the above untyped dependencies !!`,
        ),
      );

      const plural =
        unavailableLibDefs.length > 1
          ? ['libdefs', 'these packages', 'them']
          : ['a libdef', 'this package', 'it'];
      console.log(
        `\n` +
          `I've generated ${'`'}any${'`'}-typed stubs for ${plural[1]}, but ` +
          `consider submitting \n` +
          `${plural[0]} for ${plural[2]} to ` +
          `${colors.bold('https://github.com/flowtype/flow-typed/')}\n`,
      );
    }
  }

  return 0;
}

async function installNpmLibDef(
  npmLibDef: NpmLibDef,
  npmDir: string,
  overwrite: boolean,
): Promise<boolean> {
  const scopedDir =
    npmLibDef.scope === null ? npmDir : path.join(npmDir, npmLibDef.scope);
  mkdirp(scopedDir);

  const fileName = `${npmLibDef.name}_${npmLibDef.version}.js`;
  const filePath = path.join(scopedDir, fileName);

  // Find the libDef in the cached repo
  try {
    const terseFilePath = path.relative(
      path.resolve(npmDir, '..', '..'),
      filePath,
    );

    if (!overwrite && (await fs.exists(filePath))) {
      console.error(
        '  • %s\n' + '    └> %s',
        colors.bold(
          colors.red(
            `${terseFilePath} already exists and appears to have been manually ` +
              `written or changed!`,
          ),
        ),
        'Use --overwrite to overwrite the existing libdef.',
      );
      return true;
    }

    const repoVersion = await getNpmLibDefVersionHash(
      getCacheRepoDir(),
      npmLibDef,
    );
    const codeSignPreprocessor = signCodeStream(repoVersion);
    await copyFile(npmLibDef.path, filePath, codeSignPreprocessor);

    console.log(
      colors.bold('  • %s\n' + '    └> %s'),
      fileName,
      colors.green(`.${path.sep}${terseFilePath}`),
    );

    // Remove any lingering stubs
    console.log(npmLibDef.name);
    console.log(scopedDir);
    const stubName = `${npmLibDef.name}_vx.x.x.js`;
    const stubPath = path.join(scopedDir, stubName);

    if (overwrite && (await fs.exists(stubPath))) {
      await fs.unlink(stubPath);
    }

    return true;
  } catch (e) {
    console.error(`  !! Failed to install ${npmLibDef.name} at ${filePath}`);
    console.error(`  ERROR: ${e.message}`);
    return false;
  }
}

export {
  determineFlowVersion as _determineFlowVersion,
  installNpmLibDefs as _installNpmLibDefs,
  installNpmLibDef as _installNpmLibDef,
};
<commit_msg>make changed libdef message welcoming to contribute changes back (#1613)<commit_after>// @flow

import { signCodeStream } from "../lib/codeSign";

import { copyFile, mkdirp } from "../lib/fileUtils";

import { findFlowRoot } from "../lib/flowProjectUtils";

import {
  toSemverString as flowVersionToSemver,
  parseFlowSpecificVer
} from "../lib/flowVersion";
import type { FlowVersion } from "../lib/flowVersion";

import { fs, path } from "../lib/node";

import {
  findNpmLibDef,
  getInstalledNpmLibDefs,
  getNpmLibDefVersionHash
} from "../lib/npm/npmLibDefs";
import type { NpmLibDef } from "../lib/npm/npmLibDefs";

import {
  findFlowSpecificVer,
  getPackageJsonData,
  getPackageJsonDependencies
} from "../lib/npm/npmProjectUtils";

import { getCacheRepoDir } from "../lib/cacheRepoUtils";

import { getRangeLowerBound } from "../lib/semver";

import colors from "colors/safe";

import semver from "semver";

import { createStub, pkgHasFlowFiles } from "../lib/stubUtils";

import typeof Yargs from "yargs";

export const name = "install";
export const description = "Installs libdefs into the ./flow-typed directory";
export type Args = {
  _: Array<string>,
  flowVersion?: string,
  overwrite: boolean,
  skip: boolean,
  verbose: boolean,
  libdefDir?: string,
  packageDir?: string,
  ignoreDeps?: Array<string>
};
export function setup(yargs: Yargs) {
  return yargs.usage(`$0 ${name} - ${description}`).options({
    flowVersion: {
      alias: "f",
      describe:
        "The Flow version that fetched libdefs must be compatible " + "with",
      type: "string"
    },
    verbose: {
      describe: "Print additional, verbose info while installing libdefs",
      type: "boolean",
      demand: false
    },
    skip: {
      alias: "s",
      describe: "Do not generate stubs for missing libdefs",
      type: "boolean",
      demand: false
    },
    libdefDir: {
      alias: "l",
      describe: "Use a custom directory to install libdefs",
      type: "string",
      demand: false
    },
    packageDir: {
      alias: "p",
      describe: "The relative path of package.json where flow-bin is installed",
      type: "string"
    },
    ignoreDeps: {
      alias: "i",
      describe: "Dependency categories to ignore when installing definitions",
      type: "array"
    }
  });
}
export async function run(args: Args) {
  const cwd = process.cwd();
  const packageDir = args.packageDir ? path.resolve(args.packageDir) : cwd;
  const flowVersion = await determineFlowVersion(packageDir, args.flowVersion);
  const libdefDir = args.libdefDir || "flow-typed";
  const explicitLibDefs = args._.slice(1);
  const ignoreDeps = args.ignoreDeps || [];

  const coreLibDefResult = await installCoreLibDefs();
  if (coreLibDefResult !== 0) {
    return coreLibDefResult;
  }

  const npmLibDefResult = await installNpmLibDefs({
    cwd,
    flowVersion,
    explicitLibDefs,
    libdefDir: libdefDir,
    verbose: args.verbose,
    overwrite: args.overwrite,
    skip: args.skip,
    ignoreDeps: ignoreDeps
  });
  if (npmLibDefResult !== 0) {
    return npmLibDefResult;
  }
  return 0;
}

async function determineFlowVersion(cwd: string, flowVersionArg?: string) {
  if (flowVersionArg != null) {
    // Be permissive if the prefix 'v' is left off
    let flowVersionStr =
      flowVersionArg[0] === "v" ? flowVersionArg : `v${flowVersionArg}`;

    if (/^v[0-9]+\.[0-9]+$/.test(flowVersionStr)) {
      flowVersionStr = `${flowVersionStr}.0`;
    }

    return {
      kind: "specific",
      ver: parseFlowSpecificVer(flowVersionStr, flowVersionArg)
    };
  } else {
    return {
      kind: "specific",
      ver: await findFlowSpecificVer(cwd)
    };
  }
}

async function installCoreLibDefs(): Promise<number> {
  // TODO...
  return 0;
}

const FLOW_BUILT_IN_NPM_LIBS = ["react", "react-dom"];
type installNpmLibDefsArgs = {|
  cwd: string,
  flowVersion: FlowVersion,
  explicitLibDefs: Array<string>,
  libdefDir: string,
  verbose: boolean,
  overwrite: boolean,
  skip: boolean,
  ignoreDeps: Array<string>
|};
async function installNpmLibDefs({
  cwd,
  flowVersion,
  explicitLibDefs,
  libdefDir,
  verbose,
  overwrite,
  skip,
  ignoreDeps
}: installNpmLibDefsArgs): Promise<number> {
  const flowProjectRoot = await findFlowRoot(cwd);
  if (flowProjectRoot === null) {
    console.error(
      "Error: Unable to find a flow project in the current dir or any of " +
        "it's parent dirs!\n" +
        "Please run this command from within a Flow project."
    );
    return 1;
  }

  const libdefsToSearchFor: Map<string, string> = new Map();

  // If a specific pkg/version was specified, only add those packages.
  // Otherwise, extract dependencies from the package.json
  if (explicitLibDefs.length > 0) {
    for (var i = 0; i < explicitLibDefs.length; i++) {
      const term = explicitLibDefs[i];
      const termMatches = term.match(/(@[^@\/]+\/)?([^@]+)@(.+)/);
      if (termMatches == null) {
        console.error(
          "ERROR: Please specify npm package names in the format of `foo@1.2.3`"
        );
        return 1;
      }

      const [_, npmScope, pkgName, pkgVerStr] = termMatches;
      const scopedPkgName = npmScope != null ? npmScope + pkgName : pkgName;
      libdefsToSearchFor.set(scopedPkgName, pkgVerStr);
    }
    console.log(`• Searching for ${libdefsToSearchFor.size} libdefs...`);
  } else {
    const pkgJsonData = await getPackageJsonData(cwd);
    const pkgJsonDeps = getPackageJsonDependencies(pkgJsonData, ignoreDeps);
    for (const pkgName in pkgJsonDeps) {
      libdefsToSearchFor.set(pkgName, pkgJsonDeps[pkgName]);
    }

    if (libdefsToSearchFor.size === 0) {
      console.error(
        "No dependencies were found in this project's package.json!"
      );
      return 0;
    }

    if (verbose) {
      libdefsToSearchFor.forEach((ver, name) => {
        console.log(`• Found package.json dependency: ${name}@${ver}`);
      });
    } else {
      console.log(
        `• Found ${libdefsToSearchFor.size} dependencies in package.json to ` +
          `install libdefs for. Searching...`
      );
    }
  }
  const libDefsToSearchForEntries = [...libdefsToSearchFor.entries()];

  // Search for the requested libdefs
  const libDefsToInstall: Map<string, NpmLibDef> = new Map();
  const outdatedLibDefsToInstall: [
    NpmLibDef,
    { name: string, ver: string }
  ][] = [];
  const unavailableLibDefs = [];
  await Promise.all(
    libDefsToSearchForEntries.map(async ([name, ver]) => {
      if (FLOW_BUILT_IN_NPM_LIBS.indexOf(name) !== -1) {
        return;
      }

      const libDef = await findNpmLibDef(name, ver, flowVersion);
      if (libDef === null) {
        unavailableLibDefs.push({ name, ver });
      } else {
        libDefsToInstall.set(name, libDef);

        // If the libdef is outdated (but still compatible), note this so we can
        // warn the user
        const libDefLower = getRangeLowerBound(libDef.version);
        const depLower = getRangeLowerBound(ver);
        if (semver.lt(libDefLower, depLower)) {
          outdatedLibDefsToInstall.push([libDef, { name, ver }]);
        }
      }
    })
  );

  // Scan libdefs that are already installed
  const libDefsToUninstall = new Map();
  const alreadyInstalledLibDefs = await getInstalledNpmLibDefs(
    path.join(flowProjectRoot),
    libdefDir
  );
  [...alreadyInstalledLibDefs.entries()].forEach(([filePath, npmLibDef]) => {
    const fullFilePath = path.join(flowProjectRoot, filePath);
    switch (npmLibDef.kind) {
      case "LibDef":
        // If a libdef is already installed for some dependency, we need to
        // uninstall it before installing the new (potentially updated) ver
        const libDef = npmLibDef.libDef;
        //const toInstall = libDefsToInstall.has(libDef.name);
        //console.log(`Found ${libDef.name} already installed. Uninstall? ${toInstall != null ? 'yes' : 'no'}`);
        if (libDefsToInstall.has(libDef.name)) {
          libDefsToUninstall.set(libDef.name, fullFilePath);
        }
        break;

      case "Stub":
        break;

      default:
        (npmLibDef: empty);
    }
  });

  if (libDefsToInstall.size > 0) {
    console.log(`• Installing ${libDefsToInstall.size} libDefs...`);
    const flowTypedDirPath = path.join(flowProjectRoot, libdefDir, "npm");
    await mkdirp(flowTypedDirPath);
    const results = await Promise.all(
      [...libDefsToInstall.values()].map(async (def: NpmLibDef) => {
        const toUninstall = libDefsToUninstall.get(def.name);
        if (toUninstall != null) {
          await fs.unlink(toUninstall);
        }
        return installNpmLibDef(def, flowTypedDirPath, overwrite);
      })
    );

    if (results.some(res => !res)) {
      return 1;
    }
  }

  if (
    (verbose || unavailableLibDefs.length === 0) &&
    outdatedLibDefsToInstall.length > 0
  ) {
    console.log(
      "• The following installed libdefs are compatible with your " +
        "dependencies, but may not include all minor and patch changes for " +
        "your specific dependency version:\n"
    );
    outdatedLibDefsToInstall.forEach(
      ([libDef, { name: pkgName, ver: pkgVersion }]) => {
        console.log(
          "  • libdef: %s (satisfies %s)",
          colors.yellow(`${libDef.name}_${libDef.version}`),
          colors.bold(`${pkgName}@${pkgVersion}`)
        );

        const libDefPlural =
          outdatedLibDefsToInstall.length > 1
            ? ["versioned updates", "these packages"]
            : ["a versioned update", "this package"];
        console.log(
          `\n` +
            `  Consider submitting ${libDefPlural[0]} for ${libDefPlural[1]} to \n` +
            `  https://github.com/flowtype/flow-typed/\n`
        );
      }
    );
  }

  if (
    unavailableLibDefs.length > 0 &&
    unavailableLibDefs.length === explicitLibDefs.length
  ) {
    // If the user specified an explicit library to be installed, don't generate
    // a stub if no libdef exists -- just inform them that one doesn't exist
    console.log(
      colors.red(
        `!! No libdefs found in flow-typed for the explicitly requested libdefs. !!`
      ) +
        "\n" +
        "\n" +
        "Consider using `%s` to generate an empty libdef that you can fill in.",
      colors.bold(`flow-typed create-stub ${explicitLibDefs.join(" ")}`)
    );

    return 1;
  } else {
    // If a package that's missing a flow-typed libdef has any .flow files,
    // we'll skip generating a stub for it.
    const untypedMissingLibDefs = [];
    const typedMissingLibDefs = [];
    await Promise.all(
      unavailableLibDefs.map(async ({ name: pkgName, ver: pkgVer }) => {
        const hasFlowFiles = await pkgHasFlowFiles(cwd, pkgName);
        if (hasFlowFiles) {
          typedMissingLibDefs.push([pkgName, pkgVer]);
        } else {
          untypedMissingLibDefs.push([pkgName, pkgVer]);
        }
      })
    );

    if (untypedMissingLibDefs.length > 0 && !skip) {
      console.log("• Generating stubs for untyped dependencies...");
      await Promise.all(
        untypedMissingLibDefs.map(async ([pkgName, pkgVerStr]) => {
          await createStub(
            flowProjectRoot,
            pkgName,
            pkgVerStr,
            overwrite,
            libdefDir
          );
        })
      );

      console.log(
        colors.red(
          `\n!! No flow@${flowVersionToSemver(
            flowVersion
          )}-compatible libdefs ` +
            `found in flow-typed for the above untyped dependencies !!`
        )
      );

      const plural =
        unavailableLibDefs.length > 1
          ? ["libdefs", "these packages", "them"]
          : ["a libdef", "this package", "it"];
      console.log(
        `\n` +
          `I've generated ${"`"}any${"`"}-typed stubs for ${plural[1]}, but ` +
          `consider submitting \n` +
          `${plural[0]} for ${plural[2]} to ` +
          `${colors.bold("https://github.com/flowtype/flow-typed/")}\n`
      );
    }
  }

  return 0;
}

async function installNpmLibDef(
  npmLibDef: NpmLibDef,
  npmDir: string,
  overwrite: boolean
): Promise<boolean> {
  const scopedDir =
    npmLibDef.scope === null ? npmDir : path.join(npmDir, npmLibDef.scope);
  mkdirp(scopedDir);

  const fileName = `${npmLibDef.name}_${npmLibDef.version}.js`;
  const filePath = path.join(scopedDir, fileName);

  // Find the libDef in the cached repo
  try {
    const terseFilePath = path.relative(
      path.resolve(npmDir, "..", ".."),
      filePath
    );

    if (!overwrite && (await fs.exists(filePath))) {
      console.error(
        "  • %s\n" + "    %s\n    %s\n    └> %s",
        colors.bold(
          colors.red(
            `${terseFilePath} already exists and appears to have been manually ` +
              `written or changed!`
          )
        ),
        colors.green(
          `Consider contributing your changes back to flow-typed repository :)`
        ),
        `Read more at https://github.com/flowtype/flow-typed/wiki/Contributing-Library-Definitions`,
        "Use --overwrite to overwrite the existing libdef."
      );
      return true;
    }

    const repoVersion = await getNpmLibDefVersionHash(
      getCacheRepoDir(),
      npmLibDef
    );
    const codeSignPreprocessor = signCodeStream(repoVersion);
    await copyFile(npmLibDef.path, filePath, codeSignPreprocessor);

    console.log(
      colors.bold("  • %s\n" + "    └> %s"),
      fileName,
      colors.green(`.${path.sep}${terseFilePath}`)
    );

    // Remove any lingering stubs
    console.log(npmLibDef.name);
    console.log(scopedDir);
    const stubName = `${npmLibDef.name}_vx.x.x.js`;
    const stubPath = path.join(scopedDir, stubName);

    if (overwrite && (await fs.exists(stubPath))) {
      await fs.unlink(stubPath);
    }

    return true;
  } catch (e) {
    console.error(`  !! Failed to install ${npmLibDef.name} at ${filePath}`);
    console.error(`  ERROR: ${e.message}`);
    return false;
  }
}

export {
  determineFlowVersion as _determineFlowVersion,
  installNpmLibDefs as _installNpmLibDefs,
  installNpmLibDef as _installNpmLibDef
};
<|endoftext|>


<commit_before>
app.controller('AdminReportsController', ['$firebaseAuth','$http','$location', function ($firebaseAuth, $http, $location){
  var self = this;
  var auth = $firebaseAuth();
  var ctx = document.getElementById("myChart");
  var wardChart = [];
  var countChart = [];
  var allUsers = {list:[]};

  auth.$onAuthStateChanged(function(firebaseUser) {
   if (firebaseUser) {
     console.log('we are still logged in!');
     self.email = firebaseUser.email;
     // go reload idea data....
     getUserChart();
     getIdeaChart();
   } else {
     console.log('boooo');
     // redirect
     self.email = '';
    //  self.logout();
   }
  });

  getUserChart();
  getIdeaChart();

  function getUserChart() {
    var auth = $firebaseAuth();
    var firebaseUser = auth.$getAuth();
    if(firebaseUser){
      firebase.auth().currentUser.getToken().then(function(idToken) {
    $http({
      method: 'GET',
      url: '/admin/userChart',
      headers: {
        id_token: idToken
      }
    }).then(function(response) {
      for (var i = 0; i < response.data.length; i++) {
        wardChart.push(response.data[i].ward);
        countChart.push(response.data[i].count)
      }

      var data = {
        datasets: [{
          data:countChart
          // [11,16,7,3,14,17,2,11,17,9,2,1,7,8]
          ,
          backgroundColor: [
            "#FF6384",
            "#4BC0C0",
            "#FFCE56",
            "#E7E9ED",
            "#36A2EB",
            "rgb(74, 236, 110)",
            "rgb(237, 60, 65)",
            "rgb(121, 22, 113)",
            "rgb(01, 12, 153)",
            "rgb(221, 22, 13)",
            "rgb(101, 312, 53)",
            "rgb(221, 133, 133)",
            "rgb(77, 12, 153)",
          ],
          label: 'My dataset' // for legend
        }],
        labels: wardChart
      }

      new Chart(ctx, {
        data: data,
        type: "bar",
        options: {
          // legend:{
          //   labels: generateLabels:{ function(data)
          // }},

          // },
          defaultFontSize:90,
          display: true,
          defaultFontSize: 44,
          fontSize:44,
          title: {
            display: true,
            fontSize:44,
            text: 'Wards Chart'
          },

          elements: {
            arc: {
              borderColor: "#000000"
            }
          }
        }
      });
    });
  });
  }
  }//end of getAllUsers()

  function getIdeaChart() {
    var auth = $firebaseAuth();
    var firebaseUser = auth.$getAuth();
    if(firebaseUser){
      firebase.auth().currentUser.getToken().then(function(idToken) {
    $http({
      method: 'GET',
      url: '/admin/ideaChart',
      headers: {
        id_token: idToken
      }
    }).then(function(response) {
      for (var i = 0; i < response.data.length; i++) {
        wardChart.push(response.data[i].ward);
        countChart.push(response.data[i].count)
      }

});
});
}
};

}]);
//CHRIS
<commit_msg>fixed admin chart<commit_after>
app.controller('AdminReportsController', ['$firebaseAuth','$http','$location', function ($firebaseAuth, $http, $location){
  var self = this;
  var auth = $firebaseAuth();
  var ctx = document.getElementById("myChart");
  var wardChart = [];
  var countChart = [];
  var allUsers = {list:[]};

  auth.$onAuthStateChanged(function(firebaseUser) {
   if (firebaseUser) {
     console.log('we are still logged in!');
     self.email = firebaseUser.email;
     // go reload idea data....
     getUserChart();
   } else {
     console.log('boooo');
     // redirect
     self.email = '';
    //  self.logout();
   }
  });

  function getUserChart() {
    var auth = $firebaseAuth();
    var firebaseUser = auth.$getAuth();
    if(firebaseUser){
      firebase.auth().currentUser.getToken().then(function(idToken) {
    $http({
      method: 'GET',
      url: '/admin/userChart',
      headers: {
        id_token: idToken
      }
    }).then(function(response) {
      for (var i = 0; i < response.data.length; i++) {
        wardChart.push(response.data[i].ward);
        countChart.push(response.data[i].count)
      }

      var data = {
        datasets: [{
          data:countChart
          // [11,16,7,3,14,17,2,11,17,9,2,1,7,8]
          ,
          backgroundColor: [
            "#FF6384",
            "#4BC0C0",
            "#FFCE56",
            "#E7E9ED",
            "#36A2EB",
            "rgb(74, 236, 110)",
            "rgb(237, 60, 65)",
            "rgb(121, 22, 113)",
            "rgb(01, 12, 153)",
            "rgb(221, 22, 13)",
            "rgb(101, 312, 53)",
            "rgb(221, 133, 133)",
            "rgb(77, 12, 153)",
          ],
          label: 'My dataset' // for legend
        }],
        labels: wardChart
      }

      new Chart(ctx, {
        data: data,
        type: "bar",
        options: {
          // legend:{
          //   labels: generateLabels:{ function(data)
          // }},

          // },
          defaultFontSize:90,
          display: true,
          defaultFontSize: 44,
          fontSize:44,
          title: {
            display: true,
            fontSize:44,
            text: 'Wards Chart'
          },

          elements: {
            arc: {
              borderColor: "#000000"
            }
          }
        }
      });
    });
  });
  }
  }//end of getAllUsers()

  function getIdeaChart() {
    var auth = $firebaseAuth();
    var firebaseUser = auth.$getAuth();
    if(firebaseUser){
      firebase.auth().currentUser.getToken().then(function(idToken) {
    $http({
      method: 'GET',
      url: '/admin/ideaChart',
      headers: {
        id_token: idToken
      }
    }).then(function(response) {
      for (var i = 0; i < response.data.length; i++) {
        wardChart.push(response.data[i].ward);
        countChart.push(response.data[i].count)
      }

});
});
}
};

}]);
//CHRIS
<|endoftext|>


<commit_before>// Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
$(document).ready(function () {
    Socket.on('console', function (data) {
        if (typeof data === 'undefined' || typeof data.line === 'undefined') {
            return;
        }

        if (~data.line.indexOf('You need to agree to the EULA in order to run the server')) {
            swal({
                title: 'EULA Acceptance',
                text: 'By pressing \'I Accept\' below you are indicating your agreement to the Mojang EULA.',
                type: 'info',
                html: true,
                showCancelButton: true,
                showConfirmButton: true,
                cancelButtonText: 'I do not Accept',
                confirmButtonText: 'I Accept',
                closeOnConfirm: false,
                showLoaderOnConfirm: true
            }, function () {
                $.ajax({
                    type: 'POST',
                    url: Pterodactyl.meta.saveFile,
                    headers: { 'X-CSRF-Token': Pterodactyl.meta.csrfToken, },
                    data: {
                        file: 'eula.txt',
                        contents: 'eula=true'
                    }
                }).done(function (data) {
                    $('[data-attr="power"][data-action="start"]').trigger('click');
                    swal({
                        type: 'success',
                        title: '',
                        text: 'The EULA for this server has been accepted, restarting server now.',
                    });
                }).fail(function (jqXHR) {
                    console.error(jqXHR);
                    swal({
                        title: 'Whoops!',
                        text: 'An error occured while attempting to set the EULA as accepted: ' . jqXHR.responseJSON.error,
                        type: 'error'
                    })
                });
            });
        }
    });
});
<commit_msg>fix JS syntax error (using PHP concat operator in JS)<commit_after>// Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
$(document).ready(function () {
    Socket.on('console', function (data) {
        if (typeof data === 'undefined' || typeof data.line === 'undefined') {
            return;
        }

        if (~data.line.indexOf('You need to agree to the EULA in order to run the server')) {
            swal({
                title: 'EULA Acceptance',
                text: 'By pressing \'I Accept\' below you are indicating your agreement to the Mojang EULA.',
                type: 'info',
                html: true,
                showCancelButton: true,
                showConfirmButton: true,
                cancelButtonText: 'I do not Accept',
                confirmButtonText: 'I Accept',
                closeOnConfirm: false,
                showLoaderOnConfirm: true
            }, function () {
                $.ajax({
                    type: 'POST',
                    url: Pterodactyl.meta.saveFile,
                    headers: { 'X-CSRF-Token': Pterodactyl.meta.csrfToken, },
                    data: {
                        file: 'eula.txt',
                        contents: 'eula=true'
                    }
                }).done(function (data) {
                    $('[data-attr="power"][data-action="start"]').trigger('click');
                    swal({
                        type: 'success',
                        title: '',
                        text: 'The EULA for this server has been accepted, restarting server now.',
                    });
                }).fail(function (jqXHR) {
                    console.error(jqXHR);
                    swal({
                        title: 'Whoops!',
                        text: 'An error occurred while attempting to set the EULA as accepted: ' + jqXHR.responseJSON.error,
                        type: 'error'
                    })
                });
            });
        }
    });
});
<|endoftext|>


<commit_before>/**
 * @fileoverview Soundworks client side check-in module
 * @author Sebastien.Robaszkiewicz@ircam.fr, Norbert.Schnell@ircam.fr
 */
'use strict';

var ClientModule = require('./ClientModule');
var client = require('./client');
var input = require('./input');

function instructions(label) {
  return "
Go to
" +
    "
" + label + "
" +
    "
Touch the screen
when you are ready.
";
}

class ClientLocator extends ClientModule {
  constructor(options = {}) {
    super(options.name || 'locator', true, options.color);

    this.select = options.select || 'automatic'; // 'automatic' | 'label' | 'location'
    this.instructions = options.instructions || instructions;
    this.setup = options.setup || null;
    this.showBackground = options.showBackground ||  false;

    this.label = null;

    this._touchStartHandler = this._touchStartHandler.bind(this);
    this._touchMoveHandler = this._touchMoveHandler.bind(this);
    this._touchEndHandler = this._touchEndHandler.bind(this);
    this._sendCoordinates = this._sendCoordinates.bind(this);
    this._surfaceHandler = this._surfaceHandler.bind(this);

    this._currentCoordinates = null;
    this._positionRadius = 20;

    // Explanatory text
    let textDiv = document.createElement('div');
    textDiv.classList.add('message');
    let text = document.createElement('p');
    // text.innerHTML = "Indicate your location on the map and click “OK”.";
    text.innerHTML = "Indiquez votre position sur le plan";
    this._textDiv = textDiv;
    this._text = text;

    // Button
    let button = document.createElement('div');
    button.classList.add('btn');
    button.classList.add('disabled');
    button.innerHTML = "VALIDER";
    this._button = button;

    // Position circle
    let positionDiv = document.createElement('div');
    positionDiv.setAttribute('id', 'position');
    positionDiv.classList.add('position');
    positionDiv.classList.add('hidden');
    positionDiv.style.width = this._positionRadius * 2 + "px";
    positionDiv.style.height = this._positionRadius * 2 + "px";
    this._positionDiv = positionDiv;

    // Surface div
    let surfaceDiv = document.createElement('div');
    surfaceDiv.setAttribute('id', 'surface');
    surfaceDiv.classList.add('surface');
    this._surfaceDiv = surfaceDiv;

    this._textDiv.appendChild(this._text);
    this._textDiv.appendChild(this._button);
    this._surfaceDiv.appendChild(this._positionDiv);

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

  start() {
    super.start();

    client.send(this.name + ':request');
    client.receive(this.name + ':surface', this._surfaceHandler, false);

    window.addEventListener('resize', this._resize);
  }

  done() {
    window.removeEventListener('resize', this._resize);
    super.done();
  }

  reset() {
    client.coordinates = null;

    this._positionDiv.classList.add('hidden');
    this._button.classList.add('disabled');

    this._button.removeEventListener('click', this._sendCoordinates, false);
    this._surfaceDiv.removeEventListener('touchstart', this._touchStartHandler, false);
    this._surfaceDiv.removeEventListener('touchmove', this._touchMoveHandler, false);
    this._surfaceDiv.removeEventListener('touchend', this._touchEndHandler, false);

    // TODO: clean surface properly
    if (this.setup)
      this.setup.reset();
  }

  restart() {
    super.restart();
    client.send(this.name + ':restart', client.coordinates);
    this._button.removeEventListener('click', this._sendCoordinates, false);
    this.done();
  }

  _surfaceHandler(surface) {
    let heightWidthRatio = surface.height / surface.width;
    let screenHeight = window.innerHeight;
    let screenWidth = window.innerWidth;
    let screenRatio = screenHeight / screenWidth;
    let heightPx, widthPx;

    if (screenRatio > heightWidthRatio) { // TODO: refine sizes, with container, etc.
      heightPx = screenWidth * heightWidthRatio;
      widthPx = screenWidth;
    } else {
      heightPx = screenHeight;
      widthPx = screenHeight / heightWidthRatio;
    }

    this.setup.display(this._surfaceDiv, {
      showBackground: this.showBackground
    });

    // Let the participant select his or her location
    this._surfaceDiv.addEventListener('touchstart', this._touchStartHandler, false);
    this._surfaceDiv.addEventListener('touchmove', this._touchMoveHandler, false);
    this._surfaceDiv.addEventListener('touchend', this._touchEndHandler, false);

    // Build text & button interface after receiving and displaying the surface
    this.view.appendChild(this._surfaceDiv);
    this.view.appendChild(this._textDiv);

    this._resize();
    // Send the coordinates of the selected location to server
    this._button.addEventListener('click', this._sendCoordinates, false);
  }

  _resize() {
    const width = window.innerWidth;
    const height = window.innerHeight;

    const orientation = width > height ? 'landscape' : 'portrait';
    this.view.classList.remove('landscape', 'portrait');
    this.view.classList.add(orientation);

    const min = Math.min(width, height);

    this._surfaceDiv.style.width = `${min}px`;
    this._surfaceDiv.style.height = `${min}px`;

    switch (orientation) {
      case 'landscape' :
        this._textDiv.style.height = `${height}px`;
        this._textDiv.style.width = `${width - height}px`;
        break;
      case 'portrait':
        this._textDiv.style.height = `${height - width}px`;
        this._textDiv.style.width = `${width}px`;
        break;
    }
  }

  _sendCoordinates() {
    if (this._currentCoordinates !== null) {
      this._button.classList.add('selected');
      client.coordinates = this._currentCoordinates;
      client.send(this.name + ':coordinates', client.coordinates);
      this.done();
    }
  }

  _touchStartHandler(e) {
    e.preventDefault();

    if (this._positionDiv.classList.contains('hidden')) {
      this._positionDiv.classList.remove('hidden');
      this._button.classList.remove('disabled');
    }

    // TODO: handle mirror
    this._positionDiv.style.left = e.changedTouches[0].clientX - this._positionRadius + "px";
    this._positionDiv.style.top = e.changedTouches[0].clientY - this._positionRadius + "px";
  }

  _touchMoveHandler(e) {
    e.preventDefault();

    // TODO: handle mirror
    this._positionDiv.style.left = e.changedTouches[0].clientX - this._positionRadius + "px";
    this._positionDiv.style.top = e.changedTouches[0].clientY - this._positionRadius + "px";

    // TODO: handle out-of-bounds
  }

  _touchEndHandler(e) {
    e.preventDefault();

    // TODO: handle mirror
    this._positionDiv.style.left = e.changedTouches[0].clientX - this._positionRadius + "px";
    this._positionDiv.style.top = e.changedTouches[0].clientY - this._positionRadius + "px";

    let x = (e.changedTouches[0].clientX - this._surfaceDiv.offsetLeft) / this._surfaceDiv.offsetWidth;
    let y = (e.changedTouches[0].clientY - this._surfaceDiv.offsetTop) / this._surfaceDiv.offsetHeight;

    this._currentCoordinates = [x, y];

    // TODO: handle out-of-bounds
  }
}

module.exports = ClientLocator;<commit_msg>Modified locator with instructions and button on one line<commit_after>/**
 * @fileoverview Soundworks client side check-in module
 * @author Sebastien.Robaszkiewicz@ircam.fr, Norbert.Schnell@ircam.fr
 */
'use strict';

var ClientModule = require('./ClientModule');
var client = require('./client');
var input = require('./input');

class ClientLocator extends ClientModule {
  constructor(options = {}) {
    super(options.name || 'locator', true, options.color);

    this.select = options.select || 'automatic'; // 'automatic' | 'label' | 'location'
    this.instructions = options.instructions || 'Indiquez votre position dans la salle';
    this.setup = options.setup || null;
    this.showBackground = options.showBackground ||  false;

    this.label = null;

    this._touchStartHandler = this._touchStartHandler.bind(this);
    this._touchMoveHandler = this._touchMoveHandler.bind(this);
    this._touchEndHandler = this._touchEndHandler.bind(this);
    this._sendCoordinates = this._sendCoordinates.bind(this);
    this._surfaceHandler = this._surfaceHandler.bind(this);

    this._currentCoordinates = null;
    this._positionRadius = 20;

    // Explanatory text
    let textDiv = document.createElement('div');
    textDiv.classList.add('message');
    let text = document.createElement('p');
    text.innerHTML = this.instructions;
    this._textDiv = textDiv;
    this._text = text;

    // Button
    let button = document.createElement('div');
    button.classList.add('btn');
    button.classList.add('hidden');
    button.innerHTML = "Valider";
    this._button = button;

    // Position circle
    let positionDiv = document.createElement('div');
    positionDiv.setAttribute('id', 'position');
    positionDiv.classList.add('position');
    positionDiv.classList.add('hidden');
    positionDiv.style.width = this._positionRadius * 2 + "px";
    positionDiv.style.height = this._positionRadius * 2 + "px";
    this._positionDiv = positionDiv;

    // Surface div
    let surfaceDiv = document.createElement('div');
    surfaceDiv.setAttribute('id', 'surface');
    surfaceDiv.classList.add('surface');
    this._surfaceDiv = surfaceDiv;

    this._textDiv.appendChild(this._text);
    this._textDiv.appendChild(this._button);
    this._surfaceDiv.appendChild(this._positionDiv);

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

  start() {
    super.start();

    client.send(this.name + ':request');
    client.receive(this.name + ':surface', this._surfaceHandler, false);

    window.addEventListener('resize', this._resize);
  }

  done() {
    window.removeEventListener('resize', this._resize);
    super.done();
  }

  reset() {
    client.coordinates = null;

    this._positionDiv.classList.add('hidden');
    this._text.classList.remove('hidden');
    this._button.classList.add('hidden');

    this._button.removeEventListener('click', this._sendCoordinates, false);
    this._surfaceDiv.removeEventListener('touchstart', this._touchStartHandler, false);
    this._surfaceDiv.removeEventListener('touchmove', this._touchMoveHandler, false);
    this._surfaceDiv.removeEventListener('touchend', this._touchEndHandler, false);

    // TODO: clean surface properly
    if (this.setup)
      this.setup.reset();
  }

  restart() {
    super.restart();
    client.send(this.name + ':restart', client.coordinates);
    this._button.removeEventListener('click', this._sendCoordinates, false);
    this.done();
  }

  _surfaceHandler(surface) {
    let heightWidthRatio = surface.height / surface.width;
    let screenHeight = window.innerHeight;
    let screenWidth = window.innerWidth;
    let screenRatio = screenHeight / screenWidth;
    let heightPx, widthPx;

    if (screenRatio > heightWidthRatio) { // TODO: refine sizes, with container, etc.
      heightPx = screenWidth * heightWidthRatio;
      widthPx = screenWidth;
    } else {
      heightPx = screenHeight;
      widthPx = screenHeight / heightWidthRatio;
    }

    this.setup.display(this._surfaceDiv, {
      showBackground: this.showBackground
    });

    // Let the participant select his or her location
    this._surfaceDiv.addEventListener('touchstart', this._touchStartHandler, false);
    this._surfaceDiv.addEventListener('touchmove', this._touchMoveHandler, false);
    this._surfaceDiv.addEventListener('touchend', this._touchEndHandler, false);

    // Build text & button interface after receiving and displaying the surface
    this.view.appendChild(this._surfaceDiv);
    this.view.appendChild(this._textDiv);

    this._resize();
    // Send the coordinates of the selected location to server
    this._button.addEventListener('click', this._sendCoordinates, false);
  }

  _resize() {
    const width = window.innerWidth;
    const height = window.innerHeight;

    const orientation = width > height ? 'landscape' : 'portrait';
    this.view.classList.remove('landscape', 'portrait');
    this.view.classList.add(orientation);

    const min = Math.min(width, height);

    this._surfaceDiv.style.width = `${min}px`;
    this._surfaceDiv.style.height = `${min}px`;

    switch (orientation) {
      case 'landscape' :
        this._textDiv.style.height = `${height}px`;
        this._textDiv.style.width = `${width - height}px`;
        break;
      case 'portrait':
        this._textDiv.style.height = `${height - width}px`;
        this._textDiv.style.width = `${width}px`;
        break;
    }
  }

  _sendCoordinates() {
    if (this._currentCoordinates !== null) {
      this._button.classList.add('selected');
      client.coordinates = this._currentCoordinates;
      client.send(this.name + ':coordinates', client.coordinates);
      this.done();
    }
  }

  _touchStartHandler(e) {
    e.preventDefault();

    if (this._positionDiv.classList.contains('hidden')) {
      this._positionDiv.classList.remove('hidden');
      this._button.classList.remove('hidden');
      this._text.classList.add('hidden');
    }

    // TODO: handle mirror
    this._positionDiv.style.left = e.changedTouches[0].clientX - this._positionRadius + "px";
    this._positionDiv.style.top = e.changedTouches[0].clientY - this._positionRadius + "px";
  }

  _touchMoveHandler(e) {
    e.preventDefault();

    // TODO: handle mirror
    this._positionDiv.style.left = e.changedTouches[0].clientX - this._positionRadius + "px";
    this._positionDiv.style.top = e.changedTouches[0].clientY - this._positionRadius + "px";

    // TODO: handle out-of-bounds
  }

  _touchEndHandler(e) {
    e.preventDefault();

    // TODO: handle mirror
    this._positionDiv.style.left = e.changedTouches[0].clientX - this._positionRadius + "px";
    this._positionDiv.style.top = e.changedTouches[0].clientY - this._positionRadius + "px";

    let x = (e.changedTouches[0].clientX - this._surfaceDiv.offsetLeft) / this._surfaceDiv.offsetWidth;
    let y = (e.changedTouches[0].clientY - this._surfaceDiv.offsetTop) / this._surfaceDiv.offsetHeight;

    this._currentCoordinates = [x, y];

    // TODO: handle out-of-bounds
  }
}

module.exports = ClientLocator;
<|endoftext|>


<commit_before>/*
 * Copyright 2015, Gregg Tavares.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Gregg Tavares. nor the names of his
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

define([
    './attributes',
    './draw',
    './framebuffers',
    './programs',
    './textures',
    './typedarrays',
  ], function (
    attributes,
    draw,
    framebuffers,
    programs,
    textures,
    typedArrays) {
  "use strict";

  /**
   * The main TWGL module.
   *
   * @module twgl
   */

  // make sure we don't see a global gl
  var gl = undefined;  // eslint-disable-line

  /**
   * Various default settings for twgl.
   *
   * Note: You can call this any number of times. Example:
   *
   *     twgl.setDefaults({ textureColor: [1, 0, 0, 1] });
   *     twgl.setDefaults({ attribPrefix: 'a_' });
   *
   * is equivalent to
   *
   *     twgl.setDefaults({
   *       textureColor: [1, 0, 0, 1],
   *       attribPrefix: 'a_',
   *     });
   *
   * @typedef {Object} Defaults
   * @property {string} attribPrefix The prefix to stick on attributes
   *
   *   When writing shaders I prefer to name attributes with `a_`, uniforms with `u_` and varyings with `v_`
   *   as it makes it clear where they came from. But, when building geometry I prefer using unprefixed names.
   *
   *   In otherwords I'll create arrays of geometry like this
   *
   *       var arrays = {
   *         position: ...
   *         normal: ...
   *         texcoord: ...
   *       };
   *
   *   But need those mapped to attributes and my attributes start with `a_`.
   *
   *   Default: `""`
   *
   * @property {number[]} textureColor Array of 4 values in the range 0 to 1
   *
   *   The default texture color is used when loading textures from
   *   urls. Because the URL will be loaded async we'd like to be
   *   able to use the texture immediately. By putting a 1x1 pixel
   *   color in the texture we can start using the texture before
   *   the URL has loaded.
   *
   *   Default: `[0.5, 0.75, 1, 1]`
   *
   * @property {string} crossOrigin
   *
   *   If not undefined sets the crossOrigin attribute on images
   *   that twgl creates when downloading images for textures.
   *
   *   Also see {@link module:twgl.TextureOptions}.
   *
   * @memberOf module:twgl
   */

  /**
   * Sets various defaults for twgl.
   *
   * In the interest of terseness which is kind of the point
   * of twgl I've integrated a few of the older functions here
   *
   * @param {module:twgl.Defaults} newDefaults The default settings.
   * @memberOf module:twgl
   */
  function setDefaults(newDefaults) {
    attributes.setDefaults_(newDefaults);  // eslint-disable-line
    textures.setDefaults_(newDefaults);  // eslint-disable-line
  }

  /**
   * Creates a webgl context.
   * @param {HTMLCanvasElement} canvas The canvas tag to get
   *     context from. If one is not passed in one will be
   *     created.
   * @return {WebGLRenderingContext} The created context.
   */
  function create3DContext(canvas, opt_attribs) {
    var names = ["webgl", "experimental-webgl"];
    var context = null;
    for (var ii = 0; ii < names.length; ++ii) {
      try {
        context = canvas.getContext(names[ii], opt_attribs);
      } catch(e) {}  // eslint-disable-line
      if (context) {
        break;
      }
    }
    return context;
  }

  /**
   * Gets a WebGL context.
   * @param {HTMLCanvasElement} canvas a canvas element.
   * @param {WebGLContextCreationAttirbutes} [opt_attribs] optional webgl context creation attributes
   * @memberOf module:twgl
   */
  function getWebGLContext(canvas, opt_attribs) {
    var gl = create3DContext(canvas, opt_attribs);
    return gl;
  }

  /**
   * Resize a canvas to match the size it's displayed.
   * @param {HTMLCanvasElement} canvas The canvas to resize.
   * @param {number} [a] multiplier. So you can pass in `window.devicePixelRatio` if you want to.
   * @return {boolean} true if the canvas was resized.
   * @memberOf module:twgl
   */
  function resizeCanvasToDisplaySize(canvas, multiplier) {
    multiplier = multiplier || 1;
    multiplier = Math.max(1, multiplier);
    var width  = canvas.clientWidth  * multiplier | 0;
    var height = canvas.clientHeight * multiplier | 0;
    if (canvas.width !== width ||
        canvas.height !== height) {
      canvas.width = width;
      canvas.height = height;
      return true;
    }
    return false;
  }

  // Using quotes prevents Uglify from changing the names.
  // No speed diff AFAICT.
  var api = {
    "getWebGLContext": getWebGLContext,
    "resizeCanvasToDisplaySize": resizeCanvasToDisplaySize,
    "setDefaults": setDefaults,
  };

  function notPrivate(name) {
    return name[name.length - 1] !== '_';
  }

  function copyPublicProperties(src, dst) {
    Object.keys(src).filter(notPrivate).forEach(function(key) {
      dst[key] = src[key];
    });
  }

  [
    attributes,
    draw,
    framebuffers,
    programs,
    textures,
    typedArrays,
  ].forEach(function(src) {
    copyPublicProperties(src, api);
  });

  return api;

});

<commit_msg>fix docs<commit_after>/*
 * Copyright 2015, Gregg Tavares.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Gregg Tavares. nor the names of his
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

define([
    './attributes',
    './draw',
    './framebuffers',
    './programs',
    './textures',
    './typedarrays',
  ], function (
    attributes,
    draw,
    framebuffers,
    programs,
    textures,
    typedArrays) {
  "use strict";

  /**
   * The main TWGL module.
   *
   * @module twgl
   */

  // make sure we don't see a global gl
  var gl = undefined;  // eslint-disable-line

  /**
   * Various default settings for twgl.
   *
   * Note: You can call this any number of times. Example:
   *
   *     twgl.setDefaults({ textureColor: [1, 0, 0, 1] });
   *     twgl.setDefaults({ attribPrefix: 'a_' });
   *
   * is equivalent to
   *
   *     twgl.setDefaults({
   *       textureColor: [1, 0, 0, 1],
   *       attribPrefix: 'a_',
   *     });
   *
   * @typedef {Object} Defaults
   * @property {string} attribPrefix The prefix to stick on attributes
   *
   *   When writing shaders I prefer to name attributes with `a_`, uniforms with `u_` and varyings with `v_`
   *   as it makes it clear where they came from. But, when building geometry I prefer using unprefixed names.
   *
   *   In otherwords I'll create arrays of geometry like this
   *
   *       var arrays = {
   *         position: ...
   *         normal: ...
   *         texcoord: ...
   *       };
   *
   *   But need those mapped to attributes and my attributes start with `a_`.
   *
   *   Default: `""`
   *
   * @property {number[]} textureColor Array of 4 values in the range 0 to 1
   *
   *   The default texture color is used when loading textures from
   *   urls. Because the URL will be loaded async we'd like to be
   *   able to use the texture immediately. By putting a 1x1 pixel
   *   color in the texture we can start using the texture before
   *   the URL has loaded.
   *
   *   Default: `[0.5, 0.75, 1, 1]`
   *
   * @property {string} crossOrigin
   *
   *   If not undefined sets the crossOrigin attribute on images
   *   that twgl creates when downloading images for textures.
   *
   *   Also see {@link module:twgl.TextureOptions}.
   *
   * @memberOf module:twgl
   */

  /**
   * Sets various defaults for twgl.
   *
   * In the interest of terseness which is kind of the point
   * of twgl I've integrated a few of the older functions here
   *
   * @param {module:twgl.Defaults} newDefaults The default settings.
   * @memberOf module:twgl
   */
  function setDefaults(newDefaults) {
    attributes.setDefaults_(newDefaults);  // eslint-disable-line
    textures.setDefaults_(newDefaults);  // eslint-disable-line
  }

  /**
   * Creates a webgl context.
   * @param {HTMLCanvasElement} canvas The canvas tag to get
   *     context from. If one is not passed in one will be
   *     created.
   * @return {WebGLRenderingContext} The created context.
   */
  function create3DContext(canvas, opt_attribs) {
    var names = ["webgl", "experimental-webgl"];
    var context = null;
    for (var ii = 0; ii < names.length; ++ii) {
      try {
        context = canvas.getContext(names[ii], opt_attribs);
      } catch(e) {}  // eslint-disable-line
      if (context) {
        break;
      }
    }
    return context;
  }

  /**
   * Gets a WebGL context.
   * @param {HTMLCanvasElement} canvas a canvas element.
   * @param {WebGLContextCreationAttirbutes} [opt_attribs] optional webgl context creation attributes
   * @memberOf module:twgl
   */
  function getWebGLContext(canvas, opt_attribs) {
    var gl = create3DContext(canvas, opt_attribs);
    return gl;
  }

  /**
   * Resize a canvas to match the size it's displayed.
   * @param {HTMLCanvasElement} canvas The canvas to resize.
   * @param {number} [multiplier] So you can pass in `window.devicePixelRatio` if you want to.
   * @return {boolean} true if the canvas was resized.
   * @memberOf module:twgl
   */
  function resizeCanvasToDisplaySize(canvas, multiplier) {
    multiplier = multiplier || 1;
    multiplier = Math.max(1, multiplier);
    var width  = canvas.clientWidth  * multiplier | 0;
    var height = canvas.clientHeight * multiplier | 0;
    if (canvas.width !== width ||
        canvas.height !== height) {
      canvas.width = width;
      canvas.height = height;
      return true;
    }
    return false;
  }

  // Using quotes prevents Uglify from changing the names.
  // No speed diff AFAICT.
  var api = {
    "getWebGLContext": getWebGLContext,
    "resizeCanvasToDisplaySize": resizeCanvasToDisplaySize,
    "setDefaults": setDefaults,
  };

  function notPrivate(name) {
    return name[name.length - 1] !== '_';
  }

  function copyPublicProperties(src, dst) {
    Object.keys(src).filter(notPrivate).forEach(function(key) {
      dst[key] = src[key];
    });
  }

  [
    attributes,
    draw,
    framebuffers,
    programs,
    textures,
    typedArrays,
  ].forEach(function(src) {
    copyPublicProperties(src, api);
  });

  return api;

});

<|endoftext|>