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

const del = require('del');
const fs = require('fs-extra');
const minimist = require('minimist');
const objstr = require('obj-str');
const path = require('path');
const {cyan, green, red, yellow} = require('../../common/colors');
const {format} = require('./format');
const {getOutput, getStdout} = require('../../common/process');
const {log, logLocalDev, logWithoutTimestamp} = require('../../common/logging');

const argv = minimist(process.argv.slice(2), {string: ['version']});

const extensionBundlesJson =
  'build-system/compile/bundles.config.extensions.json';

/**
 * @typedef {{
 *   bundleConfig: Object,
 *   modified: !Array<string>,
 *   created: !Array<string>,
 * }}
 */
let MakeExtensionResultDef;

/**
 * @typedef {{
 *   version?: (string|undefined),
 *   bento?: (boolean|undefined),
 *   name?: (string|undefined),
 *   nocss?: (string|undefined),
 *   nojss?: (string|undefined),
 * }}
 */
let ArgsDef;

/**
 * @typedef {{
 *   name: string,
 *   version: string,
 *   latestVersion?: (string|undefined)
 *   options?: ({hasCss: boolean}|undefined)
 * }}
 */
let BundleDef;

/**
 * Convert dash-case-name to PascalCaseName.
 * @param {string} name
 * @return {string}
 */
function dashToPascalCase(name) {
  return name.replace(/(?:-|^)([a-z])/g, (_, c) => c.toUpperCase());
}

/**
 * Replaces from a map of keys/values.
 * @param {string} inputText
 * @param {Object<string, string>} replacements
 * @return {string}
 */
const replace = (inputText, replacements) =>
  Object.keys(replacements).reduce(
    (text, key) => text.replace(new RegExp(key, 'g'), replacements[key]),
    inputText
  );

/**
 * Generate a sequence of all files in a directory recursively.
 * @param {string} dir
 * @yields {string}
 */
async function* walkDir(dir) {
  for (const f of await fs.readdir(dir)) {
    const dirPath = path.join(dir, f);

    if ((await fs.stat(dirPath)).isDirectory()) {
      yield* walkDir(dirPath);
    } else {
      yield path.join(dir, f);
    }
  }
}

const getTemplateDir = (template) =>
  path.join(path.relative(process.cwd(), __dirname), 'template', template);

/**
 * @param {string} templateDir
 * @param {Object<string, string>} replacements
 * @param {string=} destinationDir
 * @return {Promise<Array<string>>}
 */
async function writeFromTemplateDir(
  templateDir,
  replacements,
  destinationDir = '.'
) {
  const destinationPath = (templatePath) =>
    path.join(
      destinationDir,
      replace(templatePath.substr(templateDir.length + 1), replacements)
    );

  const written = [];
  for await (const templatePath of walkDir(templateDir)) {
    const destination = destinationPath(templatePath);

    await fs.mkdirp(path.dirname(destination));

    // Skip if the destination file already exists
    let fileHandle;
    try {
      fileHandle = await fs.open(destination, 'wx');
    } catch (e) {
      if (e.code !== 'EEXIST') {
        throw e;
      }
      if (!argv.overwrite) {
        logLocalDev(
          yellow('WARNING:'),
          'Skipping existing file',
          cyan(destination)
        );
        continue;
      }
      logLocalDev(
        yellow('WARNING:'),
        'Overwriting existing file',
        cyan(destination)
      );
    }

    const template = await fs.readFile(templatePath, 'utf8');
    await fs.write(fileHandle, replace(template, replacements));
    await fs.close(fileHandle);

    logLocalDev(green('SUCCESS:'), 'Created', cyan(destination));

    written.push(destination);
  }
  return written;
}

/**
 * Inserts an extension entry into bundles.config.extensions.json
 *
 * @param {BundleDef} bundle
 * @param {string=} destination
 */
async function insertExtensionBundlesConfig(
  bundle,
  destination = extensionBundlesJson
) {
  let extensionBundles = [];
  try {
    extensionBundles = await fs.readJson(destination, {throws: false});
  } catch (_) {}

  const existingOrNull = extensionBundles.find(
    ({name}) => name === bundle.name
  );

  const {latestVersion, name, version, ...rest} = bundle;
  extensionBundles.push({
    name,
    version,
    latestVersion:
      (existingOrNull && existingOrNull.latestVersion) ||
      latestVersion ||
      version,
    ...rest,
  });

  await fs.mkdirp(path.dirname(destination));

  await fs.writeJson(
    destination,
    extensionBundles.sort((a, b) => {
      if (!a.name) {
        return 1;
      }
      if (!b.name) {
        return -1;
      }
      return a.name.localeCompare(b.name);
    }),
    {
      // Written file is parsed by prettier as `json-stringify`, which means
      // that default formatting by `spaces` is fine.
      spaces: 2,
    }
  );

  logLocalDev(green('SUCCESS:'), 'Wrote', cyan(path.basename(destination)));
}

/**
 * @param {Array<string>} templateDirs
 * @param {string=} destinationDir
 * @param {ArgsDef|minimist.ParsedArgs} options
 * @return {Promise<?MakeExtensionResultDef>}
 */
async function makeExtensionFromTemplates(
  templateDirs,
  destinationDir = '.',
  options = argv
) {
  const version = (
    options.version || (options.bento ? '1.0' : '0.1')
  ).toString();
  const name = (options.name || '').replace(/^amp-/, '');
  if (!name) {
    log(red('ERROR:'), 'Must specify component name with', cyan('--name'));
    return null;
  }

  const namePascalCase = dashToPascalCase(name);

  const replacements = {
    '__current_year__': `${new Date().getFullYear()}`,
    '__component_version__': version,
    '__component_version_snakecase__': version.replace(/\./g, '_'),
    '__component_name_hyphenated__': name,
    '__component_name_hyphenated_capitalized__': name.toUpperCase(),
    '__component_name_pascalcase__': namePascalCase,
    // TODO(alanorozco): Remove __storybook_experiments...__ once we stop
    // requiring the bento experiment.
    '__storybook_experiments_do_not_add_trailing_comma__':
      // Don't add a trailing comma in the template, instead we add it here.
      // This is because the property added is optional, and a double comma would
      // cause a syntax error.
      options.bento ? "experiments: ['bento']," : '',
    ...(!options.nocss
      ? {
          '__css_import__': `import {CSS} from '../../../build/amp-${name}-${version}.css'`,
          '__css_id__': `CSS`,
          '__register_element_args__': `TAG, Amp${namePascalCase}, CSS`,
        }
      : {
          '__css_import__': '',
          '__css_id__': '',
          '__register_element_args__': `TAG, Amp${namePascalCase}`,
        }),
    ...(!options.nojss
      ? {
          '__jss_import_component_css__': `import {CSS as COMPONENT_CSS} from './component.jss'`,
          '__jss_component_css__': 'COMPONENT_CSS',
          '__jss_import_use_styles__': `import {useStyles} from './component.jss'`,
          '__jss_styles_use_styles__': 'const styles = useStyles()',
          '__jss_styles_example_or_placeholder__':
            '`${styles.exampleContentHidden}`',
        }
      : {
          '__jss_import_component_css_': '',
          '__jss_component_css__': 'null',
          '__jss_import_use_styles__': '',
          '__jss_styles_use_styles__': '',
          '__jss_styles_example_or_placeholder__': `'my-classname'`,
        }),
    // eslint-disable-next-line local/no-forbidden-terms
    // This allows generated code to contain "DO NOT SUBMIT", which will cause
    // PRs to fail CI if example code isn't removed from the PR. We can't
    // actually write that out, here or in templates, without CI failing.
    // eslint-disable-next-line local/no-forbidden-terms
    '__do_not_submit__': 'DO NOT SUBMIT',
    // A rule on OWNERS assigns all filenames starting with "/validator-*" to
    // belong to wg-caching. We don't require this group to review these
    // template files, so using the __validator__ placeholder helps us exclude
    // them from ownership.
    '__validator__': 'validator',
  };

  const created = (
    await Promise.all(
      templateDirs.map((templateDirs) =>
        writeFromTemplateDir(templateDirs, replacements, destinationDir)
      )
    )
  ).flat();

  if (created.length > 0) {
    format(created);
  }

  const bundleConfig = {
    name: `amp-${name}`,
    version,
  };

  if (!options.nocss) {
    bundleConfig.options = {hasCss: true};
  }

  await insertExtensionBundlesConfig(
    bundleConfig,
    path.join(destinationDir, extensionBundlesJson)
  );

  const findCreatedByRegex = (regex) => {
    const filenames = created.filter((filename) => regex.test(filename));
    return filenames.length < 1 ? null : filenames;
  };

  const blurb = [
    `${green('FINISHED:')} Created extension ${cyan(`<amp-${name}>`)}`,
    `Boilerplate for your new component has been created in:
    ${cyan(`extensions/amp-${name}/`)}`,
  ];

  const unitTestFiles = findCreatedByRegex(new RegExp('test/test-'));
  if (unitTestFiles) {
    blurb.push(`You can run tests on your new component with the following command:
    ${cyan(`amp unit --files="${unitTestFiles.join(',')}"`)}`);
  }

  if (findCreatedByRegex(new RegExp('/storybook/'))) {
    blurb.push(`You may view the component during development in storybook:
    ${cyan(`amp storybook`)}`);
  }

  if (findCreatedByRegex(new RegExp('/validator-(.+)\\.html$'))) {
    blurb.push(`You should generate accompanying validator test result files by running:
    ${cyan(`amp validator --update_tests`)}`);
  }

  logLocalDev(`${blurb.join('\n\n')}\n`);

  return {
    bundleConfig,
    created,
    modified: [extensionBundlesJson],
  };
}

