<commit_before>const fs = require('fs-extra')
const glob = require('globby')
const args = require('minimist')(process.argv.splice(2))

const compDir = args.components
const storyDir = args.stories

//    getFileNameFromPath : string -> string
const getFileNameFromPath = () => {
  var memo = {}

  return path => {
    if (!(path in memo))
      memo[path] = path.split('/').pop().split('.').shift()

    return memo[path]
  }
}()

//    getComponent : string -> promise
const getComponent = path => {
  return new Promise((res, rej) => {
    const componentsPath = `copied-components/${path}`

    fs.copy(path, componentsPath, (err) => {
      if (err) rej(err)

      res(componentsPath)
    })
  })
}

const getSpec = path => new Promise((res, rej) => {
  const spec = require(`${__dirname}/${path}`)

  return (spec) ? res(spec) : rej(new Error('No spec'))
})

const getNote = path => new Promise((res, rej) => {
  fs.readFile(path, 'utf8', (err, md) => {
    if (err) rej(err)

    res(md)
  })
})

const getComponents = glob(`${compDir}**/*.js*`).then(paths => {
  Promise.all(paths.map(path => getComponent(path)))
    .then(paths => {
      fs.writeFile('./app/components.js', `
        ${paths.map(path => {
          return `import ${getFileNameFromPath(path)} from '${path}'`
        }).join('\n\t\t')}
        const components = {
          ${paths.map(path => getFileNameFromPath(path))}
        }
        export default components`)
    }).catch(err => console.log(err))
})

const getSpecs = glob(`${storyDir}**/*.js`).then(paths => {
  Promise.all(paths.map(path => getSpec(path)))
    .then(specs => {
      fs.writeFile('./app/specs.js', `
        const specs = ${JSON.stringify(specs)}
        export default specs
      `)
    })
})

const getNotes = glob(`${storyDir}**/*.md`).then(paths => {
  Promise.all(paths.map(path => getNote(path)))
    .then(notes => {
      fs.writeFile('./app/notes.js', `
        const notes = ${JSON.stringify(notes)}
        export default notes
      `)
    })
})
<commit_msg>use __dirname<commit_after>const fs = require('fs-extra')
const glob = require('globby')
const args = require('minimist')(process.argv.splice(2))

const compDir = args.components
const storyDir = args.stories

//    getFileNameFromPath : string -> string
const getFileNameFromPath = () => {
  var memo = {}

  return path => {
    if (!(path in memo))
      memo[path] = path.split('/').pop().split('.').shift()

    return memo[path]
  }
}()

//    getComponent : string -> promise
const getComponent = path => {
  return new Promise((res, rej) => {
    const componentsPath = `copied-components/${path}`

    fs.copy(path, componentsPath, (err) => {
      if (err) rej(err)

      res(componentsPath)
    })
  })
}

const getSpec = path => new Promise((res, rej) => {
  const spec = require(`${__dirname}/${path}`)

  return (spec) ? res(spec) : rej(new Error('No spec'))
})

const getNote = path => new Promise((res, rej) => {
  fs.readFile(path, 'utf8', (err, md) => {
    if (err) rej(err)

    res(md)
  })
})

const getComponents = glob(`${compDir}**/*.js*`).then(paths => {
  Promise.all(paths.map(path => getComponent(path)))
    .then(paths => {
      fs.writeFile(`${__dirname}/app/components.js`, `
        ${paths.map(path => {
          return `import ${getFileNameFromPath(path)} from '${path}'`
        }).join('\n\t\t')}
        const components = {
          ${paths.map(path => getFileNameFromPath(path))}
        }
        export default components`)
    }).catch(err => console.log(err))
})

const getSpecs = glob(`${storyDir}**/*.js`).then(paths => {
  Promise.all(paths.map(path => getSpec(path)))
    .then(specs => {
      fs.writeFile(`${__dirname}/app/specs.js`, `
        const specs = ${JSON.stringify(specs)}
        export default specs
      `)
    })
})

const getNotes = glob(`${storyDir}**/*.md`).then(paths => {
  Promise.all(paths.map(path => getNote(path)))
    .then(notes => {
      fs.writeFile(`${__dirname}/app/notes.js`, `
        const notes = ${JSON.stringify(notes)}
        export default notes
      `)
    })
})
<|endoftext|>


<commit_before>import {ModelError} from "./error"

// ;; Fragment is the type used to represent a node's collection of
// child nodes.
//
// Fragments are persistent data structures. That means you should
// _not_ mutate them or their content, but create new instances
// whenever needed. The API tries to make this easy.
export class Fragment {
  constructor(content, size) {
    this.content = content
    this.size = size || 0
    if (size == null) for (let i = 0; i < content.length; i++)
      this.size += content[i].nodeSize
  }

  // :: string
  // Concatenate all the text nodes found in this fragment and its
  // children.
  get textContent() {
    let text = ""
    this.content.forEach(n => text += n.textContent)
    return text
  }

  // :: () → string
  // Return a debugging string that describes this fragment.
  toString() { return "<" + this.toStringInner() + ">" }

  toStringInner() { return this.content.join(", ") }

  nodesBetween(from, to, f, nodeStart, parent) {
    for (let i = 0, pos = 0; pos < to; i++) {
      let child = this.content[i], end = pos + child.nodeSize
      if (end > from && f(child, nodeStart + pos, parent) !== false && child.content.size) {
        let start = pos + 1
        child.nodesBetween(Math.max(0, from - start),
                           Math.min(child.content.size, to - start),
                           f, nodeStart + start)
      }
      pos = end
    }
  }

  // :: (number, ?number) → Fragment
  // Cut out the sub-fragment between the two given positions.
  cut(from, to) {
    if (to == null) to = this.size
    if (from == 0 && to == this.size) return this
    let result = [], size = 0
    if (to > from) for (let i = 0, pos = 0; pos < to; i++) {
      let child = this.content[i], end = pos + child.nodeSize
      if (end > from) {
        if (pos < from || end > to) {
          if (child.isText)
            child = child.cut(Math.max(0, from - pos), Math.min(child.text.length, to - pos))
          else
            child = child.cut(Math.max(0, from - pos - 1), Math.min(child.content.size, to - pos - 1))
        }
        result.push(child)
        size += child.nodeSize
      }
      pos = end
    }
    return new Fragment(result, size)
  }

  // :: (Fragment) → Fragment
  // Create a new fragment containing the content of this fragment and
  // `other`.
  append(other) {
    if (!other.size) return this
    if (!this.size) return other
    let last = this.lastChild, first = other.firstChild, content = this.content.slice(), i = 0
    if (last.isText && last.sameMarkup(first)) {
      content[content.length - 1] = last.copy(last.text + first.text)
      i = 1
    }
    for (; i < other.content.length; i++) content.push(other.content[i])
    return new Fragment(content, this.size + other.size)
  }

  // :: (number, Node) → Fragment
  // Create a new fragment in which the node at the given index is
  // replaced by the given node.
  replaceChild(index, node) {
    let copy = this.content.slice()
    let size = this.size + node.nodeSize - copy[index].nodeSize
    copy[index] = node
    return new Fragment(copy, size)
  }

  // (Node) → Fragment
  // Create a new fragment by prepending the given node to this
  // fragment.
  addToStart(node) {
    return new Fragment([node].concat(this.content), this.size + node.nodeSize)
  }

  // (Node) → Fragment
  // Create a new fragment by appending the given node to this
  // fragment.
  addToEnd(node) {
    return new Fragment(this.content.concat(node), this.size + node.nodeSize)
  }

  // :: () → union<Object, null>
  // Create a JSON-serializeable representation of this fragment.
  toJSON() {
    return this.content.length ? this.content.map(n => n.toJSON()) : null
  }

  // :: (Schema, Object) → Fragment
  // Deserialize a fragment from its JSON representation.
  static fromJSON(schema, value) {
    return value ? new Fragment(value.map(schema.nodeFromJSON)) : Fragment.empty
  }

  // :: ([Node]) → Fragment
  // Build a fragment from an array of nodes. Ensures that adjacent
  // text nodes with the same style are joined together.
  static fromArray(array) {
    if (!array.length) return Fragment.empty
    let joined, size = 0
    for (let i = 0; i < array.length; i++) {
      let node = array[i]
      size += node.nodeSize
      if (i && node.isText && array[i - 1].sameMarkup(node)) {
        if (!joined) joined = array.slice(0, i)
        joined[joined.length - 1] = node.copy(joined[joined.length - 1].text + node.text)
      } else if (joined) {
        joined.push(node)
      }
    }
    return new Fragment(joined || array, size)
  }

  // :: (Fragment) → bool
  // Compare this fragment to another one.
  eq(other) {
    if (this.content.length != other.content.length) return false
    for (let i = 0; i < this.content.length; i++)
      if (!this.content[i].eq(other.content[i])) return false
    return true
  }

  // :: (?union<Fragment, Node, [Node]>) → Fragment
  // Create a fragment from something that can be interpreted as a set
  // of nodes. For `null`, it returns the empty fragment. For a
  // fragment, the fragment itself. For a node or array of nodes, a
  // fragment containing those nodes.
  static from(nodes) {
    if (!nodes) return Fragment.empty
    if (nodes instanceof Fragment) return nodes
    if (Array.isArray(nodes)) return this.fromArray(nodes)
    return new Fragment([nodes], nodes.nodeSize)
  }

  // :: ?Node
  // The first child of the fragment, or `null` if it is empty.
  get firstChild() { return this.content.length ? this.content[0] : null }

  // :: ?Node
  // The last child of the fragment, or `null` if it is empty.
  get lastChild() { return this.content.length ? this.content[this.content.length - 1] : null }

  // :: number
  // The number of child nodes in this fragment.
  get childCount() { return this.content.length }

  // :: (number) → Node
  // Get the child node at the given index. Raise an error when the
  // index is out of range.
  child(index) {
    let found = this.content[index]
    if (!found) throw new ModelError("Index " + index + " out of range for " + this)
    return found
  }

