<commit_before>import { takeEvery, put, select, call } from 'redux-saga/effects'

import Api from '_/ovirtapi'
import OptionsManager from '_/optionsManager'
import { fileDownload } from '_/helpers'
import { doesVmSessionExistForUserId, idFromType } from '_/utils'
import * as Actions from '_/actions'

import { callExternalAction } from '../utils'
import {
  fetchVmSessions,
} from '../index'

import { adjustVVFile } from './vvFileUtils'
import RDPBuilder from './rdpBuilder'

import { push } from 'connected-react-router'
import * as C from '_/constants'

// ----- Connection files
/**
 * Push a virt-viewer connection file (__console.vv__) to connect a user to a VM's console
 */
function* downloadVmConsole ({
  vmName,
  consoleType,
  vmId,
  consoleId,
  usbAutoshare,
  usbFilter,
  hasGuestAgent,
  skipSSO,
  openInPage,
}) {
  if (hasGuestAgent && !skipSSO) {
    const result = yield callExternalAction(Api.vmLogon, { payload: { vmId } }, true)
    if (!result || result.status !== 'complete') {
      const message = result?.error?.responseJSON?.fault?.detail ?? ''
      yield put(Actions.addConsoleError({ vmId, vmName, consoleType, status: C.CONSOLE_LOGON, consoleId }))
      yield put(Actions.addUserMessage({ messageDescriptor: { id: 'cantOpenConsole', params: { message } }, type: 'error' }))
      return
    }
  }

  yield put(Actions.dismissConsoleError({ vmId, consoleType }))

  let data = yield callExternalAction(Api.console, { type: 'INTERNAL_CONSOLE', payload: { vmId, consoleId } })
  if (data.error) {
    return
  }

  const isSpice = consoleType === C.SPICE
  if (consoleType === C.NATIVE_VNC || isSpice) {
    const legacyOptions = getLegacyOptions({ vmId })
    const options = isSpice
      ? yield getSpiceConsoleOptions({ legacyOptions, usbAutoshare, usbFilter, vmId })
      : yield getVncOptions({ legacyOptions })

    data = adjustVVFile({ data, options })
    fileDownload({ data, fileName: 'console.vv', mimeType: 'application/x-virt-viewer' })
    yield put(Actions.setConsoleStatus({ vmId, status: C.DOWNLOAD_CONSOLE, consoleType }))
  }

  const isNoVNC = consoleType === C.BROWSER_VNC
  if (isNoVNC) {
    const dataTicket = yield callExternalAction(Api.consoleProxyTicket,
      { type: 'INTRENAL_CONSOLE', payload: { vmId, consoleId } })
    const ticket = yield callExternalAction(Api.consoleTicket,
      { type: 'INTRENAL_CONSOLE', payload: { vmId, consoleId } })
    yield put(Actions.setConsoleTickets({ vmId, proxyTicket: dataTicket.proxy_ticket.value, ticket: ticket.ticket }))
    yield put(Actions.setConsoleStatus({ vmId, status: C.INIT_CONSOLE, consoleType }))
  }

  if (openInPage || isNoVNC) {
    yield put(push('/vm/' + vmId + '/console/' + consoleType))
  }
}

/**
 * Legacy options were saved in browser's local storage.
 * The UI for setting theose options was removed in previous versions.
 * However there is still a (small) chance that the data is still there.
 * Note that legacy options are per VM and therefore should overwrite global defaults.
 */
function* getLegacyOptions ({ vmId }) {
  const options = yield select(state => state.options.getIn(['options', 'consoleOptions', vmId]))
  if (options) {
    return (options.toJS && options.toJS()) || options
  }
  console.log('downloadVmConsole() console options not yet present, trying to load from local storage')
  yield getConsoleOptions(Actions.getConsoleOptions({ vmId }))
}

function* getSpiceConsoleOptions ({ legacyOptions, usbAutoshare, usbFilter, vmId }) {
  const smartcardEnabledOnVm = yield select(({ vms }) => vms.getIn(['vms', vmId, 'display', 'smartcardEnabled']))
  const newOptions = yield select(({ options, vms }) => ({
    fullscreen: options.getIn(['remoteOptions', 'fullScreenSpice', 'content']),
    ctrlAltDelToEnd: options.getIn(['remoteOptions', 'ctrlAltEndSpice', 'content']),
    smartcardEnabled: options.getIn(['remoteOptions', 'smartcardSpice', 'content']),
  }))

  const merged = {
    ...newOptions,
    ...legacyOptions,
    usbFilter,
    usbAutoshare,
  }
  merged.smartcardEnabled = smartcardEnabledOnVm && merged.smartcardEnabled
  return merged
}

function* getVncOptions ({ legacyOptions }) {
  const newOptions = yield select(({ options }) => ({
    fullscreen: options.getIn(['remoteOptions', 'fullScreenVnc', 'content']),
    ctrlAltDelToEnd: options.getIn(['remoteOptions', 'ctrlAltEndVnc', 'content']),
  }))
  return {
    ...newOptions,
    ...legacyOptions,
  }
}

/**
 * Push a RDP connection file (__console.rdp__) to connect a user to the Windows VM's RDP session
 */
function* getRDPVm ({
  openInPage,
  vmId,
  vmName,
  fqdn,
  username,
  domain,
}) {
  // note that vmName was skipped here (intentionally or not)
  // which results in address === fqdn
  const rdpBuilder = new RDPBuilder({ username, domain, fqdn })
  const data = rdpBuilder.buildRDP()
  fileDownload({ data, fileName: 'console.rdp', mimeType: 'application/rdp' })
  yield put(Actions.setConsoleStatus({ vmId, status: C.DOWNLOAD_CONSOLE, consoleType: C.RDP }))
  if (openInPage) {
    yield put(push('/vm/' + vmId + '/console/rdp'))
  }
}

// -----

/**
 * Check the sessions on a VM and if someone other then the given user has an open
 * console, flag the console as "in use" requiring manual confirmation to open the
 * console (which will disconnect the other user's existing session).
 */
export function* openConsole ({
  payload: {
    consoleType,
    vmId,
    openInPage,
    skipSSO,
    logoutOtherUsers,
  },
}) {
  yield put(Actions.setConsoleStatus({ vmId, status: C.OPEN_IN_PROGRESS, consoleType }))

  const {
    userId,
    usbAutoshare,
    usbFilter,
    vmName,
    hasGuestAgent,
    consoleId,
    fqdn,
    domain,
    username,
  } = yield select(({ config, vms }) => ({
    usbAutoshare: config.get('usbAutoshare'),
    usbFilter: config.get('usbFilter'),
    userId: config.getIn(['user', 'id']),
    domain: config.get('domain'),
    username: config.getIn(['user', 'name']),
    vmName: vms.getIn(['vms', vmId, 'name']),
    hasGuestAgent: vms.getIn(['vms', vmId, 'ssoGuestAgent']),
    consoleId: idFromType({ consoleType, vm: vms.getIn(['vms', vmId]) }),
    fqdn: vms.getIn(['vms', vmId, 'fqdn']),
  }))

  if (consoleType === C.RDP) {
    yield call(getRDPVm, {
      openInPage,
      vmId,
      vmName,
      fqdn,
      username,
      domain,
    })
    return
  }

  const sessionsInternal = yield fetchVmSessions({ vmId })
  const consoleUsers = sessionsInternal && sessionsInternal
    .filter(session => session.consoleUser)
    .map(session => session.user)
  yield put(Actions.setVmSessions({ vmId, sessions: sessionsInternal }))

  if (!logoutOtherUsers && consoleUsers.length > 0 && consoleUsers.find(user => user.id === userId) === undefined) {
    yield put(Actions.addConsoleError({
      vmId,
      vmName,
      consoleType,
      status: C.CONSOLE_IN_USE,
      consoleId,
    }))
  } else {
    yield call(downloadVmConsole, {
      vmName,
      consoleType,
      vmId,
      usbAutoshare,
      usbFilter,
      hasGuestAgent,
      consoleId,
      openInPage,
      skipSSO: skipSSO || doesVmSessionExistForUserId(sessionsInternal, userId),
    })
  }
}

// ----- Console Options (per VM) held by `OptionsManager`
export function* getConsoleOptions (action) {
  const options = OptionsManager.loadConsoleOptions(action.payload)
  yield put(Actions.setConsoleOptions({ vmId: action.payload.vmId, options }))
  return options
}

export function* saveConsoleOptions (action) {
  OptionsManager.saveConsoleOptions(action.payload)
  yield getConsoleOptions(Actions.getConsoleOptions({ vmId: action.payload.vmId }))
}

export default [
  takeEvery(C.GET_CONSOLE_OPTIONS, getConsoleOptions),
  takeEvery(C.SAVE_CONSOLE_OPTIONS, saveConsoleOptions),
  takeEvery(C.OPEN_CONSOLE, openConsole),
]
<commit_msg>Use VM name for RDP if FQDN is not available<commit_after>import { takeEvery, put, select, call } from 'redux-saga/effects'

import Api from '_/ovirtapi'
import OptionsManager from '_/optionsManager'
import { fileDownload } from '_/helpers'
import { doesVmSessionExistForUserId, idFromType } from '_/utils'
import * as Actions from '_/actions'

import { callExternalAction } from '../utils'
import {
  fetchVmSessions,
} from '../index'

import { adjustVVFile } from './vvFileUtils'
import RDPBuilder from './rdpBuilder'

import { push } from 'connected-react-router'
import * as C from '_/constants'

// ----- Connection files
/**
 * Push a virt-viewer connection file (__console.vv__) to connect a user to a VM's console
 */
function* downloadVmConsole ({
  vmName,
  consoleType,
  vmId,
  consoleId,
  usbAutoshare,
  usbFilter,
  hasGuestAgent,
  skipSSO,
  openInPage,
}) {
  if (hasGuestAgent && !skipSSO) {
    const result = yield callExternalAction(Api.vmLogon, { payload: { vmId } }, true)
    if (!result || result.status !== 'complete') {
      const message = result?.error?.responseJSON?.fault?.detail ?? ''
      yield put(Actions.addConsoleError({ vmId, vmName, consoleType, status: C.CONSOLE_LOGON, consoleId }))
      yield put(Actions.addUserMessage({ messageDescriptor: { id: 'cantOpenConsole', params: { message } }, type: 'error' }))
      return
    }
  }

  yield put(Actions.dismissConsoleError({ vmId, consoleType }))

  let data = yield callExternalAction(Api.console, { type: 'INTERNAL_CONSOLE', payload: { vmId, consoleId } })
  if (data.error) {
    return
  }

  const isSpice = consoleType === C.SPICE
  if (consoleType === C.NATIVE_VNC || isSpice) {
    const legacyOptions = getLegacyOptions({ vmId })
    const options = isSpice
      ? yield getSpiceConsoleOptions({ legacyOptions, usbAutoshare, usbFilter, vmId })
      : yield getVncOptions({ legacyOptions })

    data = adjustVVFile({ data, options })
    fileDownload({ data, fileName: 'console.vv', mimeType: 'application/x-virt-viewer' })
    yield put(Actions.setConsoleStatus({ vmId, status: C.DOWNLOAD_CONSOLE, consoleType }))
  }

  const isNoVNC = consoleType === C.BROWSER_VNC
  if (isNoVNC) {
    const dataTicket = yield callExternalAction(Api.consoleProxyTicket,
      { type: 'INTRENAL_CONSOLE', payload: { vmId, consoleId } })
    const ticket = yield callExternalAction(Api.consoleTicket,
      { type: 'INTRENAL_CONSOLE', payload: { vmId, consoleId } })
    yield put(Actions.setConsoleTickets({ vmId, proxyTicket: dataTicket.proxy_ticket.value, ticket: ticket.ticket }))
    yield put(Actions.setConsoleStatus({ vmId, status: C.INIT_CONSOLE, consoleType }))
  }

  if (openInPage || isNoVNC) {
    yield put(push('/vm/' + vmId + '/console/' + consoleType))
  }
}

