<commit_before>import { h } from '../render'

const AppState = {
  athleteId: null,
  gear: {},
  components: {},
  unit: null
}

function queryAthleteId (document) {
  // Convert "/atheletes/id" href to "id"
  return document.querySelector(
    '#athlete-profile a[href^="/athletes/"]'
  )
    .href
    .split('/')
    .pop()
}

function queryContainerNode (document) {
  return document.querySelector(
    '#dashboard-athlete-sidebar .fixed-sidebar-container div'
  )
}

function queryDisplayUnit (document) {
  // FIXME: this is iffy. Some activities (rowing) are hardcoded to
  //   meters even if display preferences are for miles.
  const distanceUnit = (
    document.querySelector('div.activity .stat abbr.unit')
  )
  if (distanceUnit !== null) {
    return distanceUnit.innerText
  }

  return ''
}

function queryOrCreateRootNode (containerNode) {
  let n = containerNode.querySelector('#gear-ratio-app')
  if (n !== null) {
    return n
  }

  n = h('div', { id: 'gear-ratio-app' })
  containerNode.appendChild(n)
  return n
}

async function initializeState (document) {
  AppState.athleteId = queryAthleteId(document)
  AppState.unit = queryDisplayUnit(document)

  AppState.gear = await fetchGear(AppState.athleteId)

  for (const bike of AppState.gear.bikes) {
    const components = await fetchBikeComponents(bike.id)
    AppState.components[bike.id] = components
  }

  console.info('initialized app state:', AppState)
}

function parseTable (table) {
  const data = []

  // TODO(erik): hardcoding is bad
  const columnNames = [
    'type',
    'make',
    'model',
    'added',
    'removed',
    'distance',
    'action'
  ]

  // TODO: this provides localized names, should use this
  // const columnNames = Array.from(
  //   table.rows[0].cells
  // ).map(it => it.innerText.trim())

  for (let i = 1; i < table.rows.length; ++i) {
    const row = table.rows[i]
    const rowData = {}

    for (let j = 0; j < row.cells.length; ++j) {
      const name = columnNames[j]
      let val = row.cells[j].innerText.trim()

      // TODO: actually parse this (localized, of course)
      if (name === 'distance') {
        val = val.replace(/[^0-9,. ]/g, '')
      }

      rowData[columnNames[j]] = val
    }

    data.push(rowData)
  }

  return data
}

async function fetchJSON (url) {
  const res = await fetch(url)
  return await res.json()
}

async function fetchHTML (url) {
  const res = await fetch(url)
  const text = await res.text()

  return new DOMParser().parseFromString(text, 'text/html')
}

async function fetchGear (athleteId) {
  const baseURL = `https://www.strava.com/athletes/${athleteId}/gear/`

  const bikes = await fetchJSON(baseURL + 'bikes')
  const shoes = await fetchJSON(baseURL + 'shoes')

  const res = {
    bikes: bikes.filter(it => it.active),
    shoes: shoes.filter(it => it.active)
  }

  return res
}

async function fetchBikeComponents (gearId) {
  const url = `https://www.strava.com/bikes/${gearId}`
  const doc = await fetchHTML(url)

  // Should have two elements:
  //   1. Bike details, not of interest
  //   2. Components
  const tables = doc.querySelectorAll('table')
  return parseTable(tables[1])
}

function render (state) {
  const distance = (d) => `${d} ${state.unit}`

  const bikes = state.gear.bikes.map(bike => {
    const href = `https://strava.com/bikes/${bike.id}`

    const components = state.components[bike.id].map(c => {
      // This implies s is the string "This bike has no active components" (localized)
      if (!c.added) {
        return h('li', {}, c.type)
      }

      return h('li', {
        style: 'display: grid; grid-template-columns: repeat(12, 1fr);'
      }, [
        h('span', {
          style: 'grid-column: 1/8;'
        }, [
          c.type
        ]),
        h('span', { style: 'grid-column: 8/12;text-align:right;', title: c.added }, [
          h('span', { style: 'border-bottom: 1px dotted #777;' }, distance(c.distance))
        ])
      ])
    })

    return h('p', { className: 'text-small' }, [
      h('div', { className: 'text-label' }, [
        h('strong', {}, h('a', { href }, bike.display_name)),
        ' • ',
        h('span', {}, distance(bike.total_distance))
      ]),
      h('ul', {}, components)
    ])
  })
  const shoes = state.gear.shoes.map(shoe => {
    const href = `https://strava.com/shoes/${shoe.id}`
    return h('div', { className: 'text-small' }, [
      h('div', { className: 'text-label' }, [
        h('a', { href }, [
          h('strong', {}, shoe.display_name)
        ]),
        h('span', {}, [
          ' • ',
          distance(shoe.total_distance)
        ])
      ])
    ])
  })

  return h('div', { className: 'card' }, [
    h('div', { className: 'card-body' }, [
      h('div', { className: 'card-section' }, 'Bikes'),
      h('div', { className: 'card-section' }, [
        h('div', {}, bikes)
      ])
    ]),
    h('div', { className: 'card-body' }, [
      h('div', { className: 'card-section' }, 'Shoes'),
      h('div', { className: 'card-section' }, [
        h('div', {}, shoes)
      ])
    ]),
    renderCardFooter()
  ])
}

function renderCardFooter () {
  return h('div', { className: 'card-footer' }, [
    h('div', { className: 'card-section' }, [
      h('a', {
        className: 'btn-card-link media media-middle',
        href: '/settings/gear'
      }, [
        h('div', { className: 'media-body' }, 'Manage Your Gear'),
        h('div', { className: 'media-right' }, [
          h('span', { className: 'app-icon-wrapper' }, [
            h('span', { className: 'app-icon icon-caret-right icon-dark icon-lg' })
          ])
        ])
      ])
    ])
  ])
}

function renderError (error) {
  return h('div', { className: 'card' }, [
    h('div', { className: 'card-body' }, [
      h('div', { className: 'card-section' }, 'Sorry :('),
      h('div', { className: 'card-section' }, [
        h('p', {}, 'Something went wrong'),
        h('p', {}, 'Details:'),
        h('pre', {}, error.toString())
      ])
    ]),
    renderCardFooter()
  ])
}

function renderInitial () {
  return h('div', { className: 'card' }, [
    h('div', { className: 'card-body' }, [
      h('div', { className: 'card-section' }, [
        h('div', { className: 'loading-container' }, [
          h('div', { className: 'spinner sm' }, [
            h('div', { className: 'graphic' }),
            h('span', { className: 'status' }, 'Loading ...')
          ])
        ])
      ])
    ]),
    renderCardFooter()
  ])
}

(async () => {
  const containerNode = queryContainerNode(document)
  const rootNode = queryOrCreateRootNode(containerNode)

  try {
    rootNode.replaceChildren(renderInitial())
    await initializeState(document)
    rootNode.replaceChildren(render(AppState))
  } catch (err) {
    console.exception('failed with', err)
    rootNode.replaceChildren(renderError(err))
  }
})()
<commit_msg>Hacky initial support for displaying links<commit_after>import { h } from '../render'
import { storage } from '../storage'

const AppState = {
  storage: null,
  athleteId: null,
  gear: {},
  components: {},
  unit: null
}

function queryAthleteId (document) {
  // Convert "/atheletes/id" href to "id"
  return document.querySelector(
    '#athlete-profile a[href^="/athletes/"]'
  )
    .href
    .split('/')
    .pop()
}

function queryContainerNode (document) {
  return document.querySelector(
    '#dashboard-athlete-sidebar .fixed-sidebar-container div'
  )
}

function queryDisplayUnit (document) {
  // FIXME: this is iffy. Some activities (rowing) are hardcoded to
  //   meters even if display preferences are for miles.
  const distanceUnit = (
    document.querySelector('div.activity .stat abbr.unit')
  )
  if (distanceUnit !== null) {
    return distanceUnit.innerText
  }

  return ''
}

function queryOrCreateRootNode (containerNode) {
  let n = containerNode.querySelector('#gear-ratio-app')
  if (n !== null) {
    return n
  }

  n = h('div', { id: 'gear-ratio-app' })
  containerNode.appendChild(n)
  return n
}

async function initializeState (document, storage) {
  AppState.athleteId = queryAthleteId(document)
  AppState.unit = queryDisplayUnit(document)
  AppState.storage = storage

  AppState.gear = await fetchGear(AppState.athleteId)

  for (const bike of AppState.gear.bikes) {
    const components = await fetchBikeComponents(bike.id)
    AppState.components[bike.id] = components
  }

  console.info('initialized app state:', AppState)
}

function parseTable (table) {
  const data = []

  // TODO(erik): hardcoding is bad
  const columnNames = [
    'type',
    'make',
    'model',
    'added',
    'removed',
    'distance',
    'action'
  ]

  // TODO: this provides localized names, should use this
  // const columnNames = Array.from(
  //   table.rows[0].cells
  // ).map(it => it.innerText.trim())

  for (let i = 1; i < table.rows.length; ++i) {
    const row = table.rows[i]
    const rowData = {}

    for (let j = 0; j < row.cells.length; ++j) {
      const name = columnNames[j]
      let val = row.cells[j].innerText.trim()

      // TODO: actually parse this (localized, of course)
      if (name === 'distance') {
        val = val.replace(/[^0-9,. ]/g, '')
      }

      rowData[columnNames[j]] = val
    }

    data.push(rowData)
  }

  return data
}

async function fetchJSON (url) {
  const res = await fetch(url)
  return await res.json()
}

async function fetchHTML (url) {
  const res = await fetch(url)
  const text = await res.text()

  return new DOMParser().parseFromString(text, 'text/html')
}

async function fetchGear (athleteId) {
  const baseURL = `https://www.strava.com/athletes/${athleteId}/gear/`

  const bikes = await fetchJSON(baseURL + 'bikes')
  const shoes = await fetchJSON(baseURL + 'shoes')

  const res = {
    bikes: bikes.filter(it => it.active),
    shoes: shoes.filter(it => it.active)
  }

  return res
}

async function fetchBikeComponents (gearId) {
  const url = `https://www.strava.com/bikes/${gearId}`
  const doc = await fetchHTML(url)

  // Should have two elements:
  //   1. Bike details, not of interest
  //   2. Components
  const tables = doc.querySelectorAll('table')
  return parseTable(tables[1])
}

