<commit_before>import { InternalError, ApiError } from './errors';

/**
 * Extract JSON body from a server response
 *
 * @function getJSON
 * @access public
 * @param {object} res - A raw response object
 * @returns {promise|undefined}
 */
async function getJSON(res) {
  const contentType = res.headers.get('Content-Type');
  const emptyCodes = [204, 205];

  if (
    !~emptyCodes.indexOf(res.status) &&
    contentType &&
    ~contentType.indexOf('json')
  ) {
    return await res.json();
  } else {
    return await Promise.resolve();
  }
}

/**
 * Blow up string or symbol types into full-fledged type descriptors,
 *   and add defaults
 *
 * @function normalizeTypeDescriptors
 * @access private
 * @param {array} types - The [RSAA].types from a validated RSAA
 * @returns {array}
 */
function normalizeTypeDescriptors(types) {
  let [requestType, successType, failureType] = types;

  if (typeof requestType === 'string' || typeof requestType === 'symbol') {
    requestType = { type: requestType };
  }

  if (typeof successType === 'string' || typeof successType === 'symbol') {
    successType = { type: successType };
  }
  successType = {
    payload: (action, state, res) => getJSON(res),
    ...successType
  };

  if (typeof failureType === 'string' || typeof failureType === 'symbol') {
    failureType = { type: failureType };
  }
  failureType = {
    payload: (action, state, res) =>
      getJSON(res).then(json => new ApiError(res.status, res.statusText, json)),
    ...failureType
  };

  return [requestType, successType, failureType];
}

/**
 * Evaluate a type descriptor to an FSA
 *
 * @function actionWith
 * @access private
 * @param {object} descriptor - A type descriptor
 * @param {array} args - The array of arguments for `payload` and `meta` function properties
 * @returns {object}
 */
async function actionWith(descriptor, args) {
  try {
    descriptor.payload =
      typeof descriptor.payload === 'function'
        ? await descriptor.payload(...args)
        : descriptor.payload;
  } catch (e) {
    descriptor.payload = new InternalError(e.message);
    descriptor.error = true;
  }

  try {
    descriptor.meta =
      typeof descriptor.meta === 'function'
        ? await descriptor.meta(...args)
        : descriptor.meta;
  } catch (e) {
    delete descriptor.meta;
    descriptor.payload = new InternalError(e.message);
    descriptor.error = true;
  }

  return descriptor;
}

export { getJSON, normalizeTypeDescriptors, actionWith };
<commit_msg>fix: default args to empty array if none are passed<commit_after>import { InternalError, ApiError } from './errors';

/**
 * Extract JSON body from a server response
 *
 * @function getJSON
 * @access public
 * @param {object} res - A raw response object
 * @returns {promise|undefined}
 */
async function getJSON(res) {
  const contentType = res.headers.get('Content-Type');
  const emptyCodes = [204, 205];

  if (
    !~emptyCodes.indexOf(res.status) &&
    contentType &&
    ~contentType.indexOf('json')
  ) {
    return await res.json();
  } else {
    return await Promise.resolve();
  }
}

/**
 * Blow up string or symbol types into full-fledged type descriptors,
 *   and add defaults
 *
 * @function normalizeTypeDescriptors
 * @access private
 * @param {array} types - The [RSAA].types from a validated RSAA
 * @returns {array}
 */
function normalizeTypeDescriptors(types) {
  let [requestType, successType, failureType] = types;

  if (typeof requestType === 'string' || typeof requestType === 'symbol') {
    requestType = { type: requestType };
  }

  if (typeof successType === 'string' || typeof successType === 'symbol') {
    successType = { type: successType };
  }
  successType = {
    payload: (action, state, res) => getJSON(res),
    ...successType
  };

  if (typeof failureType === 'string' || typeof failureType === 'symbol') {
    failureType = { type: failureType };
  }
  failureType = {
    payload: (action, state, res) =>
      getJSON(res).then(json => new ApiError(res.status, res.statusText, json)),
    ...failureType
  };

  return [requestType, successType, failureType];
}

/**
 * Evaluate a type descriptor to an FSA
 *
 * @function actionWith
 * @access private
 * @param {object} descriptor - A type descriptor
 * @param {array} args - The array of arguments for `payload` and `meta` function properties
 * @returns {object}
 */
async function actionWith(descriptor, args = []) {
  try {
    descriptor.payload =
      typeof descriptor.payload === 'function'
        ? await descriptor.payload(...args)
        : descriptor.payload;
  } catch (e) {
    descriptor.payload = new InternalError(e.message);
    descriptor.error = true;
  }

  try {
    descriptor.meta =
      typeof descriptor.meta === 'function'
        ? await descriptor.meta(...args)
        : descriptor.meta;
  } catch (e) {
    delete descriptor.meta;
    descriptor.payload = new InternalError(e.message);
    descriptor.error = true;
  }

  return descriptor;
}

export { getJSON, normalizeTypeDescriptors, actionWith };
<|endoftext|>


<commit_before>const { TAB, VT, FF, SP, NBSP, ZWNJ, ZWJ, ZWNBSP, LF, CR, LS, PS } = require('./constants').WHITESPACE;
const SINGLE_ESCAPE_CHARACTERS = require('./constants').SINGLE_ESCAPE_CHARACTERS;

const isWhiteSpace = ch => (
  ch === TAB ||
  ch === VT ||
  ch === FF ||
  ch === SP ||
  ch === NBSP ||
  ch === ZWNJ ||
  ch === ZWJ ||
  ch === ZWNBSP
);

const isDecimalDigit = ch => (
  /[0-9]/.test(ch)
);

const isLineTerminator = ch => (
  ch === LF ||
  ch === CR ||
  ch === LS ||
  ch === PS
);

exports.isWhiteSpace = isWhiteSpace;
exports.isDecimalDigit = isDecimalDigit;
exports.isLineTerminator = isLineTerminator;