/**
 * Legacy options were saved in browser's local storage.
 * The UI for setting theose options was removed in previous versions.
 * However there is still a (small) chance that the data is still there.
 * Note that legacy options are per VM and therefore should overwrite global defaults.
 */
function* getLegacyOptions ({ vmId }) {
  const options = yield select(state => state.options.getIn(['options', 'consoleOptions', vmId]))
  if (options) {
    return (options.toJS && options.toJS()) || options
  }
  console.log('downloadVmConsole() console options not yet present, trying to load from local storage')
  yield getConsoleOptions(Actions.getConsoleOptions({ vmId }))
}

function* getSpiceConsoleOptions ({ legacyOptions, usbAutoshare, usbFilter, vmId }) {
  const smartcardEnabledOnVm = yield select(({ vms }) => vms.getIn(['vms', vmId, 'display', 'smartcardEnabled']))
  const newOptions = yield select(({ options, vms }) => ({
    fullscreen: options.getIn(['remoteOptions', 'fullScreenSpice', 'content']),
    ctrlAltDelToEnd: options.getIn(['remoteOptions', 'ctrlAltEndSpice', 'content']),
    smartcardEnabled: options.getIn(['remoteOptions', 'smartcardSpice', 'content']),
  }))

  const merged = {
    ...newOptions,
    ...legacyOptions,
    usbFilter,
    usbAutoshare,
  }
  merged.smartcardEnabled = smartcardEnabledOnVm && merged.smartcardEnabled
  return merged
}

function* getVncOptions ({ legacyOptions }) {
  const newOptions = yield select(({ options }) => ({
    fullscreen: options.getIn(['remoteOptions', 'fullScreenVnc', 'content']),
    ctrlAltDelToEnd: options.getIn(['remoteOptions', 'ctrlAltEndVnc', 'content']),
  }))
  return {
    ...newOptions,
    ...legacyOptions,
  }
}

/**
 * Push a RDP connection file (__console.rdp__) to connect a user to the Windows VM's RDP session
 */
function* getRDPVm ({
  openInPage,
  vmId,
  vmName,
  fqdn,
  username,
  domain,
}) {
  const rdpBuilder = new RDPBuilder({ username, domain, fqdn, vmName })
  const data = rdpBuilder.buildRDP()
  fileDownload({ data, fileName: 'console.rdp', mimeType: 'application/rdp' })
  yield put(Actions.setConsoleStatus({ vmId, status: C.DOWNLOAD_CONSOLE, consoleType: C.RDP }))
  if (openInPage) {
    yield put(push('/vm/' + vmId + '/console/rdp'))
  }
}

// -----

/**
 * Check the sessions on a VM and if someone other then the given user has an open
 * console, flag the console as "in use" requiring manual confirmation to open the
 * console (which will disconnect the other user's existing session).
 */
export function* openConsole ({
  payload: {
    consoleType,
    vmId,
    openInPage,
    skipSSO,
    logoutOtherUsers,
  },
}) {
  yield put(Actions.setConsoleStatus({ vmId, status: C.OPEN_IN_PROGRESS, consoleType }))

  const {
    userId,
    usbAutoshare,
    usbFilter,
    vmName,
    hasGuestAgent,
    consoleId,
    fqdn,
    domain,
    username,
  } = yield select(({ config, vms }) => ({
    usbAutoshare: config.get('usbAutoshare'),
    usbFilter: config.get('usbFilter'),
    userId: config.getIn(['user', 'id']),
    domain: config.get('domain'),
    username: config.getIn(['user', 'name']),
    vmName: vms.getIn(['vms', vmId, 'name']),
    hasGuestAgent: vms.getIn(['vms', vmId, 'ssoGuestAgent']),
    consoleId: idFromType({ consoleType, vm: vms.getIn(['vms', vmId]) }),
    fqdn: vms.getIn(['vms', vmId, 'fqdn']),
  }))

  if (consoleType === C.RDP) {
    yield call(getRDPVm, {
      openInPage,
      vmId,
      vmName,
      fqdn,
      username,
      domain,
    })
    return
  }

  const sessionsInternal = yield fetchVmSessions({ vmId })
  const consoleUsers = sessionsInternal && sessionsInternal
    .filter(session => session.consoleUser)
    .map(session => session.user)
  yield put(Actions.setVmSessions({ vmId, sessions: sessionsInternal }))

  if (!logoutOtherUsers && consoleUsers.length > 0 && consoleUsers.find(user => user.id === userId) === undefined) {
    yield put(Actions.addConsoleError({
      vmId,
      vmName,
      consoleType,
      status: C.CONSOLE_IN_USE,
      consoleId,
    }))
  } else {
    yield call(downloadVmConsole, {
      vmName,
      consoleType,
      vmId,
      usbAutoshare,
      usbFilter,
      hasGuestAgent,
      consoleId,
      openInPage,
      skipSSO: skipSSO || doesVmSessionExistForUserId(sessionsInternal, userId),
    })
  }
}

// ----- Console Options (per VM) held by `OptionsManager`
export function* getConsoleOptions (action) {
  const options = OptionsManager.loadConsoleOptions(action.payload)
  yield put(Actions.setConsoleOptions({ vmId: action.payload.vmId, options }))
  return options
}

export function* saveConsoleOptions (action) {
  OptionsManager.saveConsoleOptions(action.payload)
  yield getConsoleOptions(Actions.getConsoleOptions({ vmId: action.payload.vmId }))
}

export default [
  takeEvery(C.GET_CONSOLE_OPTIONS, getConsoleOptions),
  takeEvery(C.SAVE_CONSOLE_OPTIONS, saveConsoleOptions),
  takeEvery(C.OPEN_CONSOLE, openConsole),
]
<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';

// Imports
const {spawn} = require('child_process');
const {PassThrough} = require('stream');
const {ALIASES, VERSION_STAMP} = require('./constants');

// Exports
module.exports = {
  /**
   * @function help
   *
   * @description
   * Print a help message, including:
   * - The version of the package.
   * - All available aliases and the asociated commands.
   * - Special, "universal" arguments.
   *
   * @return {Promise<void>} - A promise that resolves once the message has been printed.
   */
  help: _help,

  /**
   * @function run
   *
   * @description
   * Run a command. Could be a complex command with `|`, `&&` and `||` (but not guaranteed to work if too complex :)).
   *
   * It supports argument substitution (independently of the underlying OS) and default/fallback arguments (specified
   * either as static values or as commands to execute and use the output). The following rules apply:
   * - `$*`, `${*}`: Substitute with all arguments (if any).
   * - `$n`, `${n}`: Substitute with the nth argument (if specified), where `n` is a positive integer.
   * - `${*:value}`, `${n:value}`: Substitute with all arguments (`*`) or the nth argument (`n`). If not specified,
   *   substitute with `value`.
   * - `${*:\`command\`}`, `${n:\`command\`}`: Substitute with all arguments (`*`) or the nth argument (`n`). If not
   *   specified, run `command` and substitute with its trimmed output.
   *
   * Hint: `${0:\`command\`}` will always be substituted with the output of `command`. This is useful when you want to
   *       always use the output of `command` in an OS-independent way.
   *
   * @param {string} cmd - The command to run. Could be a complex command with `|`, `&&` and `||` (but not guaranteed to
   *     work if too complex :)).
   * @param {{[option: string]: boolean}} [config={}] - A configuration object. Available options to configure:
   *     - `debug`: If true, produce verbose, debug-friendly output. (Default: false)
   *     - `dryrun`: If true, print the commands instead of actually running them (hopefully). (Default: false)
   *                 NOTE: This is still an experimental feature and not guaranteed to work as expected.
   *     - `returnOutput`: If true, return the output of the command instead of printing it to stdout.
   *
   * @return {Promise<string>} - A promise that resolves once the command has been executed. The resolved value is
   *     either an empty string or the output of the command (if `returnOutput` is true).
   */
  run: _run,
};

// Run
if (require.main === module) {
  _help();
}

// Functions - Definitions
function _help() {
  return Promise.resolve().
    then(() => {
      const sep = '~'.repeat(75);
      const joiner = '  -->  ';
      const message = [
        '',
        versionHeader(),
        '',
        ' Available aliases',
        '===================',
        '',
        Object.keys(ALIASES).
          map(name => helpForGroup(name, ALIASES[name], joiner)).
          join(`\n${sep}\n\n`),
        sep,
        '',
        'All aliases also accept the following arguments:',
        '--al-debug:  Produce verbose, debug-friendly output.',
        '--al-dryrun: Print the commands instead of actually running them (hopefully).',
        '',
      ].join('\n');

      console.log(message);
    }).
    catch(onError);
}

function _run(cmd, config) {
  return doRun(cmd, config || {}).catch(onError);
}

function capitalize(str) {
  return `${str[0].toUpperCase()}${str.slice(1)}`;
}

function doRun(cmd, config) {
  const alArgRe = /^--al-[a-z]/;
  const args = process.argv.slice(2).filter(arg => {
    if (alArgRe.test(arg)) {
      const option = arg.replace(alArgRe, '');
      if (!config.hasOwnProperty(option)) {
        config[option] = true;
      }
      return false;
    }
    return true;
  });

  return expandCmd(cmd, args, config).then(expandedCmd => {
    if (config.debug) {
      console.log(`Input command: '${cmd}'`);
      console.log(`Expanded command: '${expandedCmd}'`);
    }
    return spawnAsPromised(expandedCmd, config);
  });
}