// TODO: This is BAD.
//  1. render should not be async, state should already be set up
//  2. TOO BIG, split it up.
//  3. linked / unlinked bikes is far too messy, clean it up and dedupe
async function render (state) {
  const distance = (d) => `${d} ${state.unit}`

  const links = await storage.bikeLinks()
  const bikesInLink = new Set()
  Object.values(links).forEach(link => link.bike_ids.forEach(id => bikesInLink.add(id)))

  const unlinkedBikes = state.gear.bikes
  // .filter(bike => !bikesInLink.has(bike.id))
    .map(bike => {
      const href = `https://strava.com/bikes/${bike.id}`
      const isLinked = bikesInLink.has(bike.id)

      const components = state.components[bike.id].map(c => {
        // This implies s is the string "This bike has no active components" (localized)
        if (!c.added) {
          return h('li', {}, c.type)
        }

        return h('li', {
          style: 'display: grid; grid-template-columns: repeat(12, 1fr);'
        }, [
          h('span', {
            style: 'grid-column: 1/8;'
          }, [
            c.type
          ]),
          h('span', { style: 'grid-column: 8/12;text-align:right;', title: c.added }, [
            h('span', { style: 'border-bottom: 1px dotted #777;' }, distance(c.distance))
          ])
        ])
      })

      return h('p', { class: 'text-small' }, [
        h('div', { class: 'text-label' }, [
          h('strong', {}, h('a', { href }, bike.display_name)),
          ' • ',
          h('span', {}, distance(bike.total_distance)),
          isLinked
            ? h('span', { title: 'Linked to xyz' }, (' • 🔗'))
            : null
        ]),
        h('ul', {}, components)
      ])
    })

  const linkedBikes = Object.values(links).map(link => {
    const href = '/settings/gear'

    const sharedComponents = {}
    for (const id of link.bike_ids) {
      const components = state.components[id].filter(c => {
        return link.shared_components.some(shared => shared === c.type)
      })

      components.forEach(c => {
        sharedComponents[c.type] = sharedComponents[c.type] || {}
        sharedComponents[c.type][id] = c
      })
    }

    const components = Object.entries(sharedComponents).map(([type, values]) => {
      // TODO: This isn't a reasonable approach given i18n, see here:
      //  https://observablehq.com/@mbostock/localized-number-parsing
      const combinedDist = Object.values(values)
        .map(v => +v.distance.replace(',', ''))
        .reduce((x, y) => x + y)

      const sourceBikes = link.bike_ids
        .filter(id => typeof sharedComponents[type][id] !== 'undefined')
        .map(id => {
          const bike = state.gear.bikes.find(b => b.id === id)
          const dist = sharedComponents[type][id].distance

          return `${bike.display_name}: ${dist}`
        })
        .join('\n')

      return h('li', {
        style: 'display: grid; grid-template-columns: repeat(12, 1fr);'
      }, [
        h('span', { style: 'grid-column: 1/8;' }, type),
        h('span', { style: 'grid-column: 8/12;text-align:right;' }, [
          h('span', {
            style: 'border-bottom: 1px dotted #777;',
            title: sourceBikes
          }, distance(combinedDist.toLocaleString()))
        ])
      ])
    })

    // XXX: hack
    const totalDistance = link.bike_ids
      .map(id => state.gear.bikes.find(b => b.id === id))
      .map(v => +v.total_distance.replace(',', ''))
      .reduce((x, y) => x + y)

    return h('p', { class: 'text-small' }, [
      h('div', { class: 'text-label' }, [
        h('strong', {}, h('a', { href }, link.name)),
        ' • ',
        h('span', {}, distance(totalDistance.toLocaleString()))
      ]),
      h('ul', {}, components)
    ])
  })

  const shoes = state.gear.shoes.map(shoe => {
    const href = `https://strava.com/shoes/${shoe.id}`
    return h('div', { class: 'text-small' }, [
      h('div', { class: 'text-label' }, [
        h('a', { href }, [
          h('strong', {}, shoe.display_name)
        ]),
        h('span', {}, [
          ' • ',
          distance(shoe.total_distance)
        ])
      ])
    ])
  })

  return h('div', { class: 'card' }, [
    h('div', { class: 'card-body' }, [
      h('div', { class: 'card-section' }, 'Bikes'),
      h('div', { class: 'card-section' }, [
        h('div', {}, unlinkedBikes),
        'Linked', // TODO: better name? Linked is confusing
        h('div', {}, linkedBikes)
      ])
    ]),
    h('div', { class: 'card-body' }, [
      h('div', { class: 'card-section' }, 'Shoes'),
      h('div', { class: 'card-section' }, [
        h('div', {}, shoes)
      ])
    ]),
    renderCardFooter()
  ])
}

function renderCardFooter () {
  return h('div', { class: 'card-footer' }, [
    h('div', { class: 'card-section' }, [
      h('a', {
        class: 'btn-card-link media media-middle',
        href: '/settings/gear'
      }, [
        h('div', { class: 'media-body' }, 'Manage Your Gear'),
        h('div', { class: 'media-right' }, [
          h('span', { class: 'app-icon-wrapper' }, [
            h('span', { class: 'app-icon icon-caret-right icon-dark icon-lg' })
          ])
        ])
      ])
    ])
  ])
}

function renderError (error) {
  return h('div', { class: 'card' }, [
    h('div', { class: 'card-body' }, [
      h('div', { class: 'card-section' }, 'Sorry :('),
      h('div', { class: 'card-section' }, [
        h('p', {}, 'Something went wrong'),
        h('p', {}, 'Details:'),
        h('pre', {}, error.toString())
      ])
    ]),
    renderCardFooter()
  ])
}

function renderInitial () {
  return h('div', { class: 'card' }, [
    h('div', { class: 'card-body' }, [
      h('div', { class: 'card-section' }, [
        h('div', { class: 'loading-container' }, [
          h('div', { class: 'spinner sm' }, [
            h('div', { class: 'graphic' }),
            h('span', { class: 'status' }, 'Loading ...')
          ])
        ])
      ])
    ]),
    renderCardFooter()
  ])
}

(async () => {
  const containerNode = queryContainerNode(document)
  const rootNode = queryOrCreateRootNode(containerNode)

  try {
    await storage.applyMigrations()

    rootNode.replaceChildren(renderInitial())
    await initializeState(document, storage)
    const rendered = await render(AppState)
    rootNode.replaceChildren(rendered)
  } catch (err) {
    console.exception('failed with', err)
    rootNode.replaceChildren(renderError(err))
  }
})()
<|endoftext|>


<commit_before>/*eslint dot-notation:0*/
import {
  Parser,
  ObserverLocator,
  EventManager,
  ListenerExpression,
  BindingExpression,
  CallExpression,
  bindingMode
} from 'aurelia-binding';

import {BehaviorInstruction} from 'aurelia-templating';
import * as LogManager from 'aurelia-logging';
import {AttributeMap} from './attribute-map';

export class SyntaxInterpreter {
  static inject = [Parser, ObserverLocator, EventManager, AttributeMap];

  constructor(parser, observerLocator, eventManager, attributeMap) {
    this.parser = parser;
    this.observerLocator = observerLocator;
    this.eventManager = eventManager;
    this.attributeMap = attributeMap;
  }

  interpret(resources, element, info, existingInstruction, context) {
    if (info.command in this) {
      return this[info.command](resources, element, info, existingInstruction, context);
    }

    return this.handleUnknownCommand(resources, element, info, existingInstruction, context);
  }

  handleUnknownCommand(resources, element, info, existingInstruction, context) {
    LogManager.getLogger('templating-binding').warn('Unknown binding command.', info);
    return existingInstruction;
  }

  determineDefaultBindingMode(element, attrName, context) {
    let tagName = element.tagName.toLowerCase();

    if (tagName === 'input' && (attrName === 'value' || attrName === 'files') && element.type !== 'checkbox' && element.type !== 'radio'
      || tagName === 'input' && attrName === 'checked' && (element.type === 'checkbox' || element.type === 'radio')
      || (tagName === 'textarea' || tagName === 'select') && attrName === 'value'
      || (attrName === 'textcontent' || attrName === 'innerhtml') && element.contentEditable === 'true'
      || attrName === 'scrolltop'
      || attrName === 'scrollleft') {
      return bindingMode.twoWay;
    }

    if (context
      && attrName in context.attributes
      && context.attributes[attrName]
      && context.attributes[attrName].defaultBindingMode >= bindingMode.oneTime) {
      return context.attributes[attrName].defaultBindingMode;
    }

    return bindingMode.oneWay;
  }

  bind(resources, element, info, existingInstruction, context) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    instruction.attributes[info.attrName] = new BindingExpression(
      this.observerLocator,
      this.attributeMap.map(element.tagName, info.attrName),
      this.parser.parse(info.attrValue),
      info.defaultBindingMode || this.determineDefaultBindingMode(element, info.attrName, context),
      resources.lookupFunctions
    );