  // :: (number) → ?Node
  // Get the child node at the given index, if it exists.
  maybeChild(index) {
    return this.content[index]
  }

  // :: ((node: Node, offset: number))
  // Call `f` for every child node, passing the node and its offset
  // into this parent node.
  forEach(f) {
    for (let i = 0, p = 0; i < this.content.length; i++) {
      let child = this.content[i]
      f(child, p)
      p += child.nodeSize
    }
  }
}

// :: Fragment
// An empty fragment. Intended to be reused whenever a node doesn't
// contain anything (rather than allocating a new empty fragment for
// each leaf node).
Fragment.empty = new Fragment([], 0)
<commit_msg>Fix typing of Fragment.to/fromJSON<commit_after>import {ModelError} from "./error"

// ;; Fragment is the type used to represent a node's collection of
// child nodes.
//
// Fragments are persistent data structures. That means you should
// _not_ mutate them or their content, but create new instances
// whenever needed. The API tries to make this easy.
export class Fragment {
  constructor(content, size) {
    this.content = content
    this.size = size || 0
    if (size == null) for (let i = 0; i < content.length; i++)
      this.size += content[i].nodeSize
  }

  // :: string
  // Concatenate all the text nodes found in this fragment and its
  // children.
  get textContent() {
    let text = ""
    this.content.forEach(n => text += n.textContent)
    return text
  }

  // :: () → string
  // Return a debugging string that describes this fragment.
  toString() { return "<" + this.toStringInner() + ">" }

  toStringInner() { return this.content.join(", ") }

  nodesBetween(from, to, f, nodeStart, parent) {
    for (let i = 0, pos = 0; pos < to; i++) {
      let child = this.content[i], end = pos + child.nodeSize
      if (end > from && f(child, nodeStart + pos, parent) !== false && child.content.size) {
        let start = pos + 1
        child.nodesBetween(Math.max(0, from - start),
                           Math.min(child.content.size, to - start),
                           f, nodeStart + start)
      }
      pos = end
    }
  }

  // :: (number, ?number) → Fragment
  // Cut out the sub-fragment between the two given positions.
  cut(from, to) {
    if (to == null) to = this.size
    if (from == 0 && to == this.size) return this
    let result = [], size = 0
    if (to > from) for (let i = 0, pos = 0; pos < to; i++) {
      let child = this.content[i], end = pos + child.nodeSize
      if (end > from) {
        if (pos < from || end > to) {
          if (child.isText)
            child = child.cut(Math.max(0, from - pos), Math.min(child.text.length, to - pos))
          else
            child = child.cut(Math.max(0, from - pos - 1), Math.min(child.content.size, to - pos - 1))
        }
        result.push(child)
        size += child.nodeSize
      }
      pos = end
    }
    return new Fragment(result, size)
  }

  // :: (Fragment) → Fragment
  // Create a new fragment containing the content of this fragment and
  // `other`.
  append(other) {
    if (!other.size) return this
    if (!this.size) return other
    let last = this.lastChild, first = other.firstChild, content = this.content.slice(), i = 0
    if (last.isText && last.sameMarkup(first)) {
      content[content.length - 1] = last.copy(last.text + first.text)
      i = 1
    }
    for (; i < other.content.length; i++) content.push(other.content[i])
    return new Fragment(content, this.size + other.size)
  }

  // :: (number, Node) → Fragment
  // Create a new fragment in which the node at the given index is
  // replaced by the given node.
  replaceChild(index, node) {
    let copy = this.content.slice()
    let size = this.size + node.nodeSize - copy[index].nodeSize
    copy[index] = node
    return new Fragment(copy, size)
  }

  // (Node) → Fragment
  // Create a new fragment by prepending the given node to this
  // fragment.
  addToStart(node) {
    return new Fragment([node].concat(this.content), this.size + node.nodeSize)
  }

  // (Node) → Fragment
  // Create a new fragment by appending the given node to this
  // fragment.
  addToEnd(node) {
    return new Fragment(this.content.concat(node), this.size + node.nodeSize)
  }

  // :: () → ?Object
  // Create a JSON-serializeable representation of this fragment.
  toJSON() {
    return this.content.length ? this.content.map(n => n.toJSON()) : null
  }

  // :: (Schema, ?Object) → Fragment
  // Deserialize a fragment from its JSON representation.
  static fromJSON(schema, value) {
    return value ? new Fragment(value.map(schema.nodeFromJSON)) : Fragment.empty
  }

  // :: ([Node]) → Fragment
  // Build a fragment from an array of nodes. Ensures that adjacent
  // text nodes with the same style are joined together.
  static fromArray(array) {
    if (!array.length) return Fragment.empty
    let joined, size = 0
    for (let i = 0; i < array.length; i++) {
      let node = array[i]
      size += node.nodeSize
      if (i && node.isText && array[i - 1].sameMarkup(node)) {
        if (!joined) joined = array.slice(0, i)
        joined[joined.length - 1] = node.copy(joined[joined.length - 1].text + node.text)
      } else if (joined) {
        joined.push(node)
      }
    }
    return new Fragment(joined || array, size)
  }

  // :: (Fragment) → bool
  // Compare this fragment to another one.
  eq(other) {
    if (this.content.length != other.content.length) return false
    for (let i = 0; i < this.content.length; i++)
      if (!this.content[i].eq(other.content[i])) return false
    return true
  }

  // :: (?union<Fragment, Node, [Node]>) → Fragment
  // Create a fragment from something that can be interpreted as a set
  // of nodes. For `null`, it returns the empty fragment. For a
  // fragment, the fragment itself. For a node or array of nodes, a
  // fragment containing those nodes.
  static from(nodes) {
    if (!nodes) return Fragment.empty
    if (nodes instanceof Fragment) return nodes
    if (Array.isArray(nodes)) return this.fromArray(nodes)
    return new Fragment([nodes], nodes.nodeSize)
  }

  // :: ?Node
  // The first child of the fragment, or `null` if it is empty.
  get firstChild() { return this.content.length ? this.content[0] : null }

  // :: ?Node
  // The last child of the fragment, or `null` if it is empty.
  get lastChild() { return this.content.length ? this.content[this.content.length - 1] : null }

  // :: number
  // The number of child nodes in this fragment.
  get childCount() { return this.content.length }

  // :: (number) → Node
  // Get the child node at the given index. Raise an error when the
  // index is out of range.
  child(index) {
    let found = this.content[index]
    if (!found) throw new ModelError("Index " + index + " out of range for " + this)
    return found
  }

  // :: (number) → ?Node
  // Get the child node at the given index, if it exists.
  maybeChild(index) {
    return this.content[index]
  }

  // :: ((node: Node, offset: number))
  // Call `f` for every child node, passing the node and its offset
  // into this parent node.
  forEach(f) {
    for (let i = 0, p = 0; i < this.content.length; i++) {
      let child = this.content[i]
      f(child, p)
      p += child.nodeSize
    }
  }
}

// :: Fragment
// An empty fragment. Intended to be reused whenever a node doesn't
// contain anything (rather than allocating a new empty fragment for
// each leaf node).
Fragment.empty = new Fragment([], 0)
<|endoftext|>


<commit_before>import Promise from 'bluebird'
import Immutable from 'immutable'
import {flatten, forEach, omit} from 'lodash'
import {v4 as uuid} from 'node-uuid'

import {version as currentVersionString} from '../../package.json'

import {randomChar, countCredits} from 'sto-helpers'

import Schedule from './schedule'
import Study from './study'

import checkStudentGraduatability from '../helpers/checkStudentGraduatability'

import present from 'present'
import debug from 'debug'
const changelog = debug('gobbldygook:model:student:changes')

const StudentRecord = Immutable.Record({
	id: null,
	name: null,
	version: currentVersionString,

	creditsNeeded: 35,

	matriculation: 1894,
	graduation: 1898,

	dateLastModified: null,
	dateCreated: null,

	studies: Immutable.OrderedMap(),
	schedules: Immutable.OrderedMap(),
	overrides: Immutable.OrderedMap(),
	fabrications: Immutable.OrderedMap(),

	settings: Immutable.Map(),
})

class Student extends StudentRecord {
	constructor(encodedStudent={}) {
		// Don't pass the list params into the StudentRecord constructor; it creates them as JS objects,
		// instead of our custom Studies, Schedules, and such.
		const startTime = present()

		const toRemove = ['studies', 'schedules', 'overrides', 'fabrications', 'settings']
		const filtered = omit(encodedStudent, toRemove)
		const immutableStudent = Immutable.fromJS(filtered)

		super(immutableStudent)

		// Then add them manually.
		if (encodedStudent) {
			return this.withMutations((student) => {
				student = student.set('id', encodedStudent.id || uuid())
				student = student.set('name', encodedStudent.name || 'Student ' + randomChar())
				student = student.set('dateCreated', encodedStudent.dateCreated || new Date())
				student = student.set('dateLastModified', encodedStudent.dateLastModified || new Date())

				forEach((encodedStudent.studies || []), study => {
					student = student.addArea(study)
				})

				forEach((encodedStudent.schedules || []), schedule => {
					student = student.addSchedule(schedule)
				})

				forEach((encodedStudent.overrides || []), (val, key) => {
					student = student.addOverride({[key]: val})
				})

				forEach((encodedStudent.fabrications || []), fabrication => {
					student = student.addFabrication(fabrication)
				})

				forEach((encodedStudent.settings || {}), (value, key) => {
					student = student.changeSetting(key, value)
				})

				student = student.set('version', currentVersionString)

				changelog(`it took ${present() - startTime} ms to make a student`)

				return student
			})
		}
	}

	changeName(newName) {
		return this.set('name', newName)
	}

	changeCreditsNeeded(newCreditsNeeded) {
		return this.set('creditsNeeded', newCreditsNeeded)
	}

	changeMatriculation(newMatriculation) {
		return this.set('matriculation', newMatriculation)
	}

	changeGraduation(newGraduation) {
		return this.set('graduation', newGraduation)
	}