/**
 * @param {function():?{modified: ?Array<string>, created: ?Array<string>}} fn
 * @return {Promise}
 */
async function affectsWorkingTree(fn) {
  const stashStdout = getStdout(`git stash push --keep-index`);

  const {created, modified} = (await fn()) || {};

  if (created) {
    await del(created);
  }

  if (modified) {
    const head = getStdout('git rev-parse HEAD').trim();
    getOutput(`git checkout ${head} ${modified.join(' ')}`);
  }

  if (!stashStdout.startsWith('No local changes')) {
    getOutput('git stash pop');
  }
}

/**
 * Generates an extension with the given name and runs all unit tests located in
 * the generated extension directory.
 * @param {string} name
 * @return {Promise<?string>} stderr if failing, null if passing
 */
async function runExtensionTests(name) {
  for (const command of [
    `amp build --extensions=${name} --core_runtime_only`,
    `amp unit --headless --files="extensions/${name}/**/test/test-*.js"`,
  ]) {
    log('Running', cyan(command) + '...');
    const result = getOutput(command);
    if (result.status !== 0) {
      return result.stderr || result.stdout;
    }
  }
  return null;
}

/**
 * @return {Promise<void>}
 */
async function makeExtension() {
  let testError;

  const {bento, nocss, nojss} = argv;

  // @ts-ignore
  const templateDirs = objstr({
    shared: true,
    bento,
    classic: !bento,
    css: !nocss,
    jss: bento && !nojss,
  })
    .split(/\s+/)
    .map((name) => getTemplateDir(name));

  const withCleanup = argv.cleanup ? affectsWorkingTree : (fn) => fn();
  await withCleanup(async () => {
    const result = await makeExtensionFromTemplates(templateDirs);
    if (!result) {
      const warningOrError = 'Could not write extension files.';
      if (argv.test) {
        testError = warningOrError;
      } else {
        log(yellow('WARNING:'), warningOrError);
      }
      return null;
    }
    const {bundleConfig, created, modified} = result;
    if (argv.test) {
      testError = await runExtensionTests(bundleConfig.name);
    }
    return {created, modified};
  });

  if (testError) {
    logWithoutTimestamp(testError);
    throw new Error(
      [
        'Failed testing generated extension',
        yellow('⤷ Try updating the template files located in:'),
        ...templateDirs.map((dir) => '\t' + dir),
        '',
      ].join('\n')
    );
  }
}

module.exports = {
  insertExtensionBundlesConfig,
  makeExtension,
  makeExtensionFromTemplates,
  writeFromTemplateDir,
};