    return instruction;
  }

  trigger(resources, element, info) {
    return new ListenerExpression(
      this.eventManager,
      info.attrName,
      this.parser.parse(info.attrValue),
      false,
      true,
      resources.lookupFunctions
    );
  }

  delegate(resources, element, info) {
    return new ListenerExpression(
      this.eventManager,
      info.attrName,
      this.parser.parse(info.attrValue),
      true,
      true,
      resources.lookupFunctions
    );
  }

  call(resources, element, info, existingInstruction) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    instruction.attributes[info.attrName] = new CallExpression(
      this.observerLocator,
      info.attrName,
      this.parser.parse(info.attrValue),
      resources.lookupFunctions
    );

    return instruction;
  }

  options(resources, element, info, existingInstruction, context) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);
    let attrValue = info.attrValue;
    let language = this.language;
    let name = null;
    let target = '';
    let current;
    let i;
    let ii;
    let inString = false;
    let inEscape = false;

    for (i = 0, ii = attrValue.length; i < ii; ++i) {
      current = attrValue[i];

      if (current === ';' && !inString) {
        info = language.inspectAttribute(resources, '?', name, target.trim());
        language.createAttributeInstruction(resources, element, info, instruction, context);

        if (!instruction.attributes[info.attrName]) {
          instruction.attributes[info.attrName] = info.attrValue;
        }

        target = '';
        name = null;
      } else if (current === ':' && name === null) {
        name = target.trim();
        target = '';
      } else if (current === '\\') {
        target += current;
        inEscape = true;
        continue;
      } else {
        target += current;

        if (name !== null && inEscape === false && current === '\'') {
          inString = !inString;
        }
      }

      inEscape = false;
    }

    if (name !== null) {
      info = language.inspectAttribute(resources, '?', name, target.trim());
      language.createAttributeInstruction(resources, element, info, instruction, context);

      if (!instruction.attributes[info.attrName]) {
        instruction.attributes[info.attrName] = info.attrValue;
      }
    }

    return instruction;
  }

  'for'(resources, element, info, existingInstruction) {
    let parts;
    let keyValue;
    let instruction;
    let attrValue;
    let isDestructuring;

    attrValue = info.attrValue;
    isDestructuring = attrValue.match(/^ *[[].+[\]]/);
    parts = isDestructuring ? attrValue.split('of ') : attrValue.split(' of ');

    if (parts.length !== 2) {
      throw new Error('Incorrect syntax for "for". The form is: "$local of $items" or "[$key, $value] of $items".');
    }

    instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    if (isDestructuring) {
      keyValue = parts[0].replace(/[[\]]/g, '').replace(/,/g, ' ').replace(/\s+/g, ' ').trim().split(' ');
      instruction.attributes.key = keyValue[0];
      instruction.attributes.value = keyValue[1];
    } else {
      instruction.attributes.local = parts[0];
    }

    instruction.attributes.items = new BindingExpression(
      this.observerLocator,
      'items',
      this.parser.parse(parts[1]),
      bindingMode.oneWay,
      resources.lookupFunctions
    );

    return instruction;
  }

  'two-way'(resources, element, info, existingInstruction) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    instruction.attributes[info.attrName] = new BindingExpression(
        this.observerLocator,
        this.attributeMap.map(element.tagName, info.attrName),
        this.parser.parse(info.attrValue),
        bindingMode.twoWay,
        resources.lookupFunctions
      );

    return instruction;
  }

  'one-way'(resources, element, info, existingInstruction) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    instruction.attributes[info.attrName] = new BindingExpression(
      this.observerLocator,
      this.attributeMap.map(element.tagName, info.attrName),
      this.parser.parse(info.attrValue),
      bindingMode.oneWay,
      resources.lookupFunctions
    );

    return instruction;
  }

  'one-time'(resources, element, info, existingInstruction) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    instruction.attributes[info.attrName] = new BindingExpression(
      this.observerLocator,
      this.attributeMap.map(element.tagName, info.attrName),
      this.parser.parse(info.attrValue),
      bindingMode.oneTime,
      resources.lookupFunctions
    );

    return instruction;
  }
}
<commit_msg>feat(SyntaxInterpreter): add capture binding command<commit_after>/*eslint dot-notation:0*/
import {
  Parser,
  ObserverLocator,
  EventManager,
  ListenerExpression,
  BindingExpression,
  CallExpression,
  bindingMode,
  delegationStrategy
} from 'aurelia-binding';

import {BehaviorInstruction} from 'aurelia-templating';
import * as LogManager from 'aurelia-logging';
import {AttributeMap} from './attribute-map';

export class SyntaxInterpreter {
  static inject = [Parser, ObserverLocator, EventManager, AttributeMap];

  constructor(parser, observerLocator, eventManager, attributeMap) {
    this.parser = parser;
    this.observerLocator = observerLocator;
    this.eventManager = eventManager;
    this.attributeMap = attributeMap;
  }

  interpret(resources, element, info, existingInstruction, context) {
    if (info.command in this) {
      return this[info.command](resources, element, info, existingInstruction, context);
    }

    return this.handleUnknownCommand(resources, element, info, existingInstruction, context);
  }

  handleUnknownCommand(resources, element, info, existingInstruction, context) {
    LogManager.getLogger('templating-binding').warn('Unknown binding command.', info);
    return existingInstruction;
  }

  determineDefaultBindingMode(element, attrName, context) {
    let tagName = element.tagName.toLowerCase();

    if (tagName === 'input' && (attrName === 'value' || attrName === 'files') && element.type !== 'checkbox' && element.type !== 'radio'
      || tagName === 'input' && attrName === 'checked' && (element.type === 'checkbox' || element.type === 'radio')
      || (tagName === 'textarea' || tagName === 'select') && attrName === 'value'
      || (attrName === 'textcontent' || attrName === 'innerhtml') && element.contentEditable === 'true'
      || attrName === 'scrolltop'
      || attrName === 'scrollleft') {
      return bindingMode.twoWay;
    }

    if (context
      && attrName in context.attributes
      && context.attributes[attrName]
      && context.attributes[attrName].defaultBindingMode >= bindingMode.oneTime) {
      return context.attributes[attrName].defaultBindingMode;
    }

    return bindingMode.oneWay;
  }

  bind(resources, element, info, existingInstruction, context) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    instruction.attributes[info.attrName] = new BindingExpression(
      this.observerLocator,
      this.attributeMap.map(element.tagName, info.attrName),
      this.parser.parse(info.attrValue),
      info.defaultBindingMode || this.determineDefaultBindingMode(element, info.attrName, context),
      resources.lookupFunctions
    );

    return instruction;
  }

  trigger(resources, element, info) {
    return new ListenerExpression(
      this.eventManager,
      info.attrName,
      this.parser.parse(info.attrValue),
      delegationStrategy.none,
      true,
      resources.lookupFunctions
    );
  }

  capture(resources, element, info) {
    return new ListenerExpression(
      this.eventManager,
      info.attrName,
      this.parser.parse(info.attrValue),
      delegationStrategy.capturing,
      true,
      resources.lookupFunctions
    );
  }

  delegate(resources, element, info) {
    return new ListenerExpression(
      this.eventManager,
      info.attrName,
      this.parser.parse(info.attrValue),
      delegationStrategy.bubbling,
      true,
      resources.lookupFunctions
    );
  }

  call(resources, element, info, existingInstruction) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    instruction.attributes[info.attrName] = new CallExpression(
      this.observerLocator,
      info.attrName,
      this.parser.parse(info.attrValue),
      resources.lookupFunctions
    );

    return instruction;
  }

  options(resources, element, info, existingInstruction, context) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);
    let attrValue = info.attrValue;
    let language = this.language;
    let name = null;
    let target = '';
    let current;
    let i;
    let ii;
    let inString = false;
    let inEscape = false;

    for (i = 0, ii = attrValue.length; i < ii; ++i) {
      current = attrValue[i];

      if (current === ';' && !inString) {
        info = language.inspectAttribute(resources, '?', name, target.trim());
        language.createAttributeInstruction(resources, element, info, instruction, context);

        if (!instruction.attributes[info.attrName]) {
          instruction.attributes[info.attrName] = info.attrValue;
        }

        target = '';
        name = null;
      } else if (current === ':' && name === null) {
        name = target.trim();
        target = '';
      } else if (current === '\\') {
        target += current;
        inEscape = true;
        continue;
      } else {
        target += current;

        if (name !== null && inEscape === false && current === '\'') {
          inString = !inString;
        }
      }

      inEscape = false;
    }

    if (name !== null) {
      info = language.inspectAttribute(resources, '?', name, target.trim());
      language.createAttributeInstruction(resources, element, info, instruction, context);

      if (!instruction.attributes[info.attrName]) {
        instruction.attributes[info.attrName] = info.attrValue;
      }
    }

    return instruction;
  }

  'for'(resources, element, info, existingInstruction) {
    let parts;
    let keyValue;
    let instruction;
    let attrValue;
    let isDestructuring;

    attrValue = info.attrValue;
    isDestructuring = attrValue.match(/^ *[[].+[\]]/);
    parts = isDestructuring ? attrValue.split('of ') : attrValue.split(' of ');

    if (parts.length !== 2) {
      throw new Error('Incorrect syntax for "for". The form is: "$local of $items" or "[$key, $value] of $items".');
    }

    instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    if (isDestructuring) {
      keyValue = parts[0].replace(/[[\]]/g, '').replace(/,/g, ' ').replace(/\s+/g, ' ').trim().split(' ');
      instruction.attributes.key = keyValue[0];
      instruction.attributes.value = keyValue[1];
    } else {
      instruction.attributes.local = parts[0];
    }

    instruction.attributes.items = new BindingExpression(
      this.observerLocator,
      'items',
      this.parser.parse(parts[1]),
      bindingMode.oneWay,
      resources.lookupFunctions
    );

    return instruction;
  }

  'two-way'(resources, element, info, existingInstruction) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    instruction.attributes[info.attrName] = new BindingExpression(
        this.observerLocator,
        this.attributeMap.map(element.tagName, info.attrName),
        this.parser.parse(info.attrValue),
        bindingMode.twoWay,
        resources.lookupFunctions
      );

    return instruction;
  }

  'one-way'(resources, element, info, existingInstruction) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    instruction.attributes[info.attrName] = new BindingExpression(
      this.observerLocator,
      this.attributeMap.map(element.tagName, info.attrName),
      this.parser.parse(info.attrValue),
      bindingMode.oneWay,
      resources.lookupFunctions
    );

    return instruction;
  }

  'one-time'(resources, element, info, existingInstruction) {
    let instruction = existingInstruction || BehaviorInstruction.attribute(info.attrName);

    instruction.attributes[info.attrName] = new BindingExpression(
      this.observerLocator,
      this.attributeMap.map(element.tagName, info.attrName),
      this.parser.parse(info.attrValue),
      bindingMode.oneTime,
      resources.lookupFunctions
    );

    return instruction;
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import moment from 'moment'

import Timeline from 'react-calendar-timeline'
// import containerResizeDetector from 'react-calendar-timeline/lib/resize-detector/container'

import generateFakeData from '../generate-fake-data'

var minTime = moment()
  .add(-6, 'months')
  .valueOf()
var maxTime = moment()
  .add(6, 'months')
  .valueOf()

var keys = {
  groupIdKey: 'id',
  groupTitleKey: 'title',
  groupRightTitleKey: 'rightTitle',
  itemIdKey: 'id',
  itemTitleKey: 'title',
  itemDivTitleKey: 'title',
  itemGroupKey: 'group',
  itemTimeStartKey: 'start',
  itemTimeEndKey: 'end'
}

export default class App extends Component {
  constructor(props) {
    super(props)

    const { groups, items } = generateFakeData()
    const defaultTimeStart = moment()
      .startOf('day')
      .toDate()
    const defaultTimeEnd = moment()
      .startOf('day')
      .add(1, 'day')
      .toDate()

    this.state = {
      groups,
      items,
      defaultTimeStart,
      defaultTimeEnd
    }
  }

  handleCanvasClick = (groupId, time, event) => {
    console.log('Canvas clicked', groupId, moment(time).format())
  }

  handleCanvasContextMenu = (group, time, e) => {
    console.log('Canvas context menu', group, moment(time).format())
  }

  handleItemClick = (itemId, _, time) => {
    console.log('Clicked: ' + itemId, moment(time).format())
  }

  handleItemSelect = (itemId, _, time) => {
    console.log('Selected: ' + itemId, moment(time).format())
  }

  handleItemDoubleClick = (itemId, _, time) => {
    console.log('Double Click: ' + itemId, moment(time).format())
  }

  handleItemContextMenu = (itemId, _, time) => {
    console.log('Context Menu: ' + itemId, moment(time).format())
  }

  handleItemMove = (itemId, dragTime, newGroupOrder) => {
    const { items, groups } = this.state

    const group = groups[newGroupOrder]

    this.setState({
      items: items.map(
        item =>
          item.id === itemId
            ? Object.assign({}, item, {
                start: dragTime,
                end: dragTime + (item.end - item.start),
                group: group.id
              })
            : item
      )
    })

    console.log('Moved', itemId, dragTime, newGroupOrder)
  }

  handleItemResize = (itemId, time, edge) => {
    const { items } = this.state

    this.setState({
      items: items.map(
        item =>
          item.id === itemId
            ? Object.assign({}, item, {
                start: edge === 'left' ? time : item.start,
                end: edge === 'left' ? item.end : time
              })
            : item
      )
    })

    console.log('Resized', itemId, time, edge)
  }

  // this limits the timeline to -6 months ... +6 months
  handleTimeChange = (visibleTimeStart, visibleTimeEnd, updateScrollCanvas) => {
    if (visibleTimeStart < minTime && visibleTimeEnd > maxTime) {
      updateScrollCanvas(minTime, maxTime)
    } else if (visibleTimeStart < minTime) {
      updateScrollCanvas(minTime, minTime + (visibleTimeEnd - visibleTimeStart))
    } else if (visibleTimeEnd > maxTime) {
      updateScrollCanvas(maxTime - (visibleTimeEnd - visibleTimeStart), maxTime)
    } else {
      updateScrollCanvas(visibleTimeStart, visibleTimeEnd)
    }
  }

  moveResizeValidator = (action, item, time, resizeEdge) => {
    if (time < new Date().getTime()) {
      var newTime =
        Math.ceil(new Date().getTime() / (15 * 60 * 1000)) * (15 * 60 * 1000)
      return newTime
    }

    return time
  }

  // itemRenderer = ({ item }) => {
  //   return (
  //

  //       {item.title}
  //
{item.itemProps['data-tip']}

  //

  //   )
  // }

  // groupRenderer = ({ group }) => {
  //   return (
  //

  //       {group.title}
  //

  //   )
  // }

  render() {
    const { groups, items, defaultTimeStart, defaultTimeEnd } = this.state

    return (
      <Timeline
        groups={groups}
        items={items}
        keys={keys}
        sidebarWidth={150}
        sidebarContent={
Above The Left
}
        // rightSidebarWidth={150}
        // rightSidebarContent={
Above The Right
}

        canMove
        canResize="right"
        canSelect
        itemsSorted
        itemTouchSendsClick={false}
        stackItems
        itemHeightRatio={0.75}
        showCursorLine
        // resizeDetector={containerResizeDetector}

        defaultTimeStart={defaultTimeStart}
        defaultTimeEnd={defaultTimeEnd}
        // itemRenderer={this.itemRenderer}
        // groupRenderer={this.groupRenderer}

        onCanvasClick={this.handleCanvasClick}
        onCanvasContextMenu={this.handleCanvasContextMenu}
        onItemClick={this.handleItemClick}
        onItemSelect={this.handleItemSelect}
        onItemContextMenu={this.handleItemContextMenu}
        onItemMove={this.handleItemMove}
        onItemResize={this.handleItemResize}
        onItemDoubleClick={this.handleItemDoubleClick}
        onTimeChange={this.handleTimeChange}
        moveResizeValidator={this.moveResizeValidator}
      />
    )
  }
}
<commit_msg>added canvas double click handler in demo<commit_after>import React, { Component } from 'react'
import moment from 'moment'