function expandCmd(cmd, args, config) {
  // 1, 3: $*
  // 2, 4: $\d+
  // 5: default/fallback value
  const re = /\$(?:(\*)|(\d+)|{(?:(?:(\*)|(\d+))(?::([^}]*))?)})/g;
  const quote = arg => /\s/.test(arg) ? `"${arg}"` : arg;
  const runConfig = Object.assign({}, config, {returnOutput: true});
  const cmdPromises = {};

  args = args.map(quote);
  let expandedCmd = cmd.replace(re, (_, g1, g2, g3, g4, g5) => {
    // Value based on the supplied arguments.
    let value = (g1 || g3) ? args.join(' ') : args[(g2 || g4) - 1];

    // No argument(s), fall back to default.
    if (!value && g5) {
      const match = /^`(.+)`$/.exec(g5);

      if (!match) {
        // It is a plain ol' fallback value.
        value = quote(g5);
      } else {
        // It is a command.
        const cmd = match[1];
        const placeholder = Math.random();

        cmdPromises[cmd] = (cmdPromises[cmd] || doRun(cmd, runConfig)).then(result => {
          expandedCmd = expandedCmd.replace(placeholder, result.trim());
          return result;
        });

        return placeholder;
      }
    }

    return value || '';
  });

  return Promise.
    all(Object.keys(cmdPromises).map(cmd => cmdPromises[cmd])).
    then(() => expandedCmd);
}

function helpForGroup(groupName, groupSpec, joiner) {
  const maxNameLen = Math.max.apply(Math, Object.keys(groupSpec).map(a => a.length));
  const indent1 = ' '.repeat(2);
  const indent2 = indent1.length + maxNameLen + joiner.length;

  return [
    `${capitalize(groupName)} aliases:`,
    '',
    Object.keys(groupSpec).
      map(name => ({name, spec: groupSpec[name]})).
      map(({name, spec}) => [padRight(name, maxNameLen), wrapLine(spec.desc || spec, indent2)]).
      map(([name, desc]) => `${indent1}${name}${joiner}${desc}`).
      join('\n'),
    '',
  ].join('\n');
}

function insertAfter(newItem, afterItem, arr) {
  let idx = 0;
  while ((idx = arr.indexOf(afterItem, idx)) !== -1) arr.splice(++idx, 0, newItem);
}

function onError(err) {
  console.error('Error:', err);
  process.exit(1);
}

function padRight(str, len) {
  return `${str}${' '.repeat(len - str.length)}`;
}

function parseSingleCmd(cmd, dryrun) {
  const tokens = cmd.
    split('"').
    reduce((arr, str, idx) => {
      const newTokens = (idx % 2) ? [`"${str}"`] : str.split(' ');
      const lastToken = arr[arr.length - 1];

      if (lastToken) arr[arr.length - 1] += newTokens.shift();

      return arr.concat(newTokens);
    }, []).
    filter(Boolean);

  if (dryrun) {
    tokens.unshift('echo');
    insertAfter('echo', '&&', tokens);
    insertAfter('echo', '||', tokens);
  }

  return {
    executable: tokens.shift(),
    args: tokens,
  };
}

function spawnAsPromised(rawCmd, {debug, dryrun, returnOutput}) {
  return new Promise((resolve, reject) => {
    let data = '';

    const pipedCmdSpecs = rawCmd.
      split(/\s+\|\s+/).
      map(cmd => parseSingleCmd(cmd, dryrun));

    const lastStdout = pipedCmdSpecs.reduce((prevStdout, cmdSpec, idx, arr) => {
      const isLast = idx === arr.length - 1;

      const executable = cmdSpec.executable;
      const args = cmdSpec.args;
      const options = {
        shell: true,
        stdio: [
          prevStdout ? 'pipe' : 'inherit',
          'pipe',
          'inherit',
        ],
      };

      if (debug) {
        console.log(`  Running: '${executable}', '${args}', (stdio: ${options.stdio})`);
      }

      const proc = spawn(executable, args, options).
        on('error', reject).
        on('exit', (code, signal) => {
          if (code !== 0) return reject(code || signal);
          if (isLast) return resolve(data);
        });

      if (prevStdout) prevStdout.pipe(proc.stdin);

      return proc.stdout;
    }, null);

    if (!returnOutput) {
      lastStdout.pipe(process.stdout);
    } else {
      const outputStream = new PassThrough();
      outputStream.on('data', d => data += d);

      lastStdout.pipe(outputStream);
    }
  });
}

function versionHeader() {
  const versionLine = `|  ${VERSION_STAMP}  |`;
  const len = versionLine.length - 2;
  const outerLine = ` ${'-'.repeat(len)} `;
  const innerLine = `|${' '.repeat(len)}|`;

  return [
    outerLine,
    innerLine,
    versionLine,
    innerLine,
    outerLine,
  ].join('\n');
}

function wrapLine(line, indentLen) {
  const limit = 75;

  if (line.length <= limit) {
    return line;
  }

  const extraIndentLen = 2;
  const extraIndent = ' '.repeat(extraIndentLen);
  const indent = ' '.repeat(indentLen);
  const sep = Date.now() + Math.random();
  const re = RegExp(/\s&&/.test(line) ? '(\\s)(&&)' : `(.{${limit},}?\\s)()`, 'g');

  let lines = line.replace(re, `$1${sep}$2`).split(sep).filter(Boolean);
  if (lines.length > 1) {
    lines = lines.map(l => wrapLine(l, indentLen + extraIndentLen));
  }

  return lines.join(`\n${indent}${extraIndent}`);
}
<commit_msg>fix(run): inherit stdout when `returnOutput` is false<commit_after>#!/usr/bin/env node
'use strict';

// Imports
const {spawn} = require('child_process');
const {PassThrough} = require('stream');
const {ALIASES, VERSION_STAMP} = require('./constants');

// Exports
module.exports = {
  /**
   * @function help
   *
   * @description
   * Print a help message, including:
   * - The version of the package.
   * - All available aliases and the asociated commands.
   * - Special, "universal" arguments.
   *
   * @return {Promise<void>} - A promise that resolves once the message has been printed.
   */
  help: _help,

  /**
   * @function run
   *
   * @description
   * Run a command. Could be a complex command with `|`, `&&` and `||` (but not guaranteed to work if too complex :)).
   *
   * It supports argument substitution (independently of the underlying OS) and default/fallback arguments (specified
   * either as static values or as commands to execute and use the output). The following rules apply:
   * - `$*`, `${*}`: Substitute with all arguments (if any).
   * - `$n`, `${n}`: Substitute with the nth argument (if specified), where `n` is a positive integer.
   * - `${*:value}`, `${n:value}`: Substitute with all arguments (`*`) or the nth argument (`n`). If not specified,
   *   substitute with `value`.
   * - `${*:\`command\`}`, `${n:\`command\`}`: Substitute with all arguments (`*`) or the nth argument (`n`). If not
   *   specified, run `command` and substitute with its trimmed output.
   *
   * Hint: `${0:\`command\`}` will always be substituted with the output of `command`. This is useful when you want to
   *       always use the output of `command` in an OS-independent way.
   *
   * @param {string} cmd - The command to run. Could be a complex command with `|`, `&&` and `||` (but not guaranteed to
   *     work if too complex :)).
   * @param {{[option: string]: boolean}} [config={}] - A configuration object. Available options to configure:
   *     - `debug`: If true, produce verbose, debug-friendly output. (Default: false)
   *     - `dryrun`: If true, print the commands instead of actually running them (hopefully). (Default: false)
   *                 NOTE: This is still an experimental feature and not guaranteed to work as expected.
   *     - `returnOutput`: If true, return the output of the command instead of printing it to stdout.
   *
   * @return {Promise<string>} - A promise that resolves once the command has been executed. The resolved value is
   *     either an empty string or the output of the command (if `returnOutput` is true).
   */
  run: _run,
};

// Run
if (require.main === module) {
  _help();
}

// Functions - Definitions
function _help() {
  return Promise.resolve().
    then(() => {
      const sep = '~'.repeat(75);
      const joiner = '  -->  ';
      const message = [
        '',
        versionHeader(),
        '',
        ' Available aliases',
        '===================',
        '',
        Object.keys(ALIASES).
          map(name => helpForGroup(name, ALIASES[name], joiner)).
          join(`\n${sep}\n\n`),
        sep,
        '',
        'All aliases also accept the following arguments:',
        '--al-debug:  Produce verbose, debug-friendly output.',
        '--al-dryrun: Print the commands instead of actually running them (hopefully).',
        '',
      ].join('\n');

      console.log(message);
    }).
    catch(onError);
}

function _run(cmd, config) {
  return doRun(cmd, config || {}).catch(onError);
}

function capitalize(str) {
  return `${str[0].toUpperCase()}${str.slice(1)}`;
}

function doRun(cmd, config) {
  const alArgRe = /^--al-[a-z]/;
  const args = process.argv.slice(2).filter(arg => {
    if (alArgRe.test(arg)) {
      const option = arg.replace(alArgRe, '');
      if (!config.hasOwnProperty(option)) {
        config[option] = true;
      }
      return false;
    }
    return true;
  });

  return expandCmd(cmd, args, config).then(expandedCmd => {
    if (config.debug) {
      console.log(`Input command: '${cmd}'`);
      console.log(`Expanded command: '${expandedCmd}'`);
    }
    return spawnAsPromised(expandedCmd, config);
  });
}

function expandCmd(cmd, args, config) {
  // 1, 3: $*
  // 2, 4: $\d+
  // 5: default/fallback value
  const re = /\$(?:(\*)|(\d+)|{(?:(?:(\*)|(\d+))(?::([^}]*))?)})/g;
  const quote = arg => /\s/.test(arg) ? `"${arg}"` : arg;
  const runConfig = Object.assign({}, config, {returnOutput: true});
  const cmdPromises = {};

  args = args.map(quote);
  let expandedCmd = cmd.replace(re, (_, g1, g2, g3, g4, g5) => {
    // Value based on the supplied arguments.
    let value = (g1 || g3) ? args.join(' ') : args[(g2 || g4) - 1];

    // No argument(s), fall back to default.
    if (!value && g5) {
      const match = /^`(.+)`$/.exec(g5);

      if (!match) {
        // It is a plain ol' fallback value.
        value = quote(g5);
      } else {
        // It is a command.
        const cmd = match[1];
        const placeholder = Math.random();

        cmdPromises[cmd] = (cmdPromises[cmd] || doRun(cmd, runConfig)).then(result => {
          expandedCmd = expandedCmd.replace(placeholder, result.trim());
          return result;
        });

        return placeholder;
      }
    }

    return value || '';
  });

  return Promise.
    all(Object.keys(cmdPromises).map(cmd => cmdPromises[cmd])).
    then(() => expandedCmd);
}