makeExtension.description = 'Create the skeleton for a new extension';
makeExtension.flags = {
  name: 'Name of the extension (the amp-* prefix is added if necessary)',
  cleanup: 'Undo file changes before exiting (useful with --test)',
  bento: 'Generate a Bento component',
  nocss:
    'Exclude extension-specific CSS (JSS is generated for --bento unless combined with --nojss)',
  nojss: 'Exclude extension-specific JSS (used with --bento)',
  test: 'Build and test the generated extension',
  version: 'Set the version number (default: 0.1; or 1.0 with --bento)',
  overwrite:
    'Overwrite existing files at the destination if present, otherwise skip',
};
<commit_msg>🏗 Set `wrapper` on generated Bento extensions (#34843)<commit_after>/**
 * Copyright 2017 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const del = require('del');
const fs = require('fs-extra');
const minimist = require('minimist');
const objstr = require('obj-str');
const path = require('path');
const {cyan, green, red, yellow} = require('../../common/colors');
const {format} = require('./format');
const {getOutput, getStdout} = require('../../common/process');
const {log, logLocalDev, logWithoutTimestamp} = require('../../common/logging');

const argv = minimist(process.argv.slice(2), {string: ['version']});

const extensionBundlesJson =
  'build-system/compile/bundles.config.extensions.json';

/**
 * @typedef {{
 *   bundleConfig: Object,
 *   modified: !Array<string>,
 *   created: !Array<string>,
 * }}
 */
let MakeExtensionResultDef;

/**
 * @typedef {{
 *   version?: (string|undefined),
 *   bento?: (boolean|undefined),
 *   name?: (string|undefined),
 *   nocss?: (string|undefined),
 *   nojss?: (string|undefined),
 * }}
 */
let ArgsDef;

/**
 * @typedef {{
 *   name: string,
 *   version: string,
 *   latestVersion?: (string|undefined)
 *   options?: ({hasCss?: boolean, wrapper?: string}|undefined)
 * }}
 */
let BundleDef;

/**
 * Convert dash-case-name to PascalCaseName.
 * @param {string} name
 * @return {string}
 */
function dashToPascalCase(name) {
  return name.replace(/(?:-|^)([a-z])/g, (_, c) => c.toUpperCase());
}

/**
 * Replaces from a map of keys/values.
 * @param {string} inputText
 * @param {Object<string, string>} replacements
 * @return {string}
 */
const replace = (inputText, replacements) =>
  Object.keys(replacements).reduce(
    (text, key) => text.replace(new RegExp(key, 'g'), replacements[key]),
    inputText
  );

/**
 * Generate a sequence of all files in a directory recursively.
 * @param {string} dir
 * @yields {string}
 */
async function* walkDir(dir) {
  for (const f of await fs.readdir(dir)) {
    const dirPath = path.join(dir, f);

    if ((await fs.stat(dirPath)).isDirectory()) {
      yield* walkDir(dirPath);
    } else {
      yield path.join(dir, f);
    }
  }
}

const getTemplateDir = (template) =>
  path.join(path.relative(process.cwd(), __dirname), 'template', template);

/**
 * @param {string} templateDir
 * @param {Object<string, string>} replacements
 * @param {string=} destinationDir
 * @return {Promise<Array<string>>}
 */
async function writeFromTemplateDir(
  templateDir,
  replacements,
  destinationDir = '.'
) {
  const destinationPath = (templatePath) =>
    path.join(
      destinationDir,
      replace(templatePath.substr(templateDir.length + 1), replacements)
    );

  const written = [];
  for await (const templatePath of walkDir(templateDir)) {
    const destination = destinationPath(templatePath);

    await fs.mkdirp(path.dirname(destination));

    // Skip if the destination file already exists
    let fileHandle;
    try {
      fileHandle = await fs.open(destination, 'wx');
    } catch (e) {
      if (e.code !== 'EEXIST') {
        throw e;
      }
      if (!argv.overwrite) {
        logLocalDev(
          yellow('WARNING:'),
          'Skipping existing file',
          cyan(destination)
        );
        continue;
      }
      logLocalDev(
        yellow('WARNING:'),
        'Overwriting existing file',
        cyan(destination)
      );
    }

    const template = await fs.readFile(templatePath, 'utf8');
    await fs.write(fileHandle, replace(template, replacements));
    await fs.close(fileHandle);

    logLocalDev(green('SUCCESS:'), 'Created', cyan(destination));

    written.push(destination);
  }
  return written;
}

/**
 * Inserts an extension entry into bundles.config.extensions.json
 *
 * @param {BundleDef} bundle
 * @param {string=} destination
 */
async function insertExtensionBundlesConfig(
  bundle,
  destination = extensionBundlesJson
) {
  let extensionBundles = [];
  try {
    extensionBundles = await fs.readJson(destination, {throws: false});
  } catch (_) {}

  const existingOrNull = extensionBundles.find(
    ({name}) => name === bundle.name
  );

  const {latestVersion, name, version, ...rest} = bundle;
  extensionBundles.push({
    name,
    version,
    latestVersion:
      (existingOrNull && existingOrNull.latestVersion) ||
      latestVersion ||
      version,
    ...rest,
  });

  await fs.mkdirp(path.dirname(destination));

  await fs.writeJson(
    destination,
    extensionBundles.sort((a, b) => {
      if (!a.name) {
        return 1;
      }
      if (!b.name) {
        return -1;
      }
      return a.name.localeCompare(b.name);
    }),
    {
      // Written file is parsed by prettier as `json-stringify`, which means
      // that default formatting by `spaces` is fine.
      spaces: 2,
    }
  );

  logLocalDev(green('SUCCESS:'), 'Wrote', cyan(path.basename(destination)));
}

/**
 * @param {Array<string>} templateDirs
 * @param {string=} destinationDir
 * @param {ArgsDef|minimist.ParsedArgs} options
 * @return {Promise<?MakeExtensionResultDef>}
 */
async function makeExtensionFromTemplates(
  templateDirs,
  destinationDir = '.',
  options = argv
) {
  const version = (
    options.version || (options.bento ? '1.0' : '0.1')
  ).toString();
  const name = (options.name || '').replace(/^amp-/, '');
  if (!name) {
    log(red('ERROR:'), 'Must specify component name with', cyan('--name'));
    return null;
  }

  const namePascalCase = dashToPascalCase(name);

  const replacements = {
    '__current_year__': `${new Date().getFullYear()}`,
    '__component_version__': version,
    '__component_version_snakecase__': version.replace(/\./g, '_'),
    '__component_name_hyphenated__': name,
    '__component_name_hyphenated_capitalized__': name.toUpperCase(),
    '__component_name_pascalcase__': namePascalCase,
    // TODO(alanorozco): Remove __storybook_experiments...__ once we stop
    // requiring the bento experiment.
    '__storybook_experiments_do_not_add_trailing_comma__':
      // Don't add a trailing comma in the template, instead we add it here.
      // This is because the property added is optional, and a double comma would
      // cause a syntax error.
      options.bento ? "experiments: ['bento']," : '',
    ...(!options.nocss
      ? {
          '__css_import__': `import {CSS} from '../../../build/amp-${name}-${version}.css'`,
          '__css_id__': `CSS`,
          '__register_element_args__': `TAG, Amp${namePascalCase}, CSS`,
        }
      : {
          '__css_import__': '',
          '__css_id__': '',
          '__register_element_args__': `TAG, Amp${namePascalCase}`,
        }),
    ...(!options.nojss
      ? {
          '__jss_import_component_css__': `import {CSS as COMPONENT_CSS} from './component.jss'`,
          '__jss_component_css__': 'COMPONENT_CSS',
          '__jss_import_use_styles__': `import {useStyles} from './component.jss'`,
          '__jss_styles_use_styles__': 'const styles = useStyles()',
          '__jss_styles_example_or_placeholder__':
            '`${styles.exampleContentHidden}`',
        }
      : {
          '__jss_import_component_css_': '',
          '__jss_component_css__': 'null',
          '__jss_import_use_styles__': '',
          '__jss_styles_use_styles__': '',
          '__jss_styles_example_or_placeholder__': `'my-classname'`,
        }),
    // eslint-disable-next-line local/no-forbidden-terms
    // This allows generated code to contain "DO NOT SUBMIT", which will cause
    // PRs to fail CI if example code isn't removed from the PR. We can't
    // actually write that out, here or in templates, without CI failing.
    // eslint-disable-next-line local/no-forbidden-terms
    '__do_not_submit__': 'DO NOT SUBMIT',
    // A rule on OWNERS assigns all filenames starting with "/validator-*" to
    // belong to wg-caching. We don't require this group to review these
    // template files, so using the __validator__ placeholder helps us exclude
    // them from ownership.
    '__validator__': 'validator',
  };

  const created = (
    await Promise.all(
      templateDirs.map((templateDirs) =>
        writeFromTemplateDir(templateDirs, replacements, destinationDir)
      )
    )
  ).flat();

  if (created.length > 0) {
    format(created);
  }

  const bundleConfig = {
    name: `amp-${name}`,
    version,
  };

  if (!options.nocss) {
    bundleConfig.options = {...bundleConfig.options, hasCss: true};
  }
  if (options.bento) {
    bundleConfig.options = {...bundleConfig.options, wrapper: 'bento'};
  }

  await insertExtensionBundlesConfig(
    bundleConfig,
    path.join(destinationDir, extensionBundlesJson)
  );

  const findCreatedByRegex = (regex) => {
    const filenames = created.filter((filename) => regex.test(filename));
    return filenames.length < 1 ? null : filenames;
  };

  const blurb = [
    `${green('FINISHED:')} Created extension ${cyan(`<amp-${name}>`)}`,
    `Boilerplate for your new component has been created in:
    ${cyan(`extensions/amp-${name}/`)}`,
  ];

  const unitTestFiles = findCreatedByRegex(new RegExp('test/test-'));
  if (unitTestFiles) {
    blurb.push(`You can run tests on your new component with the following command:
    ${cyan(`amp unit --files="${unitTestFiles.join(',')}"`)}`);
  }

  if (findCreatedByRegex(new RegExp('/storybook/'))) {
    blurb.push(`You may view the component during development in storybook:
    ${cyan(`amp storybook`)}`);
  }

  if (findCreatedByRegex(new RegExp('/validator-(.+)\\.html$'))) {
    blurb.push(`You should generate accompanying validator test result files by running:
    ${cyan(`amp validator --update_tests`)}`);
  }

  logLocalDev(`${blurb.join('\n\n')}\n`);

  return {
    bundleConfig,
    created,
    modified: [extensionBundlesJson],
  };
}

/**
 * @param {function():?{modified: ?Array<string>, created: ?Array<string>}} fn
 * @return {Promise}
 */
async function affectsWorkingTree(fn) {
  const stashStdout = getStdout(`git stash push --keep-index`);

  const {created, modified} = (await fn()) || {};

  if (created) {
    await del(created);
  }

  if (modified) {
    const head = getStdout('git rev-parse HEAD').trim();
    getOutput(`git checkout ${head} ${modified.join(' ')}`);
  }

  if (!stashStdout.startsWith('No local changes')) {
    getOutput('git stash pop');
  }
}

/**
 * Generates an extension with the given name and runs all unit tests located in
 * the generated extension directory.
 * @param {string} name
 * @return {Promise<?string>} stderr if failing, null if passing
 */
async function runExtensionTests(name) {
  for (const command of [
    `amp build --extensions=${name} --core_runtime_only`,
    `amp unit --headless --files="extensions/${name}/**/test/test-*.js"`,
  ]) {
    log('Running', cyan(command) + '...');
    const result = getOutput(command);
    if (result.status !== 0) {
      return result.stderr || result.stdout;
    }
  }
  return null;
}

/**
 * @return {Promise<void>}
 */
async function makeExtension() {
  let testError;

  const {bento, nocss, nojss} = argv;

  // @ts-ignore
  const templateDirs = objstr({
    shared: true,
    bento,
    classic: !bento,
    css: !nocss,
    jss: bento && !nojss,
  })
    .split(/\s+/)
    .map((name) => getTemplateDir(name));

  const withCleanup = argv.cleanup ? affectsWorkingTree : (fn) => fn();
  await withCleanup(async () => {
    const result = await makeExtensionFromTemplates(templateDirs);
    if (!result) {
      const warningOrError = 'Could not write extension files.';
      if (argv.test) {
        testError = warningOrError;
      } else {
        log(yellow('WARNING:'), warningOrError);
      }
      return null;
    }
    const {bundleConfig, created, modified} = result;
    if (argv.test) {
      testError = await runExtensionTests(bundleConfig.name);
    }
    return {created, modified};
  });

  if (testError) {
    logWithoutTimestamp(testError);
    throw new Error(
      [
        'Failed testing generated extension',
        yellow('⤷ Try updating the template files located in:'),
        ...templateDirs.map((dir) => '\t' + dir),
        '',
      ].join('\n')
    );
  }
}

module.exports = {
  insertExtensionBundlesConfig,
  makeExtension,
  makeExtensionFromTemplates,
  writeFromTemplateDir,
};

makeExtension.description = 'Create the skeleton for a new extension';
makeExtension.flags = {
  name: 'Name of the extension (the amp-* prefix is added if necessary)',
  cleanup: 'Undo file changes before exiting (useful with --test)',
  bento: 'Generate a Bento component',
  nocss:
    'Exclude extension-specific CSS (JSS is generated for --bento unless combined with --nojss)',
  nojss: 'Exclude extension-specific JSS (used with --bento)',
  test: 'Build and test the generated extension',
  version: 'Set the version number (default: 0.1; or 1.0 with --bento)',
  overwrite:
    'Overwrite existing files at the destination if present, otherwise skip',
};
<|endoftext|>


<commit_before>// var fs = require('fs');
var fs = require('graceful-fs');
var settings = require('../../../settings');
var JSONStream = require('JSONStream');

/**
 * Aggregates together all of the survey submissions
 * that have been written to the file system together
 * as one JSON response.
 */
module.exports = function (req, res, next) {
    var formName = req.params.formName;
    if (typeof formName === 'undefined' || formName === null) {
        res.status(400).json({
            status: 400,
            err: 'MISSING_PARAM',
            msg: 'You must specify a parameter for the formName in this end point.',
            path: '/odk/submissions/:formName.json'
        });
        return;
    }
    var dir = settings.dataDir + '/submissions/' + formName;
    var aggregate = [];
    var dataErrors = [];
    // All of the submission dirs in the form directory
    fs.readdir(dir, function (err, submissionDirs) {
        if (err) {
            if (err.errno === -2) {
                // trying to open a directory that is not there.
                res.status(404).json({
                    status: 404,
                    msg: 'You are trying to aggregate the ODK submissions for a form that has no submissions. Please submit at least one survey to see data. Also, check to see if you spelled the form name correctly. Form name: ' + formName,
                    err: err
                });
                return;
            }
            res.status(500).json({
                status: 500,
                msg: 'Problem reading submissions directory.',
                err: err
            });
            return;
        }
        var len = submissionDirs.length;
        if (submissionDirs.length === 0) {
            res.status(200).json([]);
            return;
        }
        var count = 0;
        submissionDirs.forEach(function (submissionDir) {
            if (submissionDir[0] === '.' || submissionDir.indexOf('.txt') > 0) {
                ++count;
                aggregationCallbackCheck(len, count, aggregate, []);
                return;
            }
            var dataFile = dir + '/' + submissionDir + '/data.json';
            var dataContent = '';
            // memory efficient
            try {
                var stream = fs.createReadStream(dataFile),
                parser = JSONStream.parse();
                stream.pipe(parser);
                parser.on('root', function (obj) {
                    // console.log('objects', obj)
                    ++count;
                    aggregate.push(obj);
                    aggregationCallbackCheck(len, count, aggregate, []);
                });
            } catch(e) {
                dataErrors.push({
                        status: 500,
                        msg: 'Problem reading data.json file in submission directory. dataFile: ' + dataFile,
                        err: e
                    });
                aggregationCallbackCheck(len, count, aggregate, dataErrors);
            }
        });
    });

    function aggregationCallbackCheck(len, count, data, error) {
        if (len === count) {
            if (error.length > 0){
                // send the first error in the bunch
                res.status(500).json(error[0]);
            } else {
                res.status(200).json(data);
            }
        }
    }
};
<commit_msg>Cleanup iterating through submission files with async.each<commit_after>// var fs = require('fs');
var fs = require('graceful-fs');
var settings = require('../../../settings');
var JSONStream = require('JSONStream');
var async = require('async');

/**
 * Aggregates together all of the survey submissions
 * that have been written to the file system together
 * as one JSON response.
 */
module.exports = function (req, res, next) {
    var formName = req.params.formName;
    if (typeof formName === 'undefined' || formName === null) {
        res.status(400).json({
            status: 400,
            err: 'MISSING_PARAM',
            msg: 'You must specify a parameter for the formName in this end point.',
            path: '/odk/submissions/:formName.json'
        });
        return;
    }
    var dir = settings.dataDir + '/submissions/' + formName;
    var aggregate = [];
    var dataErrors = [];
    // All of the submission dirs in the form directory
    fs.readdir(dir, function (err, submissionDirs) {
        if (err) {
            if (err.errno === -2) {
                // trying to open a directory that is not there.
                res.status(404).json({
                    status: 404,
                    msg: 'You are trying to aggregate the ODK submissions for a form that has no submissions. Please submit at least one survey to see data. Also, check to see if you spelled the form name correctly. Form name: ' + formName,
                    err: err
                });
                return;
            }
            res.status(500).json({
                status: 500,
                msg: 'Problem reading submissions directory.',
                err: err
            });
            return;
        }
        var len = submissionDirs.length;
        if (submissionDirs.length === 0) {
            res.status(200).json([]);
            return;
        }

        async.each(submissionDirs, function (submissionDir, callback) {
            // If it's not a directory, we just skip processing that path.
            if (submissionDir[0] === '.' || submissionDir.indexOf('.txt') > 0) {
                callback(); // ok, but skipping
                return;
            }
            // Otherwise, we want to open up the data.json in the submission dir.
            var dataFile = dir + '/' + submissionDir + '/data.json';
            try {
                var stream = fs.createReadStream(dataFile);
                var parser = JSONStream.parse();
                stream.pipe(parser);
                parser.on('root', function (obj) {
                    aggregate.push(obj);
                    callback(); // ok submission
                });
            } catch(e) {
                callback({
                    status: 500,
                    msg: 'Problem reading data.json file in submission directory. dataFile: ' + dataFile,
                    err: e
                }); // we have an error, break out of all async iteration
            }
        }, function (err) {
            // an error occurred...
            if (err) {
                res.status(500).json(err);
            }
            // it was a success
            else {
                res.status(200).json(aggregate);
            }
        });
    });

};
<|endoftext|>


<commit_before>/*

    Copyright (C) 2013 Abram Connelly

    This file is part of bostontraintrack.

    bostontraintrack is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    bostontraintrack is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with bostontraintrack.  If not, see <http://www.gnu.org/licenses/>.

*/

var http = require("http");
var sockio = require('socket.io')();

var g_verbose = 0;

var global_connect = { };
var global_status = { };

var global_data = { n: 0,
                    interval : 10000,
                    port : 80,

                    url : "developer.mbta.com",

                    cur_line : 0,
                    n_line : 12,
                    name : [
                      "Greenbush Line",
                      "Kingston/Plymouth Line",
                      "Middleborough/Lakeville Line",
                      "Fairmount Line",
                      "Providence/Stoughton Line",
                      "Franklin Line",
                      "Needham Line",
                      "Framingham/Worcester Line",
                      "Fitchburg Line",
                      "Lowell Line",
                      "Haverhill Line",
                      "Newburyport/Rockport Line"
                     ],

                    path : {
                      "Greenbush Line" : "/lib/RTCR/RailLine_1.json",
                      "Kingston/Plymouth Line" : "/lib/RTCR/RailLine_2.json",
                      "Middleborough/Lakeville Line" : "/lib/RTCR/RailLine_3.json",
                      "Fairmount Line" : "/lib/RTCR/RailLine_4.json",
                      "Providence/Stoughton Line" : "/lib/RTCR/RailLine_5.json",
                      "Franklin Line" : "/lib/RTCR/RailLine_6.json",
                      "Needham Line" : "/lib/RTCR/RailLine_7.json",
                      "Framingham/Worcester Line" : "/lib/RTCR/RailLine_8.json",
                      "Fitchburg Line" : "/lib/RTCR/RailLine_9.json",
                      "Lowell Line" : "/lib/RTCR/RailLine_10.json",
                      "Haverhill Line" : "/lib/RTCR/RailLine_11.json",
                      "Newburyport/Rockport Line" : "/lib/RTCR/RailLine_12.json"
                    }
                  };

console.log("setting up lines...");

function fetchCommuterLine() {
  var linename = global_data.name[ global_data.cur_line ]

  console.log("line:" , linename);
  console.log("cur_line:", global_data.cur_line, ", n_line:", global_data.n_line);

  global_data.cur_line += 1;
  global_data.cur_line %= global_data.n_line;

  if (g_verbose) { console.log("udpating " , linename ); }
  var opt = { host: global_data.url, port:global_data.port, path: global_data.path[linename] };

  console.log("opt:", opt);

  try {

    var req = http.request( opt, function(res) {
      res.setEncoding('utf8');
      var body = '';
      res.on('data', function(chunk) { body += chunk; });
      res.on('end', function() {

        console.log(body);

        try  {
          jr = JSON.parse(body);
          global_data[linename] = jr;
        } catch (ee) {
          console.log("json parse error:", ee );
        }

      });
    });

    req.on('error', function(err) { console.log("got http erro:", err); });
    req.end();

  } catch (err2) {
    console.log("http request error? " + err2);
  }

}

// Do an initial burst to setup all commuter lines
for (var line in global_data.path) {
  fetchCommuterLine();
}

setInterval( fetchCommuterLine, global_data.interval );

sockio.on('connection', function(socket) {
  console.log("connection!");

  global_data.n++;
  var local_name = global_data.n;
  global_connect[ local_name ] = socket;
  global_status [ local_name ] = { enable : false };

  socket.on("enable", function(msg) {
    console.log("enable:", local_name);
    global_status[ local_name ].enable = true;
    pushSingleUpdate( local_name );
  });

  socket.on("disable", function(msg) {
    console.log("disable:", local_name);
    global_status[ local_name ].enable = false;
  });

  socket.on("myevent", function(msg) {
    console.log("got myevent!");
    console.log(msg);

    socket.emit("update", { n: global_data.n } );
  });

  socket.on("disconnect", function() {
    console.log("disconnecting client ", local_name);
    delete global_connect[ local_name ];
    delete global_status [ local_name ];

  });

});

sockio.listen(8183);

function pushSingleUpdate( cli_id ) {

  for (var i=0; i<global_data.n_line; i++) {
    linename = global_data.name[i];

    console.log("checking linename:", linename);

    if (linename in global_data) {
      var dat = {}
      dat[ linename ] = global_data[ linename ];

      try {
        if (global_status[ cli_id ].enable) {
          global_connect[ cli_id ].emit("update", dat );
        }
      } catch (ee) {
        console.log("when trying to emit to cli_id: ", cli_id, " got error:", ee );
      }

    }
  }

}

function pushUpdate() {

  var conn_count = 0;
  for (var cli_id in global_connect) {
    conn_count++;
    pushSingleUpdate( cli_id );
  }

  var dt = Date.now();
  var dts = dt.toString();

  var enable_count=0;
  for (var i in global_status) {
    if (global_status[i].enable) { enable_count++; }
  }

  console.log( dts +  " connected clients:", conn_count, ", (", global_data.n, "), (enabled: ", enable_count, ")" );
}

setInterval( pushUpdate, 10000 );

<commit_msg>only pushes incremental updates.  Still needs testing.<commit_after>/*

    Copyright (C) 2013 Abram Connelly

    This file is part of bostontraintrack.

    bostontraintrack is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    bostontraintrack is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with bostontraintrack.  If not, see <http://www.gnu.org/licenses/>.

*/

var http = require("http");
var sockio = require('socket.io')();

var g_verbose = 0;

var global_connect = { };
var global_status = { };

var g_line_vehicle_data = {
  "Greenbush Line" : {},
  "Kingston/Plymouth Line" : {},
  "Middleborough/Lakeville Line" : {},
  "Fairmount Line" : {},
  "Providence/Stoughton Line" : {},
  "Franklin Line" : {},
  "Needham Line" : {},
  "Framingham/Worcester Line" : {},
  "Fitchburg Line" : {},
  "Lowell Line" : {},
  "Haverhill Line" : {},
  "Newburyport/Rockport Line" : {}
};

var global_data = { n: 0,
                    interval : 10000,
                    port : 80,

                    url : "developer.mbta.com",

                    cur_line : 0,
                    n_line : 12,
                    name : [
                      "Greenbush Line",
                      "Kingston/Plymouth Line",
                      "Middleborough/Lakeville Line",
                      "Fairmount Line",
                      "Providence/Stoughton Line",
                      "Franklin Line",
                      "Needham Line",
                      "Framingham/Worcester Line",
                      "Fitchburg Line",
                      "Lowell Line",
                      "Haverhill Line",
                      "Newburyport/Rockport Line"
                     ],

                    path : {
                      "Greenbush Line" : "/lib/RTCR/RailLine_1.json",
                      "Kingston/Plymouth Line" : "/lib/RTCR/RailLine_2.json",
                      "Middleborough/Lakeville Line" : "/lib/RTCR/RailLine_3.json",
                      "Fairmount Line" : "/lib/RTCR/RailLine_4.json",
                      "Providence/Stoughton Line" : "/lib/RTCR/RailLine_5.json",
                      "Franklin Line" : "/lib/RTCR/RailLine_6.json",
                      "Needham Line" : "/lib/RTCR/RailLine_7.json",
                      "Framingham/Worcester Line" : "/lib/RTCR/RailLine_8.json",
                      "Fitchburg Line" : "/lib/RTCR/RailLine_9.json",
                      "Lowell Line" : "/lib/RTCR/RailLine_10.json",
                      "Haverhill Line" : "/lib/RTCR/RailLine_11.json",
                      "Newburyport/Rockport Line" : "/lib/RTCR/RailLine_12.json"
                    }

                  };

console.log("setting up lines...");

function flushSingleClientData( cli_id )
{

  for (var route in g_line_vehicle_data)
  {
    var train_data = g_line_vehicle_data[route];
    for (var vid in train_data)
    {

      try
      {
        global_connect[ cli_id ].emit( "update", train_data[vid] );
      } catch (ee) {
        console.log("ERROR: flushing to client", cli_id, "(vehicle_id", vid , ") failed");
      }

    }
  }

}

function pushData( vehicle_data )
{

  var conn_count = 0;
  var enable_count=0;

  var dt = Date.now();
  var dts = dt.toString();

  for (var cli_id in global_connect)
  {
    conn_count++;

    try
    {
      if (global_status[ cli_id ].enable)
      {
        global_connect[ cli_id ].emit( "update", vehicle_data );
      }
    } catch (ee) {
      console.log("ERROR: pushing to client", cli_id, "(vehicle_id", vehicle_id, ") failed");
    }

  }

  console.log( dts +  " connected clients:", conn_count, ", (", global_data.n, "), (enabled: ", enable_count, "), vehicle_id:", vehicle_data.id );

}

function updateCommuterRail( linename, body )
{

  console.log("updating line:", linename);
  //console.log(body);

  var json_data;
  try  { json_data = JSON.parse(body); }
  catch (ee) { console.log("json parse error:", ee ); return; }

  var data = g_line_vehicle_data[linename];

  for ( var vehicle_id in data )
  {
    data[vehicle_id].status = "delete";
  }

  // Save it in case we want it
  //
  global_data[linename] = json_data;

  for (var ind in json_data["Messages"])
  {
    var entry = json_data.Messages[ind];
    var vid = entry["Vehicle"];

    latstr = entry["Latitude"];
    lonstr = entry["Longitude"];

    if (vid == "")
    {
      console.log("empty vid, skipping");
      continue;
    }

    if ( (latstr == "") || (lonstr == "") )
    {

      console.log("  " + linename + ">>> " + vid + " DEL");

      data[vid] = { id : vid, route : linename, status : "delete" };
      continue;
    }

    var lat = parseFloat(entry["Latitude"]);
    var lon = parseFloat(entry["Longitude"]);

    console.log("   " + linename + ">>>>" + vid + ", (" + lat + ", " + lon + ") [" + latstr + " " + lonstr + "]"  );

    var stat = "none";
    if (vid in data)
    {
      if ( (Math.abs(lat - data[vid].lat) > 0.001) ||
           (Math.abs(lon - data[vid].lon) > 0.001) )
      {
        data[vid].lat = lat;
        data[vid].lon = lon;
        data[vid].status = "update";
        data[vid].heading = entry["Heading"];
        data[vid].id = vid;
        data[vid].route = linename;
      }

    } else {
      data[vid] = { id : vid, route: linename, lat : lat, lon : lon, status : "new", heading : entry["Heading"] };
    }

  }

  var remove_list = [];

  for ( var vid in data )
  {
    pushData( data[vid] );
    if (data[vid].status == "delete") { remove_list.push(vid); }
  }

  for (var ind=0; ind<remove_list.length; ind++)
  {
    delete data[ remove_list[ind] ];
  }

}

function fetchCommuterLine() {
  var linename = global_data.name[ global_data.cur_line ]

  console.log("line:" , linename);
  console.log("cur_line:", global_data.cur_line, ", n_line:", global_data.n_line);

  global_data.cur_line++ ;
  global_data.cur_line %= global_data.n_line;

  if (g_verbose) { console.log("updating " , linename ); }
  var opt = { host: global_data.url, port:global_data.port, path: global_data.path[linename] };

  console.log("opt:", opt);

  try {
    var req = http.request( opt, function(res) {
      res.setEncoding('utf8');
      var body = '';
      res.on('data', function(chunk) { body += chunk; });
      res.on('end', function() { updateCommuterRail( linename, body ); } );
    });
    req.on('error', function(err) { console.log("got http erro:", err); });
    req.end();
  } catch (err2) {
    console.log("http request error? " + err2);
  }

}

// Do an initial burst to setup all commuter lines
//
for (var line in global_data.path) {
  fetchCommuterLine();
}

setInterval( fetchCommuterLine, global_data.interval );

sockio.on('connection', function(socket) {
  console.log("connection!");

  global_data.n++;
  var local_name = global_data.n;
  global_connect[ local_name ] = socket;
  global_status [ local_name ] = { enable : false };

  socket.on("enable", function(msg) {
    console.log("enable:", local_name);
    global_status[ local_name ].enable = true;
    flushSingleClientData( local_name );
  });

  socket.on("disable", function(msg) {
    console.log("disable:", local_name);
    global_status[ local_name ].enable = false;
  });

  socket.on("myevent", function(msg) {
    console.log("got myevent!");
    console.log(msg);

    socket.emit("update", { n: global_data.n } );
  });

  socket.on("disconnect", function() {
    console.log("disconnecting client ", local_name);
    delete global_connect[ local_name ];
    delete global_status [ local_name ];

  });

});

sockio.listen(8183);

<|endoftext|>


<commit_before>define(function (require) {

  var moment = require('moment'),
      AppDispatcher = require('dispatchers/AppDispatcher'),
      ImageConstants = require('constants/ImageConstants');

  return {

    updateImageAttributes: function (image, newAttributes) {
      if(newAttributes.end_date != null) {
        var end_date;
        if (typeof newAttributesend_date == "object") {
            end_date = newAttributes.end_date
        } else {
            end_date = moment(newAttributes.end_date);
        }
        newAttributes.end_date = end_date;
      }
      image.set({
        name: newAttributes.name,
        description: newAttributes.description,
        end_date: newAttributes.end_date,
        tags: newAttributes.tags
      });
      AppDispatcher.handleRouteAction({
        actionType: ImageConstants.IMAGE_UPDATE,
        payload: {image: image}
      });
    }

  };

});
<commit_msg>Corrected typo and opted for triple equals compare.<commit_after>define(function (require) {

  var moment = require('moment'),
      AppDispatcher = require('dispatchers/AppDispatcher'),
      ImageConstants = require('constants/ImageConstants');

  return {

    updateImageAttributes: function (image, newAttributes) {
      if(newAttributes.end_date != null) {
        var end_date;
        if (typeof newAttributes.end_date === "object") {
            end_date = newAttributes.end_date
        } else {
            end_date = moment(newAttributes.end_date);
        }
        newAttributes.end_date = end_date;
      }
      image.set({
        name: newAttributes.name,
        description: newAttributes.description,
        end_date: newAttributes.end_date,
        tags: newAttributes.tags
      });
      AppDispatcher.handleRouteAction({
        actionType: ImageConstants.IMAGE_UPDATE,
        payload: {image: image}
      });
    }

  };

});
<|endoftext|>


<commit_before>import React from 'react';
import map from './map';

export default ({
    items = [],
    selectedIndex = -1,
    getItemValue = i => i,
    renderItem = i => i,
    onSelect = () => {},
    ...rest,
}) => (
    <div {...{...rest, className: 'react-dadata__suggestions'}}>
        {items.length ? map(items, (item, key) =>
            renderItem({
                key,
                onMouseDown: () => onSelect(getItemValue(item), key),
                item,
                selectedIndex,
                value: getItemValue(item),
            })
        ) : (
            renderItem()
        )}
    </div>
);
<commit_msg>Update List.js<commit_after>import React from 'react';
import map from './map';

export default ({
    items = [],
    selectedIndex = -1,
    getItemText = i => i,
    getItemValue = i => i,
    renderItem = i => i,
    onSelect = () => {},
    ...rest,
}) => (
    <div {...{...rest, className: 'react-dadata__suggestions'}}>
        {items.length ? map(items, (item, key) =>
            renderItem({
                key,
                onMouseDown: () => onSelect(getItemValue(item), key),
                item,
                selectedIndex,
                value: getItemText(item),
            })
        ) : (
            renderItem()
        )}
    </div>
);
<|endoftext|>


<commit_before>/*
 * Canvas Feedback -- "video feedback" in an HTML5 canvas
 * Copyright (c)2015 Chris Pressey, Cat's Eye Technologies.
 * Distributed under an MIT license -- see this URL for full information:
 * https://github.com/catseye/canvas-feedback/blob/master/LICENSE
 */

function parseQuerystring(qs) {
    qs = qs || window.location.search.substring(1);
    var object = {};
    var pairs = qs.split('&');
    for (var i = 0; i < pairs.length; i++) {
        var components = pairs[i].split('=');
        var key = decodeURIComponent(components[0]);
        var value = decodeURIComponent(components[1]);
        object[key] = value;
    }
    return object;
}

function launch(prefix, containerId, config) {
    var config = config || {};
    var deps = [
        "element-factory.js",
        "animation.js",
        "preset-manager.js"
    ];
    var loaded = 0;
    for (var i = 0; i < deps.length; i++) {
        var elem = document.createElement('script');
        elem.src = prefix + deps[i];
        elem.onload = function() {
            if (++loaded < deps.length) return;

            var container = document.getElementById(containerId);

            var t = new CanvasFeedback();

            var canvas = yoob.makeCanvas(container, 400, 400);
            canvas.style.background = "white";
            canvas.style.border = "1px solid black";
            config.canvas = canvas;

            var buttonPanel = yoob.makeDiv(container);
            yoob.makeButton(buttonPanel, 'Restart', function() { t.reset(); });
            yoob.makeButton(buttonPanel, 'Pause', function() { t.pause(); });
            yoob.makeButton(buttonPanel, 'Resume', function() { t.resume(); });

            var controlPanel = config.controlPanel || container;

            var stylePanel = function(panel) {
                panel.style.background = "#e0e0e0";
                panel.style.padding = "3px";
                panel.parentNode.style.background = "#c0c0c0";
                panel.parentNode.style.textAlign = "left";
            };

            urlPanel = yoob.makePanel(controlPanel, "Image URL", false);
            var urlElem = yoob.makeTextInput(urlPanel, 64, config.imgUrl);
            urlElem.style.width = "80%";
            yoob.makeButton(urlPanel, 'Load', function() {
                t.load(urlElem.value);
            });
            stylePanel(urlPanel);

            presetPanel = yoob.makePanel(controlPanel, "Select Preset", true);
            var presetSelect = yoob.makeSelect(presetPanel, "Preset:", []);
            stylePanel(presetPanel);

            var sliderPanel = yoob.makePanel(controlPanel, "Adjust Parameters", false);
            sliderPanel.style.textAlign = "right";
            stylePanel(sliderPanel);

            var rotateSlider = yoob.makeSliderPlusTextInput(
                sliderPanel, 'Rotate (microRadians):', -3000, 3000, 5, 200,
                function(v) { t.rotationRate = v; }
            );
            rotateSlider.textInput.style.width = "auto";
            yoob.makeLineBreak(sliderPanel);

            var shrinkLeftSlider = yoob.makeSliderPlusTextInput(
                sliderPanel, 'Shrink Left (pixels):', -200, 200, 5, 1,
                function(v) { t.shrinkLeft = v; }
            );
            shrinkLeftSlider.textInput.style.width = "auto";
            yoob.makeLineBreak(sliderPanel);

            var shrinkRightSlider = yoob.makeSliderPlusTextInput(
                sliderPanel, 'Shrink Right (pixels):', -200, 200, 5, 1,
                function(v) { t.shrinkRight = v; }
            );
            shrinkRightSlider.textInput.style.width = "auto";
            yoob.makeLineBreak(sliderPanel);

            var shrinkTopSlider = yoob.makeSliderPlusTextInput(
                sliderPanel, 'Shrink Top (pixels):', -200, 200, 5, 1,
                function(v) { t.shrinkTop = v; }
            );
            shrinkTopSlider.textInput.style.width = "auto";
            yoob.makeLineBreak(sliderPanel);

            var shrinkBottomSlider = yoob.makeSliderPlusTextInput(
                sliderPanel, 'Shrink Bottom (pixels):', -200, 200, 5, 1,
                function(v) { t.shrinkBottom = v; }
            );
            shrinkBottomSlider.textInput.style.width = "auto";
            yoob.makeLineBreak(sliderPanel);

            var p = new yoob.PresetManager();
            p.init({
                'selectElem': presetSelect
            });

            var presets = {
                "Rotate + Shrink ×1": {
                    rotate: 200,
                    shrink_left: 1,
                    shrink_right: 1,
                    shrink_top: 1,
                    shrink_bottom: 1
                },
                "Rotate + Shrink ×5": {
                    rotate: 1000,
                    shrink_left: 5,
                    shrink_right: 5,
                    shrink_top: 5,
                    shrink_bottom: 5
                },
                "Rotate + Shrink ×100": {
                    rotate: 4000,
                    shrink_left: 100,
                    shrink_right: 100,
                    shrink_top: 100,
                    shrink_bottom: 100
                },
                "Rotate + Shrink Horiz ×2": {
                    rotate: 500,
                    shrink_left: 2,
                    shrink_right: 2,
                    shrink_top: 0,
                    shrink_bottom: 0
                },
                "Rotate + Shrink Horiz ×100": {
                    rotate: 6000,
                    shrink_left: 100,
                    shrink_right: 100,
                    shrink_top: 0,
                    shrink_bottom: 0
                },
                "Rotate + Shrink Vert ×100": {
                    rotate: 6000,
                    shrink_left: 0,
                    shrink_right: 0,
                    shrink_top: 100,
                    shrink_bottom: 100
                },
                "Rotate + Expand ×1": {
                    rotate: 200,
                    shrink_left: -1,
                    shrink_right: -1,
                    shrink_top: -1,
                    shrink_bottom: -1
                },
                "Rotate Only": {
                    rotate: 200,
                    shrink_left: 0,
                    shrink_right: 0,
                    shrink_top: 0,
                    shrink_bottom: 0
                },
                "Blast off!": {
                    rotate: 700,
                    shrink_left: 0,
                    shrink_right: 7,
                    shrink_top: 0,
                    shrink_bottom: 5
                },
                "Cosmotic!": {
                    rotate: 1000,
                    shrink_left: -3,
                    shrink_right: 10,
                    shrink_top: -3,
                    shrink_bottom: -3
                },
                "Squash down": {
                    rotate: -4000,
                    shrink_left: -100,
                    shrink_right: -100,
                    shrink_top: 200,
                    shrink_bottom: 0
                },
                "Extremes": {
                    rotate: 2500,
                    shrink_left: -65,
                    shrink_right: 75,
                    shrink_top: -55,
                    shrink_bottom: 85
                },
                "Identity (No effect)": {
                    rotate: 0,
                    shrink_left: 0,
                    shrink_right: 0,
                    shrink_top: 0,
                    shrink_bottom: 0
                },
                "Shrink ×1": {
                    rotate: 0,
                    shrink_left: 1,
                    shrink_right: 1,
                    shrink_top: 1,
                    shrink_bottom: 1
                },
                "Expand ×1": {
                    rotate: 0,
                    shrink_left: -1,
                    shrink_right: -1,
                    shrink_top: -1,
                    shrink_bottom: -1
                },
                "Shrink Horiz ×1": {
                    rotate: 0,
                    shrink_left: 1,
                    shrink_right: 1,
                    shrink_top: 0,
                    shrink_bottom: 0
                },
                "Shrink Vert ×1": {
                    rotate: 0,
                    shrink_left: 0,
                    shrink_right: 0,
                    shrink_top: 1,
                    shrink_bottom: 1
                },
                "Shrink Horiz Expand Vert ×1": {
                    rotate: 0,
                    shrink_left: 1,
                    shrink_right: 1,
                    shrink_top: -1,
                    shrink_bottom: -1
                },
                "Shrink Vert Expand Horiz ×1": {
                    rotate: 0,
                    shrink_left: -1,
                    shrink_right: -1,
                    shrink_top: 1,
                    shrink_bottom: 1
                }
            };

            var setPreset = function(n) {
                var obj = presets[n];
                rotateSlider.set(obj.rotate);
                shrinkLeftSlider.set(obj.shrink_left);
                shrinkRightSlider.set(obj.shrink_right);
                shrinkTopSlider.set(obj.shrink_top);
                shrinkBottomSlider.set(obj.shrink_bottom);
            };

            for (n in presets) {
                p.add(n, setPreset);
            }

            var qsArgs = parseQuerystring();
            ['imgUrl'].forEach(function(key) {
                if (qsArgs[key] !== undefined) {
                    config[key] = qsArgs[key];
                }
            });

            t.init(config);
        };
        document.body.appendChild(elem);
    }
}

CanvasFeedback = function() {
    var canvas;
    var ctx;

    var img = new Image();
    var r = 0;

    this.centerX = 200;
    this.centerY = 200;
    this.unCenterX = -200;
    this.unCenterY = -200;
    this.rotationRate = 200;

    this.shrinkLeft = 1;
    this.shrinkRight = 1;
    this.shrinkTop = 1;
    this.shrinkBottom = 1;

    this.draw = function() {
        ctx.save();
        ctx.translate(this.centerX, this.centerY);
        ctx.rotate(r);
        ctx.translate(this.unCenterX, this.unCenterY);
        r += this.rotationRate / 1000000;
        ctx.drawImage(canvas,
            this.shrinkLeft, this.shrinkTop,
            canvas.width - this.shrinkLeft - this.shrinkRight,
            canvas.height - this.shrinkTop - this.shrinkBottom);
        ctx.restore();
    };

    this.update = function() {
    };

    this.load = function(url) {
        var self = this;
        img.onload = function() {
            self.reset();
        }
        img.src = url;
    };

    this.reset = function() {
        canvas.width = img.width;
        canvas.height = img.height;
        this.centerX = img.width / 2;
        this.centerY = img.height / 2;
        this.unCenterX = -1 * this.centerX;
        this.unCenterY = -1 * this.centerY;
        r = 0;
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
        this.resume();
    };

    this.resume = function() {
        this.animation.start();
    };

    this.pause = function() {
        this.animation.stop();
    };

    this.init = function(cfg) {
        canvas = cfg.canvas;
        ctx = canvas.getContext("2d");
        var $this = this;

        this.animation = (new yoob.Animation()).init({
            'object': this,
            'mode': 'quantum'
        });
        this.animation.start();

        img.onload = function() {
            $this.reset();
        }
        img.src = cfg.imgUrl;
    };
};
<commit_msg>Refactor.  This this this.<commit_after>/*
 * Canvas Feedback -- "video feedback" in an HTML5 canvas
 * Copyright (c)2015 Chris Pressey, Cat's Eye Technologies.
 * Distributed under an MIT license -- see this URL for full information:
 * https://github.com/catseye/canvas-feedback/blob/master/LICENSE
 */

function parseQuerystring(qs) {
    qs = qs || window.location.search.substring(1);
    var object = {};
    var pairs = qs.split('&');
    for (var i = 0; i < pairs.length; i++) {
        var components = pairs[i].split('=');
        var key = decodeURIComponent(components[0]);
        var value = decodeURIComponent(components[1]);
        object[key] = value;
    }
    return object;
}

function launch(prefix, containerId, config) {
    var config = config || {};
    var deps = [
        "element-factory.js",
        "animation.js",
        "preset-manager.js"
    ];
    var loaded = 0;
    for (var i = 0; i < deps.length; i++) {
        var elem = document.createElement('script');
        elem.src = prefix + deps[i];
        elem.onload = function() {
            if (++loaded < deps.length) return;

            var container = document.getElementById(containerId);

            var t = new CanvasFeedback();

            var canvas = yoob.makeCanvas(container, 400, 400);
            canvas.style.background = "white";
            canvas.style.border = "1px solid black";
            config.canvas = canvas;

            var buttonPanel = yoob.makeDiv(container);
            yoob.makeButton(buttonPanel, 'Restart', function() { t.reset(); });
            yoob.makeButton(buttonPanel, 'Pause', function() { t.pause(); });
            yoob.makeButton(buttonPanel, 'Resume', function() { t.resume(); });

            var controlPanel = config.controlPanel || container;

            var stylePanel = function(panel) {
                panel.style.background = "#e0e0e0";
                panel.style.padding = "3px";
                panel.parentNode.style.background = "#c0c0c0";
                panel.parentNode.style.textAlign = "left";
            };

            urlPanel = yoob.makePanel(controlPanel, "Image URL", false);
            var urlElem = yoob.makeTextInput(urlPanel, 64, config.imgUrl);
            urlElem.style.width = "80%";
            yoob.makeButton(urlPanel, 'Load', function() {
                t.load(urlElem.value);
            });
            stylePanel(urlPanel);

            presetPanel = yoob.makePanel(controlPanel, "Select Preset", true);
            var presetSelect = yoob.makeSelect(presetPanel, "Preset:", []);
            stylePanel(presetPanel);

            var sliderPanel = yoob.makePanel(controlPanel, "Adjust Parameters", false);
            sliderPanel.style.textAlign = "right";
            stylePanel(sliderPanel);

            var rotateSlider = yoob.makeSliderPlusTextInput(
                sliderPanel, 'Rotate (microRadians):', -3000, 3000, 5, 200,
                function(v) { t.rotationRate = v; }
            );
            rotateSlider.textInput.style.width = "auto";
            yoob.makeLineBreak(sliderPanel);

            var shrinkLeftSlider = yoob.makeSliderPlusTextInput(
                sliderPanel, 'Shrink Left (pixels):', -200, 200, 5, 1,
                function(v) { t.shrinkLeft = v; }
            );
            shrinkLeftSlider.textInput.style.width = "auto";
            yoob.makeLineBreak(sliderPanel);

            var shrinkRightSlider = yoob.makeSliderPlusTextInput(
                sliderPanel, 'Shrink Right (pixels):', -200, 200, 5, 1,
                function(v) { t.shrinkRight = v; }
            );
            shrinkRightSlider.textInput.style.width = "auto";
            yoob.makeLineBreak(sliderPanel);

            var shrinkTopSlider = yoob.makeSliderPlusTextInput(
                sliderPanel, 'Shrink Top (pixels):', -200, 200, 5, 1,
                function(v) { t.shrinkTop = v; }
            );
            shrinkTopSlider.textInput.style.width = "auto";
            yoob.makeLineBreak(sliderPanel);

            var shrinkBottomSlider = yoob.makeSliderPlusTextInput(
                sliderPanel, 'Shrink Bottom (pixels):', -200, 200, 5, 1,
                function(v) { t.shrinkBottom = v; }
            );
            shrinkBottomSlider.textInput.style.width = "auto";
            yoob.makeLineBreak(sliderPanel);

            var p = new yoob.PresetManager();
            p.init({
                'selectElem': presetSelect
            });

            var presets = {
                "Rotate + Shrink ×1": {
                    rotate: 200,
                    shrink_left: 1,
                    shrink_right: 1,
                    shrink_top: 1,
                    shrink_bottom: 1
                },
                "Rotate + Shrink ×5": {
                    rotate: 1000,
                    shrink_left: 5,
                    shrink_right: 5,
                    shrink_top: 5,
                    shrink_bottom: 5
                },
                "Rotate + Shrink ×100": {
                    rotate: 4000,
                    shrink_left: 100,
                    shrink_right: 100,
                    shrink_top: 100,
                    shrink_bottom: 100
                },
                "Rotate + Shrink Horiz ×2": {
                    rotate: 500,
                    shrink_left: 2,
                    shrink_right: 2,
                    shrink_top: 0,
                    shrink_bottom: 0
                },
                "Rotate + Shrink Horiz ×100": {
                    rotate: 6000,
                    shrink_left: 100,
                    shrink_right: 100,
                    shrink_top: 0,
                    shrink_bottom: 0
                },
                "Rotate + Shrink Vert ×100": {
                    rotate: 6000,
                    shrink_left: 0,
                    shrink_right: 0,
                    shrink_top: 100,
                    shrink_bottom: 100
                },
                "Rotate + Expand ×1": {
                    rotate: 200,
                    shrink_left: -1,
                    shrink_right: -1,
                    shrink_top: -1,
                    shrink_bottom: -1
                },
                "Rotate Only": {
                    rotate: 200,
                    shrink_left: 0,
                    shrink_right: 0,
                    shrink_top: 0,
                    shrink_bottom: 0
                },
                "Blast off!": {
                    rotate: 700,
                    shrink_left: 0,
                    shrink_right: 7,
                    shrink_top: 0,
                    shrink_bottom: 5
                },
                "Cosmotic!": {
                    rotate: 1000,
                    shrink_left: -3,
                    shrink_right: 10,
                    shrink_top: -3,
                    shrink_bottom: -3
                },
                "Squash down": {
                    rotate: -4000,
                    shrink_left: -100,
                    shrink_right: -100,
                    shrink_top: 200,
                    shrink_bottom: 0
                },
                "Extremes": {
                    rotate: 2500,
                    shrink_left: -65,
                    shrink_right: 75,
                    shrink_top: -55,
                    shrink_bottom: 85
                },
                "Identity (No effect)": {
                    rotate: 0,
                    shrink_left: 0,
                    shrink_right: 0,
                    shrink_top: 0,
                    shrink_bottom: 0
                },
                "Shrink ×1": {
                    rotate: 0,
                    shrink_left: 1,
                    shrink_right: 1,
                    shrink_top: 1,
                    shrink_bottom: 1
                },
                "Expand ×1": {
                    rotate: 0,
                    shrink_left: -1,
                    shrink_right: -1,
                    shrink_top: -1,
                    shrink_bottom: -1
                },
                "Shrink Horiz ×1": {
                    rotate: 0,
                    shrink_left: 1,
                    shrink_right: 1,
                    shrink_top: 0,
                    shrink_bottom: 0
                },
                "Shrink Vert ×1": {
                    rotate: 0,
                    shrink_left: 0,
                    shrink_right: 0,
                    shrink_top: 1,
                    shrink_bottom: 1
                },
                "Shrink Horiz Expand Vert ×1": {
                    rotate: 0,
                    shrink_left: 1,
                    shrink_right: 1,
                    shrink_top: -1,
                    shrink_bottom: -1
                },
                "Shrink Vert Expand Horiz ×1": {
                    rotate: 0,
                    shrink_left: -1,
                    shrink_right: -1,
                    shrink_top: 1,
                    shrink_bottom: 1
                }
            };

            var setPreset = function(n) {
                var obj = presets[n];
                rotateSlider.set(obj.rotate);
                shrinkLeftSlider.set(obj.shrink_left);
                shrinkRightSlider.set(obj.shrink_right);
                shrinkTopSlider.set(obj.shrink_top);
                shrinkBottomSlider.set(obj.shrink_bottom);
            };

            for (n in presets) {
                p.add(n, setPreset);
            }

            var qsArgs = parseQuerystring();
            ['imgUrl'].forEach(function(key) {
                if (qsArgs[key] !== undefined) {
                    config[key] = qsArgs[key];
                }
            });

            t.init(config);
        };
        document.body.appendChild(elem);
    }
}

CanvasFeedback = function() {

    this.init = function(cfg) {
        this.canvas = cfg.canvas;
        this.ctx = this.canvas.getContext("2d");

        this.img = new Image();
        this.r = 0;

        this.centerX = 200;
        this.centerY = 200;
        this.unCenterX = -200;
        this.unCenterY = -200;
        this.rotationRate = 200;

        this.shrinkLeft = 1;
        this.shrinkRight = 1;
        this.shrinkTop = 1;
        this.shrinkBottom = 1;

        var $this = this;

        this.animation = (new yoob.Animation()).init({
            'object': $this,
            'mode': 'quantum'
        });
        this.animation.start();

        this.img.onload = function() {
            $this.reset();
        }
        this.img.src = cfg.imgUrl;
    };

    this.draw = function() {
        var ctx = this.ctx;
        ctx.save();
        ctx.translate(this.centerX, this.centerY);
        ctx.rotate(this.r);
        ctx.translate(this.unCenterX, this.unCenterY);
        this.r += this.rotationRate / 1000000;
        ctx.drawImage(this.canvas,
            this.shrinkLeft, this.shrinkTop,
            this.canvas.width - this.shrinkLeft - this.shrinkRight,
            this.canvas.height - this.shrinkTop - this.shrinkBottom);
        ctx.restore();
    };

    this.update = function() {
    };

    this.load = function(url) {
        var $this = this;
        this.img.onload = function() {
            $this.reset();
        }
        this.img.src = url;
    };

    this.reset = function() {
        this.canvas.width = this.img.width;
        this.canvas.height = this.img.height;
        this.centerX = this.img.width / 2;
        this.centerY = this.img.height / 2;
        this.unCenterX = -1 * this.centerX;
        this.unCenterY = -1 * this.centerY;
        this.r = 0;
        this.ctx.drawImage(this.img, 0, 0, this.canvas.width, this.canvas.height);
        this.resume();
    };

    this.resume = function() {
        this.animation.start();
    };

    this.pause = function() {
        this.animation.stop();
    };
};
<|endoftext|>


<commit_before>
  /**
   *  Dashboard filter view
   *
   *  var filterView = new cdb.ui.common.FilterView({
   *    visualizations: visualizations_model,
   *    config:         config,
   *    tables:         tables_model,
   *    user:           user_model,
   *    router:         backbone_router
   *  });
   *
   */

  cdb.ui.common.FilterView = cdb.core.View.extend({

    tagName: 'article',
    className: 'filter-view',

    options: {
      template_base: 'dashboard/views/filter_view'
    },

    events: {
      "submit form" :         "_onSubmit",
      "click li a":           "_onClickType",
      "click .tags-filter a": "_onFilterTags"
    },

    initialize: function() {
      this.user = this.options.user;
      this.router = this.options.router;
      this.visualizations = this.options.visualizations;
      this.tables = this.options.tables;

      this.model = new cdb.core.Model({ visible: true });

      this.template_base = cdb.templates.getTemplate(this.options.template_base);
      this._initBinds();
      this._initViews();
    },

    render: function() {
      var d = {
        belongsToOrg: this.user.isInsideOrg(),
        visible:      this.model.get('visible')
      };
      d = _.extend(d, this.router.model.attributes);

      this.$el.html(this.template_base(d));
      return this;
    },

    _initBinds: function() {
      _.bindAll(this, '_onFilterTags');
      this.router.model.bind('change', this.render, this);
      this.add_related_model(this.router.model);

      this.tables.bind('loaded',          this._onCollectionFetched, this);
      this.visualizations.bind('loaded',  this._onCollectionFetched, this);

      this.add_related_model(this.tables);
      this.add_related_model(this.visualizations);
    },

    _initViews: function() {
      this.filterTag = new cdb.admin.TagDropdown({
        className:          'dropdown tag_dropdown border',
        tables:             this.tables,
        visualizations:     this.visualizations,
        router:             this.router,
        host:               this.options.config.account_host,
        vertical_offset:    8,
        horizontal_offset:  5,
        template_base:      'common/views/tag_dropdown'
      });

      this.filterTag.bind("tag", this._onTagSelected, this);
      this.$el.append(this.filterTag.render().el);
      this.addView(this.filterTag);
      cdb.god.bind("closeDialogs", this.filterTag.hide, this.filterTag);
    },

    _onTagSelected: function(tag) {
      console.log("TAG: " + tag);
    },

    _onClickType: function(e) {
      this.killEvent(e);

      var where = $(e.target).attr('href');
      if (where) {
        where = where.replace('/dashboard', '');
        this.router.navigate(where, { trigger: true });
      }
    },

    _onFilterTags: function(e) {
      this.killEvent(e);
      this.filterTag.open(e, $(e.target));
    },

    _onSubmit: function(e) {
      this.killEvent(e);
      var q = this.$('input[type="text"]').val();
      var isShared = this.router.model.get('only_shared');
      var path = ( this.router.model.get('model') || 'tables' ) + ( isShared ? '/shared' : '' ) + ( q ? ('/search/' + q) : '' );
      this.router.navigate(path, { trigger: true });
    },

    _onCollectionFetched: function() {
      var model = this.router.model.get('model');
      var visible = this[model].get('total_entries') > 0;
      this.model.set('visible', visible);
      this[ visible ? 'show' : 'hide' ]();
    },

    hide: function() {
      this.$('div.filters').removeClass('visible');
    },

    show: function() {
      this.$('div.filters').addClass('visible');
    }

  });
<commit_msg>filter view bug fixed<commit_after>
  /**
   *  Dashboard filter view
   *
   *  var filterView = new cdb.ui.common.FilterView({
   *    visualizations: visualizations_model,
   *    config:         config,
   *    tables:         tables_model,
   *    user:           user_model,
   *    router:         backbone_router
   *  });
   *
   */

  cdb.ui.common.FilterView = cdb.core.View.extend({

    tagName: 'article',
    className: 'filter-view',

    options: {
      template_base: 'dashboard/views/filter_view'
    },

    events: {
      "submit form" :         "_onSubmit",
      "click li a":           "_onClickType",
      "click .tags-filter a": "_onFilterTags"
    },

    initialize: function() {
      this.user = this.options.user;
      this.router = this.options.router;
      this.visualizations = this.options.visualizations;
      this.tables = this.options.tables;

      this.model = new cdb.core.Model({ visible: true });

      this.template_base = cdb.templates.getTemplate(this.options.template_base);
      this._initBinds();
      this._initViews();
    },

    render: function() {
      var d = {
        belongsToOrg: this.user.isInsideOrg(),
        visible:      this.model.get('visible')
      };
      d = _.extend(d, this.router.model.attributes);

      this.$el.html(this.template_base(d));
      return this;
    },

    _initBinds: function() {
      _.bindAll(this, '_onFilterTags');
      this.router.model.bind('change', this.render, this);
      this.add_related_model(this.router.model);

      this.tables.bind('loaded',          this._onCollectionFetched, this);
      this.visualizations.bind('loaded',  this._onCollectionFetched, this);

      this.add_related_model(this.tables);
      this.add_related_model(this.visualizations);
    },

    _initViews: function() {
      this.filterTag = new cdb.admin.TagDropdown({
        className:          'dropdown tag_dropdown border',
        tables:             this.tables,
        visualizations:     this.visualizations,
        router:             this.router,
        host:               this.options.config.account_host,
        vertical_offset:    8,
        horizontal_offset:  5,
        template_base:      'common/views/tag_dropdown'
      });

      this.filterTag.bind("tag", this._onTagSelected, this);
      this.$el.append(this.filterTag.render().el);
      this.addView(this.filterTag);
      cdb.god.bind("closeDialogs", this.filterTag.hide, this.filterTag);
    },

    _onTagSelected: function(tag) {
      console.log("TAG: " + tag);
    },

    _onClickType: function(e) {
      this.killEvent(e);

      var where = $(e.target).attr('href');
      if (where) {
        where = where.replace('/dashboard', '');
        this.router.navigate(where, { trigger: true });
      }
    },

    _onFilterTags: function(e) {
      this.killEvent(e);
      this.filterTag.open(e, $(e.target));
    },

    _onSubmit: function(e) {
      this.killEvent(e);
      var q = this.$('input[type="text"]').val();
      var isShared = this.router.model.get('only_shared');
      var path = ( this.router.model.get('model') || 'tables' ) + ( isShared ? '/shared' : '' ) + ( q ? ('/search/' + q) : '' );
      this.router.navigate(path, { trigger: true });
    },

    _onCollectionFetched: function() {
      var model = this.router.model.get('model');
      console.log(this[model]);
      var visible = this[model].total_entries > 0;
      this.model.set('visible', visible);
      this[ visible ? 'show' : 'hide' ]();
    },

    hide: function() {
      this.$('div.filters').removeClass('visible');
    },

    show: function() {
      this.$('div.filters').addClass('visible');
    }

  });
<|endoftext|>


<commit_before>window.onload=function(){
	 var  enlace=document.querySelectorAll(".enlace");
	 var span=document.querySelectorAll('.adicional.oculto');
	 var spanv=document.querySelectorAll('.adicional.visible');

	 //onclick="muestra(); return false;" Utilizando esto como la funcion esta
	 //dentro de una funcion no es visible desde fuera.

	 var muestra=function(){
	 	//elimina la clase 'oculto'
	 	if (enlace.classList.contains("ocultar"))
	 	{if(span.length){
	 		span[0].classList.remove("oculto");
	 		span[0].classList.add("visible");
	 	}

	 	if(span.length){
	 		enlace[0].classList.remove("ocultar");
	 		enlace[0].classList.add("mostrar");
	 	}
	 }else{
	 	if(span.length){
	 		span[0].classList.remove("visible");
	 		span[0].classList.add("oculto");

	 	if(span.length){
	 		enlace[0].classList.remove("mostrar");
	 		enlace[0].classList.add("ocultar");

	 	}
	 }
	 }

	 };
	if(enlace.length){
		enlace[0].addEventListener('click',muestra,false);
	}
};
<commit_msg>Ejercicio 09OcultarMostrar finalizado<commit_after>window.onload = function() {
        var enlace = document.querySelectorAll(".enlace");
        var span = document.querySelectorAll('.adicional.oculto');
        var spanv = document.querySelectorAll('.adicional.visible');

        //onclick="muestra(); return false;" Utilizando esto como la funcion esta
        //dentro de una funcion no es visible desde fuera.

        var muestra = function() {
            //elimina la clase 'oculto'
            if (enlace[0].classList.contains("ocultar")) {
                if (span.length) {
                    span[0].classList.remove("oculto");
                    span[0].classList.add("visible");
                    enlace[0].classList.remove("ocultar");
                    enlace[0].classList.add("mostrar");
                    console.log(enlace[0].innerText);
                    enlace[0].innerText="Ocultar";
                }
            } else {
                if (span.length) {
                    span[0].classList.remove("visible");
                    span[0].classList.add("oculto");
                    enlace[0].classList.remove("mostrar");
                    enlace[0].classList.add("ocultar");
                    enlace[0].innerText = "Seguir leyendo";
                }
            }
        };
            if (enlace.length) {
                enlace[0].addEventListener('click', muestra, false);
            }
        };
<|endoftext|>