import Timeline from 'react-calendar-timeline'
// import containerResizeDetector from 'react-calendar-timeline/lib/resize-detector/container'

import generateFakeData from '../generate-fake-data'

var minTime = moment()
  .add(-6, 'months')
  .valueOf()
var maxTime = moment()
  .add(6, 'months')
  .valueOf()

var keys = {
  groupIdKey: 'id',
  groupTitleKey: 'title',
  groupRightTitleKey: 'rightTitle',
  itemIdKey: 'id',
  itemTitleKey: 'title',
  itemDivTitleKey: 'title',
  itemGroupKey: 'group',
  itemTimeStartKey: 'start',
  itemTimeEndKey: 'end'
}

export default class App extends Component {
  constructor(props) {
    super(props)

    const { groups, items } = generateFakeData()
    const defaultTimeStart = moment()
      .startOf('day')
      .toDate()
    const defaultTimeEnd = moment()
      .startOf('day')
      .add(1, 'day')
      .toDate()

    this.state = {
      groups,
      items,
      defaultTimeStart,
      defaultTimeEnd
    }
  }

  handleCanvasClick = (groupId, time, event) => {
    console.log('Canvas clicked', groupId, moment(time).format())
  }

  handleCanvasDoubleClick = (groupId, time, event) => {
    console.log('Canvas double clicked', groupId, moment(time).format())
  }

  handleCanvasContextMenu = (group, time, e) => {
    console.log('Canvas context menu', group, moment(time).format())
  }

  handleItemClick = (itemId, _, time) => {
    console.log('Clicked: ' + itemId, moment(time).format())
  }

  handleItemSelect = (itemId, _, time) => {
    console.log('Selected: ' + itemId, moment(time).format())
  }

  handleItemDoubleClick = (itemId, _, time) => {
    console.log('Double Click: ' + itemId, moment(time).format())
  }

  handleItemContextMenu = (itemId, _, time) => {
    console.log('Context Menu: ' + itemId, moment(time).format())
  }

  handleItemMove = (itemId, dragTime, newGroupOrder) => {
    const { items, groups } = this.state

    const group = groups[newGroupOrder]

    this.setState({
      items: items.map(
        item =>
          item.id === itemId
            ? Object.assign({}, item, {
                start: dragTime,
                end: dragTime + (item.end - item.start),
                group: group.id
              })
            : item
      )
    })

    console.log('Moved', itemId, dragTime, newGroupOrder)
  }

  handleItemResize = (itemId, time, edge) => {
    const { items } = this.state

    this.setState({
      items: items.map(
        item =>
          item.id === itemId
            ? Object.assign({}, item, {
                start: edge === 'left' ? time : item.start,
                end: edge === 'left' ? item.end : time
              })
            : item
      )
    })

    console.log('Resized', itemId, time, edge)
  }

  // this limits the timeline to -6 months ... +6 months
  handleTimeChange = (visibleTimeStart, visibleTimeEnd, updateScrollCanvas) => {
    if (visibleTimeStart < minTime && visibleTimeEnd > maxTime) {
      updateScrollCanvas(minTime, maxTime)
    } else if (visibleTimeStart < minTime) {
      updateScrollCanvas(minTime, minTime + (visibleTimeEnd - visibleTimeStart))
    } else if (visibleTimeEnd > maxTime) {
      updateScrollCanvas(maxTime - (visibleTimeEnd - visibleTimeStart), maxTime)
    } else {
      updateScrollCanvas(visibleTimeStart, visibleTimeEnd)
    }
  }

  moveResizeValidator = (action, item, time, resizeEdge) => {
    if (time < new Date().getTime()) {
      var newTime =
        Math.ceil(new Date().getTime() / (15 * 60 * 1000)) * (15 * 60 * 1000)
      return newTime
    }

    return time
  }

  // itemRenderer = ({ item }) => {
  //   return (
  //

  //       {item.title}
  //
{item.itemProps['data-tip']}

  //

  //   )
  // }

  // groupRenderer = ({ group }) => {
  //   return (
  //

  //       {group.title}
  //

  //   )
  // }

  render() {
    const { groups, items, defaultTimeStart, defaultTimeEnd } = this.state

    return (
      <Timeline
        groups={groups}
        items={items}
        keys={keys}
        sidebarWidth={150}
        sidebarContent={
Above The Left
}
        // rightSidebarWidth={150}
        // rightSidebarContent={
Above The Right
}

        canMove
        canResize="right"
        canSelect
        itemsSorted
        itemTouchSendsClick={false}
        stackItems
        itemHeightRatio={0.75}
        showCursorLine
        // resizeDetector={containerResizeDetector}

        defaultTimeStart={defaultTimeStart}
        defaultTimeEnd={defaultTimeEnd}
        // itemRenderer={this.itemRenderer}
        // groupRenderer={this.groupRenderer}

        onCanvasClick={this.handleCanvasClick}
        onCanvasDoubleClick={this.handleCanvasDoubleClick}
        onCanvasContextMenu={this.handleCanvasContextMenu}
        onItemClick={this.handleItemClick}
        onItemSelect={this.handleItemSelect}
        onItemContextMenu={this.handleItemContextMenu}
        onItemMove={this.handleItemMove}
        onItemResize={this.handleItemResize}
        onItemDoubleClick={this.handleItemDoubleClick}
        onTimeChange={this.handleTimeChange}
        moveResizeValidator={this.moveResizeValidator}
      />
    )
  }
}
<|endoftext|>