	changeSetting(key, value) {
		return this.setIn(['settings', key], value)
	}

	get graduatability() {
		return checkStudentGraduatability(this)
	}

	// schedule methods

	get schedulesByYear() {
		return this.schedules.groupBy(sched => sched.year)
	}

	get activeSchedules() {
		return this.schedules.filter(sched => sched.active).sortBy(sched => sched.semester)
	}

	addSchedule(newSchedule) {
		const sched = new Schedule(newSchedule)
		return this.setIn(['schedules', sched.id], sched)
	}

	destroySchedule(scheduleId) {
		changelog(`removing schedule ${scheduleId}`)

		const deadSched = this.getIn(['schedules', scheduleId])
		const scheduleIsNoMore = this.set('schedules', this.schedules.delete(scheduleId))

		if (deadSched && deadSched.active) {
			const otherSchedKey = scheduleIsNoMore.findKey((sched) =>
				sched.year === deadSched.year &&
				sched.semester === deadSched.semester &&
				sched.id !== deadSched.id)

			if (otherSchedKey) {
				return scheduleIsNoMore.setIn(['schedules', otherSchedKey, 'active'], true)
			}
		}
		return scheduleIsNoMore
	}

	destroyMultipleSchedules(ids) {
		return this.withMutations((student) => {
			changelog('destroyMultipleSchedules', ids)
			// console.groupCollapsed('destroyMultipleSchedules')
			Immutable.Seq(ids).forEach((id) => {
				// console.log('destroyMultipleSchedules', id)
				student = student.destroySchedule(id)
			})
			// console.groupEnd('destroyMultipleSchedules')
			return student
		})
	}

	moveCourse(fromScheduleId, toScheduleId, clbid) {
		changelog(`moving course ${clbid} from schedule ${fromScheduleId} to schedule ${toScheduleId}`)
		return this.withMutations((student) => {
			student = student.setIn(['schedules', fromScheduleId], student.getIn(['schedules', fromScheduleId]).removeCourse(clbid))
			student = student.setIn(['schedules', toScheduleId], student.getIn(['schedules', toScheduleId]).addCourse(clbid))
		})
	}

	// area-of-study methods

	get areasByType() {
		return this.studies.groupBy(study => study.type || 'Unknown')
	}

	addArea(areaOfStudy) {
		const study = new Study(areaOfStudy)
		return this.setIn(['studies', study.id], study)
	}

	removeArea(id) {
		return this.set('studies', this.studies.delete(id))
	}

	removeMultipleAreas(ids) {
		return this.withMutations((student) => {
			Immutable.Seq(ids).forEach((id) => {
				student = student.removeArea(id)
			})
			return student
		})
	}

	// override methods

	addOverride(override) {
		return this.withMutations(student => {
			forEach(override, (val, key) => {
				student.setIn(['overrides', key], val)
			})
		})
	}

	removeOverride(thingOverriden) {
		return this.set('overrides', this.overrides.delete(thingOverriden))
	}

	// fabrication methods

	addFabrication(fabrication) {
		return this.setIn(['fabrications', fabrication.id], fabrication)
	}

	removeFabrication(fabricationId) {
		return this.set('fabrications', this.fabrications.delete(fabricationId))
	}

	// getters

	get courses() {
		const allCourses = this.activeSchedules
			.map((schedule) => schedule.courses)
			.toArray()
		const scheduleCoursePromises = Promise.all(allCourses)
		return scheduleCoursePromises.then((scheduleCourses) => flatten(scheduleCourses))
	}

	get creditCount() {
		return this.courses.then(countCredits)
	}

	// helpers

	data() {
		return Promise.props({
			courses: this.courses,
			fabrications: this.fabrications.toList().toJS(),
			overrides: this.overrides.toList().toJS(),
			studies: this.studies.toList().toJS(),
			creditsNeeded: this.creditsNeeded,
			graduation: this.graduation,
			matriculation: this.matriculation,
		})
	}

	encode() {
		return encodeURIComponent(JSON.stringify(this))
	}

	save() {
		const oldVersion = localStorage.getItem(this.id)
		const stringified = JSON.stringify(this)
		if (oldVersion !== stringified) {
			console.log(`saving student ${this.name} (${this.id})`)
			const student = this.set('dateLastModified', new Date())
			localStorage.setItem(student.id, stringified)
		}
	}

	toJSON() {
		return {
			id: this.id,
			name: this.name,
			version: this.version,
			creditsNeeded: this.creditsNeeded,
			matriculation: this.matriculation,
			graduation: this.graduation,
			studies: this.studies.toList(),
			schedules: this.schedules.toList(),
			overrides: this.overrides.toMap(),
			fabrications: this.fabrications.toList(),
			settings: this.settings.toMap(),
		}
	}
}

export default Student
<commit_msg>update some comments and sort some props in Student<commit_after>import Promise from 'bluebird'
import Immutable from 'immutable'
import {flatten, forEach, omit} from 'lodash'
import {v4 as uuid} from 'node-uuid'

import {version as currentVersionString} from '../../package.json'

import {randomChar, countCredits} from 'sto-helpers'

import Schedule from './schedule'
import Study from './study'

import checkStudentGraduatability from '../helpers/checkStudentGraduatability'

import present from 'present'
import debug from 'debug'
const changelog = debug('gobbldygook:model:student:changes')

const StudentRecord = Immutable.Record({
	id: null,
	name: null,
	version: currentVersionString,

	creditsNeeded: 35,

	matriculation: 1894,
	graduation: 1898,

	dateLastModified: null,
	dateCreated: null,

	studies: Immutable.OrderedMap(),
	schedules: Immutable.OrderedMap(),
	overrides: Immutable.OrderedMap(),
	fabrications: Immutable.OrderedMap(),

	settings: Immutable.Map(),
})

class Student extends StudentRecord {
	constructor(encodedStudent={}) {
		const startTime = present()

		// Don't pass the list params into the StudentRecord constructor;
		// it creates them as JS objects, instead of our custom records.
		const toRemove = ['studies', 'schedules', 'overrides', 'fabrications', 'settings']
		const filtered = omit(encodedStudent, toRemove)
		const immutableStudent = Immutable.fromJS(filtered)

		super(immutableStudent)

		// Then add them manually.
		if (encodedStudent) {
			return this.withMutations((student) => {
				student = student.set('id', encodedStudent.id || uuid())
				student = student.set('name', encodedStudent.name || 'Student ' + randomChar())
				student = student.set('dateCreated', encodedStudent.dateCreated || new Date())
				student = student.set('dateLastModified', encodedStudent.dateLastModified || new Date())
				student = student.set('version', currentVersionString)

				forEach((encodedStudent.studies || []), study => {
					student = student.addArea(study)
				})

				forEach((encodedStudent.schedules || []), schedule => {
					student = student.addSchedule(schedule)
				})

				forEach((encodedStudent.overrides || []), (val, key) => {
					student = student.addOverride({[key]: val})
				})

				forEach((encodedStudent.fabrications || []), fabrication => {
					student = student.addFabrication(fabrication)
				})

				forEach((encodedStudent.settings || {}), (value, key) => {
					student = student.changeSetting(key, value)
				})

				changelog(`it took ${present() - startTime} ms to make a student`)

				return student
			})
		}
	}

	changeName(newName) {
		return this.set('name', newName)
	}

	changeCreditsNeeded(newCreditsNeeded) {
		return this.set('creditsNeeded', newCreditsNeeded)
	}

	changeMatriculation(newMatriculation) {
		return this.set('matriculation', newMatriculation)
	}

	changeGraduation(newGraduation) {
		return this.set('graduation', newGraduation)
	}

	changeSetting(key, value) {
		return this.setIn(['settings', key], value)
	}

	get graduatability() {
		return checkStudentGraduatability(this)
	}

	// schedule methods

	get schedulesByYear() {
		return this.schedules
			.groupBy(sched => sched.year)
	}

	get activeSchedules() {
		return this.schedules
			.filter(sched => sched.active)
			.sortBy(sched => sched.semester)
	}

	addSchedule(newSchedule) {
		const sched = new Schedule(newSchedule)
		return this.setIn(['schedules', sched.id], sched)
	}

	destroySchedule(scheduleId) {
		changelog(`removing schedule ${scheduleId}`)

		const deadSched = this.getIn(['schedules', scheduleId])
		const scheduleIsNoMore = this.set('schedules', this.schedules.delete(scheduleId))

		if (deadSched && deadSched.active) {
			const otherSchedKey = scheduleIsNoMore.findKey((sched) =>
				sched.year === deadSched.year &&
				sched.semester === deadSched.semester &&
				sched.id !== deadSched.id)

			if (otherSchedKey) {
				return scheduleIsNoMore.setIn(['schedules', otherSchedKey, 'active'], true)
			}
		}
		return scheduleIsNoMore
	}

	destroyMultipleSchedules(ids) {
		return this.withMutations((student) => {
			changelog('destroyMultipleSchedules', ids)
			// console.groupCollapsed('destroyMultipleSchedules')
			Immutable.Seq(ids).forEach((id) => {
				// console.log('destroyMultipleSchedules', id)
				student = student.destroySchedule(id)
			})
			// console.groupEnd('destroyMultipleSchedules')
			return student
		})
	}

	moveCourse(fromScheduleId, toScheduleId, clbid) {
		changelog(`moving course ${clbid} from schedule ${fromScheduleId} to schedule ${toScheduleId}`)
		return this.withMutations((student) => {
			student = student.setIn(['schedules', fromScheduleId], student.getIn(['schedules', fromScheduleId]).removeCourse(clbid))
			student = student.setIn(['schedules', toScheduleId], student.getIn(['schedules', toScheduleId]).addCourse(clbid))
		})
	}

	// area-of-study methods

	get areasByType() {
		return this.studies.groupBy(study => study.type || 'Unknown')
	}

	addArea(areaOfStudy) {
		const study = new Study(areaOfStudy)
		return this.setIn(['studies', study.id], study)
	}