function parseKeyword(keyword, alias) {
  {
    let res = '';
    switch (this.topState()) {
      case 'single_string_start':
        res = 'SingleStringCharacter';
        break;
      case 'double_string_start':
        res = 'DoubleStringCharacter';
        break;
      case 'identifier_start':
        res = 'UnicodeIDContinue';
        break;
      default:
        res = alias || keyword;
        break;
    }

    // look behind { 和 function
    let i = this.matches.index + this.match.length;
    const input = this.matches.input;

    // look behind for id_continue

    if (this.topState() === 'INITIAL') {
      const idContinueReg = require('unicode-6.3.0/Binary_Property/ID_Continue/regex');
      if (idContinueReg.test(input[i])) {
        this.begin('identifier_start');
        res = 'UnicodeIDStart';
      }
    }

    // 跳过空白字符
    while (i < input.length && isWhiteSpace(input[i])) { i++; }

    // throw 后面的{ 应该是表达式
    // throw 后面的function应该是表达式
    // return 后面的{ 应该是表达式
    // return 后面的function应该是表达式
    if (this.match === 'throw' || this.match === 'return') {
      if (/^{/.test(input.substring(i))) {
        this.begin('brace_start');
      }
      if (/^function/.test(input.substring(i))) {
        this.begin('function_start');
      }
    }
    // { after const/let/var should be brace not block
    if (this.match === 'const' || this.match === 'var' || this.match === 'let') {
      if (/^{/.test(input.substring(i))) {
        this.begin('brace_start');
      }
    }
    return res;
  }
}
exports.parseKeyword = parseKeyword;

function parseOperator(operator, alias) {
   // NOTICE: restrict line terminator for update express
  if (alias === 'UpdateOperator') {
    let start = this.matched.length - 3;
    let hasLF = false;
    let hasSemiColon = false;

    while (start >= 0 && require('./util').isWhiteSpace(this.matched[start])) {
      start -= 1;
    }
    while (start >= 0 && require('./util').isLineTerminator(this.matched[start])) {
      start -= 1;
      hasLF = true;
    }
    while (start >= 0 && require('./util').isWhiteSpace(this.matched[start])) {
      start -= 1;
    }
    if (this.matched[start] === ';') {
      hasSemiColon = true;
    }
    if (hasLF && !hasSemiColon) {
      throw new (require('./error').NoLineTerminatorError)('no line terminator', {
        text: this.yytext,
        token: 'UpdateOperator_LF',
        line: this.yylloc.first_line,
        loc: {
          first_line: this.yylloc.first_line,
          last_line: this.yylloc.last_line,
          first_column: this.yylloc.first_column,
          last_column: this.yylloc.last_column,
          range: [
            this.yylloc.range[0],
            this.yylloc.range[1] - 2,
          ],
        },
        offset: this.offset - 2,
      });
    }
  }

  let i = this.matches.index + this.match.length;
  const input = this.matches.input;

  while (i < input.length && isWhiteSpace(input[i])) { i++; }
  let res = '';

  switch (this.topState()) {
    case 'single_string_start':
      res = 'SingleStringCharacter';
      break;
    case 'double_string_start':
      res = 'DoubleStringCharacter';
      break;
    case 'identifier_start':
      this.popState();
      res = alias || operator;
      break;
    case 'decimal_digit_start':
      this.popState();
      res = alias || operator;
      break;
    case 'decimal_digit_dot_start':
      this.popState();
      this.popState();
      res = alias || operator;
      break;
    case 'brace_start':
      this.popState();
      res = alias || operator;
      break;
    default:
      res = alias || operator;
      break;
  }
  // TODO: 具体情况具体分析
  // case : 后面的{ 应该是语句块而不是表达式的开头
  // } 后面的{是语句块开头？
  // ) 后面的{是语句块开头?
  if (this.match === ':') {
    if (this.topState() === 'case_start') {
      this.popState();
    } else if (/^{/.test(input.substring(i))) {
      this.begin('brace_start');
    }
  } else if (this.match === ')') {

  } else if (this.match === '}') {

  } else if (this.match === ';') {

  } else if (isWhiteSpace(this.match) || isLineTerminator(this.match)) {

  } else if (/^{/.test(input.substring(i))) {
    this.begin('brace_start');
  } else if (/^function/.test(input.substring(i))) {
    this.begin('function_start');
  }
  if (res) { return res; }

  return undefined;
}

exports.parseOperator = parseOperator;

function parseIdentifier() {
  switch (this.topState()) {
    case 'single_string_start':
      return 'SingleStringCharacter';
    case 'double_string_start':
      return 'DoubleStringCharacter';
    default:
      break;
  }
  if (this.topState() === 'identifier_start') {
    return 'UnicodeIDContinue';
  }
  this.begin('identifier_start');
  return 'UnicodeIDStart';
}

exports.parseIdentifier = parseIdentifier;

function parseString(ch) {
  const isSingleQuote = ch === 'SingleStringCharacter';
  const isDoubleQuote = ch === 'DoubleStringCharacter';

  if (this.match === '\u000A' || this.match === '\u000D') {
    throw new Error('SyntaxError: Invalid or unexpected token');
  } else if (this.match === '\\') {
    if (isSingleQuote) {
      this.begin('single_escape_string');
    }
    if (isDoubleQuote) {
      this.begin('double_escape_string');
    }
    return 'EscapeSequenceStart';
  } else if (this.match === '\'' || this.match === '"') {
    if (this.match === '\'') {
      if (this.topState() === 'single_string_start') {
        this.popState();
        return 'SingleQuoteEnd';
      }
    }
    if (this.match === '"') {
      if (this.topState() === 'double_string_start') {
        this.popState();
        return 'DoubleQuoteEnd';
      }
    }
  }
  return ch;
}

exports.parseString = parseString;

function parseEscapeString() {
  this.begin('identifier_start_unicode');
  return 'UnicodeEscapeSequenceStart';
}

exports.parseEscapeString = parseEscapeString;

function parseEscapeStringCharacter() {
  if (SINGLE_ESCAPE_CHARACTERS.indexOf(this.match) !== -1) {
    this.popState();
    return 'SingleEscapeCharacter';
  }
  this.popState();
  return 'NonEscapeCharacter';
}

exports.parseEscapeStringCharacter = parseEscapeStringCharacter;

function parseToken(token, alias) {
  switch (this.topState()) {
    case 'single_string_start':
      return 'SingleStringCharacter';
    case 'double_string_start':
      return 'DoubleStringCharacter';
    case 'identifier_start':
      this.popState();
      break;
    case 'decimal_digit_start':
      this.popState();
      break;
    case 'decimal_digit_dot_start':
      this.popState();
      this.popState();
      break;
    default:
      break;
  }
  return alias || '';
}

exports.parseToken = parseToken;

// mathematical value
function getMVHexDigit(v1) {
  switch (v1) {
    case 'a':
      return 10;
    case 'b':
      return 11;
    case 'c':
      return 12;
    case 'd':
      return 13;
    case 'e':
      return 14;
    case 'f':
      return 15;
    default:
      break;
  }
  return parseInt(v1, 10);
}

function getMVHexDigits(v1, v2, v3, v4) {
  return (4096 * getMVHexDigit(v1)) +
    (256 * getMVHexDigit(v2)) +
    (16 * getMVHexDigit(v3)) +
    getMVHexDigit(v4);
}

exports.getMVHexDigits = getMVHexDigits;
<commit_msg>try to add no line terminator error<commit_after>const { TAB, VT, FF, SP, NBSP, ZWNJ, ZWJ, ZWNBSP, LF, CR, LS, PS } = require('./constants').WHITESPACE;
const SINGLE_ESCAPE_CHARACTERS = require('./constants').SINGLE_ESCAPE_CHARACTERS;

const isWhiteSpace = ch => (
  ch === TAB ||
  ch === VT ||
  ch === FF ||
  ch === SP ||
  ch === NBSP ||
  ch === ZWNJ ||
  ch === ZWJ ||
  ch === ZWNBSP
);

const isDecimalDigit = ch => (
  /[0-9]/.test(ch)
);

const isLineTerminator = ch => (
  ch === LF ||
  ch === CR ||
  ch === LS ||
  ch === PS
);

exports.isWhiteSpace = isWhiteSpace;
exports.isDecimalDigit = isDecimalDigit;
exports.isLineTerminator = isLineTerminator;

function parseKeyword(keyword, alias) {
  {
    let res = '';
    switch (this.topState()) {
      case 'single_string_start':
        res = 'SingleStringCharacter';
        break;
      case 'double_string_start':
        res = 'DoubleStringCharacter';
        break;
      case 'identifier_start':
        res = 'UnicodeIDContinue';
        break;
      default:
        res = alias || keyword;
        break;
    }

    // look behind { 和 function
    let i = this.matches.index + this.match.length;
    const input = this.matches.input;

    // look behind for id_continue

    if (this.topState() === 'INITIAL') {
      const idContinueReg = require('unicode-6.3.0/Binary_Property/ID_Continue/regex');
      if (idContinueReg.test(input[i])) {
        this.begin('identifier_start');
        res = 'UnicodeIDStart';
      }
    }

    // 跳过空白字符
    while (i < input.length && isWhiteSpace(input[i])) { i++; }

    // throw 后面的{ 应该是表达式
    // throw 后面的function应该是表达式
    // return 后面的{ 应该是表达式
    // return 后面的function应该是表达式
    if (this.match === 'throw' || this.match === 'return') {
      if (/^{/.test(input.substring(i))) {
        this.begin('brace_start');
      }
      if (/^function/.test(input.substring(i))) {
        this.begin('function_start');
      }
    }
    // { after const/let/var should be brace not block
    if (this.match === 'const' || this.match === 'var' || this.match === 'let') {
      if (/^{/.test(input.substring(i))) {
        this.begin('brace_start');
      }
    }
    return res;
  }
}
exports.parseKeyword = parseKeyword;

function parseOperator(operator, alias) {
   // NOTICE: restrict line terminator for update express
  if (alias === 'UpdateOperator') {
    let start = this.matched.length - 3;
    let hasLF = false;
    let hasSemiColon = false;

    while (start >= 0 && require('./util').isWhiteSpace(this.matched[start])) {
      start -= 1;
    }
    while (start >= 0 && require('./util').isLineTerminator(this.matched[start])) {
      start -= 1;
      hasLF = true;
    }
    while (start >= 0 && require('./util').isWhiteSpace(this.matched[start])) {
      start -= 1;
    }
    if (this.matched[start] === ';') {
      hasSemiColon = true;
    }
    if (hasLF && !hasSemiColon) {
      throw new (require('./error').NoLineTerminatorError)('no line terminator', {
        text: this.yytext,
        token: 'UpdateOperator_LF',
        line: this.yylloc.first_line,
        loc: {
          first_line: this.yylloc.first_line,
          last_line: this.yylloc.last_line,
          first_column: this.yylloc.first_column,
          last_column: this.yylloc.last_column,
          range: [
            this.yylloc.range[0],
            this.yylloc.range[1] - 2,
          ],
        },
        offset: this.offset - 2,
      });
    }
  }

  let i = this.matches.index + this.match.length;
  const input = this.matches.input;

  while (i < input.length && isWhiteSpace(input[i])) { i++; }
  let res = '';

  switch (this.topState()) {
    case 'single_string_start':
      res = 'SingleStringCharacter';
      break;
    case 'double_string_start':
      res = 'DoubleStringCharacter';
      break;
    case 'identifier_start':
      this.popState();
      res = alias || operator;
      break;
    case 'decimal_digit_start':
      this.popState();
      res = alias || operator;
      break;
    case 'decimal_digit_dot_start':
      this.popState();
      this.popState();
      res = alias || operator;
      break;
    case 'brace_start':
      this.popState();
      res = alias || operator;
      break;
    default:
      res = alias || operator;
      break;
  }
  // TODO: 具体情况具体分析
  // case : 后面的{ 应该是语句块而不是表达式的开头
  // } 后面的{是语句块开头？
  // ) 后面的{是语句块开头?
  if (this.match === ':') {
    if (this.topState() === 'case_start') {
      this.popState();
    } else if (/^{/.test(input.substring(i))) {
      this.begin('brace_start');
    }
  } else if (this.match === ')') {

  } else if (this.match === '}') {

  } else if (this.match === ';') {

  } else if (isWhiteSpace(this.match) || isLineTerminator(this.match)) {

  } else if (/^{/.test(input.substring(i))) {
    this.begin('brace_start');
  } else if (/^function/.test(input.substring(i))) {
    this.begin('function_start');
  }
  if (res) { return res; }

  return undefined;
}

exports.parseOperator = parseOperator;

function parseIdentifier() {
  switch (this.topState()) {
    case 'single_string_start':
      return 'SingleStringCharacter';
    case 'double_string_start':
      return 'DoubleStringCharacter';
    default:
      break;
  }
  if (this.topState() === 'identifier_start') {
    return 'UnicodeIDContinue';
  }
  this.begin('identifier_start');
  return 'UnicodeIDStart';
}

exports.parseIdentifier = parseIdentifier;

function parseString(ch) {
  const isSingleQuote = ch === 'SingleStringCharacter';
  const isDoubleQuote = ch === 'DoubleStringCharacter';

  if (this.match === '\u000A' || this.match === '\u000D') {
    throw new Error('SyntaxError: Invalid or unexpected token');
  } else if (this.match === '\\') {
    if (isSingleQuote) {
      this.begin('single_escape_string');
    }
    if (isDoubleQuote) {
      this.begin('double_escape_string');
    }
    return 'EscapeSequenceStart';
  } else if (this.match === '\'' || this.match === '"') {
    if (this.match === '\'') {
      if (this.topState() === 'single_string_start') {
        this.popState();
        return 'SingleQuoteEnd';
      }
    }
    if (this.match === '"') {
      if (this.topState() === 'double_string_start') {
        this.popState();
        return 'DoubleQuoteEnd';
      }
    }
  }
  return ch;
}

exports.parseString = parseString;

function parseEscapeString() {
  this.begin('identifier_start_unicode');
  return 'UnicodeEscapeSequenceStart';
}

exports.parseEscapeString = parseEscapeString;

function parseEscapeStringCharacter() {
  if (SINGLE_ESCAPE_CHARACTERS.indexOf(this.match) !== -1) {
    this.popState();
    return 'SingleEscapeCharacter';
  }
  this.popState();
  return 'NonEscapeCharacter';
}

exports.parseEscapeStringCharacter = parseEscapeStringCharacter;

function parseToken(token, alias) {
  switch (this.topState()) {
    case 'single_string_start':
      return 'SingleStringCharacter';
    case 'double_string_start':
      return 'DoubleStringCharacter';
    case 'identifier_start':
      this.popState();
      break;
    case 'decimal_digit_start':
      this.popState();
      break;
    case 'decimal_digit_dot_start':
      this.popState();
      this.popState();
      break;
    default:
      if (isLineTerminator(this.match)) {
        // jump white space and line terminator
        while (i < input.length && (isWhiteSpace(input[i]) || isLineTerminator(input[i]))) { i++; }
        // look after for =>
        if (/=>/.test(input.substring(i))) {
          throw new (require('./error').NoLineTerminatorError)('no line terminator', {
            text: this.yytext,
            token: 'ArrowFunction',
            line: this.yylloc.first_line,
            loc: {
              first_line: this.yylloc.first_line,
              last_line: this.yylloc.last_line,
              first_column: this.yylloc.first_column,
              last_column: this.yylloc.last_column,
              range: [
                this.yylloc.range[0],
                this.yylloc.range[1] - 2,
              ],
            },
            offset: this.offset - 2,
          });
        }
      }
      break;
  }
  return alias || '';
}

exports.parseToken = parseToken;

// mathematical value
function getMVHexDigit(v1) {
  switch (v1) {
    case 'a':
      return 10;
    case 'b':
      return 11;
    case 'c':
      return 12;
    case 'd':
      return 13;
    case 'e':
      return 14;
    case 'f':
      return 15;
    default:
      break;
  }
  return parseInt(v1, 10);
}

function getMVHexDigits(v1, v2, v3, v4) {
  return (4096 * getMVHexDigit(v1)) +
    (256 * getMVHexDigit(v2)) +
    (16 * getMVHexDigit(v3)) +
    getMVHexDigit(v4);
}

exports.getMVHexDigits = getMVHexDigits;
<|endoftext|>


<commit_before>import {h} from '@cycle/dom';

const INSTRUCTIONS = `
Write your sick raps in the text box below.

Reach the end of the line and don't know where to go?

Hit TAB on your keyboard or RHYME on the screen

And there you will find the word for your scheme

Don't like it? That's cool, hit it again

You'll get a new rhyme for your rap and then

Impress all your friends with your linguistic skill

And do it again, now you know the drill.

`;

function renderApp({text, notification, instructionsVisible, rhymeScheme, availableRhymes, rhymeSuggestionsVisible, selectedRhymeIndex, caretPosition}) {
  return (
    h('.container', [
      h('a.toggle-instructions', {href: "#"}, `${instructionsVisible ? 'HIDE' : 'SHOW'} INSTRUCTIONS`),
      h('.instructions', {innerHTML: INSTRUCTIONS, style: {display: instructionsVisible ? 'block' : 'none'}}),
      h('.app-inner', [
        h('button.rhyme', 'RHYME'),
        h('a', {href: 'https://www.wikiwand.com/en/Rhyme_scheme', target: '_blank'}, 'SELECT A RHYME SCHEME'),
        h('.rhyme-schemes', [
          h('input', {type: 'radio', value: 'AABB', name:'rhyme-scheme', checked: rhymeScheme === 'AABB' ? true : false}),
          h('label', 'AABB'),
          h('input', {type: 'radio', value: 'ABAB', name:'rhyme-scheme', checked: rhymeScheme === 'ABAB' ? true : false}),
          h('label', 'ABAB')
        ]),
        h('.text', [
          renderRhymeSuggestions(availableRhymes, rhymeSuggestionsVisible, selectedRhymeIndex, caretPosition),
          h('.notification', notification),
          h('textarea', {rows: 18, value: text})
        ])
      ])
    ])
  )
}

function renderRhymeSuggestions (rhymes, visible, selectedRhymeIndex, caretPosition) {
  return (
    h('.rhyme-suggestions',
      {style: {display: visible ? 'block' : 'none', top: `${caretPosition.top + 55}px`, left: `${caretPosition.left + 15}px`}},
      rhymes.map((rhyme, index) => renderRhymeSuggestion(rhyme, index === selectedRhymeIndex, index))
    )
  )
}

function renderRhymeSuggestion (rhyme, isSelected, key) {
 return h(`.rhyme-suggestion ${isSelected ? '.active' : ''}`, {key}, rhyme)
}

export default function view (state$) {
  return state$.map(renderApp);
}<commit_msg>Refactor view into lots of functions<commit_after>import {h} from '@cycle/dom';

const INSTRUCTIONS = `
Write your sick raps in the text box below.

Reach the end of the line and don't know where to go?

Hit TAB on your keyboard or RHYME on the screen

And there you will find the word for your scheme

Don't like it? That's cool, hit it again

You'll get a new rhyme for your rap and then

Impress all your friends with your linguistic skill

And do it again, now you know the drill.

`;

function renderApp(state) {
  return (
    h('.container', [
      renderInstructions(state.instructionsVisible),
      h('.app-inner', [
        h('button.rhyme', 'RHYME'),
        renderRhymeSchemeSelector(state.rhymeScheme),
        h('.text', [
          renderRhymeSuggestions(state),
          h('.notification', state.notification),
          h('textarea', {rows: 18, value: state.text})
        ])
      ])
    ])
  )
}

function renderInstructions(instructionsVisible) {
  return (
    h('div', [
      h('a.toggle-instructions', {href: "#"}, `${instructionsVisible ? 'HIDE' : 'SHOW'} INSTRUCTIONS`),
      h('.instructions', {innerHTML: INSTRUCTIONS, style: {display: instructionsVisible ? 'block' : 'none'}})
    ])
  );
}

function renderRhymeSchemeSelector (rhymeScheme) {
  return (
    h('div', [
      h('a', {href: 'https://www.wikiwand.com/en/Rhyme_scheme', target: '_blank'}, 'SELECT A RHYME SCHEME'),
      h('.rhyme-schemes', [
        h('input', {type: 'radio', value: 'AABB', name:'rhyme-scheme', checked: rhymeScheme === 'AABB' ? true : false}),
        h('label', 'AABB'),
        h('input', {type: 'radio', value: 'ABAB', name:'rhyme-scheme', checked: rhymeScheme === 'ABAB' ? true : false}),
        h('label', 'ABAB')
      ])
    ])
  );
}

function renderRhymeSuggestions ({availableRhymes, rhymeSuggestionsVisible, selectedRhymeIndex, caretPosition}) {
  return (
    h('.rhyme-suggestions',
      {
        style: {
          display: rhymeSuggestionsVisible ? 'block' : 'none',
          top: `${caretPosition.top + 55}px`, left: `${caretPosition.left + 15}px`
        }
      },

      availableRhymes.map((rhyme, index) => renderRhymeSuggestion(rhyme, index === selectedRhymeIndex, index))
    )
  )
}

function renderRhymeSuggestion (rhyme, isSelected, key) {
 return h(`.rhyme-suggestion ${isSelected ? '.active' : ''}`, {key}, rhyme)
}

export default function view (state$) {
  return state$.map(renderApp);
}<|endoftext|>


<commit_before>(function(window){

  //
  // helper methods
  //
  var cEl = document.createElement.bind(document);
  function gCl(parent, className) {
    return parent.getElementsByClassName(className)[0];
  }
  function gTl(parent, className) {
    return parent.getElementsByTagName(className)[0];
  }

  function removeClass(el, className) {
    return el.classList.remove(className);
  }

  function addClass(el, className) {
    return el.classList.add(className);
  }

  function stop_propagation(event) {
    if(typeof(event.stopPropagation) == 'function') {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
  }

  RemoteStorage.Widget.View = function() {
    if(typeof(document) === 'undefined') {
      throw "Widget not supported";
    }
    RemoteStorage.eventHandling(this,
                                'connect',
                                'disconnect',
                                'sync',
                                'display',
                                'reset');

    // re-binding the event so they can be called from the window
    for(var event in this.events){
      this.events[event] = this.events[event].bind(this);
    }

    // bubble toggling stuff
    this.toggle_bubble = function(event) {
      if(this.bubble.className.search('rs-hidden') < 0) {
        this.hide_bubble(event);
      } else {
        this.show_bubble(event);
      }
    }.bind(this);

    this.hide_bubble = function(){
      //console.log('hide bubble',this);
      addClass(this.bubble, 'rs-hidden')
      document.body.removeEventListener('click', hide_bubble_on_body_click);
    }.bind(this);

    var hide_bubble_on_body_click = function (event) {
      for(var p = event.target; p != document.body; p = p.parentElement) {
        if(p.id == 'remotestorage-widget') {
          return;
        }
      }
      this.hide_bubble();
    }.bind(this);

    this.show_bubble = function(event){
      //console.log('show bubble',this.bubble,event)
      removeClass(this.bubble, 'rs-hidden');
      if(typeof(event) != 'undefined') {
         stop_propagation(event);
       }
      document.body.addEventListener('click', hide_bubble_on_body_click);
      gTl(this.bubble,'form').userAddress.focus();
    }.bind(this);

    this.display = function(domID) {

      if(typeof(this.div) !== 'undefined')
        return this.div;

      var element = cEl('div');
      var style = cEl('style');
      style.innerHTML = RemoteStorage.Assets.widgetCss;

      element.id = "remotestorage-widget";

      element.innerHTML = RemoteStorage.Assets.widget;

      element.appendChild(style);
      if(domID) {
        var parent = document.getElementById(domID);
        if(! parent) {
          throw "Failed to find target DOM element with id=\"" + domID + "\"";
        }
        parent.appendChild(element);
      } else {
        document.body.appendChild(element);
      }

      var el;
      //sync button
      el = gCl(element, 'rs-sync');
      gTl(el, 'img').src = RemoteStorage.Assets.syncIcon;
      el.addEventListener('click', this.events.sync);

      //disconnect button
      el = gCl(element, 'rs-disconnect');
      gTl(el, 'img').src = RemoteStorage.Assets.disconnectIcon;
      el.addEventListener('click', this.events.disconnect);

      //get me out of here
      var el = gCl(element, 'remotestorage-reset').addEventListener('click', this.events.reset);
      //connect button
      var cb = gCl(element,'connect');
      gTl(cb, 'img').src = RemoteStorage.Assets.connectIcon;
      cb.addEventListener('click', this.events.connect);

      // input
      this.form = gTl(element, 'form')
      el = this.form.userAddress;
      el.addEventListener('keyup', function(event) {
        if(event.target.value) cb.removeAttribute('disabled');
        else cb.setAttribute('disabled','disabled');
      });
      if(this.userAddress) {
        el.value = this.userAddress;
      }

      //the cube
      el = gCl(element, 'rs-cube');
      el.src = RemoteStorage.Assets.remoteStorageIcon;
      el.addEventListener('click', this.toggle_bubble);
      this.cube = el

      //the bubble
      this.bubble = gCl(element,'rs-bubble');
      // what is the meaning of this hiding the b
      var bubbleDontCatch = { INPUT: true, BUTTON: true, IMG: true };
      this.bubble.addEventListener('click', function(event) {
        if(! bubbleDontCatch[event.target.tagName] && ! (this.div.classList.contains('remotestorage-state-unauthorized') )) {

          this.show_bubble(event);
        };
      }.bind(this))
      this.hide_bubble();

      this.div = element;

      this.states.initial.call(this);
      this.events.display.call(this);
      return this.div;
    };

  }

  RemoteStorage.Widget.View.prototype = {

    // Methods:
    //
    //  display(domID)
    //    draws the widget inside of the dom element with the id domID
    //   returns: the widget div
    //
    //  showBubble()
    //    shows the bubble
    //  hideBubble()
    //    hides the bubble
    //  toggleBubble()
    //    shows the bubble when hidden and the other way around
    //
    //  setState(state, args)
    //    calls states[state]
    //    args are the arguments for the
    //    state(errors mostly)
    //
    // setUserAddres
    //    set userAddress of the input field
    //
    // States:
    //  initial      - not connected
    //  authing      - in auth flow
    //  connected    - connected to remote storage, not syncing at the moment
    //  busy         - connected, syncing at the moment
    //  offline      - connected, but no network connectivity
    //  error        - connected, but sync error happened
    //  unauthorized - connected, but request returned 401
    //
    // Events:
    // connect    : fired when the connect button is clicked
    // sync       : fired when the sync button is clicked
    // disconnect : fired when the disconnect button is clicked
    // reset      : fired after crash triggers disconnect
    // display    : fired when finished displaying the widget
    setState : function(state, args) {
      RemoteStorage.log('widget.view.setState(',state,',',args,');');
      var s = this.states[state];
      if(typeof(s) === 'undefined') {
        throw new Error("Bad State assigned to view: " + state);
      }
      s.apply(this,args);
    },
    setUserAddress : function(addr) {
      this.userAddress = addr || '';

      var el;
      if(this.div && (el = gTl(this.div, 'form').userAddress)) {
        el.value = this.userAddress;
      }
    },

    states :  {
      initial : function(message) {
        var cube = this.cube;
        var info = message || 'This app allows you to use your own storage! Find more info on remotestorage.io';
        if(message) {
          cube.src = RemoteStorage.Assets.remoteStorageIconError;
          removeClass(this.cube, 'remotestorage-loading');
          this.show_bubble();
          setTimeout(function(){
            cube.src = RemoteStorage.Assets.remoteStorageIcon;
          },3512)
        } else {
          this.hide_bubble();
        }
        this.div.className = "remotestorage-state-initial";
        gCl(this.div, 'rs-status-text').innerHTML = "Connect remotestorage";

        //if address not empty connect button enabled
        //TODO check if this works
        var cb = gCl(this.div, 'connect')
        if(cb.value)
          cb.removeAttribute('disabled');

        var infoEl = gCl(this.div, 'rs-info-msg');
        infoEl.innerHTML = info;

        if(message) {
          infoEl.classList.add('remotestorage-error-info');
        } else {
          infoEl.classList.remove('remotestorage-error-info');
        }

      },
      authing : function() {
        this.div.removeEventListener('click', this.events.connect);
        this.div.className = "remotestorage-state-authing";
        gCl(this.div, 'rs-status-text').innerHTML = "Connecting "+this.userAddress+"";
        addClass(this.cube, 'remotestorage-loading'); //TODO needs to be undone, when is that neccesary
      },
      connected : function() {
        this.div.className = "remotestorage-state-connected";
        gCl(this.div, 'userAddress').innerHTML = this.userAddress;
        this.cube.src = RemoteStorage.Assets.remoteStorageIcon;
        removeClass(this.cube, 'remotestorage-loading');
      },
      busy : function() {
        this.div.className = "remotestorage-state-busy";
        addClass(this.cube, 'remotestorage-loading'); //TODO needs to be undone when is that neccesary
        this.hide_bubble();
      },
      offline : function() {
        this.div.className = "remotestorage-state-offline";
        this.cube.src = RemoteStorage.Assets.remoteStorageIconOffline;
        gCl(this.div, 'rs-status-text').innerHTML = 'Offline';
      },
      error : function(err) {
        var errorMsg = err;
        this.div.className = "remotestorage-state-error";

        gCl(this.div, 'bubble-text').innerHTML = ' Sorry! An error occured.'
        if(err instanceof Error || err instanceof DOMError) {
          errorMsg = err.message + '\n\n' +
            err.stack;
        }
        gCl(this.div, 'rs-error-msg').textContent = errorMsg;
        this.cube.src = RemoteStorage.Assets.remoteStorageIconError;
        this.show_bubble();
      },
      unauthorized : function() {
        this.div.className = "remotestorage-state-unauthorized";
        this.cube.src = RemoteStorage.Assets.remoteStorageIconError;
        this.show_bubble();
        this.div.addEventListener('click', this.events.connect);
      }
    },

    events : {
      connect : function(event) {
        stop_propagation(event);
        event.preventDefault();
        this._emit('connect', gTl(this.div, 'form').userAddress.value);
      },
      sync : function(event) {
        stop_propagation(event);
        event.preventDefault();

        this._emit('sync');
      },
      disconnect : function(event) {
        stop_propagation(event);
        event.preventDefault();
        this._emit('disconnect');
      },
      reset : function(event){
        event.preventDefault();
        var result = window.confirm("Are you sure you want to reset everything? That will probably make the error go away, but also clear your entire localStorage and reload the page. Please make sure you know what you are doing, before clicking 'yes' :-)");
        if(result){
          this._emit('reset');
        }
      },
      display : function(event) {
        if(event)
          event.preventDefault();
        this._emit('display');
      }
    }
  };
})(this);
<commit_msg>minor bug fixed in view and \nin Error-State commented  DOMError check, not supported by most browsers #373(isn't DOMError also instanceof Error??)<commit_after>(function(window){

  //
  // helper methods
  //
  var cEl = document.createElement.bind(document);
  function gCl(parent, className) {
    return parent.getElementsByClassName(className)[0];
  }
  function gTl(parent, className) {
    return parent.getElementsByTagName(className)[0];
  }

  function removeClass(el, className) {
    return el.classList.remove(className);
  }

  function addClass(el, className) {
    return el.classList.add(className);
  }

  function stop_propagation(event) {
    if(typeof(event.stopPropagation) == 'function') {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
  }

  RemoteStorage.Widget.View = function() {
    if(typeof(document) === 'undefined') {
      throw "Widget not supported";
    }
    RemoteStorage.eventHandling(this,
                                'connect',
                                'disconnect',
                                'sync',
                                'display',
                                'reset');

    // re-binding the event so they can be called from the window
    for(var event in this.events){
      this.events[event] = this.events[event].bind(this);
    }

    // bubble toggling stuff
    this.toggle_bubble = function(event) {
      if(this.bubble.className.search('rs-hidden') < 0) {
        this.hide_bubble(event);
      } else {
        this.show_bubble(event);
      }
    }.bind(this);

    this.hide_bubble = function(){
      //console.log('hide bubble',this);
      addClass(this.bubble, 'rs-hidden')
      document.body.removeEventListener('click', hide_bubble_on_body_click);
    }.bind(this);

    var hide_bubble_on_body_click = function (event) {
      for(var p = event.target; p != document.body; p = p.parentElement) {
        if(p.id == 'remotestorage-widget') {
          return;
        }
      }
      this.hide_bubble();
    }.bind(this);

    this.show_bubble = function(event){
      //console.log('show bubble',this.bubble,event)
      removeClass(this.bubble, 'rs-hidden');
      if(typeof(event) != 'undefined') {
         stop_propagation(event);
       }
      document.body.addEventListener('click', hide_bubble_on_body_click);
      gTl(this.bubble,'form').userAddress.focus();
    }.bind(this);

    this.display = function(domID) {

      if(typeof(this.div) !== 'undefined')
        return this.div;

      var element = cEl('div');
      var style = cEl('style');
      style.innerHTML = RemoteStorage.Assets.widgetCss;

      element.id = "remotestorage-widget";

      element.innerHTML = RemoteStorage.Assets.widget;

      element.appendChild(style);
      if(domID) {
        var parent = document.getElementById(domID);
        if(! parent) {
          throw "Failed to find target DOM element with id=\"" + domID + "\"";
        }
        parent.appendChild(element);
      } else {
        document.body.appendChild(element);
      }

      var el;
      //sync button
      el = gCl(element, 'rs-sync');
      gTl(el, 'img').src = RemoteStorage.Assets.syncIcon;
      el.addEventListener('click', this.events.sync);

      //disconnect button
      el = gCl(element, 'rs-disconnect');
      gTl(el, 'img').src = RemoteStorage.Assets.disconnectIcon;
      el.addEventListener('click', this.events.disconnect);

      //get me out of here
      var el = gCl(element, 'remotestorage-reset').addEventListener('click', this.events.reset);
      //connect button
      var cb = gCl(element,'connect');
      gTl(cb, 'img').src = RemoteStorage.Assets.connectIcon;
      cb.addEventListener('click', this.events.connect);

      // input
      this.form = gTl(element, 'form')
      el = this.form.userAddress;
      el.addEventListener('keyup', function(event) {
        if(event.target.value) cb.removeAttribute('disabled');
        else cb.setAttribute('disabled','disabled');
      });
      if(this.userAddress) {
        el.value = this.userAddress;
      }

      //the cube
      el = gCl(element, 'rs-cube');
      el.src = RemoteStorage.Assets.remoteStorageIcon;
      el.addEventListener('click', this.toggle_bubble);
      this.cube = el

      //the bubble
      this.bubble = gCl(element,'rs-bubble');
      // what is the meaning of this hiding the b
      var bubbleDontCatch = { INPUT: true, BUTTON: true, IMG: true };
      this.bubble.addEventListener('click', function(event) {
        if(! bubbleDontCatch[event.target.tagName] && ! (this.div.classList.contains('remotestorage-state-unauthorized') )) {

          this.show_bubble(event);
        };
      }.bind(this))
      this.hide_bubble();

      this.div = element;

      this.states.initial.call(this);
      this.events.display.call(this);
      return this.div;
    };

  }

  RemoteStorage.Widget.View.prototype = {

    // Methods:
    //
    //  display(domID)
    //    draws the widget inside of the dom element with the id domID
    //   returns: the widget div
    //
    //  showBubble()
    //    shows the bubble
    //  hideBubble()
    //    hides the bubble
    //  toggleBubble()
    //    shows the bubble when hidden and the other way around
    //
    //  setState(state, args)
    //    calls states[state]
    //    args are the arguments for the
    //    state(errors mostly)
    //
    // setUserAddres
    //    set userAddress of the input field
    //
    // States:
    //  initial      - not connected
    //  authing      - in auth flow
    //  connected    - connected to remote storage, not syncing at the moment
    //  busy         - connected, syncing at the moment
    //  offline      - connected, but no network connectivity
    //  error        - connected, but sync error happened
    //  unauthorized - connected, but request returned 401
    //
    // Events:
    // connect    : fired when the connect button is clicked
    // sync       : fired when the sync button is clicked
    // disconnect : fired when the disconnect button is clicked
    // reset      : fired after crash triggers disconnect
    // display    : fired when finished displaying the widget
    setState : function(state, args) {
      RemoteStorage.log('widget.view.setState(',state,',',args,');');
      var s = this.states[state];
      if(typeof(s) === 'undefined') {
        throw new Error("Bad State assigned to view: " + state);
      }
      s.apply(this,args);
    },
    setUserAddress : function(addr) {
      this.userAddress = addr || '';

      var el;
      if(this.div && (el = gTl(this.div, 'form').userAddress)) {
        el.value = this.userAddress;
      }
    },

    states :  {
      initial : function(message) {
        var cube = this.cube;
        var info = message || 'This app allows you to use your own storage! Find more info on remotestorage.io';
        if(message) {
          cube.src = RemoteStorage.Assets.remoteStorageIconError;
          removeClass(this.cube, 'remotestorage-loading');
          this.show_bubble();
          setTimeout(function(){
            cube.src = RemoteStorage.Assets.remoteStorageIcon;
          },3512)
        } else {
          this.hide_bubble();
        }
        this.div.className = "remotestorage-state-initial";
        gCl(this.div, 'rs-status-text').innerHTML = "Connect remotestorage";

        //if address not empty connect button enabled
        //TODO check if this works
        var cb = gCl(this.div, 'connect')
        if(cb.value)
          cb.removeAttribute('disabled');

        var infoEl = gCl(this.div, 'rs-info-msg');
        infoEl.innerHTML = info;

        if(message) {
          infoEl.classList.add('remotestorage-error-info');
        } else {
          infoEl.classList.remove('remotestorage-error-info');
        }

      },
      authing : function() {
        this.div.removeEventListener('click', this.events.connect);
        this.div.className = "remotestorage-state-authing";
        gCl(this.div, 'rs-status-text').innerHTML = "Connecting "+this.userAddress+"";
        addClass(this.cube, 'remotestorage-loading'); //TODO needs to be undone, when is that neccesary
      },
      connected : function() {
        this.div.className = "remotestorage-state-connected";
        gCl(this.div, 'userAddress').innerHTML = this.userAddress;
        this.cube.src = RemoteStorage.Assets.remoteStorageIcon;
        removeClass(this.cube, 'remotestorage-loading');
      },
      busy : function() {
        this.div.className = "remotestorage-state-busy";
        addClass(this.cube, 'remotestorage-loading'); //TODO needs to be undone when is that neccesary
        this.hide_bubble();
      },
      offline : function() {
        this.div.className = "remotestorage-state-offline";
        this.cube.src = RemoteStorage.Assets.remoteStorageIconOffline;
        gCl(this.div, 'rs-status-text').innerHTML = 'Offline';
      },
      error : function(err) {
        var errorMsg = err;
        this.div.className = "remotestorage-state-error";

        gCl(this.div, 'rs-bubble-text').innerHTML = ' Sorry! An error occured.'
        if(err instanceof Error /*|| err instanceof DOMError*/) { //I don't know what an DOMError is and my browser doesn't know too(how to handle this?)
          errorMsg = err.message + '\n\n' +
            err.stack;
        }
        gCl(this.div, 'rs-error-msg').textContent = errorMsg;
        this.cube.src = RemoteStorage.Assets.remoteStorageIconError;
        this.show_bubble();
      },
      unauthorized : function() {
        this.div.className = "remotestorage-state-unauthorized";
        this.cube.src = RemoteStorage.Assets.remoteStorageIconError;
        this.show_bubble();
        this.div.addEventListener('click', this.events.connect);
      }
    },

    events : {
      connect : function(event) {
        stop_propagation(event);
        event.preventDefault();
        this._emit('connect', gTl(this.div, 'form').userAddress.value);
      },
      sync : function(event) {
        stop_propagation(event);
        event.preventDefault();

        this._emit('sync');
      },
      disconnect : function(event) {
        stop_propagation(event);
        event.preventDefault();
        this._emit('disconnect');
      },
      reset : function(event){
        event.preventDefault();
        var result = window.confirm("Are you sure you want to reset everything? That will probably make the error go away, but also clear your entire localStorage and reload the page. Please make sure you know what you are doing, before clicking 'yes' :-)");
        if(result){
          this._emit('reset');
        }
      },
      display : function(event) {
        if(event)
          event.preventDefault();
        this._emit('display');
      }
    }
  };
})(this);
<|endoftext|>


<commit_before>/*globals require, module, setImmediate */

'use strict';

var check, EventEmitter, errors, events,
    terminators, escapes;

check = require('check-types');
EventEmitter = require('events').EventEmitter;
errors = require('./errors');
events = require('./events');

terminators = {
    obj: '}',
    arr: ']'
};

escapes = {
    '"': '"',
    '\\': '\\',
    '/': '/',
    'b': '\b',
    'f': '\f',
    'n': '\n',
    'r': '\r',
    't': '\t'
};

module.exports = walk;

function walk (json) {
    var emitter, position, column, scopes, handlers, insideString;

    check.assert.string(json, 'JSON must be a string.');

    emitter = new EventEmitter();
    position = {
        index: 0,
        current: {
            line: 1,
            column: 1
        },
        previous: {}
    };
    scopes = [];
    handlers = {
        arr: value,
        obj: property
    };

    defer(value);

    return emitter;

    function value () {
        var character;

        ignoreWhitespace();

        character = next();

        switch (character) {
            case '[':
                return array();
            case '{':
                return object();
            case '"':
                return string();
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '-':
            case '.':
                return number(character);
            case 'f':
                return literalFalse();
            case 'n':
                return literalNull();
            case 't':
                return literalTrue();
            default:
                error(character, 'value', 'previous');
                value();
        }
    }

    function ignoreWhitespace () {
        while (isWhitespace(character())) {
            next();
        }
    }

    function next () {
        var result;

        if (isEnd()) {
            end();
        }

        result = character();

        position.index += 1;
        position.previous.line = position.current.line;
        position.previous.column = position.current.column;

        if (result === '\n') {
            position.current.line += 1;
            position.current.column = 1;
        } else {
            position.current.column += 1;
        }

        return result;
    }

    function isEnd () {
        return position.index === json.length;
    }

    function end () {
        if (insideString) {
            error('EOF', '"', 'current');
        }

        while (scopes.length > 0) {
            error('EOF', terminators[scopes.pop()], 'current');
        }

        emitter.emit(events.end);

        throw events.end;
    }

    function error (actual, expected, positionKey) {
        emitter.emit(
            events.error,
            errors.create(
                actual,
                expected,
                position[positionKey].line,
                position[positionKey].column
            )
        );
    }

    function character () {
        return json[position.index];
    }

    function array () {
        scope(events.array, value);
    }

    function scope (event, contentHandler) {
        emitter.emit(event);
        scopes.push(event);
        if (!endScope(event)) {
            defer(contentHandler);
        }
    }

    function object () {
        scope(events.object, property);
    }

    function property () {
        ignoreWhitespace();
        checkCharacter(next(), '"');

        walkString(events.property);

        ignoreWhitespace();
        checkCharacter(next(), ':');

        defer(value);
    }

    function walkString (event) {
        var quoting, string;

        insideString = true;
        quoting = false;
        string = '';

        while (quoting || character() !== '"') {
            if (quoting) {
                quoting = false;
                string += escape(next());
            } else if (character() === '\\') {
                quoting = true;
                next();
            } else {
                string += next();
            }
        }

        insideString = false;

        emitter.emit(event, string);
    }

    function escape (character) {
        if (escapes[character]) {
            return escapes[character];
        }

        if (character === 'u') {
            return escapeHex();
        }

        error(character, 'escape character', 'previous');

        return '\\' + character;
    }

    function escapeHex () {
        var hexits, i, character;

        hexits = '';

        for (i = 0; i < 4; i += 1) {
            character = next();
            if (isHexit(character)) {
                hexits += character;
            }
        }

        if (hexits.length === 4) {
            return String.fromCharCode(parseInt(hexits, 16));
        }

        error(character, 'hex digit', 'previous');

        return '\\u' + hexits + character;
    }

    function checkCharacter (character, expected) {
        if (character !== expected) {
            return error(character, expected, 'previous');
        }
    }

    function endScope (scope) {
        if (character() === terminators[scope]) {
            emitter.emit(events.endPrefix + scope);
            scopes.pop();
            next();
            defer(endValue);
            return true;
        }

        return false;
    }

    function endValue () {
        var scope;

        ignoreWhitespace();

        if (scopes.length === 0 && !isEnd()) {
            error(character(), 'EOF', 'current');
            return defer(value);
        }

        scope = scopes[scopes.length - 1];

        if (!endScope(scope)) {
            checkCharacter(next(), ',');
            defer(handlers[scope]);
        }
    }

    function string () {
        walkString(events.string);
        next();
        defer(endValue);
    }

    function number (firstCharacter) {
        var digits = firstCharacter + walkDigits();

        if (character() === '.') {
            digits += next() + walkDigits();
        }

        if (character() === 'e' || character() === 'E') {
            digits += next();

            if (character() === '+' || character() === '-') {
                digits += next();
            }

            digits += walkDigits();
        }

        emitter.emit(events.number, parseFloat(digits));
        defer(endValue);
    }

    function walkDigits () {
        var digits = '';

        while (!isEnd() && isDigit(character())) {
            digits += next();
        }

        return digits;
    }

    function literalFalse () {
        literal([ 'a', 'l', 's', 'e' ], false);
    }

    function literal (expectedCharacters, value) {
        var actual, expected, invalid;

        while (expectedCharacters.length > 0 && !isEnd()) {
            actual = next();
            expected = expectedCharacters.shift();

            if (actual !== expected) {
                invalid = true;
                break;
            }
        }

        if (invalid) {
            error(actual, expected, 'previous');
        } else if (expectedCharacters.length > 0) {
            error('EOF', expectedCharacters.shift(), 'current');
        } else {
            emitter.emit(events.literal, value);
        }

        defer(endValue);
    }

    function literalNull () {
        literal([ 'u', 'l', 'l' ], null);
    }

    function literalTrue () {
        literal([ 'r', 'u', 'e' ], true);
    }
}

function defer (fn) {
    setImmediate(function () {
        try {
            fn();
        } catch (error) {
            /*jshint noempty:false */
        }
    });
}

function isWhitespace (character) {
    switch (character) {
        case ' ':
        case '\t':
        case '\r':
        case '\n':
            return true;
    }

    return false;
}

function isHexit (character) {
    return isDigit(character) || isInRange(character, 'a', 'f');
}

function isDigit (character) {
    return isInRange(character, '0', '9');
}

function isInRange (character, lower, upper) {
    var code = character.charCodeAt(0);

    return code >= lower.charCodeAt(0) && code <= upper.charCodeAt(0);
}

<commit_msg>Start trying to convert walk logic to accept streams.<commit_after>/*globals require, module, setImmediate */

'use strict';

var util, Writable, EventEmitter, check, errors, events, terminators, escapes;

// TODO: Make `isEnd` async (consider promises, don't forget usage)
// TODO: Update calls to `isEnd` to await result
// TODO: Consider when to test `walking` and `finished` (currently in `end`)
// TODO: Consider how `end` should behave in the unfinished case (currently throws)
// NOTE: Exceptions swallowed by `defer`

util = require('util');
Writable = require('stream').Writable;
EventEmitter = require('events').EventEmitter;
check = require('check-types');
errors = require('./errors');
events = require('./events');

terminators = {
    obj: '}',
    arr: ']'
};

escapes = {
    '"': '"',
    '\\': '\\',
    '/': '/',
    'b': '\b',
    'f': '\f',
    'n': '\n',
    'r': '\r',
    't': '\t'
};

module.exports = begin;

function begin () {
    var json, emitter, stream,
        position, scopes, handlers,
        walking, insideString, finished;

    json = '';
    emitter = new EventEmitter();
    stream = new JsonStream(proceed);

    position = {
        index: 0,
        current: {
            line: 1,
            column: 1
        },
        previous: {}
    };
    scopes = [];
    handlers = {
        arr: value,
        obj: property
    };

    stream.on('finish', finish);

    return {
        emitter: emitter,
        stream: stream
    };

    function proceed (chunk) {
        json += chunk;

        if (!walking) {
            walking = true;
            defer(value);
        }
    }

    function finish () {
        finished = true;
    }

    function value () {
        var character;

        ignoreWhitespace();

        character = next();

        switch (character) {
            case '[':
                return array();
            case '{':
                return object();
            case '"':
                return string();
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '-':
            case '.':
                return number(character);
            case 'f':
                return literalFalse();
            case 'n':
                return literalNull();
            case 't':
                return literalTrue();
            default:
                error(character, 'value', 'previous');
                value();
        }
    }

    function ignoreWhitespace () {
        while (isWhitespace(character())) {
            next();
        }
    }

    function next () {
        var result;

        if (isEnd()) {
            end();
        }

        result = character();

        position.index += 1;
        position.previous.line = position.current.line;
        position.previous.column = position.current.column;

        if (result === '\n') {
            position.current.line += 1;
            position.current.column = 1;
        } else {
            position.current.column += 1;
        }

        return result;
    }

    function isEnd (callback) {
        if (walking) {
            return position.index === json.length;
        }

        defer(isEnd.bind(null, callback));
    }

    function end () {
        if (!finished) {
            walking = false;
            return;
        }

        if (insideString) {
            error('EOF', '"', 'current');
        }

        while (scopes.length > 0) {
            error('EOF', terminators[scopes.pop()], 'current');
        }

        emitter.emit(events.end);

        throw events.end;
    }

    function error (actual, expected, positionKey) {
        emitter.emit(
            events.error,
            errors.create(
                actual,
                expected,
                position[positionKey].line,
                position[positionKey].column
            )
        );
    }

    function character () {
        return json[position.index];
    }

    function array () {
        scope(events.array, value);
    }

    function scope (event, contentHandler) {
        emitter.emit(event);
        scopes.push(event);
        if (!endScope(event)) {
            defer(contentHandler);
        }
    }

    function object () {
        scope(events.object, property);
    }

    function property () {
        ignoreWhitespace();
        checkCharacter(next(), '"');

        walkString(events.property);

        ignoreWhitespace();
        checkCharacter(next(), ':');

        defer(value);
    }

    function walkString (event) {
        var quoting, string;

        insideString = true;
        quoting = false;
        string = '';

        while (quoting || character() !== '"') {
            if (quoting) {
                quoting = false;
                string += escape(next());
            } else if (character() === '\\') {
                quoting = true;
                next();
            } else {
                string += next();
            }
        }

        insideString = false;

        emitter.emit(event, string);
    }

    function escape (character) {
        if (escapes[character]) {
            return escapes[character];
        }

        if (character === 'u') {
            return escapeHex();
        }

        error(character, 'escape character', 'previous');

        return '\\' + character;
    }

    function escapeHex () {
        var hexits, i, character;

        hexits = '';

        for (i = 0; i < 4; i += 1) {
            character = next();
            if (isHexit(character)) {
                hexits += character;
            }
        }

        if (hexits.length === 4) {
            return String.fromCharCode(parseInt(hexits, 16));
        }

        error(character, 'hex digit', 'previous');

        return '\\u' + hexits + character;
    }

    function checkCharacter (character, expected) {
        if (character !== expected) {
            return error(character, expected, 'previous');
        }
    }

    function endScope (scope) {
        if (character() === terminators[scope]) {
            emitter.emit(events.endPrefix + scope);
            scopes.pop();
            next();
            defer(endValue);
            return true;
        }

        return false;
    }

    function endValue () {
        var scope;

        ignoreWhitespace();

        if (scopes.length === 0 && !isEnd()) {
            error(character(), 'EOF', 'current');
            return defer(value);
        }

        scope = scopes[scopes.length - 1];

        if (!endScope(scope)) {
            checkCharacter(next(), ',');
            defer(handlers[scope]);
        }
    }

    function string () {
        walkString(events.string);
        next();
        defer(endValue);
    }

    function number (firstCharacter) {
        var digits = firstCharacter + walkDigits();

        if (character() === '.') {
            digits += next() + walkDigits();
        }

        if (character() === 'e' || character() === 'E') {
            digits += next();

            if (character() === '+' || character() === '-') {
                digits += next();
            }

            digits += walkDigits();
        }

        emitter.emit(events.number, parseFloat(digits));
        defer(endValue);
    }

    function walkDigits () {
        var digits = '';

        while (!isEnd() && isDigit(character())) {
            digits += next();
        }

        return digits;
    }

    function literalFalse () {
        literal([ 'a', 'l', 's', 'e' ], false);
    }

    function literal (expectedCharacters, value) {
        var actual, expected, invalid;

        while (expectedCharacters.length > 0 && !isEnd()) {
            actual = next();
            expected = expectedCharacters.shift();

            if (actual !== expected) {
                invalid = true;
                break;
            }
        }

        if (invalid) {
            error(actual, expected, 'previous');
        } else if (expectedCharacters.length > 0) {
            error('EOF', expectedCharacters.shift(), 'current');
        } else {
            emitter.emit(events.literal, value);
        }

        defer(endValue);
    }

    function literalNull () {
        literal([ 'u', 'l', 'l' ], null);
    }

    function literalTrue () {
        literal([ 'r', 'u', 'e' ], true);
    }
}

function JsonStream (write, options) {
    if (!(this instanceof JsonStream)) {
        return new JsonStream();
    }

    this._write = function (chunk, encoding, callback) {
        write(chunk.toString());
    };

    return Writable.call(this);
}

util.inherits(JsonStream, Writable);

function defer (fn) {
    setImmediate(function () {
        try {
            fn();
        } catch (error) {
            /*jshint noempty:false */
        }
    });
}

function isWhitespace (character) {
    switch (character) {
        case ' ':
        case '\t':
        case '\r':
        case '\n':
            return true;
    }

    return false;
}

function isHexit (character) {
    return isDigit(character) || isInRange(character, 'a', 'f');
}

function isDigit (character) {
    return isInRange(character, '0', '9');
}

function isInRange (character, lower, upper) {
    var code = character.charCodeAt(0);

    return code >= lower.charCodeAt(0) && code <= upper.charCodeAt(0);
}

<|endoftext|>


<commit_before>var Deque = require("collections/deque")
var convexHull = require("quick-hull-2d")
var _visibility = require("vishull2d")
var intersect = require("segseg")
var explanations = require("./explanation")

function visibility(pts, cen){
    // convert vertex chain to line segments
    var segments = [
        [[0,0], [0,height]],
        [[0,height], [width,height]],
        [[width,height], [width, 0]],
        [[width, 0], [0,0]] ]
    for (var i = 0; i < pts.length; ++i) {
        var j = i+1;
        if (j == pts.length) j = 0;
        segments.push([pts[i], pts[j]]);
    }
    return _visibility(segments, cen);
}

function leftTurn(p0, p1, p2){
    var a = p1[0] - p0[0],
        b = p1[1] - p0[1],
        c = p2[0] - p1[0],
        d = p2[1] - p1[1];
    return a*d - b*c < -0.001;
}

function rightTurn(p0, p1, p2){
    var a = p1[0] - p0[0],
        b = p1[1] - p0[1],
        c = p2[0] - p1[0],
        d = p2[1] - p1[1];
    return a*d - b*c > 0.001;
}

function intersectsAny(p0, p1){
    var ret = false;
    g_lines.selectAll(".err").remove();
    for (var i = 0; i < points.length-2; i++){
        if (intersect(p0, p1, points[i], points[i+1])){
            ret = true
            console.log("gotcha")
            var p0 = points[i], p1 = points[i+1];
            g_lines.append("line")
                .attr("x1", p0[0])
                .attr("y1", p0[1])
                .attr("x2", p1[0])
                .attr("y2", p1[1])
                .attr("class", "err")
        }
    }
    return ret;
}

// Proceeding from p0 to p1, what point on the canvas boundary do you hit?
function toBoundary(p0, p1){
    var x = p1[0], y = p1[1],
        dx = x - p0[0],
        dy = y - p0[1];
    if (x===0 || y===0) return p1;

    // left wall
    var k = -x/dx;
    var h = y + dy*k;
    if (k > 0 && h >= 0 && h <= height) return [0, h];

    // top wall
    var k = -y/dy;
    var w = x + dx*k;
    if (k > 0 && w >= 0 && w <= width) return [w, 0];

    // right wall
    var k = (width-x)/dx;
    var h = y + dy*k;
    if (k > 0 && h >= 0 && h <= height) return [width, h];

    // top wall
    var k = (height-y)/dy;
    var w = x + dx*k;
    if (k > 0 && w >= 0 && w <= width) return [w, height];

    console.warn("toBoundary found unsatisfactory result for", p0, p1);
    return p1;
}

// Given two points on boundary edges, return an array of points of the
// corners hit traveling clockwise
function corners(b0, b1){
    var top = 0, right = 1, bottom = 2, left = 3;

    sideOf = function(b){
        if (b[0]===0){
            return left;
        }else if (b[0]===width){
            return right;
        }else if (b[1]===0){
            return top;
        }else if (b[1]===height){
            return bottom;
        }else{
            console.warn("corners called with non-boundary point", b)
        }
    }

    var s0 = sideOf(b0), s1 = sideOf(b1);

    var cornerPoints = [[width,0], [width, height], [0,height], [0,0]];
    ret = []

    while (s0 != s1){
        ret.push(cornerPoints[s0])
        s0++;
        s0 %= 4;
    }

    return ret;
}

var line_gen = d3.svg.line();
function line(){
    return g_lines.select("#path_poly").datum(points).attr("d", line_gen)
}

function hull_point(p){
    var g = g_points.append("g").attr("class", "hull-vertex").translate(p).datum(p);
    g.append("circle").attr("r", 10);
    if (p.s){ g.append("text").text(p.s).attr("dy", "4px"); }
    return g;
}

function interior_point(p){
    var g = g_points.append("g").attr("class", "interior-vertex").translate(p)
    g.append("circle").attr("r", 4)
    return g;
}

var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");

d3.selection.prototype.translate = function(a, b) {
  return arguments.length == 1
      ? this.attr("transform", "translate(" + a + ")")
      : this.attr("transform", "translate(" + a + "," + b + ")")
};

var red = "#FF7777", blue = "#7777FF", purple = "#DA54FF", yellow = "#FFFF84";

var margin = {top: 120, right: 20, bottom: 20, left: 400},
    width = window.innerWidth - margin.left - margin.right,
    height = window.innerHeight - margin.top - margin.bottom;

console.log("width", width, "height", height);

var svg_deque = d3.select("#deque")
            .attr("width", width + margin.right)
            .attr("height", margin.top - 10)

var svg_polygon = d3.select("#polygon")
            .attr("width", width + margin.right)
            .attr("height", height + margin.bottom)

d3.selectAll("svg").append("rect")
    .attr({width: width-2, x: 1, y: 1, class: "bg"})
    .attr("height", function(d,i){return i ? height : margin.top - 30})

var g_deque = svg_deque.append("g")
    .attr("transform", "translate("+((width - 60*4)/2)+",0)");

svg_deque.selectAll(".cover").data([0,0.5]).enter().append("rect")
    .attr({class: "cover", width: (width+margin.right)/2, height: margin.top})
    .attr("x", function(d){return d*(width+margin.right)});

var g_yellow = svg_polygon.append("g"),
    g_regions = svg_polygon.append("g"),
    g_lines  = svg_polygon.append("g"),
    g_points = svg_polygon.append("g");

g_lines.append("path").attr("id", "path_poly");
g_lines.append("path").attr("id", "path_hull");

var points = [];
var deque, lastOnHull;
var freeze = false;
var state = 0;
var initalLeftTurn;

var text = d3.select("#text");

function first3(){
    freeze = true;
    state = 1;
    text.html(explanations.okayStop)
    var a = points[0], b = points[1], c = points[2];
}

function revealDeque(){
    state++;
    text.html(explanations.dequeIntro);
    var a = points[0], b = points[1], c = points[2];
    lastOnHull = c;
    if (leftTurn(a,b,c)){
        deque = new Deque([b,a])
    }else{
        deque = new Deque([a,b])
    }
    renderDeque();
    svg_deque.selectAll(".cover").transition()
        .duration(1000)
        .attr("x", function(d,i){
            return i ? width+margin.right+10 : -(width+margin.right)/2 -10;
        })
        .remove();
}

function renderDeque(){
    g_deque.transition().duration(750)
        .attr("transform", "translate("+((width - 60*(deque.length+2))/2)+",0)")
    var items = g_deque.selectAll(".deque-vertex")
        .data([lastOnHull].concat(deque.toArray(), [lastOnHull]))
    var entering = items.enter().append("g");
    entering.append("rect")
        .attr({width: "40px", height: "40px", rx: "8px", ry: "8px"})
    entering.append("text")
        .translate(20,20)
        .attr("dy", "5px")
    items.select("text").text(function(d){ return d.s});
    items.attr("transform", function(d,i){return "translate(" + (i*60) + ","+ (margin.top / 2 - 35)+")"})
        .attr("class", "deque-vertex");
}

function pointC(){
    state++;
    var a = points[0], b = points[1], c = points[2];
    initialLeftTurn = leftTurn(a,b,c);
    text.html(explanations.pointC(initialLeftTurn));
    renderFills();
}

function renderFills(){
    svg_polygon.selectAll(".hull-vertex circle")
        .transition()
        //.duration(2000)
        .style("fill", function(d,i){
                        if (d.s === lastOnHull.s) { return purple; }
        })
        .transition()
        //.delay(3000)
        .style("fill", function(d){
                        if (d.s === lastOnHull.s) { return purple; }
                        if (d.s === deque.peek().s) { return red; }
                        if (d.s === deque.peekBack().s) { return blue; }
                        return "white";
        });

    var last;
    svg_deque.selectAll(".deque-vertex rect")
        .call(function(s){last = s.size()-1})
        .transition()
        //.duration(2000)
        .style("fill", function(d,i){
                        if (i == 0 || i == last) { return purple }
        })
        .transition()
        //.delay(3000)
        .style("fill", function(d,i){
                        if (i == 0 || i == last) { return purple }
                        if (i == 1) { return red }
                        if (i == last-1) { return blue }
        });
}

function rbpRegions(){
    state++;
    text.html(explanations.rbpRegions);
    renderRBPregions();
}

function renderRBPregions(){
    var transitionOutLen = 200;
    var transitionInLen = 300; // 1200?
    g_regions.selectAll("path.region").transition().duration(transitionOutLen)
        .style("fill", "white")
        .remove();
    var region = function(order, color, p1, p2, p3, p4){
        var b0 = toBoundary(p1, p2),
            b1 = toBoundary(p3, p4),
            outline = convexHull([b0, lastOnHull, b1].concat(corners(b0, b1)));
        g_regions.append("path")
            .datum(outline)
            .attr("d", line_gen)
            .attr("class", "region")
            .style("fill", "white")
          .transition()
            .duration(transitionInLen)
            .delay(order*transitionInLen + transitionOutLen)
            .style("fill", color)
    }
    var p_r = deque.peek();
    var p_b = deque.peekBack();
    var p_p = lastOnHull;
    region(0, blue,   p_p, p_b, p_r, p_p);
    region(1, red,    p_b, p_p, p_p, p_r);
    region(2, purple, p_r, p_p, p_b, p_p);
}

function yellowRegion(){
    state++;
    freeze = false;
    text.html(explanations.yellowRegion);
    renderYellowRegion();
}

function renderYellowRegion(){
    var transitionOutLen = 200;
    var transitionInLen = 300; // 1200?
    g_yellow.selectAll("path").transition().duration(transitionOutLen)
        .style("fill", "white")
        .remove();

    g_yellow.append("path")
        .datum(visibility(points, points[points.length-1]))
        .attr("d", line_gen)
        .style("fill", "white")
        .attr("class", "region")
      .transition()
        //.duration(1200)
        .style("fill", yellow)
}

function newPoint(pos){
    freeze = true;
    var red = rightTurn(deque.peek(), lastOnHull, pos);
    var blue = leftTurn(deque.peekBack(), lastOnHull, pos);
    console.log("red:", red, "blue:", blue)

    // TODO: Fix code duplication
    if (!red && !blue){
        interior_point(pos);
        points.push(pos);
        line();
        text.html(explanations.pointInYellow);
        renderYellowRegion();
        freeze = false;
    }

    if (red && !blue){
        hull_point(pos);
        points.push(pos);
        line();
        text.html(explanations.pointInRed);
        state = 20;
        deque.push(lastOnHull);
        var leftEdge = lastOnHull;
        while (rightTurn(deque.peek(), leftEdge, pos)){
            leftEdge = deque.shift();
        }
        deque.unshift(leftEdge);
        lastOnHull = pos;
        renderDeque();
        renderFills();
        renderRBPregions();
        renderYellowRegion();
        freeze = false;
    }

    if (!red && blue){
        hull_point(pos);
        points.push(pos);
        line();
        text.html(explanations.pointInBlue);
        state = 30;
        deque.unshift(lastOnHull);
        var rightEdge = lastOnHull;
        while (leftTurn(deque.peekBack(), rightEdge, pos)){
            rightEdge = deque.pop();
        }
        deque.push(rightEdge);
        lastOnHull = pos;
        renderDeque();
        renderFills();
        renderRBPregions();
        renderYellowRegion();
        freeze = false;
    }

    if (red && blue){
        hull_point(pos);
        points.push(pos);
        line();
        text.html(explanations.pointInPurple);
        state = 30;
        var leftEdge = lastOnHull;
        while (rightTurn(deque.peek(), leftEdge, pos)){
            leftEdge = deque.shift();
        }
        deque.unshift(leftEdge);
        var rightEdge = lastOnHull;
        while (leftTurn(deque.peekBack(), rightEdge, pos)){
            rightEdge = deque.pop();
        }
        deque.push(rightEdge);
        lastOnHull = pos;
        renderDeque();
        renderFills();
        renderRBPregions();
        renderYellowRegion();
        freeze = false;
    }

}

svg_polygon.on("click", function(){
    if (freeze) return;
    var pos = d3.mouse(svg_polygon.node());
    if (points.length > 0){
        var prev = points[points.length-1]
        if (intersectsAny(prev, pos)){
            return;
        }
    }
    pos.s = alphabet[points.length];
    if (points.length >= 3) return newPoint(pos);
    hull_point(pos);
    points.push(pos);
    line();
    if (points.length === 3) first3();
})

d3.select("body").on("keydown", function(){
    if (d3.event.keyCode == 32){
        switch (state){
            case 1:
                revealDeque();
            break;
            case 2:
                pointC();
            break;
            case 3:
                rbpRegions();
            break;
            case 4:
                yellowRegion();
            break;
            default:
        }
    }
})
<commit_msg>More consistent names<commit_after>var Deque = require("collections/deque")
var convexHull = require("quick-hull-2d")
var _visibility = require("vishull2d")
var intersect = require("segseg")
var explanations = require("./explanation")

function visibility(pts, cen){
    // convert vertex chain to line segments
    var segments = [
        [[0,0], [0,height]],
        [[0,height], [width,height]],
        [[width,height], [width, 0]],
        [[width, 0], [0,0]] ]
    for (var i = 0; i < pts.length; ++i) {
        var j = i+1;
        if (j == pts.length) j = 0;
        segments.push([pts[i], pts[j]]);
    }
    return _visibility(segments, cen);
}

function leftTurn(p0, p1, p2){
    var a = p1[0] - p0[0],
        b = p1[1] - p0[1],
        c = p2[0] - p1[0],
        d = p2[1] - p1[1];
    return a*d - b*c < -0.001;
}

function rightTurn(p0, p1, p2){
    var a = p1[0] - p0[0],
        b = p1[1] - p0[1],
        c = p2[0] - p1[0],
        d = p2[1] - p1[1];
    return a*d - b*c > 0.001;
}

function intersectsAny(p0, p1){
    var ret = false;
    g_lines.selectAll(".err").remove();
    for (var i = 0; i < points.length-2; i++){
        if (intersect(p0, p1, points[i], points[i+1])){
            ret = true
            console.log("gotcha")
            var p0 = points[i], p1 = points[i+1];
            g_lines.append("line")
                .attr("x1", p0[0])
                .attr("y1", p0[1])
                .attr("x2", p1[0])
                .attr("y2", p1[1])
                .attr("class", "err")
        }
    }
    return ret;
}

// Proceeding from p0 to p1, what point on the canvas boundary do you hit?
function toBoundary(p0, p1){
    var x = p1[0], y = p1[1],
        dx = x - p0[0],
        dy = y - p0[1];
    if (x===0 || y===0) return p1;

    // left wall
    var k = -x/dx;
    var h = y + dy*k;
    if (k > 0 && h >= 0 && h <= height) return [0, h];

    // top wall
    var k = -y/dy;
    var w = x + dx*k;
    if (k > 0 && w >= 0 && w <= width) return [w, 0];

    // right wall
    var k = (width-x)/dx;
    var h = y + dy*k;
    if (k > 0 && h >= 0 && h <= height) return [width, h];

    // top wall
    var k = (height-y)/dy;
    var w = x + dx*k;
    if (k > 0 && w >= 0 && w <= width) return [w, height];

    console.warn("toBoundary found unsatisfactory result for", p0, p1);
    return p1;
}

// Given two points on boundary edges, return an array of points of the
// corners hit traveling clockwise
function corners(b0, b1){
    var top = 0, right = 1, bottom = 2, left = 3;

    sideOf = function(b){
        if (b[0]===0){
            return left;
        }else if (b[0]===width){
            return right;
        }else if (b[1]===0){
            return top;
        }else if (b[1]===height){
            return bottom;
        }else{
            console.warn("corners called with non-boundary point", b)
        }
    }

    var s0 = sideOf(b0), s1 = sideOf(b1);

    var cornerPoints = [[width,0], [width, height], [0,height], [0,0]];
    ret = []

    while (s0 != s1){
        ret.push(cornerPoints[s0])
        s0++;
        s0 %= 4;
    }

    return ret;
}

var line_gen = d3.svg.line();
function line(){
    return g_lines.select("#path_poly").datum(points).attr("d", line_gen)
}

function hullPoint(p){
    var g = g_points.append("g").attr("class", "hull-vertex").translate(p).datum(p);
    g.append("circle").attr("r", 10);
    if (p.s){ g.append("text").text(p.s).attr("dy", "4px"); }
    return g;
}

function interiorPoint(p){
    var g = g_points.append("g").attr("class", "interior-vertex").translate(p)
    g.append("circle").attr("r", 4)
    return g;
}

var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");

d3.selection.prototype.translate = function(a, b) {
  return arguments.length == 1
      ? this.attr("transform", "translate(" + a + ")")
      : this.attr("transform", "translate(" + a + "," + b + ")")
};

var red = "#FF7777", blue = "#7777FF", purple = "#DA54FF", yellow = "#FFFF84";

var margin = {top: 120, right: 20, bottom: 20, left: 400},
    width = window.innerWidth - margin.left - margin.right,
    height = window.innerHeight - margin.top - margin.bottom;

console.log("width", width, "height", height);

var svg_deque = d3.select("#deque")
            .attr("width", width + margin.right)
            .attr("height", margin.top - 10)

var svg_polygon = d3.select("#polygon")
            .attr("width", width + margin.right)
            .attr("height", height + margin.bottom)

d3.selectAll("svg").append("rect")
    .attr({width: width-2, x: 1, y: 1, class: "bg"})
    .attr("height", function(d,i){return i ? height : margin.top - 30})

var g_deque = svg_deque.append("g")
    .attr("transform", "translate("+((width - 60*4)/2)+",0)");

svg_deque.selectAll(".cover").data([0,0.5]).enter().append("rect")
    .attr({class: "cover", width: (width+margin.right)/2, height: margin.top})
    .attr("x", function(d){return d*(width+margin.right)});

var g_yellow = svg_polygon.append("g"),
    g_regions = svg_polygon.append("g"),
    g_lines  = svg_polygon.append("g"),
    g_points = svg_polygon.append("g");

g_lines.append("path").attr("id", "path_poly");
g_lines.append("path").attr("id", "path_hull");

var points = [];
var deque, lastOnHull;
var freeze = false;
var state = 0;
var initalLeftTurn;

var text = d3.select("#text");

function first3(){
    freeze = true;
    state = 1;
    text.html(explanations.okayStop)
    var a = points[0], b = points[1], c = points[2];
}

function revealDeque(){
    state++;
    text.html(explanations.dequeIntro);
    var a = points[0], b = points[1], c = points[2];
    lastOnHull = c;
    if (leftTurn(a,b,c)){
        deque = new Deque([b,a])
    }else{
        deque = new Deque([a,b])
    }
    renderDeque();
    svg_deque.selectAll(".cover").transition()
        .duration(1000)
        .attr("x", function(d,i){
            return i ? width+margin.right+10 : -(width+margin.right)/2 -10;
        })
        .remove();
}

function renderDeque(){
    g_deque.transition().duration(750)
        .attr("transform", "translate("+((width - 60*(deque.length+2))/2)+",0)")
    var items = g_deque.selectAll(".deque-vertex")
        .data([lastOnHull].concat(deque.toArray(), [lastOnHull]))
    var entering = items.enter().append("g");
    entering.append("rect")
        .attr({width: "40px", height: "40px", rx: "8px", ry: "8px"})
    entering.append("text")
        .translate(20,20)
        .attr("dy", "5px")
    items.select("text").text(function(d){ return d.s});
    items.attr("transform", function(d,i){return "translate(" + (i*60) + ","+ (margin.top / 2 - 35)+")"})
        .attr("class", "deque-vertex");
}

function pointC(){
    state++;
    var a = points[0], b = points[1], c = points[2];
    initialLeftTurn = leftTurn(a,b,c);
    text.html(explanations.pointC(initialLeftTurn));
    renderFills();
}

function renderFills(){
    svg_polygon.selectAll(".hull-vertex circle")
        .transition()
        //.duration(2000)
        .style("fill", function(d,i){
                        if (d.s === lastOnHull.s) { return purple; }
        })
        .transition()
        //.delay(3000)
        .style("fill", function(d){
                        if (d.s === lastOnHull.s) { return purple; }
                        if (d.s === deque.peek().s) { return red; }
                        if (d.s === deque.peekBack().s) { return blue; }
                        return "white";
        });

    var last;
    svg_deque.selectAll(".deque-vertex rect")
        .call(function(s){last = s.size()-1})
        .transition()
        //.duration(2000)
        .style("fill", function(d,i){
                        if (i == 0 || i == last) { return purple }
        })
        .transition()
        //.delay(3000)
        .style("fill", function(d,i){
                        if (i == 0 || i == last) { return purple }
                        if (i == 1) { return red }
                        if (i == last-1) { return blue }
        });
}

function rbpRegions(){
    state++;
    text.html(explanations.rbpRegions);
    renderRBPregions();
}

function renderRBPregions(){
    var transitionOutLen = 200;
    var transitionInLen = 300; // 1200?
    g_regions.selectAll("path.region").transition().duration(transitionOutLen)
        .style("fill", "white")
        .remove();
    var region = function(order, color, p1, p2, p3, p4){
        var b0 = toBoundary(p1, p2),
            b1 = toBoundary(p3, p4),
            outline = convexHull([b0, lastOnHull, b1].concat(corners(b0, b1)));
        g_regions.append("path")
            .datum(outline)
            .attr("d", line_gen)
            .attr("class", "region")
            .style("fill", "white")
          .transition()
            .duration(transitionInLen)
            .delay(order*transitionInLen + transitionOutLen)
            .style("fill", color)
    }
    var p_r = deque.peek();
    var p_b = deque.peekBack();
    var p_p = lastOnHull;
    region(0, blue,   p_p, p_b, p_r, p_p);
    region(1, red,    p_b, p_p, p_p, p_r);
    region(2, purple, p_r, p_p, p_b, p_p);
}

function yellowRegion(){
    state++;
    freeze = false;
    text.html(explanations.yellowRegion);
    renderYellowRegion();
}

function renderYellowRegion(){
    var transitionOutLen = 200;
    var transitionInLen = 300; // 1200?
    g_yellow.selectAll("path").transition().duration(transitionOutLen)
        .style("fill", "white")
        .remove();

    g_yellow.append("path")
        .datum(visibility(points, points[points.length-1]))
        .attr("d", line_gen)
        .style("fill", "white")
        .attr("class", "region")
      .transition()
        //.duration(1200)
        .style("fill", yellow)
}

function newPoint(pos){
    freeze = true;
    var red = rightTurn(deque.peek(), lastOnHull, pos);
    var blue = leftTurn(deque.peekBack(), lastOnHull, pos);
    console.log("red:", red, "blue:", blue)

    // TODO: Fix code duplication
    if (!red && !blue){
        interiorPoint(pos);
        points.push(pos);
        line();
        text.html(explanations.pointInYellow);
        renderYellowRegion();
        freeze = false;
    }

    if (red && !blue){
        hullPoint(pos);
        points.push(pos);
        line();
        text.html(explanations.pointInRed);
        state = 20;
        deque.push(lastOnHull);
        var leftEdge = lastOnHull;
        while (rightTurn(deque.peek(), leftEdge, pos)){
            leftEdge = deque.shift();
        }
        deque.unshift(leftEdge);
        lastOnHull = pos;
        renderDeque();
        renderFills();
        renderRBPregions();
        renderYellowRegion();
        freeze = false;
    }

    if (!red && blue){
        hullPoint(pos);
        points.push(pos);
        line();
        text.html(explanations.pointInBlue);
        state = 30;
        deque.unshift(lastOnHull);
        var rightEdge = lastOnHull;
        while (leftTurn(deque.peekBack(), rightEdge, pos)){
            rightEdge = deque.pop();
        }
        deque.push(rightEdge);
        lastOnHull = pos;
        renderDeque();
        renderFills();
        renderRBPregions();
        renderYellowRegion();
        freeze = false;
    }

    if (red && blue){
        hullPoint(pos);
        points.push(pos);
        line();
        text.html(explanations.pointInPurple);
        state = 30;
        var leftEdge = lastOnHull;
        while (rightTurn(deque.peek(), leftEdge, pos)){
            leftEdge = deque.shift();
        }
        deque.unshift(leftEdge);
        var rightEdge = lastOnHull;
        while (leftTurn(deque.peekBack(), rightEdge, pos)){
            rightEdge = deque.pop();
        }
        deque.push(rightEdge);
        lastOnHull = pos;
        renderDeque();
        renderFills();
        renderRBPregions();
        renderYellowRegion();
        freeze = false;
    }

}

svg_polygon.on("click", function(){
    if (freeze) return;
    var pos = d3.mouse(svg_polygon.node());
    if (points.length > 0){
        var prev = points[points.length-1]
        if (intersectsAny(prev, pos)){
            return;
        }
    }
    pos.s = alphabet[points.length];
    if (points.length >= 3) return newPoint(pos);
    hullPoint(pos);
    points.push(pos);
    line();
    if (points.length === 3) first3();
})

d3.select("body").on("keydown", function(){
    if (d3.event.keyCode == 32){
        switch (state){
            case 1:
                revealDeque();
            break;
            case 2:
                pointC();
            break;
            case 3:
                rbpRegions();
            break;
            case 4:
                yellowRegion();
            break;
            default:
        }
    }
})
<|endoftext|>