function helpForGroup(groupName, groupSpec, joiner) {
  const maxNameLen = Math.max.apply(Math, Object.keys(groupSpec).map(a => a.length));
  const indent1 = ' '.repeat(2);
  const indent2 = indent1.length + maxNameLen + joiner.length;

  return [
    `${capitalize(groupName)} aliases:`,
    '',
    Object.keys(groupSpec).
      map(name => ({name, spec: groupSpec[name]})).
      map(({name, spec}) => [padRight(name, maxNameLen), wrapLine(spec.desc || spec, indent2)]).
      map(([name, desc]) => `${indent1}${name}${joiner}${desc}`).
      join('\n'),
    '',
  ].join('\n');
}

function insertAfter(newItem, afterItem, arr) {
  let idx = 0;
  while ((idx = arr.indexOf(afterItem, idx)) !== -1) arr.splice(++idx, 0, newItem);
}

function onError(err) {
  console.error('Error:', err);
  process.exit(1);
}

function padRight(str, len) {
  return `${str}${' '.repeat(len - str.length)}`;
}

function parseSingleCmd(cmd, dryrun) {
  const tokens = cmd.
    split('"').
    reduce((arr, str, idx) => {
      const newTokens = (idx % 2) ? [`"${str}"`] : str.split(' ');
      const lastToken = arr[arr.length - 1];

      if (lastToken) arr[arr.length - 1] += newTokens.shift();

      return arr.concat(newTokens);
    }, []).
    filter(Boolean);

  if (dryrun) {
    tokens.unshift('echo');
    insertAfter('echo', '&&', tokens);
    insertAfter('echo', '||', tokens);
  }

  return {
    executable: tokens.shift(),
    args: tokens,
  };
}

function spawnAsPromised(rawCmd, {debug, dryrun, returnOutput}) {
  return new Promise((resolve, reject) => {
    let data = '';

    const pipedCmdSpecs = rawCmd.
      split(/\s+\|\s+/).
      map(cmd => parseSingleCmd(cmd, dryrun));

    const lastStdout = pipedCmdSpecs.reduce((prevStdout, cmdSpec, idx, arr) => {
      const isLast = idx === arr.length - 1;
      const pipeOuput = !isLast || returnOutput;

      const executable = cmdSpec.executable;
      const args = cmdSpec.args;
      const options = {
        shell: true,
        stdio: [
          prevStdout ? 'pipe' : 'inherit',
          pipeOuput ? 'pipe' : 'inherit',
          'inherit',
        ],
      };

      if (debug) {
        console.log(`  Running ${idx + 1}/${arr.length}: '${executable}', '${args}', (stdio: ${options.stdio})`);
      }

      const proc = spawn(executable, args, options).
        on('error', reject).
        on('exit', (code, signal) => {
          if (code !== 0) return reject(code || signal);
          if (isLast) return resolve(data);
        });

      if (prevStdout) prevStdout.pipe(proc.stdin);

      return proc.stdout;
    }, null);

    if (returnOutput) {
      const outputStream = new PassThrough();
      outputStream.on('data', d => data += d);
      lastStdout.pipe(outputStream);
    }
  });
}

function versionHeader() {
  const versionLine = `|  ${VERSION_STAMP}  |`;
  const len = versionLine.length - 2;
  const outerLine = ` ${'-'.repeat(len)} `;
  const innerLine = `|${' '.repeat(len)}|`;

  return [
    outerLine,
    innerLine,
    versionLine,
    innerLine,
    outerLine,
  ].join('\n');
}

function wrapLine(line, indentLen) {
  const limit = 75;

  if (line.length <= limit) {
    return line;
  }

  const extraIndentLen = 2;
  const extraIndent = ' '.repeat(extraIndentLen);
  const indent = ' '.repeat(indentLen);
  const sep = Date.now() + Math.random();
  const re = RegExp(/\s&&/.test(line) ? '(\\s)(&&)' : `(.{${limit},}?\\s)()`, 'g');

  let lines = line.replace(re, `$1${sep}$2`).split(sep).filter(Boolean);
  if (lines.length > 1) {
    lines = lines.map(l => wrapLine(l, indentLen + extraIndentLen));
  }

  return lines.join(`\n${indent}${extraIndent}`);
}
<|endoftext|>


<commit_before>// Find the key for the larger of the values.
//
// obj should be an object with two keys and numerical values.  Returns the key
// corresponding to the larger value, or null if they are equal.
function winner(obj) {
  const [k1, k2] = Object.keys(obj);
  if (obj[k1] > obj[k2]) {
    return k1;
  } else if (obj[k2] > obj[k1]) {
    return k2;
  } else {
    return null;
  }
}

// Return whether the precincts are adjacent.
//
// Each argument should be an object with properties x, y, width, and height.
// TODO(benkraft): Maybe shouldn't allow just touching at corners?
function adjacent(precinct1, precinct2) {
  return (
    precinct1.x + precinct1.width >= precinct2.x
    && precinct2.x + precinct2.width >= precinct1.x
    && precinct1.y + precinct1.height >= precinct2.y
    && precinct2.y + precinct2.height >= precinct1.y);
}

// Return whether all precincts in the list are adjacent.
//
// See adjacent() above for details.
function contiguous(precincts) {
  // TODO(benkraft): Maybe there is a non-n^2 way?
  if (precincts.length <= 1) {
    return true;  // vacuously
  }
  console.log(precincts);

  // Build an adjacency list
  const adjacency = Array.from(precincts, () => []);
  precincts.forEach((p1, i) => {
    precincts.slice(i + 1).forEach((p2, j) => {
      if (adjacent(p1, p2)) {
        adjacency[i].push(j + i + 1);
        adjacency[j + i + 1].push(i);
      }
    });
  });

  // DFS on the list to see if we get to everything
  const found = adjacency.map(() => false);
  const stack = [0];
  while (stack.length) {
    let item = stack.pop();
    if (!found[item]) {
      found[item] = true;
      Array.prototype.push.apply(stack, adjacency[item]);
    }
  }

  return found.every(x => x);
}

export {winner, contiguous};
<commit_msg>Remove stray logging<commit_after>// Find the key for the larger of the values.
//
// obj should be an object with two keys and numerical values.  Returns the key
// corresponding to the larger value, or null if they are equal.
function winner(obj) {
  const [k1, k2] = Object.keys(obj);
  if (obj[k1] > obj[k2]) {
    return k1;
  } else if (obj[k2] > obj[k1]) {
    return k2;
  } else {
    return null;
  }
}

// Return whether the precincts are adjacent.
//
// Each argument should be an object with properties x, y, width, and height.
// TODO(benkraft): Maybe shouldn't allow just touching at corners?
function adjacent(precinct1, precinct2) {
  return (
    precinct1.x + precinct1.width >= precinct2.x
    && precinct2.x + precinct2.width >= precinct1.x
    && precinct1.y + precinct1.height >= precinct2.y
    && precinct2.y + precinct2.height >= precinct1.y);
}

// Return whether all precincts in the list are adjacent.
//
// See adjacent() above for details.
function contiguous(precincts) {
  // TODO(benkraft): Maybe there is a non-n^2 way?
  if (precincts.length <= 1) {
    return true;  // vacuously
  }

  // Build an adjacency list
  const adjacency = Array.from(precincts, () => []);
  precincts.forEach((p1, i) => {
    precincts.slice(i + 1).forEach((p2, j) => {
      if (adjacent(p1, p2)) {
        adjacency[i].push(j + i + 1);
        adjacency[j + i + 1].push(i);
      }
    });
  });

  // DFS on the list to see if we get to everything
  const found = adjacency.map(() => false);
  const stack = [0];
  while (stack.length) {
    let item = stack.pop();
    if (!found[item]) {
      found[item] = true;
      Array.prototype.push.apply(stack, adjacency[item]);
    }
  }

  return found.every(x => x);
}

export {winner, contiguous};
<|endoftext|>