	removeArea(id) {
		return this.set('studies', this.studies.delete(id))
	}

	removeMultipleAreas(ids) {
		return this.withMutations((student) => {
			Immutable.Seq(ids).forEach((id) => {
				student = student.removeArea(id)
			})
			return student
		})
	}

	// override methods

	addOverride(override) {
		return this.withMutations(student => {
			forEach(override, (val, key) => {
				student.setIn(['overrides', key], val)
			})
		})
	}

	removeOverride(thingOverriden) {
		return this.set('overrides', this.overrides.delete(thingOverriden))
	}

	// fabrication methods

	addFabrication(fabrication) {
		return this.setIn(['fabrications', fabrication.id], fabrication)
	}

	removeFabrication(fabricationId) {
		return this.set('fabrications', this.fabrications.delete(fabricationId))
	}

	// getters

	get courses() {
		const allCourses = this.activeSchedules
			.map((schedule) => schedule.courses)
			.toArray()
		const scheduleCoursePromises = Promise.all(allCourses)
		return scheduleCoursePromises.then((scheduleCourses) => flatten(scheduleCourses))
	}

	get creditCount() {
		return this.courses.then(countCredits)
	}

	// helpers

	data() {
		return Promise.props({
			courses: this.courses,
			creditsNeeded: this.creditsNeeded,
			fabrications: this.fabrications.toList().toJS(),
			graduation: this.graduation,
			matriculation: this.matriculation,
			overrides: this.overrides.toList().toJS(),
			studies: this.studies.toList().toJS(),
		})
	}

	encode() {
		return encodeURIComponent(JSON.stringify(this))
	}

	save() {
		// grab the old (still-JSON-encoded) student from localstorage
		// compare it to the current one
		// if they're different, update dateLastModified, stringify, and save.
		const oldVersion = localStorage.getItem(this.id)
		const stringified = JSON.stringify(this)
		if (oldVersion !== stringified) {
			console.log(`saving student ${this.name} (${this.id})`)
			const student = this.set('dateLastModified', new Date())
			localStorage.setItem(student.id, stringified)
		}
	}

	toJSON() {
		return {
			id: this.id,
			name: this.name,
			version: this.version,
			creditsNeeded: this.creditsNeeded,
			matriculation: this.matriculation,
			graduation: this.graduation,
			studies: this.studies.toList(),
			schedules: this.schedules.toList(),
			overrides: this.overrides.toMap(),
			fabrications: this.fabrications.toList(),
			settings: this.settings.toMap(),
		}
	}
}

export default Student
<|endoftext|>


<commit_before>/**
 * GameAI.js
 *
 * FooFighter.js — GameAI module
 *
 * This module controls the generation of enemy entities, collision detection,
 * and overall game engine goodness.
 */

;(function( FooFighter ){

'use strict';

function GameAI ( gameState ){
    var d = new Date();
    this.gameState = gameState;
    this.game = gameState.game;
    this.asteroids = this.gameState.groups.asteroids;
    this.lasers = this.gameState.groups.lasers;
    this.gameInProgress = false;
    this.lastEnemyCreated = d.getTime();
    this.config = {
        enemyThrottleVal: 1000,
        bigToSmallPercentage: 0.6,
        points: {
            'meteorBig.png': 5,
            'meteorSmall.png': 10
        }
    };
    this.bindEvents();
}

var proto = GameAI.prototype;

proto.bindEvents = function(){
    var vent = this.gameState.vent;

    vent.on('start', this.start.bind(this));
    vent.on('update', this.checkCollisions.bind(this));
    vent.on('update', this.addEnemiesCheck.bind(this));
    vent.on('asteroid-killed', this.asteroidExplosion.bind(this));
    return this;
};

proto.start = function(){
    this.gameInProgress = true;
    return this;
};

proto.stop = function(){
    this.gameInProgress = false;
    return this;
};

proto.addEnemiesCheck = function(){
    var throttleVal = this.config.enemyThrottleVal,
        lastTime = this.lastEnemyCreated,
        currTime = new Date().getTime();

    if ( this.gameInProgress === true) {
        if ( currTime - lastTime >= throttleVal ) {
            this.createAsteroid();
            this.lastEnemyCreated = currTime;
        }
    }
    return this;
};

proto.createAsteroid = function(){
    var asteroids = this.asteroids,
        percent = this.config.bigToSmallPercentage,
        sizeVal = 0,
        newRoid;

    if ( Math.random() >= percent ) {
        sizeVal = 1;
    }

    newRoid = new FooFighter.Asteroid(this.gameState).create(sizeVal);
    asteroids.add(newRoid.sprite);
    return this;
};

proto.checkCollisions = function(){
    this.game.physics.collide(
        this.asteroids, this.lasers, this.collisionHandler, null, this
    );
    return this;
};

proto.collisionHandler = function ( asteroid, laser ) {
    var points = this.config.points;

    asteroid.kill();
    laser.kill();
    this.gameState.score += points[asteroid.currentFrame.name];
    return this;
};

proto.asteroidExplosion = function ( asteroid ) {
    var type = asteroid.sprite.currentFrame.name,
        posX = asteroid.sprite.x,
        posY = asteroid.sprite.y,
        i;

    for (i = 0; i < 3; i++) {
        this.createAsteroid();
    }
    return this;
};

FooFighter.GameAI = GameAI;

})(FooFighter);
<commit_msg>collision detection for player<commit_after>/**
 * GameAI.js
 *
 * FooFighter.js — GameAI module
 *
 * This module controls the generation of enemy entities, collision detection,
 * and overall game engine goodness.
 */

;(function( FooFighter ){

'use strict';

function GameAI ( gameState ){
    var d = new Date();
    this.gameState = gameState;
    this.game = gameState.game;
    this.asteroids = this.gameState.groups.asteroids;
    this.lasers = this.gameState.groups.lasers;
    this.gameInProgress = false;
    this.lastEnemyCreated = d.getTime();
    this.config = {
        enemyThrottleVal: 1000,
        bigToSmallPercentage: 0.6,
        points: {
            'meteorBig.png': 5,
            'meteorSmall.png': 10
        }
    };
    this.bindEvents();
}

var proto = GameAI.prototype;

proto.bindEvents = function(){
    var vent = this.gameState.vent;

    vent.on('start', this.start.bind(this));
    vent.on('update', this.checkCollisions.bind(this));
    vent.on('update', this.addEnemiesCheck.bind(this));
    // vent.on('asteroid-killed', this.asteroidExplosion.bind(this));
    return this;
};

proto.start = function(){
    this.gameInProgress = true;
    return this;
};

proto.stop = function(){
    this.gameInProgress = false;
    return this;
};

proto.addEnemiesCheck = function(){
    var throttleVal = this.config.enemyThrottleVal,
        lastTime = this.lastEnemyCreated,
        currTime = new Date().getTime();

    if ( this.gameInProgress === true) {
        if ( currTime - lastTime >= throttleVal ) {
            this.createAsteroid();
            this.lastEnemyCreated = currTime;
        }
    }
    return this;
};

proto.createAsteroid = function(){
    var asteroids = this.asteroids,
        percent = this.config.bigToSmallPercentage,
        sizeVal = 0,
        newRoid;

    if ( Math.random() >= percent ) {
        sizeVal = 1;
    }

    newRoid = new FooFighter.Asteroid(this.gameState).create(sizeVal);
    asteroids.add(newRoid.sprite);
    return this;
};

proto.checkCollisions = function(){
    this.game.physics.collide(
        this.asteroids,
        this.lasers,
        this.collisionHandlerLaser,
        null,
        this
    );
    this.game.physics.collide(
        this.gameState.entities.player.sprite,
        this.asteroids, this.collisionHandlerPlayer,
        null,
        this
    );
    return this;
};

proto.collisionHandlerLaser = function ( asteroid, laser ) {
    var points = this.config.points;

    asteroid.kill();
    laser.kill();
    this.gameState.score += points[asteroid.currentFrame.name];
};

proto.collisionHandlerPlayer = function ( player, asteroid ) {
    player.kill();
};

proto.asteroidExplosion = function ( asteroid ) {
    var type = asteroid.sprite.currentFrame.name,
        posX = asteroid.sprite.x,
        posY = asteroid.sprite.y,
        i;

    for (i = 0; i < 3; i++) {
        this.createAsteroid();
    }
    return this;
};

FooFighter.GameAI = GameAI;

})(FooFighter);
<|endoftext|>


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

import * as WasmDis from '../third_party/wasmparser/package/dist/esm/WasmDis.js';
import * as WasmParser from '../third_party/wasmparser/package/dist/esm/WasmParser.js';

class BinaryReaderWithProgress extends WasmParser.BinaryReader {
  /**
   * @param {!function(number):void} progressCallback
   */
  constructor(progressCallback) {
    super();
    /** @type {number} */
    this._percentage = 0;
    this._progressCallback = progressCallback;
  }

  read() {
    if (!super.read()) {
      return false;
    }
    const percentage = Math.floor((this.position / this.length) * 100);
    if (this._percentage !== percentage) {
      this._progressCallback.call(undefined, percentage);
      this._percentage = percentage;
    }
    return true;
  }
}

const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const BASE64_CODES = new Uint8Array(123);
for (let index = 0; index < BASE64_CHARS.length; ++index) {
  BASE64_CODES[BASE64_CHARS.charCodeAt(index)] = index;
}

/**
 * Decodes Base64-encoded data from a string without performing any kind of checking.
 *
 * @param {string} input valid Base64-encoded data
 * @return {!ArrayBuffer} decoded result
 */