<commit_before>/**
 *  weather.js
 *  -----
 *  Impact Weather Add-on for the Impact Day/Night Cycle Plugin
 *  https://github.com/chessmasterhong/impact-day-night
 *
 *  Kevin Chan (chessmasterhong)
 *
 *  An add-on for the Impact Day/Night Cycle Plugin for the Impact game engine
 *  that simulates weather conditions.
 */

ig.module(
    'plugins.weather'
)
.requires(
    'impact.game'
)
.defines(function() {
    "use strict";

    ig.Weather = ig.Game.extend({
        // Set weather condition
        // 0 = clear, 1 = rain, 2 = snow, 3 = fog (EXPERIMENTAL!!)
        condition: 0,

        particles: {
            max: 100, // Maximum number of particles to generate before stopping
            curr: 0   // Keep track of current number of particles
        },

        //---------------------------------------------------------------------
        // Init
        init: function() {
        }, // End init
        //---------------------------------------------------------------------

        //---------------------------------------------------------------------
        // Update
        update: function() {
            if(this.condition && typeof this.nextParticle === 'undefined')
                this.nextParticle = new ig.Timer();

            // Generate particles based on weather condition
            if(this.condition === 1) {
                // Rain
                if(this.nextParticle.delta() >= 0 && this.particles.curr < this.particles.max) {
                    this.particles.curr++;
                    this.nextParticle.set(1 / ig.system.height);
                    ig.game.spawnEntity(
                        EntityRain,
                        Math.random() * (ig.game.screen.x + ig.system.width - ig.game.screen.x) + ig.game.screen.x,
                        ig.game.screen.y
                    );
                } else
                    this.nextParticle.set(0);
            } else if(this.condition === 2) {
                // Snow
                if(this.nextParticle.delta() >= 0 && this.particles.curr < this.particles.max) {
                    this.particles.curr++;
                    this.nextParticle.set(1 / (this.particles.max - this.particles.curr));
                    ig.game.spawnEntity(
                        EntitySnow,
                        Math.random() * (ig.game.screen.x + ig.system.width - ig.game.screen.x) + ig.game.screen.x,
                        ig.game.screen.y
                    );
                } else
                    this.nextParticle.set(0);
            } else {
                if(this.particles.curr > 0 && this.nextParticle.delta() >= 0) {
                    var r = ig.game.getEntitiesByType(EntityRain)[0];
                    if(typeof r !== 'undefined') {
                        r.kill();
                        this.particles.curr--;
                    }

                    var s = ig.game.getEntitiesByType(EntitySnow)[0];
                    if(typeof s !== 'undefined') {
                        s.kill();
                        this.particles.curr--;
                    }

                    this.nextParticle.set(2 / this.particles.curr);
                }
            }
        }, // End update
        //---------------------------------------------------------------------

        //---------------------------------------------------------------------
        // Draw
        draw: function() {

            if(this.condition === 3) {
                // Fog
                var r, g, b, size = 4;
                for(var x = ig.game.screen.x; x < ig.game.screen.x + ig.system.width; x += size) {
                    for(var y = ig.game.screen.y; y < ig.game.screen.y + ig.system.height; y += size) {
                        r = g = b = Math.round(255 * PerlinNoise.noise(size * x / ig.system.width, size * y / ig.system.height, 0.6));
                        ig.system.context.fillStyle = 'rgba(' + r + ', ' + g + ', ' + b + ', 0.3)';
                        ig.system.context.fillRect(x, y, size, size);
                    }
                }
            }
        } // End draw
        //---------------------------------------------------------------------
    });

    //#########################################################################
    // Particles

    // Rain particle
    var EntityRain = ig.Entity.extend({
        vel: {x: 20, y: 400},
        maxVel: {x: 100, y: 400},

        weight: 1,

        init: function(x, y, settings) {
            this.parent(x, y, settings);

            // Randomize initial velocity
            this.vel.x *= Math.random() + 0.5; // 0.5 - 1.5
            this.vel.y *= Math.random() + 1;   // 1.0 - 2.0 (rain should not "fall" upwards...)

            // Randomize raindrop weight
            this.weight = Math.random() + 0.5; // 0.5 - 1.5
        },

        update: function() {
            this.parent();

            // Handle entity moving out of screen bounds
            // Wraparound to opposite side of screen
            if(this.pos.y > ig.game.screen.y + ig.system.height)
               this.pos.y = ig.game.screen.y;
            else if(this.pos.x > ig.game.screen.x + ig.system.width)
                this.pos.x = ig.game.screen.x;
            else if(this.pos.x < ig.game.screen.x)
                this.pos.x = ig.game.screen.x + ig.system.width;
        },

        draw: function() {
            // Draw rain
            ig.system.context.strokeStyle = 'rgba(200, 200, 200, 0.6)';
            ig.system.context.lineWidth = this.weight;
            ig.system.context.beginPath();
            ig.system.context.moveTo(this.pos.x, this.pos.y);
            ig.system.context.lineTo(this.pos.x + this.vel.x * 0.05, this.pos.y + this.vel.y * 0.02);
            ig.system.context.closePath();
            ig.system.context.stroke();
        },

        handleMovementTrace: function(res) {
            this.pos.x += this.vel.x * ig.system.tick;
            this.pos.y += this.vel.y * ig.system.tick;
        }
    }); // End EntityRain

    // Snow particle
    var EntitySnow = ig.Entity.extend({
        vel: {x: 60, y: 80},
        maxVel: {x: 100, y: 100},

        radius: 1,

        init: function(x, y, settings) {
            this.parent(x, y, settings);

            // Randomize initial velocity
            this.vel.x *= Math.random() * 2 - 1; // -1.0 - 1.0
            this.vel.y *= Math.abs(Math.random() * 2 - 1); // 0.0 - 1.0 (skewed towards 0) (snow should not "fall" upwards...)

            // Randomize snow particle size
            this.radius = Math.random() * 0.5 + 1 // 1.0 - 1.5
        },

        update: function() {
            this.parent();

            // Handle entity moving out of screen bounds
            // Wraparound to opposite side of screen
            if(this.pos.y > ig.game.screen.y + ig.system.height)
               this.pos.y = ig.game.screen.y;
            else if(this.pos.x > ig.game.screen.x + ig.system.width)
                this.pos.x = ig.game.screen.x;
            else if(this.pos.x < ig.game.screen.x)
                this.pos.x = ig.game.screen.x + ig.system.width;
        },

        draw: function() {
            // Draw snow
            ig.system.context.fillStyle = 'rgba(255, 255, 255, 0.8)';
            ig.system.context.beginPath();
                ig.system.context.arc(
                    this.pos.x,
                    this.pos.y,
                    this.radius,
                    0,
                    2 * Math.PI
                );
            ig.system.context.closePath();
            ig.system.context.fill();
        },

        handleMovementTrace: function(res) {
            this.pos.x += this.vel.x * ig.system.tick;
            this.pos.y += this.vel.y * ig.system.tick;
        }
    }); // End EntitySnow

    // Perlin Noise Generator
    //
    // My modifications: Minor code adaptation for use in ImpactJS. Algorithm remains unmodified.
    // Ken Perlin's original Java implementation: http://cs.nyu.edu/~perlin/noise
    // Kas Thomas's JavaScript port: http://asserttrue.blogspot.com/2011/12/perlin-noise-in-javascript_31.html
    var PerlinNoise = {
        noise: function(x, y, z) {
            var p = new Array(512);
            var permutation = [
                151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,
                99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,
                11,32, 57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,
                139,48,27,166, 77,146,158,231,83,111,229,122, 60,211,133,230,220,105,92,41,55,
                46,245,40,244,102,143,54, 65,25,63,161, 1,216,80,73,209, 76,132,187,208,89,18,
                169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186, 3, 64,52,217,226,
                250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,
                189,28, 42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,
                172,9,129, 22,39,253,19,98,108,110, 79,113,224,232,178,185,112,104,218,246,97,
                228,251, 34,242,193,238,210,144, 12,191,179,162,241,81,51,145,235,249, 14,239,
                107, 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121, 50,45,127, 4,150,
                254,138,236,205,93,222,114,67,29, 24,72,243,141,128,195,78,66,215, 61,156,180
            ];

            for(var i = 0; i < 256; i++)
                p[256+i] = p[i] = permutation[i];

                var X = Math.floor(x) & 255,
                    Y = Math.floor(y) & 255,
                    Z = Math.floor(z) & 255;

                x -= Math.floor(x);
                y -= Math.floor(y);
                z -= Math.floor(z);

                var u = this.fade(x),
                    v = this.fade(y),
                    w = this.fade(z);

                var A = p[X  ]+Y, AA = p[A]+Z, AB = p[A+1]+Z,
                    B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z;

                // Returns a number between 0 and 1
                return this.scale(this.lerp(w, this.lerp(v, this.lerp(u, this.grad(p[AA  ], x  , y  , z  ),
                                                                         this.grad(p[BA  ], x-1, y  , z  )),
                                                            this.lerp(u, this.grad(p[AB  ], x  , y-1, z  ),
                                                                         this.grad(p[BB  ], x-1, y-1, z  ))),
                                               this.lerp(v, this.lerp(u, this.grad(p[AA+1], x  , y  , z-1),
                                                                         this.grad(p[BA+1], x-1, y  , z-1)),
                                                            this.lerp(u, this.grad(p[AB+1], x  , y-1, z-1),
                                                                         this.grad(p[BB+1], x-1, y-1, z-1)))));
        },

        fade: function(t) { return t * t * t * (t * (t * 6 - 15) + 10); },
        lerp: function(t, a, b) { return a + t * (b - a); },
        grad: function(hash, x, y, z) {
            var h = hash & 15;
            var u = h < 8 ? x : y,
                v = h < 4 ? y : h === 12 || h === 14 ? x : z;
            return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
        },
        scale: function(n) { return (1 + n) / 2; }
    };
});
<commit_msg>Added random particle lifetime.<commit_after>/**
 *  weather.js
 *  -----
 *  Impact Weather Add-on for the Impact Day/Night Cycle Plugin
 *  https://github.com/chessmasterhong/impact-day-night
 *
 *  Kevin Chan (chessmasterhong)
 *
 *  An add-on for the Impact Day/Night Cycle Plugin for the Impact game engine
 *  that simulates weather conditions.
 */