<commit_before>(function () {
	// Commenting out proper scoping for console access     var Hockey = {};
	Hockey = {};
	Hockey.stage = new Kinetic.Stage({
		container: 'container',
		width:800,
		height:800
	});

	Hockey.lastPosition = { x: 0, y: 0 };
	Hockey.rink = new Rink(Hockey);
	Hockey.puck = new Puck(Hockey);
	Hockey.teams = [];
	var playersLayer = new Kinetic.Layer( {id: 'players'} );
	for(var i = 1; i <= 2; ++i) {
		Hockey.teams.push(new Team(Hockey, i, playersLayer));
	}

	Hockey.detectCollisions = function() {
		var playerCollisions = [];
		for(i = 0; i < this.teams.length; i++) {
			for(j = 0; j < this.teams[i].players.length; j++) {
				playerCollision = this.teams[i].players[j].detectCollision();
				if(playerCollision && playerCollision.collisionType) {
					playerCollisions.push(playerCollision);
				}
				this.teams[i].players[j].rotationSpeed = 0; // Reset the rotation speed after every detection, I think
			}
		}
		return playerCollisions.length > 0 ? playerCollisions : null;
	}

	Hockey.handleCollisions = function() {
		var collisions = this.detectCollisions();
		if(collisions != null) {
			for(var i = 0; i < collisions.length; i++) {
				var collision = collisions[i];
				if(collision.collisionType == Player.CONSTANTS.collisionTypes.SHOT) {
					var shotAngle = (collision.player.stickAngle + (Math.PI / 2 * collision.player.rotationDirection));
					var shotSpeed = 5 * collision.rotationSpeed + collision.movementSpeed; // init value for a stationary puck
					if ( Math.abs(collision.movementSpeed) + collision.rotationSpeed == 0) { // stationary player
						shotSpeed = this.puck.velocity * .2;
					} else if (this.puck.velocity > 0) { // moving puck, stationary player
						var shotSpeed = Math.min( 10, (collision.rotationSpeed + _hockey.puck.velocity + collision.rotationSpeed) * 2 + 1); //
					}
					if(shotSpeed > 1) {  // don't bother trying to shoot w/o velocity
						if(console) console.log('Shot by Player', collision.player.position.positionId, ' on team ', collision.player.team.id);
						this.puck.shoot(shotSpeed, shotAngle);
					}
				} else if(collision.collisionType == Player.CONSTANTS.collisionTypes.BUMP) {
					// Body Stroke direction should depend on the angle where the puck hits the circle of the body.  This is just a placeholder
					var bumpSpeed = Math.max(1.2, this.puck.velocity * .3);
					//if(console) console.log("Bump off player", collision.player.position.positionId, " on team ", collision.player.team.id, collision.deflectionAngle, this.puck.velocity, bumpSpeed);
					// move the puck out of the range of another bump before moving to prevent the dreaded double/triple/infiniti-bump
					// new x and y positions are out of wack, let's revisit this
					this.puck.puck.setX(collision.newX + this.rink.offset.x);
					this.puck.puck.setY(collision.newY + this.rink.offset.y);
					this.puck.layer.draw();
					this.puck.shoot(bumpSpeed, collision.deflectionAngle);
				}
			}
		}
	}

	Hockey.stage.add(playersLayer);
	var _hockey = Hockey;
	Hockey.stage.on('mousemove touchmove', function(event) {
		var eventPosition = this.getPointerPosition();
		var x = eventPosition.x;
		var y = eventPosition.y - _hockey.rink.offset.y;

		// Control team 0
		var selectedPlayer = _hockey.teams[0].getSelectedPlayer();
		if(selectedPlayer != null) {
			if(!selectedPlayer.animation || !selectedPlayer.animation.isRunning()) {
				// Move vertically only
				selectedPlayer.move(selectedPlayer.location.x, y);
			}
			if(Math.abs(x - _hockey.lastPosition.x) > 1) { // Don't rotate on slight deviations
				var rotationRads = (x - _hockey.lastPosition.x)
				if(Math.abs(rotationRads) < 5) { // rotate at least the 5 px of movement worth
					rotationRads = rotationRads < 0 ? -5 : 5;
				}
				selectedPlayer.rotate( rotationRads / 10);
			}

			_hockey.handleCollisions();

		}
		_hockey.lastPosition = {x: x, y: y};
	});

	// assume 600px height rink for meow
	var scale = window.innerHeight / 600
	//console.log(document.getElementById("zoom-wrapper").style["-webkit-transform"] = "scale("+scale + ")");
})();<commit_msg>No need for the giant stage/canvas<commit_after>(function () {
	// Commenting out proper scoping for console access     var Hockey = {};
	Hockey = {};
	Hockey.stage = new Kinetic.Stage({
		container: 'container',
		width:235,
		height:550
	});

	Hockey.lastPosition = { x: 0, y: 0 };
	Hockey.rink = new Rink(Hockey);
	Hockey.puck = new Puck(Hockey);
	Hockey.teams = [];
	var playersLayer = new Kinetic.Layer( {id: 'players'} );
	for(var i = 1; i <= 2; ++i) {
		Hockey.teams.push(new Team(Hockey, i, playersLayer));
	}

	Hockey.detectCollisions = function() {
		var playerCollisions = [];
		for(i = 0; i < this.teams.length; i++) {
			for(j = 0; j < this.teams[i].players.length; j++) {
				playerCollision = this.teams[i].players[j].detectCollision();
				if(playerCollision && playerCollision.collisionType) {
					playerCollisions.push(playerCollision);
				}
				this.teams[i].players[j].rotationSpeed = 0; // Reset the rotation speed after every detection, I think
			}
		}
		return playerCollisions.length > 0 ? playerCollisions : null;
	}

	Hockey.handleCollisions = function() {
		var collisions = this.detectCollisions();
		if(collisions != null) {
			for(var i = 0; i < collisions.length; i++) {
				var collision = collisions[i];
				if(collision.collisionType == Player.CONSTANTS.collisionTypes.SHOT) {
					var shotAngle = (collision.player.stickAngle + (Math.PI / 2 * collision.player.rotationDirection));
					var shotSpeed = 5 * collision.rotationSpeed + collision.movementSpeed; // init value for a stationary puck
					if ( Math.abs(collision.movementSpeed) + collision.rotationSpeed == 0) { // stationary player
						shotSpeed = this.puck.velocity * .2;
					} else if (this.puck.velocity > 0) { // moving puck, stationary player
						var shotSpeed = Math.min( 10, (collision.rotationSpeed + _hockey.puck.velocity + collision.rotationSpeed) * 2 + 1); //
					}
					if(shotSpeed > 1) {  // don't bother trying to shoot w/o velocity
						if(console) console.log('Shot by Player', collision.player.position.positionId, ' on team ', collision.player.team.id);
						this.puck.shoot(shotSpeed, shotAngle);
					}
				} else if(collision.collisionType == Player.CONSTANTS.collisionTypes.BUMP) {
					// Body Stroke direction should depend on the angle where the puck hits the circle of the body.  This is just a placeholder
					var bumpSpeed = Math.max(1.2, this.puck.velocity * .3);
					if(console) console.log("Bump off player", collision.player.position.positionId, " on team ", collision.player.team.id, collision.deflectionAngle, this.puck.velocity, bumpSpeed);
					// move the puck out of the range of another bump before moving to prevent the dreaded double/triple/infiniti-bump
					// new x and y positions are out of wack, let's revisit this
					this.puck.puck.setX(collision.newX + this.rink.offset.x);
					this.puck.puck.setY(collision.newY + this.rink.offset.y);
					this.puck.layer.draw();
					this.puck.shoot(bumpSpeed, collision.deflectionAngle);
				}
			}
		}
	}

	Hockey.stage.add(playersLayer);
	var _hockey = Hockey;
	Hockey.stage.on('mousemove touchmove', function(event) {
		var eventPosition = this.getPointerPosition();
		var x = eventPosition.x;
		var y = eventPosition.y - _hockey.rink.offset.y;

		// Control team 0
		var selectedPlayer = _hockey.teams[0].getSelectedPlayer();
		if(selectedPlayer != null) {
			if(!selectedPlayer.animation || !selectedPlayer.animation.isRunning()) {
				// Move vertically only
				selectedPlayer.move(selectedPlayer.location.x, y);
			}
			if(Math.abs(x - _hockey.lastPosition.x) > 1) { // Don't rotate on slight deviations
				var rotationRads = (x - _hockey.lastPosition.x)
				if(Math.abs(rotationRads) < 5) { // rotate at least the 5 px of movement worth
					rotationRads = rotationRads < 0 ? -5 : 5;
				}
				selectedPlayer.rotate( rotationRads / 10);
			}

			_hockey.handleCollisions();

		}
		_hockey.lastPosition = {x: x, y: y};
	});

	// assume 600px height rink for meow
	var scale = window.innerHeight / 600
	//console.log(document.getElementById("zoom-wrapper").style["-webkit-transform"] = "scale("+scale + ")");
})();<|endoftext|>


<commit_before>'use strict';

const gulp = require('gulp');
const $ = require('gulp-load-plugins')();

const buildPath = 'dist/';

gulp.task('minify', ['babel'], function () {
	return gulp.src(['dist/sjs.js'])
		.pipe($.uglifyjs('sjs.min.js'))
		.pipe(gulp.dest(buildPath));
});

gulp.task('babel', function () {
	return gulp.src(['src/sjs.js'])
		.pipe($.babel({
			presets: ['es2015'],
			plugins: ['es6-promise']
		}))
		.pipe(gulp.dest(buildPath));
});

gulp.task('default', ['watch']);

gulp.task('watch', function () {
	gulp.watch(['src/sjs.js'], ['minify']);
});
<commit_msg>remove the babel plugins<commit_after>'use strict';

const gulp = require('gulp');
const $ = require('gulp-load-plugins')();

const buildPath = 'dist/';

gulp.task('minify', ['babel'], function () {
	return gulp.src(['dist/sjs.js'])
		.pipe($.uglifyjs('sjs.min.js'))
		.pipe(gulp.dest(buildPath));
});

gulp.task('babel', function () {
	return gulp.src(['src/sjs.js'])
		.pipe($.babel({
			presets: ['es2015'],
			//plugins: ['es6-promise']
		}))
		.pipe(gulp.dest(buildPath));
});

gulp.task('default', ['watch']);

gulp.task('watch', function () {
	gulp.watch(['src/sjs.js'], ['minify']);
});
<|endoftext|>


<commit_before>var px = 0, py = 0, pz = 0, cpz = 0;
var pointerX = 0, pointerY = 0;

var engine, camera, geometry, renderer;

var hasLeap = false;

var zero = new SQR.V3();
var temp = new SQR.V3();
var up = new SQR.V3();

var th = 0;
var tempQ = new SQR.Quaternion();

function v(x, y, z) {
    return new SQR.V3(x, y, z);
}

function addPointer(x, y, z) {
    var p1 = new SQR.Transform();
    p1.geometry = geometry;
    p1.renderer = renderer;
    p1.position.set(x, y, z).norm().mul(radius);

    p1.position.copyTo(temp);
    up.set(0,1,0);
    p1.useQuaternion = true;
    p1.rotationQ.lookAt(temp, up);

    return p1;
}

function addRing() {
    var mesh = new SQR.Transform();

    mesh.add(addPointer(0, 10, 0));
    mesh.add(addPointer(0, -10, 0));

    mesh.add(addPointer(10, 0, 0));
    mesh.add(addPointer(-10, 0, 0));

    mesh.add(addPointer(10, 10, 0));
    mesh.add(addPointer(10, -10, 0));
    mesh.add(addPointer(-10, 10, 0));
    mesh.add(addPointer(-10, -10, 0));

    // if(!touch) {
        mesh.add(addPointer(0, 0, 10));
        mesh.add(addPointer(0, 0, -10));

        mesh.add(addPointer(0, 10, 10));
        mesh.add(addPointer(0, 10, -10));
        mesh.add(addPointer(0, -10, 10));
        mesh.add(addPointer(0, -10, -10));

        mesh.add(addPointer(10, 0, 10));
        mesh.add(addPointer(10, 0, -10));
        mesh.add(addPointer(-10, 0, 10));
        mesh.add(addPointer(-10, 0, -10));
    // }

    engine.add(mesh);

    return mesh;
}

var readLeapValues = function(frame) {
    hasLeap = true;
    if(frame.hands.length > 0) {
        var hand = frame.hands[0];

        var rotX = (hand._rotation[2]);
        var rotY = (hand._rotation[1]);
        var rotZ = (hand._rotation[0]);

        var posX = (hand.palmPosition[0]);
        var posY = (hand.palmPosition[2]);
        var posZ = (hand.palmPosition[1]);

        px = posX / 100;
        py = posZ / 100;
        pz = Math.max(0, posY / 8);
    }
};

document.addEventListener('touchmove', function(e) {
    e.preventDefault();
    pointerX = e.targetTouches[0].pageX;
    pointerY = e.targetTouches[0].pageY;
});

document.addEventListener('mousemove', function(e) {
    e.preventDefault();
    pointerX = e.pageX;
    pointerY = e.pageY;
});

function render() {
    requestAnimFrame(render);

    if(!hasLeap) {
        px += pointerX / window.innerWidth;
        py += pointerY / window.innerHeight;
    }

    cpz += (pz - cpz) / 10;

    // Inner rotation around own axis
    for(var i = 0; i < m1.numChildren; i++) {
        var p = m1.children[i].position;
        tempQ.fromAngleAxis(0.001, p.x, p.y, p.z);
        m1.children[i].rotationQ.mul(tempQ);
        m1.children[i].position.norm().mul(7 + cpz * 0.5);
    }

    for(var i = 0; i < m2.numChildren; i++) {
        var p = m2.children[i].position;
        tempQ.fromAngleAxis(-0.002, p.x, p.y, p.z);
        m2.children[i].rotationQ.mul(tempQ);
        m2.children[i].position.norm().mul(25 + cpz);
    }

    m1.rotation.x += (py*2 - m1.rotation.x) / 10;
    m1.rotation.y += (px*2 - m1.rotation.y) / 10;

    m2.rotation.x -= (py + m2.rotation.x) / 10;
    m2.rotation.y -= (px + m2.rotation.y) / 10;

    engine.render(camera);
}