function base64Decode(input) {
  let bytesLength = ((input.length * 3) / 4) >>> 0;
  if (input.charCodeAt(input.length - 2) === 0x3d /* '=' */) {
    bytesLength -= 2;
  } else if (input.charCodeAt(input.length - 1) === 0x3d /* '=' */) {
    bytesLength -= 1;
  }

  const bytes = new Uint8Array(bytesLength);
  for (let index = 0, offset = 0; index < input.length; index += 4) {
    const a = BASE64_CODES[input.charCodeAt(index + 0)];
    const b = BASE64_CODES[input.charCodeAt(index + 1)];
    const c = BASE64_CODES[input.charCodeAt(index + 2)];
    const d = BASE64_CODES[input.charCodeAt(index + 3)];
    bytes[offset++] = (a << 2) | (b >> 4);
    bytes[offset++] = ((b & 0x0f) << 4) | (c >> 2);
    bytes[offset++] = ((c & 0x03) << 6) | (d & 0x3f);
  }
  return bytes.buffer;
}

self.onmessage = async function(event) {
  const method = /** @type {string} */ (event.data.method);
  const params = /** @type !{content: string} */ (event.data.params);
  if (!method || method !== 'disassemble') {
    return;
  }

  const NAME_GENERATOR_WEIGHT = 30;
  const DISASSEMBLY_WEIGHT = 69;
  const FINALIZATION_WEIGHT = 100 - (NAME_GENERATOR_WEIGHT + DISASSEMBLY_WEIGHT);

  const buffer = base64Decode(params.content);

  let parser = new BinaryReaderWithProgress(percentage => {
    this.postMessage({event: 'progress', params: {percentage: percentage * (NAME_GENERATOR_WEIGHT / 100)}});
  });
  parser.setData(buffer, 0, buffer.byteLength);
  const nameGenerator = new WasmDis.DevToolsNameGenerator();
  nameGenerator.read(parser);

  const dis = new WasmDis.WasmDisassembler();
  dis.addOffsets = true;
  dis.exportMetadata = nameGenerator.getExportMetadata();
  dis.nameResolver = nameGenerator.getNameResolver();
  parser = new BinaryReaderWithProgress(percentage => {
    this.postMessage(
        {event: 'progress', params: {percentage: NAME_GENERATOR_WEIGHT + percentage * (DISASSEMBLY_WEIGHT / 100)}});
  });
  parser.setData(buffer, 0, buffer.byteLength);
  dis.disassembleChunk(parser);
  const {lines, offsets, functionBodyOffsets} = dis.getResult();

  // Truncate the output to 1M lines, because CodeMirror gets glitchy above that.
  // TODO(bmeurer): This is not very performant and we also risk running out of
  // memory in the worker (seems to work for the cases that we know about for now),
  // so we should look into using the chunked disassembly to implement this in a
  // more reasonable fashion.
  const MAX_LINES = 1000 * 1000;
  if (lines.length > MAX_LINES) {
    lines[MAX_LINES] = ';; .... text is truncated due to size';
    lines.splice(MAX_LINES + 1);
    if (offsets) {
      offsets.splice(MAX_LINES + 1);
    }
  }

  this.postMessage({event: 'progress', params: {percentage: FINALIZATION_WEIGHT}});

  const source = lines.join('\n');

  this.postMessage({event: 'progress', params: {percentage: 100}});

  this.postMessage({method: 'disassemble', result: {source, offsets, functionBodyOffsets}});
};

/* Legacy exported object */
self.WasmParserWorker = self.WasmParserWorker || {};

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

import * as WasmDis from '../third_party/wasmparser/package/dist/esm/WasmDis.js';
import * as WasmParser from '../third_party/wasmparser/package/dist/esm/WasmParser.js';

class BinaryReaderWithProgress extends WasmParser.BinaryReader {
  /**
   * @param {!function(number):void} progressCallback
   */
  constructor(progressCallback) {
    super();
    /** @type {number} */
    this._percentage = 0;
    this._progressCallback = progressCallback;
  }

  read() {
    if (!super.read()) {
      return false;
    }
    const percentage = Math.floor((this.position / this.length) * 100);
    if (this._percentage !== percentage) {
      this._progressCallback.call(undefined, percentage);
      this._percentage = percentage;
    }
    return true;
  }
}

const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const BASE64_CODES = new Uint8Array(123);
for (let index = 0; index < BASE64_CHARS.length; ++index) {
  BASE64_CODES[BASE64_CHARS.charCodeAt(index)] = index;
}

/**
 * Decodes Base64-encoded data from a string without performing any kind of checking.
 *
 * @param {string} input valid Base64-encoded data
 * @return {!ArrayBuffer} decoded result
 */
function base64Decode(input) {
  let bytesLength = ((input.length * 3) / 4) >>> 0;
  if (input.charCodeAt(input.length - 2) === 0x3d /* '=' */) {
    bytesLength -= 2;
  } else if (input.charCodeAt(input.length - 1) === 0x3d /* '=' */) {
    bytesLength -= 1;
  }

  const bytes = new Uint8Array(bytesLength);
  for (let index = 0, offset = 0; index < input.length; index += 4) {
    const a = BASE64_CODES[input.charCodeAt(index + 0)];
    const b = BASE64_CODES[input.charCodeAt(index + 1)];
    const c = BASE64_CODES[input.charCodeAt(index + 2)];
    const d = BASE64_CODES[input.charCodeAt(index + 3)];
    bytes[offset++] = (a << 2) | (b >> 4);
    bytes[offset++] = ((b & 0x0f) << 4) | (c >> 2);
    bytes[offset++] = ((c & 0x03) << 6) | (d & 0x3f);
  }
  return bytes.buffer;
}

self.onmessage = async function(event) {
  const method = /** @type {string} */ (event.data.method);
  const params = /** @type !{content: string} */ (event.data.params);
  if (!method || method !== 'disassemble') {
    return;
  }

  const NAME_GENERATOR_WEIGHT = 30;
  const DISASSEMBLY_WEIGHT = 69;
  const FINALIZATION_WEIGHT = NAME_GENERATOR_WEIGHT + DISASSEMBLY_WEIGHT;

  const buffer = base64Decode(params.content);

  let parser = new BinaryReaderWithProgress(percentage => {
    this.postMessage({event: 'progress', params: {percentage: percentage * (NAME_GENERATOR_WEIGHT / 100)}});
  });
  parser.setData(buffer, 0, buffer.byteLength);
  const nameGenerator = new WasmDis.DevToolsNameGenerator();
  nameGenerator.read(parser);

  const dis = new WasmDis.WasmDisassembler();
  dis.addOffsets = true;
  dis.exportMetadata = nameGenerator.getExportMetadata();
  dis.nameResolver = nameGenerator.getNameResolver();
  parser = new BinaryReaderWithProgress(percentage => {
    this.postMessage(
        {event: 'progress', params: {percentage: NAME_GENERATOR_WEIGHT + percentage * (DISASSEMBLY_WEIGHT / 100)}});
  });
  parser.setData(buffer, 0, buffer.byteLength);
  dis.disassembleChunk(parser);
  const {lines, offsets, functionBodyOffsets} = dis.getResult();

  // Truncate the output to 1M lines, because CodeMirror gets glitchy above that.
  // TODO(bmeurer): This is not very performant and we also risk running out of
  // memory in the worker (seems to work for the cases that we know about for now),
  // so we should look into using the chunked disassembly to implement this in a
  // more reasonable fashion.
  const MAX_LINES = 1000 * 1000;
  if (lines.length > MAX_LINES) {
    lines[MAX_LINES] = ';; .... text is truncated due to size';
    lines.splice(MAX_LINES + 1);
    if (offsets) {
      offsets.splice(MAX_LINES + 1);
    }
  }

  this.postMessage({event: 'progress', params: {percentage: FINALIZATION_WEIGHT}});

  const source = lines.join('\n');

  this.postMessage({event: 'progress', params: {percentage: 100}});

  this.postMessage({method: 'disassemble', result: {source, offsets, functionBodyOffsets}});
};

/* Legacy exported object */
self.WasmParserWorker = self.WasmParserWorker || {};

/* Legacy exported object */
WasmParserWorker = WasmParserWorker || {};
<|endoftext|>


<commit_before><commit_msg>docs: add style active color<commit_after>var els = document.querySelector('.sidebar').querySelectorAll('.link')

els.forEach(function(el) {
  console.log(el.href)
  if (el.href == location.href) {
    el.style.color = '#0275d8'
  }
})
<|endoftext|>


<commit_before>'use strict';

var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
$interpolateMinErr.throwNoconcat = function(text) {
  throw $interpolateMinErr('noconcat',
      "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
      "interpolations that concatenate multiple expressions when a trusted value is " +
      "required.  See http://docs.angularjs.org/api/ng.$sce", text);
};

$interpolateMinErr.interr = function(text, err) {
  return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
};

/**
 * @ngdoc provider
 * @name $interpolateProvider
 *
 * @description
 *
 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
 *
 *

 * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular
 * template within a Python Jinja template (or any other template language). Mixing templating
 * languages is **very dangerous**. The embedding template language will not safely escape Angular
 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
 * security bugs!
 *

 *
 * @example
<example name="custom-interpolation-markup" module="customInterpolationApp">
<file name="index.html">

    //demo.label//

</file>
<file name="protractor.js" type="protractor">
  it('should interpolate binding with custom symbols', function() {
    expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
  });
</file>
</example>
 */