ig.module(
    'plugins.weather'
)
.requires(
    'impact.game'
)
.defines(function() {
    "use strict";

    ig.Weather = ig.Game.extend({
        // Set weather condition
        // 0 = clear, 1 = rain, 2 = snow, 3 = fog (EXPERIMENTAL!!)
        condition: 0,

        particles: {
            max : 100, // Maximum number of particles to generate before stopping
            curr: 0    // Keep track of current number of particles
        },

        //---------------------------------------------------------------------
        // Init
        init: function() {
        }, // End init
        //---------------------------------------------------------------------

        //---------------------------------------------------------------------
        // Update
        update: function() {
            if(this.condition && typeof this.nextParticle === 'undefined')
                this.nextParticle = new ig.Timer();

            // Generate particles based on weather condition
            if(this.condition === 1) {
                // Rain
                if(this.nextParticle.delta() >= 0 && this.particles.curr < this.particles.max) {
                    this.particles.curr++;
                    this.nextParticle.set(1 / ig.system.height);
                    ig.game.spawnEntity(
                        EntityRain,
                        Math.random() * (ig.game.screen.x + ig.system.width - ig.game.screen.x) + ig.game.screen.x,
                        ig.game.screen.y
                    );
                } else
                    this.nextParticle.set(0);
            } else if(this.condition === 2) {
                // Snow
                if(this.nextParticle.delta() >= 0 && this.particles.curr < this.particles.max) {
                    this.particles.curr++;
                    this.nextParticle.set(1 / (this.particles.max - this.particles.curr));
                    ig.game.spawnEntity(
                        EntitySnow,
                        Math.random() * (ig.game.screen.x + ig.system.width - ig.game.screen.x) + ig.game.screen.x,
                        ig.game.screen.y
                    );
                } else
                    this.nextParticle.set(0);
            } else {
                if(this.particles.curr > 0 && this.nextParticle.delta() >= 0) {
                    var r = ig.game.getEntitiesByType(EntityRain)[0];
                    if(typeof r !== 'undefined') {
                        r.kill();
                        this.particles.curr--;
                    }

                    var s = ig.game.getEntitiesByType(EntitySnow)[0];
                    if(typeof s !== 'undefined') {
                        s.kill();
                        this.particles.curr--;
                    }

                    this.nextParticle.set(2 / this.particles.curr);
                }
            }
        }, // End update
        //---------------------------------------------------------------------

        //---------------------------------------------------------------------
        // Draw
        draw: function() {

            if(this.condition === 3) {
                // Fog
                var r, g, b, size = 4;
                for(var x = ig.game.screen.x; x < ig.game.screen.x + ig.system.width; x += size) {
                    for(var y = ig.game.screen.y; y < ig.game.screen.y + ig.system.height; y += size) {
                        r = g = b = Math.round(255 * PerlinNoise.noise(size * x / ig.system.width, size * y / ig.system.height, 0.6));
                        ig.system.context.fillStyle = 'rgba(' + r + ', ' + g + ', ' + b + ', 0.3)';
                        ig.system.context.fillRect(x, y, size, size);
                    }
                }
            }
        } // End draw
        //---------------------------------------------------------------------
    });

    //#########################################################################
    // Particles

    // Rain particle
    var EntityRain = ig.Entity.extend({
        vel: {x: 20, y: 400},
        maxVel: {x: 100, y: 400},

        lifetime: 10, // Raindrop lifetime
        weight: 1,    // Raindrop weight

        init: function(x, y, settings) {
            this.parent(x, y, settings);

            // Randomize raindrop lifetime
            this.lifetime = ig.system.height / this.vel.y;
            this.lifetimeTimer = new ig.Timer(Math.abs(Math.random() * this.lifetime * 1.5 - this.lifetime) + this.lifetime / 2); // Range: 0.5 - lifetime (skewed towards lifetime)

            // Randomize initial velocity
            this.vel.x *= Math.random() + 0.5; // Range: 0.5 - 1.5
            this.vel.y *= Math.random() + 1;   // Range: 1.0 - 2.0 (rain should not "fall" upwards...)

            // Randomize raindrop weight
            this.weight = Math.random() + 0.5; // Range: 0.5 - 1.5
        },

        update: function() {
            this.parent();

            // Handle entity moving out of screen bounds
            // Wraparound to opposite side of screen
            if(this.pos.y > ig.game.screen.y + ig.system.height || this.lifetimeTimer.delta() >= 0) {
               this.pos.y = ig.game.screen.y;
               this.lifetimeTimer.set(Math.random() * this.lifetime + this.lifetime / 2);
            } else if(this.pos.x > ig.game.screen.x + ig.system.width)
                this.pos.x = ig.game.screen.x;
            else if(this.pos.x < ig.game.screen.x)
                this.pos.x = ig.game.screen.x + ig.system.width;
        },

        draw: function() {
            // Draw rain
            ig.system.context.strokeStyle = 'rgba(200, 200, 200, 0.6)';
            ig.system.context.lineWidth = this.weight;
            ig.system.context.beginPath();
            ig.system.context.moveTo(this.pos.x, this.pos.y);
            ig.system.context.lineTo(this.pos.x + this.vel.x * 0.05, this.pos.y + this.vel.y * 0.02);
            ig.system.context.closePath();
            ig.system.context.stroke();
        },

        handleMovementTrace: function(res) {
            this.pos.x += this.vel.x * ig.system.tick;
            this.pos.y += this.vel.y * ig.system.tick;
        }
    }); // End EntityRain

    // Snow particle
    var EntitySnow = ig.Entity.extend({
        vel: {x: 60, y: 80},
        maxVel: {x: 100, y: 100},

        lifetime: 10, // Particle lifetime
        radius: 1,    // Particle radius

        init: function(x, y, settings) {
            this.parent(x, y, settings);

            // Randomize snow particle lifetime
            this.lifetime = ig.system.height / this.vel.y * 1.5;
            this.lifetimeTimer = new ig.Timer(Math.abs(Math.random() * this.lifetime * 1.5 - this.lifetime) + this.lifetime / 2); // Range: 0.5 - lifetime (skewed towards lifetime)

            // Randomize initial velocity
            this.vel.x *= Math.random() * 2 - 1; // Range: -1.0 - 1.0
            this.vel.y *= Math.abs(Math.random() * 2 - 1); // Range: 0.0 - 1.0 (skewed towards 0) (snow should not "fall" upwards...)

            // Randomize snow particle size
            this.radius = Math.random() * 0.5 + 1 // Range: 1.0 - 1.5
        },

        update: function() {
            this.parent();

            // Handle entity moving out of screen bounds
            // Wraparound to opposite side of screen
            if(this.pos.y > ig.game.screen.y + ig.system.height || this.lifetimeTimer.delta() >= 0) {
               this.pos.y = ig.game.screen.y;
               this.lifetimeTimer.set(Math.random() * this.lifetime + this.lifetime / 2);
            } else if(this.pos.x > ig.game.screen.x + ig.system.width)
                this.pos.x = ig.game.screen.x;
            else if(this.pos.x < ig.game.screen.x)
                this.pos.x = ig.game.screen.x + ig.system.width;
        },

        draw: function() {
            // Draw snow
            ig.system.context.fillStyle = 'rgba(255, 255, 255, 0.8)';
            ig.system.context.beginPath();
                ig.system.context.arc(
                    this.pos.x,
                    this.pos.y,
                    this.radius,
                    0,
                    2 * Math.PI
                );
            ig.system.context.closePath();
            ig.system.context.fill();
        },

        handleMovementTrace: function(res) {
            this.pos.x += this.vel.x * ig.system.tick;
            this.pos.y += this.vel.y * ig.system.tick;
        }
    }); // End EntitySnow

    // Perlin Noise Generator
    //
    // My modifications: Minor code adaptation for use in ImpactJS. Algorithm remains unmodified.
    // Ken Perlin's original Java implementation: http://cs.nyu.edu/~perlin/noise
    // Kas Thomas's JavaScript port: http://asserttrue.blogspot.com/2011/12/perlin-noise-in-javascript_31.html
    var PerlinNoise = {
        noise: function(x, y, z) {
            var p = new Array(512);
            var permutation = [
                151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,
                99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,
                11,32, 57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,
                139,48,27,166, 77,146,158,231,83,111,229,122, 60,211,133,230,220,105,92,41,55,
                46,245,40,244,102,143,54, 65,25,63,161, 1,216,80,73,209, 76,132,187,208,89,18,
                169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186, 3, 64,52,217,226,
                250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,
                189,28, 42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,
                172,9,129, 22,39,253,19,98,108,110, 79,113,224,232,178,185,112,104,218,246,97,
                228,251, 34,242,193,238,210,144, 12,191,179,162,241,81,51,145,235,249, 14,239,
                107, 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121, 50,45,127, 4,150,
                254,138,236,205,93,222,114,67,29, 24,72,243,141,128,195,78,66,215, 61,156,180
            ];

            for(var i = 0; i < 256; i++)
                p[256+i] = p[i] = permutation[i];

                var X = Math.floor(x) & 255,
                    Y = Math.floor(y) & 255,
                    Z = Math.floor(z) & 255;

                x -= Math.floor(x);
                y -= Math.floor(y);
                z -= Math.floor(z);

                var u = this.fade(x),
                    v = this.fade(y),
                    w = this.fade(z);

                var A = p[X  ]+Y, AA = p[A]+Z, AB = p[A+1]+Z,
                    B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z;

                // Returns a number between 0 and 1
                return this.scale(this.lerp(w, this.lerp(v, this.lerp(u, this.grad(p[AA  ], x  , y  , z  ),
                                                                         this.grad(p[BA  ], x-1, y  , z  )),
                                                            this.lerp(u, this.grad(p[AB  ], x  , y-1, z  ),
                                                                         this.grad(p[BB  ], x-1, y-1, z  ))),
                                               this.lerp(v, this.lerp(u, this.grad(p[AA+1], x  , y  , z-1),
                                                                         this.grad(p[BA+1], x-1, y  , z-1)),
                                                            this.lerp(u, this.grad(p[AB+1], x  , y-1, z-1),
                                                                         this.grad(p[BB+1], x-1, y-1, z-1)))));
        },

        fade: function(t) { return t * t * t * (t * (t * 6 - 15) + 10); },
        lerp: function(t, a, b) { return a + t * (b - a); },
        grad: function(hash, x, y, z) {
            var h = hash & 15;
            var u = h < 8 ? x : y,
                v = h < 4 ? y : h === 12 || h === 14 ? x : z;
            return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
        },
        scale: function(n) { return (1 + n) / 2; }
    };
});
<|endoftext|>