window.addEventListener('resize', function() {
    engine.setSize(window.innerWidth, window.innerHeight);
    engine.setProjection(45);
});

// Action

engine = new SQR.Squareroot(document.getElementById('canvas'), document.getElementById("div-container"));
engine.setBackground('#000000');
engine.setSize(window.innerWidth, window.innerHeight);
engine.setProjection(45);

camera = new SQR.Transform();
camera.position.z = 120;
engine.add(camera);

geometry = new SQR.Geometry();

var blue = new SQR.Color(186, 100, 70);
var red = new SQR.Color(0, 100, 70);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, -1), v(-2, 2, 0), blue);
geometry.addTriangle(v(2, -2, 0), v(0, 0, -1), v(2, 2, 0), blue);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, -1), v(2, -2, 0), blue);
geometry.addTriangle(v(-2, 2, 0), v(0, 0, -1), v(2, 2, 0), blue);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, 6), v(-2, 2, 0), blue);
geometry.addTriangle(v(2, -2, 0), v(0, 0, 6), v(2, 2, 0), blue);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, 6), v(2, -2, 0), blue);
geometry.addTriangle(v(-2, 2, 0), v(0, 0, 6), v(2, 2, 0), blue);

renderer = new SQR.Polygon();
renderer.useLight = true;
radius = window.innerWidth / 100;
m1 = addRing();

geometry = new SQR.Geometry();

geometry.addTriangle(v(-2, -2, 0), v(0, 0, -1), v(-2, 2, 0), red);
geometry.addTriangle(v(2, -2, 0), v(0, 0, -1), v(2, 2, 0), red);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, -1), v(2, -2, 0), red);
geometry.addTriangle(v(-2, 2, 0), v(0, 0, -1), v(2, 2, 0), red);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, 6), v(-2, 2, 0), red);
geometry.addTriangle(v(2, -2, 0), v(0, 0, 6), v(2, 2, 0), red);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, 6), v(2, -2, 0), red);
geometry.addTriangle(v(-2, 2, 0), v(0, 0, 6), v(2, 2, 0), red);

radius = window.innerWidth / 33;
m2 = addRing();

if(window.Leap) {
    Leap.loop(readLeapValues);
}

render();

document.title = "Polygons | v0.26";

<commit_msg>Tweaking polygons demo for touch screens (2)<commit_after>var px = 0, py = 0, pz = 0, cpz = 0;
var pointerX = 0, pointerY = 0;

var engine, camera, geometry, renderer;

var hasLeap = false;

var zero = new SQR.V3();
var temp = new SQR.V3();
var up = new SQR.V3();

var th = 0;
var tempQ = new SQR.Quaternion();

function v(x, y, z) {
    return new SQR.V3(x, y, z);
}

function addPointer(x, y, z) {
    var p1 = new SQR.Transform();
    p1.geometry = geometry;
    p1.renderer = renderer;
    p1.position.set(x, y, z).norm().mul(radius);

    p1.position.copyTo(temp);
    up.set(0,1,0);
    p1.useQuaternion = true;
    p1.rotationQ.lookAt(temp, up);

    return p1;
}

function addRing() {
    var mesh = new SQR.Transform();

    mesh.add(addPointer(0, 10, 0));
    mesh.add(addPointer(0, -10, 0));

    mesh.add(addPointer(10, 0, 0));
    mesh.add(addPointer(-10, 0, 0));

    mesh.add(addPointer(10, 10, 0));
    mesh.add(addPointer(10, -10, 0));
    mesh.add(addPointer(-10, 10, 0));
    mesh.add(addPointer(-10, -10, 0));

    // if(!touch) {
        mesh.add(addPointer(0, 0, 10));
        mesh.add(addPointer(0, 0, -10));

        mesh.add(addPointer(0, 10, 10));
        mesh.add(addPointer(0, 10, -10));
        mesh.add(addPointer(0, -10, 10));
        mesh.add(addPointer(0, -10, -10));

        mesh.add(addPointer(10, 0, 10));
        mesh.add(addPointer(10, 0, -10));
        mesh.add(addPointer(-10, 0, 10));
        mesh.add(addPointer(-10, 0, -10));
    // }

    engine.add(mesh);

    return mesh;
}

var readLeapValues = function(frame) {
    hasLeap = true;
    if(frame.hands.length > 0) {
        var hand = frame.hands[0];

        var rotX = (hand._rotation[2]);
        var rotY = (hand._rotation[1]);
        var rotZ = (hand._rotation[0]);

        var posX = (hand.palmPosition[0]);
        var posY = (hand.palmPosition[2]);
        var posZ = (hand.palmPosition[1]);

        px = posX / 100;
        py = posZ / 100;
        pz = Math.max(0, posY / 8);
    }
};

document.addEventListener('touchmove', function(e) {
    e.preventDefault();
    pointerX = e.targetTouches[0].pageX;
    pointerY = e.targetTouches[0].pageY;
});

document.addEventListener('mousemove', function(e) {
    e.preventDefault();
    pointerX = e.pageX;
    pointerY = e.pageY;
});

function render() {
    requestAnimFrame(render);

    if(!hasLeap) {
        px = (pointerX / window.innerWidth) - 0.5;
        py = (pointerY / window.innerHeight) - 0.5;
    }

    cpz += (pz - cpz) / 10;

    // Inner rotation around own axis
    for(var i = 0; i < m1.numChildren; i++) {
        var p = m1.children[i].position;
        tempQ.fromAngleAxis(0.001, p.x, p.y, p.z);
        m1.children[i].rotationQ.mul(tempQ);
        m1.children[i].position.norm().mul(7 + cpz * 0.5);
    }

    for(var i = 0; i < m2.numChildren; i++) {
        var p = m2.children[i].position;
        tempQ.fromAngleAxis(-0.002, p.x, p.y, p.z);
        m2.children[i].rotationQ.mul(tempQ);
        m2.children[i].position.norm().mul(25 + cpz);
    }

    m1.rotation.x += (py*2 - m1.rotation.x) / 10;
    m1.rotation.y += (px*2 - m1.rotation.y) / 10;

    m2.rotation.x -= (py + m2.rotation.x) / 10;
    m2.rotation.y -= (px + m2.rotation.y) / 10;

    engine.render(camera);
}

window.addEventListener('resize', function() {
    engine.setSize(window.innerWidth, window.innerHeight);
    engine.setProjection(45);
});

// Action

engine = new SQR.Squareroot(document.getElementById('canvas'), document.getElementById("div-container"));
engine.setBackground('#000000');
engine.setSize(window.innerWidth, window.innerHeight);
engine.setProjection(45);

camera = new SQR.Transform();
camera.position.z = 120;
engine.add(camera);

geometry = new SQR.Geometry();

var blue = new SQR.Color(186, 100, 70);
var red = new SQR.Color(0, 100, 70);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, -1), v(-2, 2, 0), blue);
geometry.addTriangle(v(2, -2, 0), v(0, 0, -1), v(2, 2, 0), blue);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, -1), v(2, -2, 0), blue);
geometry.addTriangle(v(-2, 2, 0), v(0, 0, -1), v(2, 2, 0), blue);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, 6), v(-2, 2, 0), blue);
geometry.addTriangle(v(2, -2, 0), v(0, 0, 6), v(2, 2, 0), blue);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, 6), v(2, -2, 0), blue);
geometry.addTriangle(v(-2, 2, 0), v(0, 0, 6), v(2, 2, 0), blue);

renderer = new SQR.Polygon();
renderer.useLight = true;
radius = window.innerWidth / 100;
m1 = addRing();

geometry = new SQR.Geometry();

geometry.addTriangle(v(-2, -2, 0), v(0, 0, -1), v(-2, 2, 0), red);
geometry.addTriangle(v(2, -2, 0), v(0, 0, -1), v(2, 2, 0), red);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, -1), v(2, -2, 0), red);
geometry.addTriangle(v(-2, 2, 0), v(0, 0, -1), v(2, 2, 0), red);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, 6), v(-2, 2, 0), red);
geometry.addTriangle(v(2, -2, 0), v(0, 0, 6), v(2, 2, 0), red);

geometry.addTriangle(v(-2, -2, 0), v(0, 0, 6), v(2, -2, 0), red);
geometry.addTriangle(v(-2, 2, 0), v(0, 0, 6), v(2, 2, 0), red);

radius = window.innerWidth / 33;
m2 = addRing();

if(window.Leap) {
    Leap.loop(readLeapValues);
}

render();

document.title = "Polygons | v0.27";

<|endoftext|>


<commit_before>#!/usr/bin/env nodemon

var fs = require('fs');
var express = require('express');
var crypto = require('crypto');
var url = require('url');
var _ = require('underscore');

var app = express();
var serverPort = 3000;

var ROOM_TYPES = [
    {
        id: 'shirt-sizes',
        title: 'Shirt Sizes',
        code: 'a',
        cards: [
            {
                value: 1,
                label: 'XS'
            }, {
                value: 2,
                label: 'S'
            }, {
                value: 3,
                label: 'M'
            }, {
                value: 4,
                label: 'L'
            }, {
                value: 5,
                label: 'XL'
            }
        ]
    },

    {
        id: 'sequence',
        title: 'Sequence',
        code: 'b',
        cards: [
            {
                value: 0,
                label: '0'
            }, {
                value: 1,
                label: '1'
            }, {
                value: 2,
                label: '2'
            }, {
                value: 3,
                label: '3'
            }, {
                value: 4,
                label: '4'
            }, {
                value: 5,
                label: '5'
            }, {
                value: 6,
                label: '6'
            }, {
                value: 7,
                label: '7'
            }, {
                value: 8,
                label: '8'
            }, {
                value: 9,
                label: '9'
            }, {
                value: 10,
                label: '10'
            }
        ]
    },

    {
        id: 'modified-fibonacci',
        title: 'Modified Fibonacci',
        code: 'c',
        cards: [
            {
                value: 0,
                label: '0'
            }, {
                value: 0.5,
                label: "\u00BD"
            }, {
                value: 1,
                label: '1'
            }, {
                value: 2,
                label: '2'
            }, {
                value: 3,
                label: '3'
            }, {
                value: 5,
                label: '5'
            }, {
                value: 8,
                label: '8'
            }, {
                value: 13,
                label: '13'
            }, {
                value: 20,
                label: '20'
            }, {
                value: 40,
                label: '40'
            }, {
                value: 100,
                label: '100'
            }
        ]
    }
];

app.set('view engine', 'jade');
app.set('views', __dirname + '/views');