function $InterpolateProvider() {
  var startSymbol = '{{';
  var endSymbol = '}}';

  /**
   * @ngdoc method
   * @name $interpolateProvider#startSymbol
   * @description
   * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
   *
   * @param {string=} value new value to set the starting symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.startSymbol = function(value) {
    if (value) {
      startSymbol = value;
      return this;
    } else {
      return startSymbol;
    }
  };

  /**
   * @ngdoc method
   * @name $interpolateProvider#endSymbol
   * @description
   * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
   *
   * @param {string=} value new value to set the ending symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.endSymbol = function(value) {
    if (value) {
      endSymbol = value;
      return this;
    } else {
      return endSymbol;
    }
  };

  this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
    var startSymbolLength = startSymbol.length,
        endSymbolLength = endSymbol.length,
        escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
        escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');

    function escape(ch) {
      return '\\\\\\' + ch;
    }

    function unescapeText(text) {
      return text.replace(escapedStartRegexp, startSymbol).
        replace(escapedEndRegexp, endSymbol);
    }

    function stringify(value) {
      if (value == null) { // null || undefined
        return '';
      }
      switch (typeof value) {
        case 'string':
          break;
        case 'number':
          value = '' + value;
          break;
        default:
          value = toJson(value);
      }

      return value;
    }

    //TODO: this is the same as the constantWatchDelegate in parse.js
    function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
      var unwatch;
      return unwatch = scope.$watch(function constantInterpolateWatch(scope) {
        unwatch();
        return constantInterp(scope);
      }, listener, objectEquality);
    }

    /**
     * @ngdoc service
     * @name $interpolate
     * @kind function
     *
     * @requires $parse
     * @requires $sce
     *
     * @description
     *
     * Compiles a string with markup into an interpolation function. This service is used by the
     * HTML {@link ng.$compile $compile} service for data binding. See
     * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
     * interpolation markup.
     *
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var exp = $interpolate('Hello {{name | uppercase}}!');
     *   expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
     * ```
     *
     * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
     * `true`, the interpolation function will return `undefined` unless all embedded expressions
     * evaluate to a value other than `undefined`.
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var context = {greeting: 'Hello', name: undefined };
     *
     *   // default "forgiving" mode
     *   var exp = $interpolate('{{greeting}} {{name}}!');
     *   expect(exp(context)).toEqual('Hello !');
     *
     *   // "allOrNothing" mode
     *   exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
     *   expect(exp(context)).toBeUndefined();
     *   context.name = 'Angular';
     *   expect(exp(context)).toEqual('Hello Angular!');
     * ```
     *
     * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
     *
     * ####Escaped Interpolation
     * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
     * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
     * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
     * or binding.
     *
     * This enables web-servers to prevent script injection attacks and defacing attacks, to some
     * degree, while also enabling code examples to work without relying on the
     * {@link ng.directive:ngNonBindable ngNonBindable} directive.
     *
     * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
     * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all
     * interpolation start/end markers with their escaped counterparts.**
     *
     * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
     * output when the $interpolate service processes the text. So, for HTML elements interpolated
     * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
     * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
     * this is typically useful only when user-data is used in rendering a template from the server, or
     * when otherwise untrusted data is used by a directive.
     *
     * <example>
     *  <file name="index.html">
     *

     *
{{apptitle}}: \{\{ username = "defaced value"; \}\}
     *

     *
{{username}} attempts to inject code which will deface the
     *        application, but fails to accomplish their task, because the server has correctly
     *        escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
     *        characters.

     *
Instead, the result of the attempted script injection is visible, and can be removed
     *        from the database by an administrator.

     *

     *  </file>
     * </example>
     *
     * @knownIssue
     * It is currently not possible for an interpolated expression to contain the interpolation end
     * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e.
     * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string.
     *
     * @param {string} text The text with markup to interpolate.
     * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
     *    embedded expression in order to return an interpolation function. Strings with no
     *    embedded expression will return null for the interpolation function.
     * @param {string=} trustedContext when provided, the returned function passes the interpolated
     *    result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
     *    trustedContext)} before returning it.  Refer to the {@link ng.$sce $sce} service that
     *    provides Strict Contextual Escaping for details.
     * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
     *    unless all embedded expressions evaluate to a value other than `undefined`.
     * @returns {function(context)} an interpolation function which is used to compute the
     *    interpolated string. The function has these parameters:
     *
     * - `context`: evaluation context for all expressions embedded in the interpolated text
     */
    function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
      // Provide a quick exit and simplified result function for text with no interpolation
      if (!text.length || text.indexOf(startSymbol) === -1) {
        var constantInterp;
        if (!mustHaveExpression) {
          var unescapedText = unescapeText(text);
          constantInterp = valueFn(unescapedText);
          constantInterp.exp = text;
          constantInterp.expressions = [];
          constantInterp.$$watchDelegate = constantWatchDelegate;
        }
        return constantInterp;
      }

      allOrNothing = !!allOrNothing;
      var startIndex,
          endIndex,
          index = 0,
          expressions = [],
          parseFns = [],
          textLength = text.length,
          exp,
          concat = [],
          expressionPositions = [];

      while (index < textLength) {
        if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
             ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
          if (index !== startIndex) {
            concat.push(unescapeText(text.substring(index, startIndex)));
          }
          exp = text.substring(startIndex + startSymbolLength, endIndex);
          expressions.push(exp);
          parseFns.push($parse(exp, parseStringifyInterceptor));
          index = endIndex + endSymbolLength;
          expressionPositions.push(concat.length);
          concat.push('');
        } else {
          // we did not find an interpolation, so we have to add the remainder to the separators array
          if (index !== textLength) {
            concat.push(unescapeText(text.substring(index)));
          }
          break;
        }
      }

      // Concatenating expressions makes it hard to reason about whether some combination of
      // concatenated values are unsafe to use and could easily lead to XSS.  By requiring that a
      // single expression be used for iframe[src], object[src], etc., we ensure that the value
      // that's used is assigned or constructed by some JS code somewhere that is more testable or
      // make it obvious that you bound the value to some user controlled value.  This helps reduce
      // the load when auditing for XSS issues.
      if (trustedContext && concat.length > 1) {
          $interpolateMinErr.throwNoconcat(text);
      }

      if (!mustHaveExpression || expressions.length) {
        var compute = function(values) {
          for (var i = 0, ii = expressions.length; i < ii; i++) {
            if (allOrNothing && isUndefined(values[i])) return;
            concat[expressionPositions[i]] = values[i];
          }
          return concat.join('');
        };

        var getValue = function(value) {
          return trustedContext ?
            $sce.getTrusted(trustedContext, value) :
            $sce.valueOf(value);
        };

        return extend(function interpolationFn(context) {
            var i = 0;
            var ii = expressions.length;
            var values = new Array(ii);

            try {
              for (; i < ii; i++) {
                values[i] = parseFns[i](context);
              }

              return compute(values);
            } catch (err) {
              $exceptionHandler($interpolateMinErr.interr(text, err));
            }

          }, {
          // all of these properties are undocumented for now
          exp: text, //just for compatibility with regular watchers created via $watch
          expressions: expressions,
          $$watchDelegate: function(scope, listener) {
            var lastValue;
            return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
              var currValue = compute(values);
              if (isFunction(listener)) {
                listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
              }
              lastValue = currValue;
            });
          }
        });
      }

      function parseStringifyInterceptor(value) {
        try {
          value = getValue(value);
          return allOrNothing && !isDefined(value) ? value : stringify(value);
        } catch (err) {
          $exceptionHandler($interpolateMinErr.interr(text, err));
        }
      }
    }

    /**
     * @ngdoc method
     * @name $interpolate#startSymbol
     * @description
     * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
     *
     * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
     * the symbol.
     *
     * @returns {string} start symbol.
     */
    $interpolate.startSymbol = function() {
      return startSymbol;
    };

    /**
     * @ngdoc method
     * @name $interpolate#endSymbol
     * @description
     * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
     *
     * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
     * the symbol.
     *
     * @returns {string} end symbol.
     */
    $interpolate.endSymbol = function() {
      return endSymbol;
    };

    return $interpolate;
  }];
}

<commit_msg>docs($interpolate): add known issue about custom interpolation symbols<commit_after>'use strict';

var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
$interpolateMinErr.throwNoconcat = function(text) {
  throw $interpolateMinErr('noconcat',
      "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
      "interpolations that concatenate multiple expressions when a trusted value is " +
      "required.  See http://docs.angularjs.org/api/ng.$sce", text);
};

$interpolateMinErr.interr = function(text, err) {
  return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
};

/**
 * @ngdoc provider
 * @name $interpolateProvider
 *
 * @description
 *
 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
 *
 *

 * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular
 * template within a Python Jinja template (or any other template language). Mixing templating
 * languages is **very dangerous**. The embedding template language will not safely escape Angular
 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
 * security bugs!
 *

 *
 * @example
<example name="custom-interpolation-markup" module="customInterpolationApp">
<file name="index.html">

    //demo.label//

</file>
<file name="protractor.js" type="protractor">
  it('should interpolate binding with custom symbols', function() {
    expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
  });
</file>
</example>
 */