<commit_before>
$( document ).ready(function() {

            /*GLOBAL STATE VARS */
            mode = "none";
            pauseButtonState = "pause";
            hasCalib = false;
            log = [];
            inBetweenSpeech = 5; //seconds
            timeOut = 3; //seconds
            successCount = 0; //count of # of times in bounds
            loopRun = 0;
            loopSuccessRun = 0;
            wasInBounds = false;
            /*END GLOBAL STATE */

            /* BUTTON LISTENERS */

            $("#calibMode").click(function(){
                if(!hasCalib){
                    var msg = new SpeechSynthesisUtterance('Move your cursor and follow it with your eyes');
                    setTimeout(window.speechSynthesis.speak(msg), 2000);
                    msg = new SpeechSynthesisUtterance('Click in various spots while looking at the cursor');
                    setTimeout(window.speechSynthesis.speak(msg), 10000);
                    msg = new SpeechSynthesisUtterance('Make sure you get points on and around the eyes and face');
                    setTimeout(window.speechSynthesis.speak(msg), 10000);
                }
                mode = "calibration";
                webgazer.showPredictionPoints(true);
                $('#gazedot').css('opacity',0.7);
                hasCalib = true;
            });

            $("#useMode").click(function(){
                if(!hasCalib){
                    alert("Please calibrate first");
                    return false;
                }
                else{
                    if(mode !== "use"){
                        loopRun= 0; //reset, so that speech doesn't go bonkers
                    }
                    mode = "use";
                    webgazer.showPredictionPoints(true);
                    $('#gazedot').css('opacity',0);
                }
            });

            $("#pause").click(function(){
                if(pauseButtonState === "pause"){
                    mode = "pause";
                    pauseButtonState = "resume";
                    webgazer.pause();
                    $("#pause").html('RESUME');
                }
                else if(pauseButtonState === "resume"){
                    mode = "resume";
                    pauseButtonState = "pause";
                    webgazer.resume();
                    $("#pause").html('PAUSE');
                }
            });

            $("#devMode").click(function(){
                if(mode !== "developer" && mode !== "forceOut" && mode !== "forceIn"){
                    mode = "developer";
                    console.log(mode);
                    $('#gazedot').css('opacity',0.7);
                }
                else{
                    mode = "use";
                    $('#gazedot').css('opacity',0);
                }

            });

            $("#forceIn").click(function(){
                if(mode !== "developer" && mode !== "forceOut"){
                    alert("You must be in developer mode to use this feature");
                }
                else mode = "forceIn";
                console.log(mode);
            });

            $("#forceOut").click(function(){
                if(mode !== "developer" && mode !== "forceIn"){
                    alert("You must be in developer mode to use this feature");
                }
                else mode = "forceOut";
                console.log(mode);
            });

            $('#relinquish').click(function(){
                if(mode !== "developer" && mode !== "forceIn" && mode !== "forceOut"){
                    mode = "use";
                }
            });
            /*END LISTENERS*/

            function getTransform(el) {
                var results = $(el).css('-webkit-transform').match(/matrix(?:(3d)\(\d+(?:, \d+)*(?:, (\d+))(?:, (\d+))(?:, (\d+)), \d+\)|\(\d+(?:, \d+)*(?:, (\d+))(?:, (\d+))\))/)

                if(!results) return /*[0, 0, 0]*/undefined;
                if(results[1] == '3d') return results.slice(2,5);

                results.push(0);
                return results.slice(5, 8);
            }
            setInterval(updatePosDisplay, 100);
            function updatePosDisplay(){
                var transform = getTransform('#gazedot');
                ++loopRun;
                $('#pos').text(transform);
                console.log(transform);
                console.log(isInBounds(transform));
                //$('#isinbounds').text(isInBounds(transform)) removed
                if(isInBounds(transform) && mode !== "forceOut" || mode === "forceIn"){
                    $('#pos').css('background-color','blue');
                    log.push(true);
                    if(mode !== "calibration"){
                        ++loopSuccessRun;
                            if(loopSuccessRun >= timeOut * 10){
                                $('#buttonbox').css('background-color', 'green');
                                console.log("success");
                                document.getElementById('buttonbox').id = 'buttonboximmune';
                            }
                    }
                    if(loopRun % (10*inBetweenSpeech) === 0|| loopRun === 0||!wasInBounds){
                        if(mode !== "calibration"){
                            var msg = new SpeechSynthesisUtterance('Good');
                            window.speechSynthesis.speak(msg);
                        }
                        wasInBounds = true;
                    }
                }
                else if(!isInBounds(transform) && mode !== "forceIn" || mode === "forceOut"){
                    $('#pos').css('background-color','red');
                    log.push(false);
                    if(loopRun % (10*inBetweenSpeech) === 0|| loopRun === 0||wasInBounds){
                        if(mode !== "calibration"){
                            loopSuccessRun = 0;
                            var msg = new SpeechSynthesisUtterance('Make eye contact');
                            window.speechSynthesis.speak(msg);
                        }
                    }
                    wasInBounds = false;
                }
            }

            function isInBounds(coords){
                var w = window.innerWidth;
                var h = window.innerHeight;
                var topBound = .66115702478*h;
                var bottomBound = .38567493112*h;
                var leftBound = .30769230769*w;
                var rightBound = .69336723881*w;
                var left = coords[0];
                var top = coords[1];
                var x = left;
                var y = h- top;
                console.log('x: '+x);
                console.log('y: '+y);
                console.log('bottom: '+bottomBound);
                console.log('top: '+topBound);
                return x >= leftBound && x <= rightBound && y >= bottomBound && y <= topBound;
            }
});<commit_msg>Dev mode - PROBABLY WON'T WORK<commit_after>
$( document ).ready(function() {

            /*GLOBAL STATE VARS */
            mode = "none";
            pauseButtonState = "pause";
            hasCalib = false;
            log = [];
            inBetweenSpeech = 5; //seconds
            timeOut = 3; //seconds
            successCount = 0; //count of # of times in bounds
            loopRun = 0;
            loopSuccessRun = 0;
            wasInBounds = false;
            /*END GLOBAL STATE */

            /* BUTTON LISTENERS */

            $("#calibMode").click(function(){
                if(!hasCalib){
                    var msg = new SpeechSynthesisUtterance('Move your cursor and follow it with your eyes');
                    setTimeout(window.speechSynthesis.speak(msg), 2000);
                    msg = new SpeechSynthesisUtterance('Click in various spots while looking at the cursor');
                    setTimeout(window.speechSynthesis.speak(msg), 10000);
                    msg = new SpeechSynthesisUtterance('Make sure you get points on and around the eyes and face');
                    setTimeout(window.speechSynthesis.speak(msg), 10000);
                }
                mode = "calibration";
                webgazer.showPredictionPoints(true);
                $('#gazedot').css('opacity',0.7);
                hasCalib = true;
            });

            $("#useMode").click(function(){
                if(!hasCalib){
                    alert("Please calibrate first");
                    return false;
                }
                else{
                    if(mode !== "use"){
                        loopRun= 0; //reset, so that speech doesn't go bonkers
                    }
                    mode = "use";
                    webgazer.showPredictionPoints(true);
                    $('#gazedot').css('opacity',0);
                }
            });

            $("#pause").click(function(){
                if(pauseButtonState === "pause"){
                    mode = "pause";
                    pauseButtonState = "resume";
                    webgazer.pause();
                    $("#pause").html('RESUME');
                }
                else if(pauseButtonState === "resume"){
                    mode = "resume";
                    pauseButtonState = "pause";
                    webgazer.resume();
                    $("#pause").html('PAUSE');
                }
            });

            $("#devMode").click(function(){
                if(mode !== "developer" && mode !== "forceOut" && mode !== "forceIn"){
                    mode = "developer";
                    console.log(mode);
                    $('#gazedot').css('opacity',0.7);
                }
                else{
                    mode = "use";
                    $('#gazedot').css('opacity',0);
                }

            });

            $("#forceIn").click(function(){
                if(mode !== "developer" && mode !== "forceOut"){
                    alert("You must be in developer mode to use this feature");
                }
                else mode = "forceIn";
                console.log(mode);
            });

            $("#forceOut").click(function(){
                if(mode !== "developer" && mode !== "forceIn"){
                    alert("You must be in developer mode to use this feature");
                }
                else mode = "forceOut";
                console.log(mode);
            });

            $('#relinquish').click(function(){
                if(mode == "developer" }} mode == "forceIn" || mode == "forceOut"){
                    mode = "use";
                }
            });
            /*END LISTENERS*/

            function getTransform(el) {
                var results = $(el).css('-webkit-transform').match(/matrix(?:(3d)\(\d+(?:, \d+)*(?:, (\d+))(?:, (\d+))(?:, (\d+)), \d+\)|\(\d+(?:, \d+)*(?:, (\d+))(?:, (\d+))\))/)

                if(!results) return /*[0, 0, 0]*/undefined;
                if(results[1] == '3d') return results.slice(2,5);

                results.push(0);
                return results.slice(5, 8);
            }
            setInterval(updatePosDisplay, 100);
            function updatePosDisplay(){
                var transform = getTransform('#gazedot');
                ++loopRun;
                $('#pos').text(transform);
                console.log(transform);
                console.log(isInBounds(transform));
                //$('#isinbounds').text(isInBounds(transform)) removed
                if(isInBounds(transform) && mode !== "forceOut" || mode === "forceIn"){
                    $('#pos').css('background-color','blue');
                    log.push(true);
                    if(mode !== "calibration"){
                        ++loopSuccessRun;
                            if(loopSuccessRun >= timeOut * 10){
                                $('#buttonbox').css('background-color', 'green');
                                console.log("success");
                                document.getElementById('buttonbox').id = 'buttonboximmune';
                            }
                    }
                    if(loopRun % (10*inBetweenSpeech) === 0|| loopRun === 0||!wasInBounds){
                        if(mode !== "calibration"){
                            var msg = new SpeechSynthesisUtterance('Good');
                            window.speechSynthesis.speak(msg);
                        }
                        wasInBounds = true;
                    }
                }
                else if(!isInBounds(transform) && mode !== "forceIn" || mode === "forceOut"){
                    $('#pos').css('background-color','red');
                    log.push(false);
                    if(loopRun % (10*inBetweenSpeech) === 0|| loopRun === 0||wasInBounds){
                        if(mode !== "calibration"){
                            loopSuccessRun = 0;
                            var msg = new SpeechSynthesisUtterance('Make eye contact');
                            window.speechSynthesis.speak(msg);
                        }
                    }
                    wasInBounds = false;
                }
            }

            function isInBounds(coords){
                var w = window.innerWidth;
                var h = window.innerHeight;
                var topBound = .66115702478*h;
                var bottomBound = .38567493112*h;
                var leftBound = .30769230769*w;
                var rightBound = .69336723881*w;
                var left = coords[0];
                var top = coords[1];
                var x = left;
                var y = h- top;
                console.log('x: '+x);
                console.log('y: '+y);
                console.log('bottom: '+bottomBound);
                console.log('top: '+topBound);
                return x >= leftBound && x <= rightBound && y >= bottomBound && y <= topBound;
            }
});<|endoftext|>