app.use(express.bodyParser());
app.use(app.router);

// TODO replace this with a real data store
var rooms = {};

function createUrl (request) {
    return url.format({
        protocol: request.protocol,
        hostname: request.host,
        port: serverPort,
        pathname: _.toArray(arguments).slice(1).join('/').replace('//', '/')
    });
}

function redirect (url) {
    return function (request, response) {
        response.redirect(301, url);
    };
}

function index (request, response) {
    response.render('index', {
        assetsUrl: app.get('assetsUrl'),
        apiUrl: app.get('apiUrl'),
        appRoot: app.get('appRoot')
    });
}

function normalizeCards (cards) {
    var prevValue = null;

    for (var i = 0; i < cards.length; i += 1) {
        var card = cards[i];
        var value;
        var label;

        if (card) {
            if (_.isString(card)) {
                if (prevValue === null) {
                    prevValue = i + 1;
                } else {
                    prevValue += 1;
                }

                value = prevValue;
                label = card;
            } else if (_.isNumber(card)) {
                prevValue = card;
                value = card;
                label = card.toString(10);
            } else {
                if (_.isNumber(card.value)) {
                    prevValue = card.value;
                    value = card.value;
                } else {
                    if (prevValue === null) {
                        prevValue = i + 1;
                    } else {
                        prevValue += 1;
                    }

                    value = prevValue;
                }

                if (_.isString(card.label)) {
                    label = card.label;
                } else {
                    label = value.toString(10);
                }
            }
        }

        cards[i] = {
            value: value,
            label: label
        };
    }
}

function create (request, response) {
    var roomType = request.body.type;
    var cards = request.body.cards;

    if (_.isArray(cards) && cards.length > 0) {
        roomType = 'custom';
        normalizeCards(cards);
    } else {
        if (! _.isString(roomType) || roomType === '') {
            roomType = 'modified-fibonacci';
        }

        cards = ROOM_TYPES[roomType];

        if (! cards) {
            response.send(400, 'Invalid room type ' + roomType);
            return;
        }
    }

    crypto.randomBytes(7, function (ex, buf) {
        if (ex) {
            response.send(500);
            console.log('Error generating random room id: ', ex);
            return;
        }

        for (var i = 0; i < buf.length; i += 1) {
            var val = buf[i] % 36;

            if (val === 0) {
                // Map 0 to o to reduce typos since they look similar
                buf[i] = 111;
            } else if (val === 1) {
                // Map 1 to l to reduce typos since they look similar
                buf[i] = 108;
            } else if (val <= 9) {
                buf[i] = val + 48;
            } else {
                buf[i] = val + 97 - 10;
            }
        }

        var room_id = buf.toString();
        var created = new Date();
        var room = {
            id: room_id,
            type: roomType,
            cards: cards,
            created: created,
            access: created,
            url: createUrl(request, request.app.get('appRoot'), room_id)
        };

        rooms[room_id] = room;
        response.send(200, room);
    });
}

if (app.get('env') === 'production') {
    // TODO assetsUrl
    app.set('appRoot', '/');

    app.get('/', index);
    app.get('/:room_id', index);
} else {
    app.set('assetsUrl', '/assets');
    app.set('appRoot', '/app/');

    app.get('/', redirect('/app/'));
    app.get('/app/', index);
    app.get('/app/:room_id', index);
    app.get('/app', redirect('/app/'));
    app.use('/assets', express['static'](__dirname + '/assets'));
}

app.api = {
    root: app.get('appRoot') + 'api',

    get: function (path, callback) {
        app.get(this.root + '/' + path, callback);
    },

    post: function (path, callback) {
        app.post(this.root + '/' + path, callback);
    }
};

app.set('apiUrl', app.api.root);

app.api.get('roomTypes', function (request, response) {
    response.send(200, ROOM_TYPES);
});

app.api.post('create', create);

app.listen(serverPort);
console.log('Server is running at http://localhost:' + serverPort + ' (nodejs ' + process.version + ', ' + app.get('env') + ')');

<commit_msg>Added infinity, question, and break (coffee cup) cards<commit_after>#!/usr/bin/env nodemon

var fs = require('fs');
var express = require('express');
var crypto = require('crypto');
var url = require('url');
var _ = require('underscore');

var app = express();
var serverPort = 3000;

var INFINITY_CARD = {
    value: Number.MAX_VALUE,
    label: "\u221E"
};

var QUESTION_CARD = {
    label: '?'
};

var BREAK_CARD = {
    label: "\u2615"
};

var ROOM_TYPES = [
    {
        id: 'shirt-sizes',
        title: 'Shirt Sizes',
        code: 'a',
        cards: [
            {
                value: 1,
                label: 'XS'
            }, {
                value: 2,
                label: 'S'
            }, {
                value: 3,
                label: 'M'
            }, {
                value: 4,
                label: 'L'
            }, {
                value: 5,
                label: 'XL'
            },
            INFINITY_CARD,
            QUESTION_CARD,
            BREAK_CARD
        ]
    },

    {
        id: 'sequence',
        title: 'Sequence',
        code: 'b',
        cards: [
            {
                value: 0,
                label: '0'
            }, {
                value: 1,
                label: '1'
            }, {
                value: 2,
                label: '2'
            }, {
                value: 3,
                label: '3'
            }, {
                value: 4,
                label: '4'
            }, {
                value: 5,
                label: '5'
            }, {
                value: 6,
                label: '6'
            }, {
                value: 7,
                label: '7'
            }, {
                value: 8,
                label: '8'
            }, {
                value: 9,
                label: '9'
            }, {
                value: 10,
                label: '10'
            },
            INFINITY_CARD,
            QUESTION_CARD,
            BREAK_CARD
        ]
    },

    {
        id: 'modified-fibonacci',
        title: 'Modified Fibonacci',
        code: 'c',
        cards: [
            {
                value: 0,
                label: '0'
            }, {
                value: 0.5,
                label: "\u00BD"
            }, {
                value: 1,
                label: '1'
            }, {
                value: 2,
                label: '2'
            }, {
                value: 3,
                label: '3'
            }, {
                value: 5,
                label: '5'
            }, {
                value: 8,
                label: '8'
            }, {
                value: 13,
                label: '13'
            }, {
                value: 20,
                label: '20'
            }, {
                value: 40,
                label: '40'
            }, {
                value: 100,
                label: '100'
            },
            INFINITY_CARD,
            QUESTION_CARD,
            BREAK_CARD
        ]
    }
];

app.set('view engine', 'jade');
app.set('views', __dirname + '/views');

app.use(express.bodyParser());
app.use(app.router);

// TODO replace this with a real data store
var rooms = {};

function createUrl (request) {
    return url.format({
        protocol: request.protocol,
        hostname: request.host,
        port: serverPort,
        pathname: _.toArray(arguments).slice(1).join('/').replace('//', '/')
    });
}

function redirect (url) {
    return function (request, response) {
        response.redirect(301, url);
    };
}

function index (request, response) {
    response.render('index', {
        assetsUrl: app.get('assetsUrl'),
        apiUrl: app.get('apiUrl'),
        appRoot: app.get('appRoot')
    });
}

function normalizeCards (cards) {
    var prevValue = null;

    for (var i = 0; i < cards.length; i += 1) {
        var card = cards[i];
        var value;
        var label;

        if (card) {
            if (_.isString(card)) {
                if (prevValue === null) {
                    prevValue = i + 1;
                } else {
                    prevValue += 1;
                }

                value = prevValue;
                label = card;
            } else if (_.isNumber(card)) {
                prevValue = card;
                value = card;
                label = card.toString(10);
            } else {
                if (_.isNumber(card.value)) {
                    prevValue = card.value;
                    value = card.value;
                } else {
                    if (prevValue === null) {
                        prevValue = i + 1;
                    } else {
                        prevValue += 1;
                    }

                    value = prevValue;
                }

                if (_.isString(card.label)) {
                    label = card.label;
                } else {
                    label = value.toString(10);
                }
            }
        }

        cards[i] = {
            value: value,
            label: label
        };
    }
}

function create (request, response) {
    var roomType = request.body.type;
    var cards = request.body.cards;

    if (_.isArray(cards) && cards.length > 0) {
        roomType = 'custom';
        normalizeCards(cards);
    } else {
        if (! _.isString(roomType) || roomType === '') {
            roomType = 'modified-fibonacci';
        }

        cards = ROOM_TYPES[roomType];

        if (! cards) {
            response.send(400, 'Invalid room type ' + roomType);
            return;
        }
    }

    crypto.randomBytes(7, function (ex, buf) {
        if (ex) {
            response.send(500);
            console.log('Error generating random room id: ', ex);
            return;
        }

        for (var i = 0; i < buf.length; i += 1) {
            var val = buf[i] % 36;

            if (val === 0) {
                // Map 0 to o to reduce typos since they look similar
                buf[i] = 111;
            } else if (val === 1) {
                // Map 1 to l to reduce typos since they look similar
                buf[i] = 108;
            } else if (val <= 9) {
                buf[i] = val + 48;
            } else {
                buf[i] = val + 97 - 10;
            }
        }

        var room_id = buf.toString();
        var created = new Date();
        var room = {
            id: room_id,
            type: roomType,
            cards: cards,
            created: created,
            access: created,
            url: createUrl(request, request.app.get('appRoot'), room_id)
        };

        rooms[room_id] = room;
        response.send(200, room);
    });
}

if (app.get('env') === 'production') {
    // TODO assetsUrl
    app.set('appRoot', '/');

    app.get('/', index);
    app.get('/:room_id', index);
} else {
    app.set('assetsUrl', '/assets');
    app.set('appRoot', '/app/');

    app.get('/', redirect('/app/'));
    app.get('/app/', index);
    app.get('/app/:room_id', index);
    app.get('/app', redirect('/app/'));
    app.use('/assets', express['static'](__dirname + '/assets'));
}

app.api = {
    root: app.get('appRoot') + 'api',

    get: function (path, callback) {
        app.get(this.root + '/' + path, callback);
    },

    post: function (path, callback) {
        app.post(this.root + '/' + path, callback);
    }
};

app.set('apiUrl', app.api.root);

app.api.get('roomTypes', function (request, response) {
    response.send(200, ROOM_TYPES);
});

app.api.post('create', create);

app.listen(serverPort);
console.log('Server is running at http://localhost:' + serverPort + ' (nodejs ' + process.version + ', ' + app.get('env') + ')');

<|endoftext|>