function $InterpolateProvider() {
  var startSymbol = '{{';
  var endSymbol = '}}';

  /**
   * @ngdoc method
   * @name $interpolateProvider#startSymbol
   * @description
   * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
   *
   * @param {string=} value new value to set the starting symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.startSymbol = function(value) {
    if (value) {
      startSymbol = value;
      return this;
    } else {
      return startSymbol;
    }
  };

  /**
   * @ngdoc method
   * @name $interpolateProvider#endSymbol
   * @description
   * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
   *
   * @param {string=} value new value to set the ending symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.endSymbol = function(value) {
    if (value) {
      endSymbol = value;
      return this;
    } else {
      return endSymbol;
    }
  };

  this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
    var startSymbolLength = startSymbol.length,
        endSymbolLength = endSymbol.length,
        escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
        escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');

    function escape(ch) {
      return '\\\\\\' + ch;
    }

    function unescapeText(text) {
      return text.replace(escapedStartRegexp, startSymbol).
        replace(escapedEndRegexp, endSymbol);
    }

    function stringify(value) {
      if (value == null) { // null || undefined
        return '';
      }
      switch (typeof value) {
        case 'string':
          break;
        case 'number':
          value = '' + value;
          break;
        default:
          value = toJson(value);
      }

      return value;
    }

    //TODO: this is the same as the constantWatchDelegate in parse.js
    function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
      var unwatch;
      return unwatch = scope.$watch(function constantInterpolateWatch(scope) {
        unwatch();
        return constantInterp(scope);
      }, listener, objectEquality);
    }

    /**
     * @ngdoc service
     * @name $interpolate
     * @kind function
     *
     * @requires $parse
     * @requires $sce
     *
     * @description
     *
     * Compiles a string with markup into an interpolation function. This service is used by the
     * HTML {@link ng.$compile $compile} service for data binding. See
     * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
     * interpolation markup.
     *
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var exp = $interpolate('Hello {{name | uppercase}}!');
     *   expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
     * ```
     *
     * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
     * `true`, the interpolation function will return `undefined` unless all embedded expressions
     * evaluate to a value other than `undefined`.
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var context = {greeting: 'Hello', name: undefined };
     *
     *   // default "forgiving" mode
     *   var exp = $interpolate('{{greeting}} {{name}}!');
     *   expect(exp(context)).toEqual('Hello !');
     *
     *   // "allOrNothing" mode
     *   exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
     *   expect(exp(context)).toBeUndefined();
     *   context.name = 'Angular';
     *   expect(exp(context)).toEqual('Hello Angular!');
     * ```
     *
     * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
     *
     * ####Escaped Interpolation
     * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
     * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
     * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
     * or binding.
     *
     * This enables web-servers to prevent script injection attacks and defacing attacks, to some
     * degree, while also enabling code examples to work without relying on the
     * {@link ng.directive:ngNonBindable ngNonBindable} directive.
     *
     * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
     * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all
     * interpolation start/end markers with their escaped counterparts.**
     *
     * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
     * output when the $interpolate service processes the text. So, for HTML elements interpolated
     * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
     * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
     * this is typically useful only when user-data is used in rendering a template from the server, or
     * when otherwise untrusted data is used by a directive.
     *
     * <example>
     *  <file name="index.html">
     *

     *
{{apptitle}}: \{\{ username = "defaced value"; \}\}
     *

     *
{{username}} attempts to inject code which will deface the
     *        application, but fails to accomplish their task, because the server has correctly
     *        escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
     *        characters.

     *
Instead, the result of the attempted script injection is visible, and can be removed
     *        from the database by an administrator.

     *

     *  </file>
     * </example>
     *
     * @knownIssue
     * It is currently not possible for an interpolated expression to contain the interpolation end
     * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e.
     * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string.
     *
     * @knownIssue
     * All directives and components must use the standard `{{` `}}` interpolation symbols
     * in their templates. If you change the application interpolation symbols the {@link $compile}
     * service will attempt to denormalize the standard symbols to the custom symbols.
     * The denormalization process is not clever enough to know not to replace instances of the standard
     * symbols where they would not normally be treated as interpolation symbols. For example in the following
     * code snippet the closing braces of the literal object will get incorrectly denormalized:
     *
     * ```
     * <div data-context='{"context":{"id":3,"type":"page"}}">
     * ```
     *
     * The workaround is to ensure that such instances are separated by whitespace:
     * ```
     * <div data-context='{"context":{"id":3,"type":"page"} }">
     * ```
     *
     * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information.
     *
     * @param {string} text The text with markup to interpolate.
     * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
     *    embedded expression in order to return an interpolation function. Strings with no
     *    embedded expression will return null for the interpolation function.
     * @param {string=} trustedContext when provided, the returned function passes the interpolated
     *    result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
     *    trustedContext)} before returning it.  Refer to the {@link ng.$sce $sce} service that
     *    provides Strict Contextual Escaping for details.
     * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
     *    unless all embedded expressions evaluate to a value other than `undefined`.
     * @returns {function(context)} an interpolation function which is used to compute the
     *    interpolated string. The function has these parameters:
     *
     * - `context`: evaluation context for all expressions embedded in the interpolated text
     */
    function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
      // Provide a quick exit and simplified result function for text with no interpolation
      if (!text.length || text.indexOf(startSymbol) === -1) {
        var constantInterp;
        if (!mustHaveExpression) {
          var unescapedText = unescapeText(text);
          constantInterp = valueFn(unescapedText);
          constantInterp.exp = text;
          constantInterp.expressions = [];
          constantInterp.$$watchDelegate = constantWatchDelegate;
        }
        return constantInterp;
      }

      allOrNothing = !!allOrNothing;
      var startIndex,
          endIndex,
          index = 0,
          expressions = [],
          parseFns = [],
          textLength = text.length,
          exp,
          concat = [],
          expressionPositions = [];

      while (index < textLength) {
        if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
             ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
          if (index !== startIndex) {
            concat.push(unescapeText(text.substring(index, startIndex)));
          }
          exp = text.substring(startIndex + startSymbolLength, endIndex);
          expressions.push(exp);
          parseFns.push($parse(exp, parseStringifyInterceptor));
          index = endIndex + endSymbolLength;
          expressionPositions.push(concat.length);
          concat.push('');
        } else {
          // we did not find an interpolation, so we have to add the remainder to the separators array
          if (index !== textLength) {
            concat.push(unescapeText(text.substring(index)));
          }
          break;
        }
      }

      // Concatenating expressions makes it hard to reason about whether some combination of
      // concatenated values are unsafe to use and could easily lead to XSS.  By requiring that a
      // single expression be used for iframe[src], object[src], etc., we ensure that the value
      // that's used is assigned or constructed by some JS code somewhere that is more testable or
      // make it obvious that you bound the value to some user controlled value.  This helps reduce
      // the load when auditing for XSS issues.
      if (trustedContext && concat.length > 1) {
          $interpolateMinErr.throwNoconcat(text);
      }

      if (!mustHaveExpression || expressions.length) {
        var compute = function(values) {
          for (var i = 0, ii = expressions.length; i < ii; i++) {
            if (allOrNothing && isUndefined(values[i])) return;
            concat[expressionPositions[i]] = values[i];
          }
          return concat.join('');
        };

        var getValue = function(value) {
          return trustedContext ?
            $sce.getTrusted(trustedContext, value) :
            $sce.valueOf(value);
        };

        return extend(function interpolationFn(context) {
            var i = 0;
            var ii = expressions.length;
            var values = new Array(ii);

            try {
              for (; i < ii; i++) {
                values[i] = parseFns[i](context);
              }

              return compute(values);
            } catch (err) {
              $exceptionHandler($interpolateMinErr.interr(text, err));
            }

          }, {
          // all of these properties are undocumented for now
          exp: text, //just for compatibility with regular watchers created via $watch
          expressions: expressions,
          $$watchDelegate: function(scope, listener) {
            var lastValue;
            return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
              var currValue = compute(values);
              if (isFunction(listener)) {
                listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
              }
              lastValue = currValue;
            });
          }
        });
      }

      function parseStringifyInterceptor(value) {
        try {
          value = getValue(value);
          return allOrNothing && !isDefined(value) ? value : stringify(value);
        } catch (err) {
          $exceptionHandler($interpolateMinErr.interr(text, err));
        }
      }
    }

    /**
     * @ngdoc method
     * @name $interpolate#startSymbol
     * @description
     * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
     *
     * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
     * the symbol.
     *
     * @returns {string} start symbol.
     */
    $interpolate.startSymbol = function() {
      return startSymbol;
    };

    /**
     * @ngdoc method
     * @name $interpolate#endSymbol
     * @description
     * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
     *
     * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
     * the symbol.
     *
     * @returns {string} end symbol.
     */
    $interpolate.endSymbol = function() {
      return endSymbol;
    };

    return $interpolate;
  }];
}

<|endoftext|>


<commit_before>// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
function is_touch_device() {
    return !!('ontouchstart' in window) // works on most browsers
        || !!('onmsgesturechange' in window); // works on ie10
}

function get_board_dimension() {
    var top_row = $('#row1');
    var bottom_row = $('#row16');

    var top = top_row.position().top;
    var bottom = bottom_row.position().top + bottom_row.outerHeight();
    var left = top_row.position().left;
    var right = top_row.position().left + top_row.outerWidth();
    var width = right - left;
    var height = bottom - top;
    return { 'top': top, 'bottom': bottom, 'left': left, 'right': right, 'width': width, 'height': height };
}

var current_row = 0;
function toggle_panel_color(panel) {
    if ($(panel).hasClass("black") || $(panel).hasClass("white")) {
        $(panel).toggleClass("black white");
    }
    else {
        $(panel).addClass("white invisible-text");
    }
}

function unmagnify_row(i, duration, delay) {
    duration /= 1000;
    delay /= 1000;
    var row = $('#row' + i);
    row.css({
        'transition-duration': '0.001s, ' + duration + 's, ' + duration + 's, ' + duration + 's, ' + duration + 's',
        'transition-delay': delay + 's'
    });
    row.removeClass('magnified');
}

function magnify_row(i, duration, delay) {
    duration /= 1000;
    delay /= 1000;
    var row = $('#row' + i);
    row.css({
        'transition-duration': '0.001s, ' + duration + 's, ' + duration + 's, ' + duration + 's, ' + duration + 's',
        'transition-delay': delay + 's'
    });
    row.addClass('magnified');
}

function move_focus(i, duration, delay) {
    duration /= 1000;
    delay /= 1000;

    var board = get_board_dimension();
    var row = $('#row' + i);

    var unmasked_top = row.position().top;
    var unmasked_bottom = unmasked_top + row.outerHeight();

    var transition_props = {
        'transition-duration': duration + 's, ' + duration + 's, ' + duration + 's, ' + duration + 's',
        'transition-delay': delay + 's',
    };
    var upper_mask = $('#upper-mask');
    var lower_mask = $('#lower-mask');
    upper_mask.css(transition_props);
    lower_mask.css(transition_props);
    upper_mask.css('transform', 'translate3d(0px, ' + ((unmasked_top - board.top) - upper_mask.outerHeight()) + 'px, 0px)');
    lower_mask.css('transform', 'translate3d(0px, ' + (unmasked_bottom - board.top) + 'px, 0px)');
}