<commit_before>
$( document ).ready(function() {

            /*GLOBAL STATE VARS */
            mode = "none";
            pauseButtonState = "pause";
            hasCalib = false;
            log = [];
            inBetweenSpeech = 5; //seconds
            timeOut = 5; //seconds
            successCount = 0; //count of # of times in bounds
            loopRun = 0;
            loopSuccessRun = 0;
            wasInBounds = false;
            /*END GLOBAL STATE */

            $("#calibMode").click(function(){
                if(!hasCalib){
                    var msg = new SpeechSynthesisUtterance('Move your cursor and follow it with your eyes');
                    setTimeout(window.speechSynthesis.speak(msg), 2000);
                    msg = new SpeechSynthesisUtterance('Click in various spots while looking at the cursor');
                    setTimeout(window.speechSynthesis.speak(msg), 10000);
                    msg = new SpeechSynthesisUtterance('Make sure you get points on and around the eyes and face');
                    setTimeout(window.speechSynthesis.speak(msg), 10000);
                }
                mode = "calibration";
                webgazer.showPredictionPoints(true);
                $('#gazedot').css('opacity',0.7);
                hasCalib = true;
            });
            $("#useMode").click(function(){
                if(!hasCalib){
                    alert("Please calibrate first");
                    return false;
                }
                else{
                    if(mode !== "use"){
                        loopRun= 0; //reset, so that speech doesn't go bonkers
                    }
                    mode = "use";
                    webgazer.showPredictionPoints(true);
                    $('#gazedot').css('opacity',0);
                }
            });
            $("#pause").click(function(){
                if(pauseButtonState === "pause"){
                    mode = "pause";
                    pauseButtonState = "resume";
                    webgazer.pause();
                    $("#pause").html('RESUME');
                }
                else if(pauseButtonState === "resume"){
                    mode = "resume";
                    pauseButtonState = "pause";
                    webgazer.resume();
                    $("#pause").html('PAUSE');
                }
            });

            function getTransform(el) {
                var results = $(el).css('-webkit-transform').match(/matrix(?:(3d)\(\d+(?:, \d+)*(?:, (\d+))(?:, (\d+))(?:, (\d+)), \d+\)|\(\d+(?:, \d+)*(?:, (\d+))(?:, (\d+))\))/)

                if(!results) return /*[0, 0, 0]*/undefined;
                if(results[1] == '3d') return results.slice(2,5);

                results.push(0);
                return results.slice(5, 8);
            }
            setInterval(updatePosDisplay, 100);
            function updatePosDisplay(){
                var transform = getTransform('#gazedot');
                ++loopRun;
                $('#pos').text(transform);
                console.log(transform);
                console.log(isInBounds(transform));
                //$('#isinbounds').text(isInBounds(transform)) removed
                if(isInBounds(transform)){
                    $('#pos').css('background-color','blue');
                    log.push(true);
                    if(loopRun % (10*inBetweenSpeech) === 0|| loopRun === 0||!wasInBounds){
                        if(mode !== "calibration"){
                            ++loopSuccessRun;
                            if(loopSuccessRun == 30){
                                $('#buttonbox').css('background-color', 'green');
                                console.log("success");
                                document.getElementById('buttonbox').id = 'buttonboximmune';
                            }
                            var msg = new SpeechSynthesisUtterance('Good');
                            window.speechSynthesis.speak(msg);
                        }
                        wasInBounds = true;
                    }
                }
                else{
                    $('#pos').css('background-color','red');
                    log.push(false);
                    if(loopRun % (10*inBetweenSpeech) === 0|| loopRun === 0||wasInBounds){
                        if(mode !== "calibration"){
                            loopSuccessRun = 0;
                            var msg = new SpeechSynthesisUtterance('Make eye contact');
                            window.speechSynthesis.speak(msg);
                        }
                    }
                    wasInBounds = false;
                }
            }

            function isInBounds(coords){
                var w = window.innerWidth;
                var h = window.innerHeight;
                var topBound = .66115702478*h;
                var bottomBound = .38567493112*h;
                var leftBound = .30769230769*w;
                var rightBound = .69336723881*w;
                var left = coords[0];
                var top = coords[1];
                var x = left;
                var y = h- top;
                console.log('x: '+x);
                console.log('y: '+y);
                console.log('bottom: '+bottomBound);
                console.log('top: '+topBound);
                return x >= leftBound && x <= rightBound && y >= bottomBound && y <= topBound;
            }
});<commit_msg>Success timer - PROBABLY WON'T WORK<commit_after>
$( document ).ready(function() {

            /*GLOBAL STATE VARS */
            mode = "none";
            pauseButtonState = "pause";
            hasCalib = false;
            log = [];
            inBetweenSpeech = 5; //seconds
            timeOut = 5; //seconds
            successCount = 0; //count of # of times in bounds
            loopRun = 0;
            loopSuccessRun = 0;
            wasInBounds = false;
            /*END GLOBAL STATE */

            $("#calibMode").click(function(){
                if(!hasCalib){
                    var msg = new SpeechSynthesisUtterance('Move your cursor and follow it with your eyes');
                    setTimeout(window.speechSynthesis.speak(msg), 2000);
                    msg = new SpeechSynthesisUtterance('Click in various spots while looking at the cursor');
                    setTimeout(window.speechSynthesis.speak(msg), 10000);
                    msg = new SpeechSynthesisUtterance('Make sure you get points on and around the eyes and face');
                    setTimeout(window.speechSynthesis.speak(msg), 10000);
                }
                mode = "calibration";
                webgazer.showPredictionPoints(true);
                $('#gazedot').css('opacity',0.7);
                hasCalib = true;
            });
            $("#useMode").click(function(){
                if(!hasCalib){
                    alert("Please calibrate first");
                    return false;
                }
                else{
                    if(mode !== "use"){
                        loopRun= 0; //reset, so that speech doesn't go bonkers
                    }
                    mode = "use";
                    webgazer.showPredictionPoints(true);
                    $('#gazedot').css('opacity',0);
                }
            });
            $("#pause").click(function(){
                if(pauseButtonState === "pause"){
                    mode = "pause";
                    pauseButtonState = "resume";
                    webgazer.pause();
                    $("#pause").html('RESUME');
                }
                else if(pauseButtonState === "resume"){
                    mode = "resume";
                    pauseButtonState = "pause";
                    webgazer.resume();
                    $("#pause").html('PAUSE');
                }
            });

            function getTransform(el) {
                var results = $(el).css('-webkit-transform').match(/matrix(?:(3d)\(\d+(?:, \d+)*(?:, (\d+))(?:, (\d+))(?:, (\d+)), \d+\)|\(\d+(?:, \d+)*(?:, (\d+))(?:, (\d+))\))/)

                if(!results) return /*[0, 0, 0]*/undefined;
                if(results[1] == '3d') return results.slice(2,5);

                results.push(0);
                return results.slice(5, 8);
            }
            setInterval(updatePosDisplay, 100);
            function updatePosDisplay(){
                var transform = getTransform('#gazedot');
                ++loopRun;
                $('#pos').text(transform);
                console.log(transform);
                console.log(isInBounds(transform));
                //$('#isinbounds').text(isInBounds(transform)) removed
                if(isInBounds(transform)){
                    $('#pos').css('background-color','blue');
                    log.push(true);
                    if(loopRun % (10*inBetweenSpeech) === 0|| loopRun === 0||!wasInBounds){
                        if(mode !== "calibration"){
                            ++loopSuccessRun;
                            if(loopSuccessRun >= 3){
                                $('#buttonbox').css('background-color', 'green');
                                console.log("success");
                                document.getElementById('buttonbox').id = 'buttonboximmune';
                            }
                            var msg = new SpeechSynthesisUtterance('Good');
                            window.speechSynthesis.speak(msg);
                        }
                        wasInBounds = true;
                    }
                }
                else{
                    $('#pos').css('background-color','red');
                    log.push(false);
                    if(loopRun % (10*inBetweenSpeech) === 0|| loopRun === 0||wasInBounds){
                        if(mode !== "calibration"){
                            loopSuccessRun = 0;
                            var msg = new SpeechSynthesisUtterance('Make eye contact');
                            window.speechSynthesis.speak(msg);
                        }
                    }
                    wasInBounds = false;
                }
            }

            function isInBounds(coords){
                var w = window.innerWidth;
                var h = window.innerHeight;
                var topBound = .66115702478*h;
                var bottomBound = .38567493112*h;
                var leftBound = .30769230769*w;
                var rightBound = .69336723881*w;
                var left = coords[0];
                var top = coords[1];
                var x = left;
                var y = h- top;
                console.log('x: '+x);
                console.log('y: '+y);
                console.log('bottom: '+bottomBound);
                console.log('top: '+topBound);
                return x >= leftBound && x <= rightBound && y >= bottomBound && y <= topBound;
            }
});<|endoftext|>