<commit_before>$(function() {
    var appID = "28E04352";
    var namespace = "urn:x-cast:codenames.update";
    var session = null;
    var board = null;

    function Board(words) {
        this.words = words;
        this.selectedCards = [];

        var seed = _.shuffle(_.range(25));
        this.currentBlue = (Math.random() > 0.5);
        this.blue = seed.splice(0, 7);
        this.red = seed.splice(0, 7);
        this.assassin = seed.splice(0, 1);
        this.currentBlue ? this.blue.push(seed[0]) : this.red.push(seed[0]);
    }

    if (!chrome.cast || !chrome.cast.isAvailable) {
        setTimeout(initializeCastApi, 1000);
    }

    function initializeCastApi() {
        var request = new chrome.cast.SessionRequest(appID);
        var apiConfig = new chrome.cast.ApiConfig(request, sessionListener, function() {});
        chrome.cast.initialize(apiConfig, onInitSuccess, onError);
    }

    function sessionListener(e) {
        session = e;
    }

    function onInitSuccess() {
        console.log("Successfully initialized");
    }

    function onError(message) {
        console.log(message);
    }

    function initialize() {
      clearBoard();
      createBoard();
    }

    function clearBoard() {
        $(".card").text("");
        $(".card").removeClass("blue-team red-team assassin");
    }

    function createBoard() {
      fetch("../words.txt").then(function(response) {
            response.text().then(function(text) {
                parseWords(text);
                displayBoard();
            });
        });
    }

    function displayBoard() {
      displayCurrent();
      displayTeams();
      displayWords();
    }

    function parseWords(text) {
        var words = _.shuffle(text.split("\n")).splice(200, 25);
        board = new Board(words);
    }

    function displayCurrent() {
        $(".team-indicator").removeClass().addClass(board.currentBlue ? "blue-team" : "red-team");
        $(".team-indicator").text(board.currentBlue? "Blue Team" : "Red Team");
    }

    function displayTeams() {
        $(".card").addClass(function(index, currentClassName) {
            if (board.blue.includes(index)) {
                return "blue-team";
            } else if (board.red.includes(index)) {
                return "red-team";
            } else if (board.assassin.includes(index)) {
                return "assassin";
            }
        });
    }

    function displayWords() {
        $(".card").each(function(index) {
            this.textContent = board.words[index];
        });
    }

    initialize();
});<commit_msg>Team indicator class no longer removed by jquery<commit_after>$(function() {
    var appID = "28E04352";
    var namespace = "urn:x-cast:codenames.update";
    var session = null;
    var board = null;

    function Board(words) {
        this.words = words;
        this.selectedCards = [];

        var seed = _.shuffle(_.range(25));
        this.currentBlue = (Math.random() > 0.5);
        this.blue = seed.splice(0, 7);
        this.red = seed.splice(0, 7);
        this.assassin = seed.splice(0, 1);
        this.currentBlue ? this.blue.push(seed[0]) : this.red.push(seed[0]);
    }

    if (!chrome.cast || !chrome.cast.isAvailable) {
        setTimeout(initializeCastApi, 1000);
    }

    function initializeCastApi() {
        var request = new chrome.cast.SessionRequest(appID);
        var apiConfig = new chrome.cast.ApiConfig(request, sessionListener, function() {});
        chrome.cast.initialize(apiConfig, onInitSuccess, onError);
    }

    function sessionListener(e) {
        session = e;
    }

    function onInitSuccess() {
        console.log("Successfully initialized");
    }

    function onError(message) {
        console.log(message);
    }

    function initialize() {
      clearBoard();
      createBoard();
    }

    function clearBoard() {
        $(".card").text("");
        $(".card").removeClass("blue-team red-team assassin");
    }

    function createBoard() {
      fetch("../words.txt").then(function(response) {
            response.text().then(function(text) {
                parseWords(text);
                displayBoard();
            });
        });
    }

    function displayBoard() {
      displayCurrent();
      displayTeams();
      displayWords();
    }

    function parseWords(text) {
        var words = _.shuffle(text.split("\n")).splice(200, 25);
        board = new Board(words);
    }

    function displayCurrent() {
        $(".team-indicator").removeClass("blue-team red-team").addClass(board.currentBlue ? "blue-team" : "red-team");
        $(".team-indicator").text(board.currentBlue? "Blue Team" : "Red Team");
    }

    function displayTeams() {
        $(".card").addClass(function(index, currentClassName) {
            if (board.blue.includes(index)) {
                return "blue-team";
            } else if (board.red.includes(index)) {
                return "red-team";
            } else if (board.assassin.includes(index)) {
                return "assassin";
            }
        });
    }

    function displayWords() {
        $(".card").each(function(index) {
            this.textContent = board.words[index];
        });
    }

    initialize();
});<|endoftext|>


<commit_before>Template.registerHelper("activeRoute", function(route) {
	var currentRoute = Router.current().route.getName();
	return currentRoute === route ? " active" : "";
});

Template.registerHelper("calendarTime", function(date) {
	if(date) {
		return moment(date).calendar();
	}
	return "Unknown";
});

Template.registerHelper("equals", function(a, b) {
	return a === b;
});

Template.navigation.events({
	"click #logout": function(e) {
		Meteor.logout(function(error) {
			if(error) {
				alert(error);
			}
			else {
				Router.go("login");
			}
		});
	}
});

Template.login.events({
	"click #facebookLogin": function(e) {
		Meteor.loginWithFacebook(function(error) {
			if(error) {
				alert(error);
			}
			else {
				Router.go("target");
			}
		});
	},
	"click #googleLogin": function(e) {
		Meteor.loginWithGoogle(function(error) {
			if(error) {
				alert(error);
			}
			else {
				Router.go("target");
			}
		});
	}
});

Template.adminPanel.helpers({
	"actions": function() {
		return Actions.find({}, {sort: {"timestamp": -1}}).fetch();
	},
	"message": function() {
		var a = "Unknown";
		var m = "claims to have killed";
		var t = "Unknown";
		var assassin = Meteor.users.findOne(this.assassin);
		var target = Meteor.users.findOne(this.target);

		if(assassin) {a = assassin.profile.name;}
		if(target) {t = target.profile.name;}
		if(this.confirmed) {m = "killed";}

		return "" + a + " " + m + " " + t + "";
	}
});

Template.target.helpers({
	"target": function() {
		return Meteor.users.findOne(Meteor.user().target).profile.name;
	},
	"killTargetAvailable": function() {
		var action = Actions.find({"assassin": Meteor.userId()}, {sort: {"timestamp": -1}}).fetch()[0];
		if(!action || action.confirmed) {
			return "";
		}
		else {
			return "disabled";
		}
	},
	"actions": function() {
		return Actions.find({}, {sort: {"timestamp": -1}}).fetch();
	},
	"message": function() {
		var a = "Unknown";
		var m = "claims to have killed";
		var t = "Unknown";
		var c = "";
		var assassin = Meteor.users.findOne(this.assassin);
		var target = Meteor.users.findOne(this.target);

		if(this.assassin === Meteor.userId()) {a = "You"; m = "claim to have killed";}
		else if(assassin) {a = assassin.profile.name;}

		if(this.target === Meteor.userId()) {t = "You"; c = '
Is this correct? Yes / No';}
		else if(target) {t = target.profile.name;}
		if(this.confirmed) {m = "killed"; c = "";}

		return "" + a + " " + m + " " + t + "" + c;
	}
});

Template.target.events({
	"click #killTarget": function(e) {
		e.preventDefault();
		Meteor.call("killTarget", Meteor.user().target);
	},
	"click .confirmKill": function(e) {
		e.preventDefault();
		Meteor.call("confirmKill", this._id);
	},
	"click .denyKill": function(e) {
		e.preventDefault();
		// TODO: denyKill
	}
});

Template.leaderboard.helpers({
	"userList": function() {
		return Meteor.users.find({"inGame": true}, {sort: {"kills": -1}}).fetch();
	}
});<commit_msg>Prevent killing if you are dead<commit_after>Template.registerHelper("activeRoute", function(route) {
	var currentRoute = Router.current().route.getName();
	return currentRoute === route ? " active" : "";
});

Template.registerHelper("calendarTime", function(date) {
	if(date) {
		return moment(date).calendar();
	}
	return "Unknown";
});

Template.registerHelper("equals", function(a, b) {
	return a === b;
});

Template.navigation.events({
	"click #logout": function(e) {
		Meteor.logout(function(error) {
			if(error) {
				alert(error);
			}
			else {
				Router.go("login");
			}
		});
	}
});

Template.login.events({
	"click #facebookLogin": function(e) {
		Meteor.loginWithFacebook(function(error) {
			if(error) {
				alert(error);
			}
			else {
				Router.go("target");
			}
		});
	},
	"click #googleLogin": function(e) {
		Meteor.loginWithGoogle(function(error) {
			if(error) {
				alert(error);
			}
			else {
				Router.go("target");
			}
		});
	}
});

Template.adminPanel.helpers({
	"actions": function() {
		return Actions.find({}, {sort: {"timestamp": -1}}).fetch();
	},
	"message": function() {
		var a = "Unknown";
		var m = "claims to have killed";
		var t = "Unknown";
		var assassin = Meteor.users.findOne(this.assassin);
		var target = Meteor.users.findOne(this.target);

		if(assassin) {a = assassin.profile.name;}
		if(target) {t = target.profile.name;}
		if(this.confirmed) {m = "killed";}

		return "" + a + " " + m + " " + t + "";
	}
});

Template.target.helpers({
	"target": function() {
		return Meteor.users.findOne(Meteor.user().target).profile.name;
	},
	"killTargetAvailable": function() {
		var action = Actions.find({"assassin": Meteor.userId()}, {sort: {"timestamp": -1}}).fetch()[0];
		if(!Meteor.user().alive) {
			return "disabled";
		}
		if(!action || action.confirmed) {
			return "";
		}
		else {
			return "disabled";
		}
	},
	"actions": function() {
		return Actions.find({}, {sort: {"timestamp": -1}}).fetch();
	},
	"message": function() {
		var a = "Unknown";
		var m = "claims to have killed";
		var t = "Unknown";
		var c = "";
		var assassin = Meteor.users.findOne(this.assassin);
		var target = Meteor.users.findOne(this.target);

		if(this.assassin === Meteor.userId()) {a = "You"; m = "claim to have killed";}
		else if(assassin) {a = assassin.profile.name;}

		if(this.target === Meteor.userId()) {t = "You"; c = '
Is this correct? Yes / No';}
		else if(target) {t = target.profile.name;}
		if(this.confirmed) {m = "killed"; c = "";}

		return "" + a + " " + m + " " + t + "" + c;
	}
});

Template.target.events({
	"click #killTarget": function(e) {
		e.preventDefault();
		Meteor.call("killTarget", Meteor.user().target);
	},
	"click .confirmKill": function(e) {
		e.preventDefault();
		Meteor.call("confirmKill", this._id);
	},
	"click .denyKill": function(e) {
		e.preventDefault();
		// TODO: denyKill
	}
});

Template.leaderboard.helpers({
	"userList": function() {
		return Meteor.users.find({"inGame": true}, {sort: {"kills": -1}}).fetch();
	}
});<|endoftext|>