function move_cursor_button(i) {
    var board = get_board_dimension();
    var row = $('#row' + i);

    var unmasked_top = row.position().top;
    var unmasked_bottom = unmasked_top + row.outerHeight();

    var prev_button = $('#prev-button');
    var next_button = $('#next-button');
    prev_button.css({
        'left': (board.left - prev_button.outerWidth()) + 'px',
        'top': (unmasked_top - prev_button.outerHeight() - 10) + 'px',
        'visibility': i === 1 ? 'hidden' : 'visible',
    });
    next_button.css({
        'left': (board.left - next_button.outerWidth()) + 'px',
        'top': (unmasked_bottom + 10) + 'px',
        'visibility': i === 16 ? 'hidden' : 'visible',
    });
}

function initialize_mask_transformations() {
    var board = get_board_dimension();

    $('#upper-mask').css('transform', 'translate3d(0px, ' + (-board.height) + 'px, 0px)');
    $('#lower-mask').css('transform', 'translate3d(0px, ' + board.height + 'px, 0px)');
}

function select_row(i) {
    unmagnify_row(current_row, 400, 0)
    current_row = i;
    move_focus(current_row, 400, 400);
    move_cursor_button(current_row);
    magnify_row(current_row, 400, 800);
}

var mouse_tracking;
function on_panel_mousedown(e) {
    mouse_tracking = { target: this };
    toggle_panel_color(this);
}

function on_panel_mousemove(e) {
    if (mouse_tracking && mouse_tracking.target !== this) {
        toggle_panel_color(this);
        mouse_tracking.target = this;
    }
}

function on_panel_mouseup(e) {
    mouse_tracking = null;
}

var touch_tracking = [];
function on_panel_touchstart(e) {
    touch_tracking[e.identifier] = { target: this };
    toggle_panel_color(this);
}

function on_panel_touchmove(e) {
    if (touch_tracking[e.identifier] && touch_tracking[e.identifier].target !== this) {
        toggle_panel_color(this);
        touch_tracking[e.identifier].target = this;
    }
}

function on_panel_touchend(e) {
    touch_tracking[e.identifier] = null;
}

$(function() {
    // create the board
    for (var i = 1; i <= 16; ++i) {
        var html = '
';
        for (var j = 1; j <= 16; ++j) {
            html += '

?

';
        }
        html += '
'
        $(html).appendTo('#board');
        $('').appendTo('#board');
    }

    // masks
    initialize_mask_transformations();

    // initial focus
    current_row = 1;
    move_focus(current_row, 400, 0);
    move_cursor_button(current_row, 400, 400);
    magnify_row(current_row, 400, 400);

    // events
    if (is_touch_device()) {
        // disable the overscroll effect on touch-capable environments.
        // http://www.html5rocks.com/en/mobile/touch/
        // http://stackoverflow.com/questions/2890361/disable-scrolling-in-an-iphone-web-application
        $(document).on('touchmove', function(e) {
            e.preventDefault();
        });

        $('#prev-button').on('touchstart', function() {
            if (current_row > 1) {
                select_row(current_row - 1);
            }
        });
        $('#next-button').on('touchstart', function() {
            if (current_row < 16) {
                select_row(current_row + 1);
            }
        });

        $(".panel").each(function() {
            $(this).on('touchstart', on_panel_touchstart);
            $(this).on('touchmove', on_panel_touchmove);
            $(this).on('touchend', on_panel_touchend);
        });
    }
    else {
        $('#prev-button').on('click', function() {
            if (current_row > 1) {
                select_row(current_row - 1);
            }
        });
        $('#next-button').on('click', function() {
            if (current_row < 16) {
                select_row(current_row + 1);
            }
        });

        $(".panel").each(function() {
            $(this).on('mousedown', on_panel_mousedown);
            $(this).on('mousemove', on_panel_mousemove);
            $(this).on('mouseup', on_panel_mouseup);
        });
    }
});
<commit_msg>Define functions in a function and use the strict mode.<commit_after>$(function() {
    "use strict";

    var current_row = 0;
    var mouse_tracking;
    var touch_tracking = [];

    // http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
    function is_touch_device() {
        return !!('ontouchstart' in window) // works on most browsers
            || !!('onmsgesturechange' in window); // works on ie10
    }

    function get_board_dimension() {
        var top_row = $('#row1');
        var bottom_row = $('#row16');

        var top = top_row.position().top;
        var bottom = bottom_row.position().top + bottom_row.outerHeight();
        var left = top_row.position().left;
        var right = top_row.position().left + top_row.outerWidth();
        var width = right - left;
        var height = bottom - top;
        return { 'top': top, 'bottom': bottom, 'left': left, 'right': right, 'width': width, 'height': height };
    }

    function toggle_panel_color(panel) {
        if ($(panel).hasClass("black") || $(panel).hasClass("white")) {
            $(panel).toggleClass("black white");
        }
        else {
            $(panel).addClass("white invisible-text");
        }
    }

    function unmagnify_row(i, duration, delay) {
        duration /= 1000;
        delay /= 1000;
        var row = $('#row' + i);
        row.css({
            'transition-duration': '0.001s, ' + duration + 's, ' + duration + 's, ' + duration + 's, ' + duration + 's',
            'transition-delay': delay + 's'
        });
        row.removeClass('magnified');
    }

    function magnify_row(i, duration, delay) {
        duration /= 1000;
        delay /= 1000;
        var row = $('#row' + i);
        row.css({
            'transition-duration': '0.001s, ' + duration + 's, ' + duration + 's, ' + duration + 's, ' + duration + 's',
            'transition-delay': delay + 's'
        });
        row.addClass('magnified');
    }

    function move_focus(i, duration, delay) {
        duration /= 1000;
        delay /= 1000;

        var board = get_board_dimension();
        var row = $('#row' + i);

        var unmasked_top = row.position().top;
        var unmasked_bottom = unmasked_top + row.outerHeight();

        var transition_props = {
            'transition-duration': duration + 's, ' + duration + 's, ' + duration + 's, ' + duration + 's',
            'transition-delay': delay + 's',
        };
        var upper_mask = $('#upper-mask');
        var lower_mask = $('#lower-mask');
        upper_mask.css(transition_props);
        lower_mask.css(transition_props);
        upper_mask.css('transform', 'translate3d(0px, ' + ((unmasked_top - board.top) - upper_mask.outerHeight()) + 'px, 0px)');
        lower_mask.css('transform', 'translate3d(0px, ' + (unmasked_bottom - board.top) + 'px, 0px)');
    }

    function move_cursor_button(i) {
        var board = get_board_dimension();
        var row = $('#row' + i);

        var unmasked_top = row.position().top;
        var unmasked_bottom = unmasked_top + row.outerHeight();

        var prev_button = $('#prev-button');
        var next_button = $('#next-button');
        prev_button.css({
            'left': (board.left - prev_button.outerWidth()) + 'px',
            'top': (unmasked_top - prev_button.outerHeight() - 10) + 'px',
            'visibility': i === 1 ? 'hidden' : 'visible',
        });
        next_button.css({
            'left': (board.left - next_button.outerWidth()) + 'px',
            'top': (unmasked_bottom + 10) + 'px',
            'visibility': i === 16 ? 'hidden' : 'visible',
        });
    }

    function initialize_mask_transformations() {
        var board = get_board_dimension();

        $('#upper-mask').css('transform', 'translate3d(0px, ' + (-board.height) + 'px, 0px)');
        $('#lower-mask').css('transform', 'translate3d(0px, ' + board.height + 'px, 0px)');
    }

    function select_row(i) {
        unmagnify_row(current_row, 400, 0)
        current_row = i;
        move_focus(current_row, 400, 400);
        move_cursor_button(current_row);
        magnify_row(current_row, 400, 800);
    }

    function on_panel_mousedown(e) {
        mouse_tracking = { target: this };
        toggle_panel_color(this);
    }

    function on_panel_mousemove(e) {
        if (mouse_tracking && mouse_tracking.target !== this) {
            toggle_panel_color(this);
            mouse_tracking.target = this;
        }
    }

    function on_panel_mouseup(e) {
        mouse_tracking = null;
    }

    function on_panel_touchstart(e) {
        touch_tracking[e.identifier] = { target: this };
        toggle_panel_color(this);
    }

    function on_panel_touchmove(e) {
        if (touch_tracking[e.identifier] && touch_tracking[e.identifier].target !== this) {
            toggle_panel_color(this);
            touch_tracking[e.identifier].target = this;
        }
    }

    function on_panel_touchend(e) {
        touch_tracking[e.identifier] = null;
    }

    // create the board
    for (var i = 1; i <= 16; ++i) {
        var html = '
';
        for (var j = 1; j <= 16; ++j) {
            html += '

?

';
        }
        html += '
'
        $(html).appendTo('#board');
        $('').appendTo('#board');
    }

    // masks
    initialize_mask_transformations();

    // initial focus
    current_row = 1;
    move_focus(current_row, 400, 0);
    move_cursor_button(current_row, 400, 400);
    magnify_row(current_row, 400, 400);

    // events
    if (is_touch_device()) {
        // disable the overscroll effect on touch-capable environments.
        // http://www.html5rocks.com/en/mobile/touch/
        // http://stackoverflow.com/questions/2890361/disable-scrolling-in-an-iphone-web-application
        $(document).on('touchmove', function(e) {
            e.preventDefault();
        });

        $('#prev-button').on('touchstart', function() {
            if (current_row > 1) {
                select_row(current_row - 1);
            }
        });
        $('#next-button').on('touchstart', function() {
            if (current_row < 16) {
                select_row(current_row + 1);
            }
        });

        $(".panel").each(function() {
            $(this).on('touchstart', on_panel_touchstart);
            $(this).on('touchmove', on_panel_touchmove);
            $(this).on('touchend', on_panel_touchend);
        });
    }
    else {
        $('#prev-button').on('click', function() {
            if (current_row > 1) {
                select_row(current_row - 1);
            }
        });
        $('#next-button').on('click', function() {
            if (current_row < 16) {
                select_row(current_row + 1);
            }
        });

        $(".panel").each(function() {
            $(this).on('mousedown', on_panel_mousedown);
            $(this).on('mousemove', on_panel_mousemove);
            $(this).on('mouseup', on_panel_mouseup);
        });
    }
});
<|endoftext|>