<commit_before>jsio('from shared.javascript import Singleton, bind, forEach')

jsio('import shared.Pool')
jsio('import shared.keys')

jsio('import client.Client')
jsio('import client.TemplateFactory')
jsio('import client.ViewFactory')

// expose fin to global namespace
fin = Singleton(function(){

/**********************************
 * The core API: connect, create, *
 * observe, set, query & release  *
 **********************************/
	/*
	 * Connect to the fin database. The callback will be called
	 * once you're connected with the server
	 */
	this.connect = function(callback) {
		this._client.connect(callback)
	}

	/*
	 * Create an item with the given data as properties,
	 * and get notified of the new item id when it's been created
	 */
	this.create = function(data, callback) {
		if (typeof callback != 'function') { throw logger.error('Second argument to fin.create should be a callback') }
		this.requestResponse('FIN_REQUEST_CREATE_ITEM', { data: data }, callback)
	}

	/*
	 * Observe an item property, and get notified any time it changes
	 */
	this.observe = function(itemId, propName, callback) {
		if (!itemId || !propName || !callback) { logger.error("observe requires three arguments", itemId, propName, callback); }

		return this._observe({ id: itemId, property: propName }, callback)
	}

	/*
	 * Mutate a fin item with the given operation
	 */
	this.set = function(itemID, propName, value) {
		this._mutate({
			id: itemID,
			prop: propName,
			op: 'set',
			args: [JSON.stringify(value)]
		})
	}

	/*
	 * Query fin for items matching a set of properties, and get notified
	 * any time an item enters or leaves the matching set
	 */
	this.query = function(query, callback) {
		if (!query || !callback) { logger.error("query requires two arguments", query, callback) }

		var queryJSON = JSON.stringify(query),
			key = shared.keys.getQueryKey(queryJSON),
			subId = this._observe({ key: key, type: 'SET' }, callback)

		if (this._subscriptionPool.count(key) == 1) {
			this.send('FIN_REQUEST_MONITOR_QUERY', queryJSON)
		}

		return subId
	}

	/*
	 * Release an observation or query
	 */
	this.release = function(subId) {
		if (typeof subId == 'string') {
			var channel = this._subIdToChannel[subId],
				key = this._subscriptionPool.remove(channel, subId),
				keyInfo = shared.keys.getKeyInfo(key),
				itemID = keyInfo.id

			if (this._subscriptionPool.count(channel) == 0) {
				if (itemID != this._localID) {
					this.send('FIN_REQUEST_UNSUBSCRIBE', channel)
				}
				delete this._listLength[channel]
			}

			delete this._subIdToChannel[subId]
		} else { // it's a fin template element
			this._templateFactory.releaseTemplate(subId)
		}
	}

	/*
	 * Get the last cached mutation of a currently observed item property
	 */
	this.getCachedMutation = function(itemID, propName) {
		if (itemID == 'LOCAL') { itemID = this._localID }
		else if (itemID == 'GLOBAL') { itemID = this._globalID }

		var key = shared.keys.getItemPropertyKey(itemID, propName)
		return this._mutationCache[key]
	}

/***********
 * Set API *
 ***********/
	this.observeSet = function(itemId, propName, callback) {
		this._observe({ id: itemId, property: propName, type: 'SET' }, callback)
	}

	this.addToSet = function(itemId, propName, member) {
		this._mutate({ id: itemId, op: 'sadd', prop: propName, args: [JSON.stringify(member)] })
	}

	this.removeFromSet = function(itemId, propName, member) {
		this._mutate({ id: itemId, op: 'srem', prop: propName, args: [JSON.stringify(member)] })
	}

/************
 * List API *
 ************/
	/*
	 * Observe an item property list, and get notified any time it changes
	 */
	this.observeList = function(itemId, propName, callback, length) {
		if (!itemId || !propName || !callback) { logger.error("observe requires at least three arguments", itemId, propName, callback, length); }

		var subId = this._observe({ id: itemId, property: propName, snapshot: false }, callback)
		this.extendList(itemId, propName, length || 10)
		return subId
	}

	/*
	 * Extend the history of an observed list
	 */
	this._listLength = {}
	this.extendList = function(itemId, propName, maxLen) {
		if (!itemId || !propName || !maxLen) { logger.error("extendList requires three arguments", itemId, propName, maxLen); }

		var listKey = shared.keys.getItemPropertyKey(itemId, propName),
			listLength = this._listLength[listKey] || 0

		if (maxLen <= listLength) { return }
		this._listLength[listKey] = maxLen

		var args = { key: listKey, from: listLength, to: maxLen }
		this.requestResponse('FIN_REQUEST_EXTEND_LIST', args, bind(this, function(items) {
			var mutation = { id: listKey, op: 'listAppend', args: items, index: listLength }
			this._handleMutation(mutation)
		}))
	}

	this.appendToList = function(itemId, propName /*, val1, val2, ... */) {
		var values = Array.prototype.slice.call(arguments, 2)
		this._listOp(itemId, propName, 'listAppend', values)
	}

	this.prependToList = function(itemId, propName /*, val1, val2, ... */) {
		var values = Array.prototype.slice.call(arguments, 2)
		this._listOp(itemId, propName, 'listPrepend', values)
	}

	this._listOp = function(itemId, propName, op, values) {
		for (var i=0; i < values.length; i++) {
			values[i] = JSON.stringify(values[i])
		}
		this._mutate({ id: itemId, op: op, prop: propName, args: values })
	}

/********************
 * Miscelaneous API *
 ********************/
	/*
	 * Make a request with an associated requestId,
	 * and call the callback upon response
	 */
	this.requestResponse = function(frameName, args, callback) {
		var requestId = this._scheduleCallback(callback)
		args._requestId = requestId
		this.send(frameName, args)
	}

	/*
	 * Send a frame to the server
	 */
	this.send = function(frameName, args) {
		this._client.sendFrame(frameName, args)
	}

	/*
	 * Register a handler for a type of event from the server
	 */
	this.registerEventHandler = function(frameName, callback) {
		this._client.registerEventHandler(frameName, callback)
	}

	/*
	 * Focus an item property for editing. Any other focused client gets blurred.
	 * When another client requests focus, onBlurCallback gets called
	 */
	this.focus = function(itemId, propName, onBlurCallback) {
		var sessionId = this._client.getSessionID(),
			focusProp = shared.keys.getFocusProperty(propName),
			sendFocusInfo = { session: sessionId, user: gUserId, time: fin.now() },
			subId, observation, releaseFn

		this.set(itemId, focusProp, JSON.stringify(sendFocusInfo))

		observation = { id: itemId, property: focusProp, useCache: false, snapshot: false }
		subId = this._observe(observation, bind(this, function(mutation, focusJSON) {
			if (!subId) { return }

			var focusInfo
			try { focusInfo = JSON.parse(focusJSON) }
			catch(e) { } // There are old focus keys sitting around with non JSON values

			if (!focusInfo || focusInfo.session == sessionId) { return }

			releaseFn()
			onBlurCallback(focusInfo)
		}))

		releaseFn = bind(this, function () {
			if (!subId) { return }
			this.release(subId)
			subId = null
			this.set(itemId, focusProp, null)
		})

		return releaseFn
	}

	/*
	 * Get approximately the current server time
	 */
	// TODO The timestamp should be offset by a time given by the server
	this.now = function() { return new Date().getTime() }

	/*
	 * Generate a unique ID
	 */
	var _unique = 1
	this.unique = function() { return 'fan_u' + _unique++ }

	/*
	 * Apply a template to a fin item (or multiple items)
	 */
	this.applyTemplate = function(templateString, itemIds) {
		return this._templateFactory.applyTemplate(templateString, itemIds)
	}

	/*
	 * Create a view directly, and get a reference to the javascript object. Make sure you release it correctly!
	 */
	this.createView = function(viewName) {
		var args = Array.prototype.slice.call(arguments, 1)
		return this._viewFactory.createView(viewName, args)
	}

	/*
	 * Register a template view
	 */
	this.registerView = function(viewName, viewCtor) {
		this._viewFactory.registerView(viewName, viewCtor)
	}

/*******************
 * Private methods *
 *******************/
	this.init = function() {
		this._requestCallbacks = {}

		this._viewFactory = new client.ViewFactory()
		this._templateFactory = new client.TemplateFactory(this._viewFactory)

		this._client = new client.Client()

		this._client.registerEventHandler('FIN_RESPONSE', bind(this, function(response) {
			logger.info('FIN_RESPONSE', response._requestId, response.data)
			this._executeCallback(response._requestId, response.data)
		}))

		this._client.registerEventHandler('FIN_EVENT_MUTATION', bind(this, function(mutationJSON) {
			var mutation = JSON.parse(mutationJSON)
			logger.info('FIN_EVENT_MUTATION', mutation)
			this._handleMutation(mutation)
		}))
	}

	this._subIdToChannel = {}
	this._subscriptionPool = new shared.Pool()
	this._observe = function(params, callback) {
		var itemID
		if (params.id == 'LOCAL') {
			itemID = this._localID
		} else {
			itemID = (params.id == 'GLOBAL' ? this._globalID : params.id)
		}

		var pool = this._subscriptionPool,
			key = params.key || shared.keys.getItemPropertyKey(itemID, params.property),
			subId = pool.add(key, callback),
			cachedMutation = this._mutationCache[key]

		if (itemID != this._localID && pool.count(key) == 1) {
			var netParams = { key: key, type: (params.type || 'BYTES') }
			if (typeof params.snapshot != 'undefined') {
				netParams.snapshot = params.snapshot
			}
			this.send('FIN_REQUEST_OBSERVE', netParams)
		} else if (cachedMutation && params.useCache !== false) {
			this._handleMutation(cachedMutation, callback)
		}

		this._subIdToChannel[subId] = key
		return subId
	}

	this._localID = '__fin_local'
	this._globalID = 0
	this._mutate = function(params) {
		var mutation = {
			op: params.op,
			args: params.args,
			prop: params.prop
		}

		if (params.id == 'LOCAL') {
			mutation.id = shared.keys.getItemPropertyKey(this._localID, params.prop)
		} else {
			var id = (params.id == 'GLOBAL' ? this._globalID : params.id)
			mutation.id = shared.keys.getItemPropertyKey(id, params.prop)
			this.send('FIN_REQUEST_MUTATE', mutation)
		}

		this._handleMutation(mutation)
	}

	this._deserializeMutation = function(mutation) {
		var args = mutation.args,
			operation = mutation.op
		switch(operation) {
			case 'set':
				mutation.value = args[0] = JSON.parse(args[0])
				break
			case 'listAppend':
			case 'listPrepend':
			case 'sadd':
			case 'srem':
				for (var i=0; i < args.length; i++) { args[i] = JSON.parse(args[i]) }
				break
			default: throw logger.error("Unknown operation for deserialization " + operation)
		}
	}

	this._mutationCache = {}
	this._handleMutation = function(mutation, singleCallback) {
		if (singleCallback) {
			var args = [mutation.op].concat(mutation.args)
			setTimeout(function() { singleCallback(mutation, mutation.value) })
		} else {
			var key = mutation.id,
				subs = this._subscriptionPool.get(key)

			this._deserializeMutation(mutation)
			this._cacheMutation(mutation, key)

			for (var subId in subs) {
				subs[subId](mutation, mutation.value)
			}
		}
	}

	this._cacheMutation = function(mutation, key) {
		var mutationCache = this._mutationCache,
			cachedMutation = mutationCache[key],
			cachedArgs = cachedMutation && cachedMutation.args

		if (!cachedMutation) {
			return mutationCache[key] = mutation
		}

		switch(mutation.op) {
			case 'set':
				mutationCache[key] = mutation
				break
			case 'listAppend':
				cachedArgs = cachedArgs.concat(mutation.args)
				break
			case 'listPrepend':
				cachedArgs = mutation.args.concat(cachedArgs)
				break
			case 'sadd':
				for (var i=0, itemId; itemId = mutation.args[i]; i++) {
					if (cachedArgs.indexOf(itemId) == -1) { cachedArgs.push(itemId) }
				}
				break
			case 'srem':
				for (var i=0, itemId; itemId = mutation.args[i]; i++) {
					cachedArgs.splice(cachedArgs.indexOf(itemId), 1)
				}
				break
			default:
				throw logger.error("Unknown operation for caching "+ operation)
		}
		return mutationCache[key]
	}

	this._uniqueRequestId = 0
	this._scheduleCallback = function(callback) {
		var requestId = 'r' + this._uniqueRequestId++
		this._requestCallbacks[requestId] = callback
		return requestId
	}

	this._executeCallback = function(requestId, response) {
		var callback = this._requestCallbacks[requestId]
		delete this._requestCallbacks[requestId]
		callback(response)
	}
})
<commit_msg>Rename appendToList and prependToList to append and prepend<commit_after>jsio('from shared.javascript import Singleton, bind, forEach')

jsio('import shared.Pool')
jsio('import shared.keys')

jsio('import client.Client')
jsio('import client.TemplateFactory')
jsio('import client.ViewFactory')

// expose fin to global namespace
fin = Singleton(function(){

/**********************************
 * The core API: connect, create, *
 * observe, set, query & release  *
 **********************************/
	/*
	 * Connect to the fin database. The callback will be called
	 * once you're connected with the server
	 */
	this.connect = function(callback) {
		this._client.connect(callback)
	}

	/*
	 * Create an item with the given data as properties,
	 * and get notified of the new item id when it's been created
	 */
	this.create = function(data, callback) {
		if (typeof callback != 'function') { throw logger.error('Second argument to fin.create should be a callback') }
		this.requestResponse('FIN_REQUEST_CREATE_ITEM', { data: data }, callback)
	}

	/*
	 * Observe an item property, and get notified any time it changes
	 */
	this.observe = function(itemId, propName, callback) {
		if (!itemId || !propName || !callback) { logger.error("observe requires three arguments", itemId, propName, callback); }

		return this._observe({ id: itemId, property: propName }, callback)
	}

	/*
	 * Mutate a fin item with the given operation
	 */
	this.set = function(itemID, propName, value) {
		this._mutate({
			id: itemID,
			prop: propName,
			op: 'set',
			args: [JSON.stringify(value)]
		})
	}

	/*
	 * Query fin for items matching a set of properties, and get notified
	 * any time an item enters or leaves the matching set
	 */
	this.query = function(query, callback) {
		if (!query || !callback) { logger.error("query requires two arguments", query, callback) }

		var queryJSON = JSON.stringify(query),
			key = shared.keys.getQueryKey(queryJSON),
			subId = this._observe({ key: key, type: 'SET' }, callback)

		if (this._subscriptionPool.count(key) == 1) {
			this.send('FIN_REQUEST_MONITOR_QUERY', queryJSON)
		}

		return subId
	}

	/*
	 * Release an observation or query
	 */
	this.release = function(subId) {
		if (typeof subId == 'string') {
			var channel = this._subIdToChannel[subId],
				key = this._subscriptionPool.remove(channel, subId),
				keyInfo = shared.keys.getKeyInfo(key),
				itemID = keyInfo.id

			if (this._subscriptionPool.count(channel) == 0) {
				if (itemID != this._localID) {
					this.send('FIN_REQUEST_UNSUBSCRIBE', channel)
				}
				delete this._listLength[channel]
			}

			delete this._subIdToChannel[subId]
		} else { // it's a fin template element
			this._templateFactory.releaseTemplate(subId)
		}
	}

	/*
	 * Get the last cached mutation of a currently observed item property
	 */
	this.getCachedMutation = function(itemID, propName) {
		if (itemID == 'LOCAL') { itemID = this._localID }
		else if (itemID == 'GLOBAL') { itemID = this._globalID }

		var key = shared.keys.getItemPropertyKey(itemID, propName)
		return this._mutationCache[key]
	}

/***********
 * Set API *
 ***********/
	this.observeSet = function(itemId, propName, callback) {
		this._observe({ id: itemId, property: propName, type: 'SET' }, callback)
	}

	this.addToSet = function(itemId, propName, member) {
		this._mutate({ id: itemId, op: 'sadd', prop: propName, args: [JSON.stringify(member)] })
	}

	this.removeFromSet = function(itemId, propName, member) {
		this._mutate({ id: itemId, op: 'srem', prop: propName, args: [JSON.stringify(member)] })
	}

/************
 * List API *
 ************/
	/*
	 * Observe an item property list, and get notified any time it changes
	 */
	this.observeList = function(itemId, propName, callback, length) {
		if (!itemId || !propName || !callback) { logger.error("observe requires at least three arguments", itemId, propName, callback, length); }

		var subId = this._observe({ id: itemId, property: propName, snapshot: false }, callback)
		this.extendList(itemId, propName, length || 10)
		return subId
	}

	/*
	 * Extend the history of an observed list
	 */
	this._listLength = {}
	this.extendList = function(itemId, propName, maxLen) {
		if (!itemId || !propName || !maxLen) { logger.error("extendList requires three arguments", itemId, propName, maxLen); }

		var listKey = shared.keys.getItemPropertyKey(itemId, propName),
			listLength = this._listLength[listKey] || 0

		if (maxLen <= listLength) { return }
		this._listLength[listKey] = maxLen

		var args = { key: listKey, from: listLength, to: maxLen }
		this.requestResponse('FIN_REQUEST_EXTEND_LIST', args, bind(this, function(items) {
			var mutation = { id: listKey, op: 'listAppend', args: items, index: listLength }
			this._handleMutation(mutation)
		}))
	}

	this.append = function(itemId, propName /*, val1, val2, ... */) {
		var values = Array.prototype.slice.call(arguments, 2)
		this._listOp(itemId, propName, 'listAppend', values)
	}

	this.prepend = function(itemId, propName /*, val1, val2, ... */) {
		var values = Array.prototype.slice.call(arguments, 2)
		this._listOp(itemId, propName, 'listPrepend', values)
	}

	this._listOp = function(itemId, propName, op, values) {
		for (var i=0; i < values.length; i++) {
			values[i] = JSON.stringify(values[i])
		}
		this._mutate({ id: itemId, op: op, prop: propName, args: values })
	}

/********************
 * Miscelaneous API *
 ********************/
	/*
	 * Make a request with an associated requestId,
	 * and call the callback upon response
	 */
	this.requestResponse = function(frameName, args, callback) {
		var requestId = this._scheduleCallback(callback)
		args._requestId = requestId
		this.send(frameName, args)
	}

	/*
	 * Send a frame to the server
	 */
	this.send = function(frameName, args) {
		this._client.sendFrame(frameName, args)
	}

	/*
	 * Register a handler for a type of event from the server
	 */
	this.registerEventHandler = function(frameName, callback) {
		this._client.registerEventHandler(frameName, callback)
	}

	/*
	 * Focus an item property for editing. Any other focused client gets blurred.
	 * When another client requests focus, onBlurCallback gets called
	 */
	this.focus = function(itemId, propName, onBlurCallback) {
		var sessionId = this._client.getSessionID(),
			focusProp = shared.keys.getFocusProperty(propName),
			sendFocusInfo = { session: sessionId, user: gUserId, time: fin.now() },
			subId, observation, releaseFn

		this.set(itemId, focusProp, JSON.stringify(sendFocusInfo))

		observation = { id: itemId, property: focusProp, useCache: false, snapshot: false }
		subId = this._observe(observation, bind(this, function(mutation, focusJSON) {
			if (!subId) { return }

			var focusInfo
			try { focusInfo = JSON.parse(focusJSON) }
			catch(e) { } // There are old focus keys sitting around with non JSON values

			if (!focusInfo || focusInfo.session == sessionId) { return }

			releaseFn()
			onBlurCallback(focusInfo)
		}))

		releaseFn = bind(this, function () {
			if (!subId) { return }
			this.release(subId)
			subId = null
			this.set(itemId, focusProp, null)
		})

		return releaseFn
	}

	/*
	 * Get approximately the current server time
	 */
	// TODO The timestamp should be offset by a time given by the server
	this.now = function() { return new Date().getTime() }

	/*
	 * Generate a unique ID
	 */
	var _unique = 1
	this.unique = function() { return 'fan_u' + _unique++ }

	/*
	 * Apply a template to a fin item (or multiple items)
	 */
	this.applyTemplate = function(templateString, itemIds) {
		return this._templateFactory.applyTemplate(templateString, itemIds)
	}

	/*
	 * Create a view directly, and get a reference to the javascript object. Make sure you release it correctly!
	 */
	this.createView = function(viewName) {
		var args = Array.prototype.slice.call(arguments, 1)
		return this._viewFactory.createView(viewName, args)
	}

	/*
	 * Register a template view
	 */
	this.registerView = function(viewName, viewCtor) {
		this._viewFactory.registerView(viewName, viewCtor)
	}

/*******************
 * Private methods *
 *******************/
	this.init = function() {
		this._requestCallbacks = {}

		this._viewFactory = new client.ViewFactory()
		this._templateFactory = new client.TemplateFactory(this._viewFactory)

		this._client = new client.Client()

		this._client.registerEventHandler('FIN_RESPONSE', bind(this, function(response) {
			logger.info('FIN_RESPONSE', response._requestId, response.data)
			this._executeCallback(response._requestId, response.data)
		}))

		this._client.registerEventHandler('FIN_EVENT_MUTATION', bind(this, function(mutationJSON) {
			var mutation = JSON.parse(mutationJSON)
			logger.info('FIN_EVENT_MUTATION', mutation)
			this._handleMutation(mutation)
		}))
	}

	this._subIdToChannel = {}
	this._subscriptionPool = new shared.Pool()
	this._observe = function(params, callback) {
		var itemID
		if (params.id == 'LOCAL') {
			itemID = this._localID
		} else {
			itemID = (params.id == 'GLOBAL' ? this._globalID : params.id)
		}

		var pool = this._subscriptionPool,
			key = params.key || shared.keys.getItemPropertyKey(itemID, params.property),
			subId = pool.add(key, callback),
			cachedMutation = this._mutationCache[key]

		if (itemID != this._localID && pool.count(key) == 1) {
			var netParams = { key: key, type: (params.type || 'BYTES') }
			if (typeof params.snapshot != 'undefined') {
				netParams.snapshot = params.snapshot
			}
			this.send('FIN_REQUEST_OBSERVE', netParams)
		} else if (cachedMutation && params.useCache !== false) {
			this._handleMutation(cachedMutation, callback)
		}

		this._subIdToChannel[subId] = key
		return subId
	}

	this._localID = '__fin_local'
	this._globalID = 0
	this._mutate = function(params) {
		var mutation = {
			op: params.op,
			args: params.args,
			prop: params.prop
		}

		if (params.id == 'LOCAL') {
			mutation.id = shared.keys.getItemPropertyKey(this._localID, params.prop)
		} else {
			var id = (params.id == 'GLOBAL' ? this._globalID : params.id)
			mutation.id = shared.keys.getItemPropertyKey(id, params.prop)
			this.send('FIN_REQUEST_MUTATE', mutation)
		}

		this._handleMutation(mutation)
	}

	this._deserializeMutation = function(mutation) {
		var args = mutation.args,
			operation = mutation.op
		switch(operation) {
			case 'set':
				mutation.value = args[0] = JSON.parse(args[0])
				break
			case 'listAppend':
			case 'listPrepend':
			case 'sadd':
			case 'srem':
				for (var i=0; i < args.length; i++) { args[i] = JSON.parse(args[i]) }
				break
			default: throw logger.error("Unknown operation for deserialization " + operation)
		}
	}

	this._mutationCache = {}
	this._handleMutation = function(mutation, singleCallback) {
		if (singleCallback) {
			var args = [mutation.op].concat(mutation.args)
			setTimeout(function() { singleCallback(mutation, mutation.value) })
		} else {
			var key = mutation.id,
				subs = this._subscriptionPool.get(key)

			this._deserializeMutation(mutation)
			this._cacheMutation(mutation, key)

			for (var subId in subs) {
				subs[subId](mutation, mutation.value)
			}
		}
	}

	this._cacheMutation = function(mutation, key) {
		var mutationCache = this._mutationCache,
			cachedMutation = mutationCache[key],
			cachedArgs = cachedMutation && cachedMutation.args

		if (!cachedMutation) {
			return mutationCache[key] = mutation
		}

		switch(mutation.op) {
			case 'set':
				mutationCache[key] = mutation
				break
			case 'listAppend':
				cachedArgs = cachedArgs.concat(mutation.args)
				break
			case 'listPrepend':
				cachedArgs = mutation.args.concat(cachedArgs)
				break
			case 'sadd':
				for (var i=0, itemId; itemId = mutation.args[i]; i++) {
					if (cachedArgs.indexOf(itemId) == -1) { cachedArgs.push(itemId) }
				}
				break
			case 'srem':
				for (var i=0, itemId; itemId = mutation.args[i]; i++) {
					cachedArgs.splice(cachedArgs.indexOf(itemId), 1)
				}
				break
			default:
				throw logger.error("Unknown operation for caching "+ operation)
		}
		return mutationCache[key]
	}

	this._uniqueRequestId = 0
	this._scheduleCallback = function(callback) {
		var requestId = 'r' + this._uniqueRequestId++
		this._requestCallbacks[requestId] = callback
		return requestId
	}

	this._executeCallback = function(requestId, response) {
		var callback = this._requestCallbacks[requestId]
		delete this._requestCallbacks[requestId]
		callback(response)
	}
})
<|endoftext|>


<commit_before>'use strict';

function draw_logic(){
    var row = Math.floor(canvas_height / 80);
    do{
        var row_y = canvas_height - row * 80 - 50;

        canvas_buffer.fillStyle = color_left;
        var column = Math.floor(canvas_width / 200);
        do{
            var column_x =
              column * 200
              + (row % 2 === 0 ? 100 : 0);

            canvas_draw_path(
              [
                {
                  'type': 'moveTo',
                  'x': column_x,
                  'y': row_y,
                },
                {
                  'x': column_x + 100,
                  'y': row_y - 30,
                },
                {
                  'x': column_x + 100,
                  'y': row_y + 20,
                },
                {
                  'x': column_x,
                  'y': row_y + 50,
                },
              ],
              {}
            );
        }while(column--);

        canvas_buffer.fillStyle = color_right;
        column = Math.floor(canvas_width / 200);
        do{
            column_x =
              column * 200
              - (row % 2 === 0 ? 100 : 0);

            canvas_draw_path(
              [
                {
                  'type': 'moveTo',
                  'x': column_x + 100,
                  'y': row_y - 30,
                },
                {
                  'x': column_x + 200,
                  'y': row_y,
                },
                {
                  'x': column_x + 200,
                  'y': row_y + 50,
                },
                {
                  'x': column_x + 100,
                  'y': row_y + 20,
                },
              ],
              {}
            );
        }while(column--);
    }while(row--);
}

function update_colors(){
    color_left = random_hex();
    color_right = random_hex();
    document.getElementById('canvas').style.background = random_hex();
}

var color_left = '';
var color_right = '';

window.onload = function(e){
    canvas_init();
    input_init({
      'keybinds': {
        'all': {
          'todo': update_colors,
        },
      },
      'mousebinds': {
        'mousedown': {
          'todo': update_colors,
        },
      },
    });

    update_colors();
};
<commit_msg>switched canvas.js to args<commit_after>'use strict';

function draw_logic(){
    var row = Math.floor(canvas_height / 80);
    do{
        var row_y = canvas_height - row * 80 - 50;

        canvas_buffer.fillStyle = color_left;
        var column = Math.floor(canvas_width / 200);
        do{
            var column_x =
              column * 200
              + (row % 2 === 0 ? 100 : 0);

            canvas_draw_path({
              'vertices': [
                {
                  'type': 'moveTo',
                  'x': column_x,
                  'y': row_y,
                },
                {
                  'x': column_x + 100,
                  'y': row_y - 30,
                },
                {
                  'x': column_x + 100,
                  'y': row_y + 20,
                },
                {
                  'x': column_x,
                  'y': row_y + 50,
                },
              ],
            });
        }while(column--);

        canvas_buffer.fillStyle = color_right;
        column = Math.floor(canvas_width / 200);
        do{
            column_x =
              column * 200
              - (row % 2 === 0 ? 100 : 0);

            canvas_draw_path({
              'vertices': [
                {
                  'type': 'moveTo',
                  'x': column_x + 100,
                  'y': row_y - 30,
                },
                {
                  'x': column_x + 200,
                  'y': row_y,
                },
                {
                  'x': column_x + 200,
                  'y': row_y + 50,
                },
                {
                  'x': column_x + 100,
                  'y': row_y + 20,
                },
              ],
            });
        }while(column--);
    }while(row--);
}

function update_colors(){
    color_left = random_hex();
    color_right = random_hex();
    document.getElementById('canvas').style.background = random_hex();
}

var color_left = '';
var color_right = '';

window.onload = function(e){
    canvas_init();
    input_init({
      'keybinds': {
        'all': {
          'todo': update_colors,
        },
      },
      'mousebinds': {
        'mousedown': {
          'todo': update_colors,
        },
      },
    });

    update_colors();
};
<|endoftext|>


<commit_before>$(function() {

    $("#contactFrom input,#contactForm textarea").jqBootstrapValidation({
        preventSubmit: true,
        submitError: function($form, event, errors) {
            // additional error messages or events
        },
        submitSuccess: function($form, event) {
            // Prevent spam click and default submit behaviour
            $("#btnSubmit").attr("disabled", true);
            event.preventDefault();

            // get values from FORM
            var name = $("input#name").val();
            var email = $("input#email").val();
            var phone = $("input#phone").val();
            var message = $("textarea#message").val();
            var firstName = name; // For Success/Failure Message
            // Check for white space in name for Success/Fail message
            if (firstName.indexOf(' ') >= 0) {
                firstName = name.split(' ').slice(0, -1).join(' ');
            }
            $.ajax({
                url: "././mail/contact_me.php",
                type: "POST",
                data: {
                    name: name,
                    phone: phone,
                    email: email,
                    message: message
                },
                cache: false,
                success: function() {
                    // Enable button & show success message
                    $("#btnSubmit").attr("disabled", false);
                    $('#success').html("
");
                    $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-success')
                        .append("Your message has been sent. ");
                    $('#success > .alert-success')
                        .append('
');

                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
                error: function() {
                    // Fail message
                    $('#success').html("
");
                    $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!");
                    $('#success > .alert-danger').append('
');
                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
            })
        },
        filter: function() {
            return $(this).is(":visible");
        },
    });

    $("a[data-toggle=\"tab\"]").click(function(e) {
        e.preventDefault();
        $(this).tab("show");
    });
});

// When clicking on Full hide fail/success boxes
$('#name').focus(function() {
    $('#success').html('');
});
<commit_msg>Fixed a typo causing a validation bug<commit_after>$(function() {

    $("#contactForm input,#contactForm textarea").jqBootstrapValidation({
        preventSubmit: true,
        submitError: function($form, event, errors) {
            // additional error messages or events
        },
        submitSuccess: function($form, event) {
            // Prevent spam click and default submit behaviour
            $("#btnSubmit").attr("disabled", true);
            event.preventDefault();

            // get values from FORM
            var name = $("input#name").val();
            var email = $("input#email").val();
            var phone = $("input#phone").val();
            var message = $("textarea#message").val();
            var firstName = name; // For Success/Failure Message
            // Check for white space in name for Success/Fail message
            if (firstName.indexOf(' ') >= 0) {
                firstName = name.split(' ').slice(0, -1).join(' ');
            }
            $.ajax({
                url: "././mail/contact_me.php",
                type: "POST",
                data: {
                    name: name,
                    phone: phone,
                    email: email,
                    message: message
                },
                cache: false,
                success: function() {
                    // Enable button & show success message
                    $("#btnSubmit").attr("disabled", false);
                    $('#success').html("
");
                    $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-success')
                        .append("Your message has been sent. ");
                    $('#success > .alert-success')
                        .append('
');

                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
                error: function() {
                    // Fail message
                    $('#success').html("
");
                    $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!");
                    $('#success > .alert-danger').append('
');
                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
            })
        },
        filter: function() {
            return $(this).is(":visible");
        },
    });

    $("a[data-toggle=\"tab\"]").click(function(e) {
        e.preventDefault();
        $(this).tab("show");
    });
});

// When clicking on Full hide fail/success boxes
$('#name').focus(function() {
    $('#success').html('');
});
<|endoftext|>


<commit_before>$(function() {

    $("input,textarea").jqBootstrapValidation({
        preventSubmit: true,
        submitError: function($form, event, errors) {
            // additional error messages or events
        },
        submitSuccess: function($form, event) {
            // Prevent spam click and default submit behaviour
            $("#btnSubmit").attr("disabled", true);
            event.preventDefault();

            // get values from FORM
            var name = $("input#name").val();
            var email = $("input#email").val();
            var phone = $("input#phone").val();
            var message = $("textarea#message").val();
            var firstName = name; // For Success/Failure Message
            // Check for white space in name for Success/Fail message
            if (firstName.indexOf(' ') >= 0) {
                firstName = name.split(' ').slice(0, -1).join(' ');
            }
            $.ajax({
                url: "././mail/contact_me.php",
                type: "POST",
                data: {
                    name: name,
                    phone: phone,
                    email: email,
                    message: message
                },
                cache: false,
                success: function() {
                    // Enable button & show success message
                    $("#btnSubmit").attr("disabled", false);
                    $('#success').html("
");
                    $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-success')
                        .append("Your message has been sent. ");
                    $('#success > .alert-success')
                        .append('
');

                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
                error: function() {
                    // Fail message
                    $('#success').html("
");
                    $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!");
                    $('#success > .alert-danger').append('
');
                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
            })
        },
        filter: function() {
            return $(this).is(":visible");
        },
    });

    $("a[data-toggle=\"tab\"]").click(function(e) {
        e.preventDefault();
        $(this).tab("show");
    });
});

// When clicking on Full hide fail/success boxes
$('#name').focus(function() {
    $('#success').html('');
});
<commit_msg>Change js script to reflect new form service<commit_after>$(function() {

    $("input,textarea").jqBootstrapValidation({
        preventSubmit: true,
        submitError: function($form, event, errors) {
            // additional error messages or events
        },
        submitSuccess: function($form, event) {
            // Prevent spam click and default submit behaviour
            $("#btnSubmit").attr("disabled", true);
            event.preventDefault();

            // get values from FORM
            var name = $("input#name").val();
            var email = $("input#email").val();
            var phone = $("input#phone").val();
            var message = $("textarea#message").val();
            var firstName = name; // For Success/Failure Message
            // Check for white space in name for Success/Fail message
            if (firstName.indexOf(' ') >= 0) {
                firstName = name.split(' ').slice(0, -1).join(' ');
            }
            $.ajax({
                url: "http://formspree.io/amelie.anglade@gmail.com",
                type: "POST",
                data: {
                    name: name,
                    phone: phone,
                    email: email,
                    message: message
                },
                cache: false,
                success: function() {
                    // Enable button & show success message
                    $("#btnSubmit").attr("disabled", false);
                    $('#success').html("
");
                    $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-success')
                        .append("Your message has been sent. ");
                    $('#success > .alert-success')
                        .append('
');

                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
                error: function() {
                    // Fail message
                    $('#success').html("
");
                    $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!");
                    $('#success > .alert-danger').append('
');
                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
            })
        },
        filter: function() {
            return $(this).is(":visible");
        },
    });

    $("a[data-toggle=\"tab\"]").click(function(e) {
        e.preventDefault();
        $(this).tab("show");
    });
});

// When clicking on Full hide fail/success boxes
$('#name').focus(function() {
    $('#success').html('');
});
<|endoftext|>


<commit_before>$(function() {

  $("#contactForm input,#contactForm textarea").jqBootstrapValidation({
    preventSubmit: true,
    submitError: function($form, event, errors) {
      // additional error messages or events
    },
    submitSuccess: function($form, event) {
      event.preventDefault(); // prevent default submit behaviour
      // get values from FORM
      var name = $("input#name").val();
      var email = $("input#email").val();
      var phone = $("input#phone").val();
      var message = $("textarea#message").val();
      var firstName = name; // For Success/Failure Message
      // Check for white space in name for Success/Fail message
      if (firstName.indexOf(' ') >= 0) {
        firstName = name.split(' ').slice(0, -1).join(' ');
      }
      $this = $("#sendMessageButton");
      $this.prop("disabled", true); // Disable submit button until AJAX call is complete to prevent duplicate messages
      $.ajax({
        url: "././mail/contact_me.php",
        type: "POST",
        data: {
          name: name,
          phone: phone,
          email: email,
          message: message
        },
        cache: false,
        success: function() {
          // Success message
          $('#success').html("
");
          $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
            .append("</button>");
          $('#success > .alert-success')
            .append("Your message has been sent. ");
          $('#success > .alert-success')
            .append('
');
          //clear all fields
          $('#contactForm').trigger("reset");
        },
        error: function() {
          // Fail message
          $('#success').html("
");
          $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
            .append("</button>");
          $('#success > .alert-danger').append($("").text("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!"));
          $('#success > .alert-danger').append('
');
          //clear all fields
          $('#contactForm').trigger("reset");
        },
        complete: function() {
          setTimeout(function() {
            $this.prop("disabled", false); // Re-enable submit button when AJAX call is complete
          }, 1000);
        }
      });
    },
    filter: function() {
      return $(this).is(":visible");
    },
  });

  $("a[data-toggle=\"tab\"]").click(function(e) {
    e.preventDefault();
    $(this).tab("show");
  });
});

/*When clicking on Full hide fail/success boxes */
$('#name').focus(function() {
  $('#success').html('');
});
<commit_msg>Update contact_me.js<commit_after>$(function() {

  $("#contactForm input,#contactForm textarea").jqBootstrapValidation({
    preventSubmit: true,
    submitError: function($form, event, errors) {
      // additional error messages or events
    },
    submitSuccess: function($form, event) {
      event.preventDefault(); // prevent default submit behaviour
      // get values from FORM
      var name = $("input#name").val();
      var email = $("input#email").val();
      var phone = $("input#phone").val();
      var message = $("textarea#message").val();
      var firstName = name; // For Success/Failure Message
      // Check for white space in name for Success/Fail message
      if (firstName.indexOf(' ') >= 0) {
        firstName = name.split(' ').slice(0, -1).join(' ');
      }
      $this = $("#sendMessageButton");
      $this.prop("disabled", true); // Disable submit button until AJAX call is complete to prevent duplicate messages
      $.ajax({
        url: "././mail/contact_me.php",
        type: "POST",
        data: {
          name: name,
          phone: phone,
          email: email,
          message: message
        },
        cache: false,
        success: function() {
          // Success message
          $('#success').html("
");
          $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
            .append("</button>");
          $('#success > .alert-success')
            .append("Your message has been sent. ");
          $('#success > .alert-success')
            .append('
');
          //clear all fields
          $('#contactForm').trigger("reset");
        },
        error: function() {
          // Fail message
          $('#success').html("
");
          $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
            .append("</button>");
          $('#success > .alert-danger').append($("").text("Whoops! Sorry " + firstName + ", it seems that something went wrong! Please contact me directly on hello@magdahanak.com"));
          $('#success > .alert-danger').append('
');
          //clear all fields
          $('#contactForm').trigger("reset");
        },
        complete: function() {
          setTimeout(function() {
            $this.prop("disabled", false); // Re-enable submit button when AJAX call is complete
          }, 1000);
        }
      });
    },
    filter: function() {
      return $(this).is(":visible");
    },
  });

  $("a[data-toggle=\"tab\"]").click(function(e) {
    e.preventDefault();
    $(this).tab("show");
  });
});

/*When clicking on Full hide fail/success boxes */
$('#name').focus(function() {
  $('#success').html('');
});
<|endoftext|>


<commit_before>$(function() {

    $("#contactForm input,#contactForm textarea").jqBootstrapValidation({
        preventSubmit: true,
        submitError: function($form, event, errors) {
            // additional error messages or events
        },
        submitSuccess: function($form, event) {
            // Prevent spam click and default submit behaviour
            $("#btnSubmit").attr("disabled", true);
            event.preventDefault();

            // get values from FORM
            var name = $("input#name").val();
            var email = $("input#email").val();
            var phone = $("input#phone").val();
            var message = $("textarea#message").val();
            var firstName = name; // For Success/Failure Message
            // Check for white space in name for Success/Fail message
            if (firstName.indexOf(' ') >= 0) {
                firstName = name.split(' ').slice(0, -1).join(' ');
            }
            $.ajax({
                url: "././mail/contact_me.php",
                type: "POST",
                data: {
                    name: name,
                    phone: phone,
                    email: email,
                    message: message
                },
                cache: false,
                success: function() {
                    // Enable button & show success message
                    $("#btnSubmit").attr("disabled", false);
                    $('#success').html("
");
                    $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-success')
                        .append("Your message has been sent. ");
                    $('#success > .alert-success')
                        .append('
');

                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
                error: function() {
                    // Fail message
                    $('#success').html("
");
                    $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!");
                    $('#success > .alert-danger').append('
');
                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
            })
        },
        filter: function() {
            return $(this).is(":visible");
        },
    });

    $("a[data-toggle=\"tab\"]").click(function(e) {
        e.preventDefault();
        $(this).tab("show");
    });
});

// When clicking on Full hide fail/success boxes
$('#name').focus(function() {
    $('#success').html('');
});
<commit_msg>contact form<commit_after>$(function() {

    $("#contactForm input,#contactForm textarea").jqBootstrapValidation({
        preventSubmit: true,
        submitError: function($form, event, errors) {
            // additional error messages or events
        },
        submitSuccess: function($form, event) {
            // Prevent spam click and default submit behaviour
            $("#btnSubmit").attr("disabled", true);
            event.preventDefault();

            // get values from FORM
            var name = $("input#name").val();
            var email = $("input#email").val();
            var phone = $("input#phone").val();
            var message = $("textarea#message").val();
            var firstName = name; // For Success/Failure Message
            // Check for white space in name for Success/Fail message
            if (firstName.indexOf(' ') >= 0) {
                firstName = name.split(' ').slice(0, -1).join(' ');
            }
            $.ajax({
                url: "../mail/contact_me.php",
                type: "POST",
                data: {
                    name: name,
                    phone: phone,
                    email: email,
                    message: message
                },
                cache: false,
                success: function() {
                    // Enable button & show success message
                    $("#btnSubmit").attr("disabled", false);
                    $('#success').html("
");
                    $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-success')
                        .append("Your message has been sent. ");
                    $('#success > .alert-success')
                        .append('
');

                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
                error: function() {
                    // Fail message
                    $('#success').html("
");
                    $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!");
                    $('#success > .alert-danger').append('
');
                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
            })
        },
        filter: function() {
            return $(this).is(":visible");
        },
    });

    $("a[data-toggle=\"tab\"]").click(function(e) {
        e.preventDefault();
        $(this).tab("show");
    });
});

// When clicking on Full hide fail/success boxes
$('#name').focus(function() {
    $('#success').html('');
});
<|endoftext|>


<commit_before>/*global updateCount x*/

/**
 * User interface connection to the simulation.
 * @constructor
 */
function Controller(particles) {
    this.particles = particles;
    this.obstacle = null;
    this.init();
    this.mousedown = false;

    var _this = this,
        canvas = particles.igloo.gl.canvas;
    $(canvas).on('mousemove', function(event) {
        var coords = Controller.coords(event);
        _this.obstacle.position[0] = coords[0];
        _this.obstacle.position[1] = coords[1];
        _this.obstacle.enabled = true;
        particles.updateObstacles();
        if (_this.mousedown) _this.place();
    });
    $(canvas).on('mouseout', function() {
        _this.obstacle.enabled = false;
        particles.updateObstacles();
        _this.mousedown = false;
    });
    $(canvas).on('mousedown', function() {
        _this.mousedown = true;
    });
    $(canvas).on('mouseup', function(event) {
        if (event.which === 1) _this.place();
        _this.mousedown = false;
    });
    $(window).on('keyup', function(event) {
        switch (event.which) {
        case 67: // c
            _this.clear();
            break;
        case 68: // d
            _this.adjust(2);
            break;
        case 72: // h
            _this.adjust(0.5);
            break;
        }
    });
    $('.controls .increase').on('click', function() {
        _this.adjust(2);
    });
    $('.controls .decrease').on('click', function() {
        _this.adjust(0.5);
    });
    $('.controls .reset').on('click', function() {
        _this.adjust(1);
    });
    $('.controls .bigger').on('click', function() {
        _this.obstacle.size *= 1.5;
        _this.particles.updateObstacles();
    });
    $('.controls .smaller').on('click', function() {
        _this.obstacle.size *= 0.67;
        _this.particles.updateObstacles();
    });
    $('.controls .clear').on('click', function() {
        _this.clear();
    });
}

/**
 * Create and capture the mouse obstacle.
 * @returns {Controller} this
 */
Controller.prototype.init = function() {
    this.obstacle = this.particles.addObstacle([0, 0], 20);
    this.obstacle.enabled = false;
    this.particles.updateObstacles();
    return this;
};

/**
 * Place a new obstacle at the mouse location.
 * @returns {Controller} this
 */
Controller.prototype.place = function() {
    var center = this.obstacle.position,
        radius = this.obstacle.size;
    this.particles.addObstacle(center.slice(0), radius);
    this.particles.updateObstacles();
    return this;
};

/**
 * Clear all obstacles.
 * @returns {Controller} this
 */
Controller.prototype.clear = function() {
    this.particles.obstacles.length = 0;
    this.init();
    return this;
};

/**
 * Immediately adjust the particle count.
 * @param {number} factor multiplies the particle count
 * @returns {Controller} this
 */
Controller.prototype.adjust = function(factor) {
    this.particles.setCount(this.particles.getCount() * factor);
    updateCount();
};

/**
 * @param {Object} event
 * @returns {Array} the simulation coodinates from the event
 */
Controller.coords = function(event) {
    var $target = $(event.target),
        offset = $target.offset(),
        border = 1,
        x = event.pageX - offset.left - border,
        y = $target.height() - (event.pageY - offset.top - border);
    return [x, y];
};
<commit_msg>Add save and restore methods.<commit_after>/*global updateCount x*/

/**
 * User interface connection to the simulation.
 * @constructor
 */
function Controller(particles) {
    this.particles = particles;
    this.obstacle = null;
    this.init();
    this.mousedown = false;

    var _this = this,
        canvas = particles.igloo.gl.canvas;
    $(canvas).on('mousemove', function(event) {
        var coords = Controller.coords(event);
        _this.obstacle.position[0] = coords[0];
        _this.obstacle.position[1] = coords[1];
        _this.obstacle.enabled = true;
        particles.updateObstacles();
        if (_this.mousedown) _this.place();
    });
    $(canvas).on('mouseout', function() {
        _this.obstacle.enabled = false;
        particles.updateObstacles();
        _this.mousedown = false;
    });
    $(canvas).on('mousedown', function() {
        _this.mousedown = true;
    });
    $(canvas).on('mouseup', function(event) {
        if (event.which === 1) _this.place();
        _this.mousedown = false;
    });
    $(window).on('keyup', function(event) {
        switch (event.which) {
        case 67: // c
            _this.clear();
            break;
        case 68: // d
            _this.adjust(2);
            break;
        case 72: // h
            _this.adjust(0.5);
            break;
        }
    });
    $('.controls .increase').on('click', function() {
        _this.adjust(2);
    });
    $('.controls .decrease').on('click', function() {
        _this.adjust(0.5);
    });
    $('.controls .reset').on('click', function() {
        _this.adjust(1);
    });
    $('.controls .bigger').on('click', function() {
        _this.obstacle.size *= 1.5;
        _this.particles.updateObstacles();
    });
    $('.controls .smaller').on('click', function() {
        _this.obstacle.size *= 0.67;
        _this.particles.updateObstacles();
    });
    $('.controls .clear').on('click', function() {
        _this.clear();
    });
}

/**
 * Create and capture the mouse obstacle.
 * @returns {Controller} this
 */
Controller.prototype.init = function() {
    this.obstacle = this.particles.addObstacle([0, 0], 20);
    this.obstacle.enabled = false;
    this.particles.updateObstacles();
    return this;
};

/**
 * Place a new obstacle at the mouse location.
 * @returns {Controller} this
 */
Controller.prototype.place = function() {
    var center = this.obstacle.position,
        radius = this.obstacle.size;
    this.particles.addObstacle(center.slice(0), radius);
    this.particles.updateObstacles();
    return this;
};

/**
 * Clear all obstacles.
 * @returns {Controller} this
 */
Controller.prototype.clear = function() {
    this.particles.obstacles.length = 0;
    this.init();
    return this;
};

/**
 * Immediately adjust the particle count.
 * @param {number} factor multiplies the particle count
 * @returns {Controller} this
 */
Controller.prototype.adjust = function(factor) {
    this.particles.setCount(this.particles.getCount() * factor);
    updateCount();
};

/**
 * Captures the simulation's particle count and obstacle configuration.
 * @returns {Object} the simulation save state object
 */
Controller.prototype.save = function() {
    var save = {
        particles: this.particles.getCount(),
        obstacles: []
    };
    this.particles.obstacles.forEach(function(o) {
        if (o.enabled) {
            save.obstacles.push({
                position: Controller.round(o.position),
                size: o.size
            });
        }
    });
    return save;
};

/**
 * Restore the simulation's state from a save object.
 * @param {Object} save
 * @returns {Controller} this
 */
Controller.prototype.restore = function(save) {
    if (this.particles.getCount() !== save.particles) {
        this.particles.setCount(save.particles);
    }
    this.clear();
    var ps = this.particles;
    save.obstacles.forEach(function(o) {
        ps.addObstacle(o.position, o.size);
    });
    return this;
};

/**
 * @param {Object} event
 * @returns {Array} the simulation coodinates from the event
 */
Controller.coords = function(event) {
    var $target = $(event.target),
        offset = $target.offset(),
        border = 1,
        x = event.pageX - offset.left - border,
        y = $target.height() - (event.pageY - offset.top - border);
    return [x, y];
};

/**
 * @param {Array|ArrayBufferView|value} value
 * @param {number} [precision=4]
 * @returns {Array|number} a copy of the array/value rounded to PRECISION
 */
Controller.round = function(value, precision) {
    precision = precision || 4;
    if ('length' in value) {
        return Array.prototype.map.call(value, function (x) {
            return Number(x.toPrecision(precision));
        });
    } else {
        return Number(value.toPrecision(precision));
    }
};
<|endoftext|>


<commit_before>define(["js/html/HtmlElement", "js/core/Bus", "js/core/WindowManager", "js/core/ExternalInterface", "js/core/ErrorProvider"], function (HtmlElement, Bus, WindowManager, ExternalInterface, ErrorProvider) {

    var browserClassMap = {
        "hasTouch": ["touch", "no-touch"],
        "supportsTransition": ["transition", "no-transition"],
        "isMobile": ["mobile", "desktop"],
        "supportViewPortRelativeSize": ["vs-support", "no-vs-support"]
    };

    /***
     * @summary The stage is the main container for each application.
     * It contains all root HTML elements like windows, tooltips or popups.
     * Usually an application is one window on the stage.
     *
     */
    return HtmlElement.inherit("js.core.Stage", {
        $containerOrder: {
            'windows': 0,
            'popups': 1,
            'tooltips': 2
        },

        defaults: {
            tagName: "div",
            componentClass: 'stage'
        },

        ctor: function (requireJsContext, applicationContext, document, window) {

            this.$requirejsContext = requireJsContext;
            this.$applicationContext = applicationContext;
            this.$applicationFactory = null;
            this.$document = document;
            this.$window = window;
            this.$bus = new Bus(this);
            this.$externalInterface = new ExternalInterface(this);

            this.$containers = {};
            this.$elements = {};

            this.callBase(null, false, this, null, this);

            this.$browser = this._createBrowserObject();

            this._annotateBrowserInformation(this.$browser);

        },

        _addInjectionFactories: function (injection) {
            injection.addFactory({
                type: "js.core.ErrorProvider",
                factory: ErrorProvider,
                singleton: true
            });
        },

        /**
         * Creates the browser object with the following information
         *
         * * isBrowser - false if runs on node
         * * hasTouch - true if touchend in window
         * * has3D - true if  'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()
         * * msPointerEnabled - true if  "msPointerEnabled" in window.navigator
         * * isMobile - true if userAgent is iphone, ipad, ipod blackberry, palm etc..
         * * isIOS - true if iPad, iPhone or iPod
         * * mobileType - the name of the mobile device
         * * os - the name of the operating system (windows, mac, linux, unix)
         * * name -  the name of the browser (safari, firefox, ie, chrome)
         * * supportsTransition
         *
         * @returns {Object}
         * @private
         */
        _createBrowserObject: function () {

            function getVendorPrefix() {
                if ('WebkitTransition' in s) return "webkit";
                if ('MozTransition' in s) return "Moz";
                if ('msTransition' in s) return "MS";
                if ('OTransition' in s) return "o";
                return "";
            }

            var browser = {};

            browser.isBrowser = this.runsInBrowser();

            if (this.runsInBrowser()) {
                var window = this.$window,
                    document = this.$document,
                    body = document.body || document.getElementsByTagName("body")[0],
                    navigator, s;

                if (window) {
                    browser.hasTouch = "ontouchend" in window;
                    browser.has3D = ('WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix());
                    browser.msPointerEnabled = "msPointerEnabled" in window.navigator;

                    navigator = window.navigator;

                    s = window.document.createElement('div').style;
                }

                if (navigator) {

                    var userAgent = navigator.userAgent || navigator.appVersion;
                    var mobile = /(iphone|ipad|ipod|android|blackberry|mini|windows\sce|palm)/i.exec(userAgent);
                    browser.isMobile = !!mobile;
                    browser.isIOS = !!userAgent.match(/(iPad|iPhone|iPod)/g);

                    if (mobile) {
                        browser.mobileType = mobile[1].toLowerCase();
                    }

                    var os = /win|mac|linux|x11/i.exec(userAgent);
                    if (os) {
                        os = {
                            win: "windows",
                            mac: "mac",
                            linux: "linux",
                            x11: "unix"
                        }[os[0].toLowerCase()];

                        browser.os = os;
                    }

                    var browserName = /(firefox)|(chrome)|(safari)/i.exec(userAgent);
                    if (browserName) {
                        browser.isFF = !!browserName[1];
                        browser.isChrome = !!browserName[2];
                        browser.isSafari = !!browserName[3];
                        browserName = browserName[0].toLowerCase();
                        browser.name = browserName;
                    }

                    browserName = /msie\s(\d+)/i.exec(userAgent);
                    if (browserName) {
                        browserName = "ie ie" + browserName[1];
                        browser.name = browserName;
                        browser.isIE = true;
                    } else if ("ActiveXObject" in window) {
                        browser.isIE = true;
                        browser.name = "ie ie11";
                    } else {
                        browser.isIE = false;
                    }

                    browser.name = browser.name || "";

                    var version = /(?:opera|chrome|safari|firefox|msie|trident)\/?\s*([\d]+)/i.exec(userAgent);
                    if (version && version [1]) {
                        browser.version = version[1];
                    }
                }

                if (s) {
                    browser.supportsTransition = 'transition' in s ||
                        'WebkitTransition' in s ||
                        'MozTransition' in s ||
                        'msTransition' in s ||
                        'OTransition' in s;

                    browser.vendorPrefix = getVendorPrefix();
                }

            }

            return browser;

        },
        /***
         * Goes through the browser object and adds classes for each entry to
         * the rendered stage element
         *
         * @param {Object} browser
         * @private
         */
        _annotateBrowserInformation: function (browser) {
            var classes = ["stage"], value;

            for (var key in browser) {
                if (browser.hasOwnProperty(key)) {

                    value = browser[key];

                    if (typeof(value) === "boolean") {
                        if (browserClassMap.hasOwnProperty(key)) {
                            classes.push(browserClassMap[key][value ? 0 : 1]);
                        } else if (key === 'isIOS' && value) {
                            classes.push('ios');
                            classes.push('ios' + this._getIOSVersion());
                        }
                    } else {
                        if (key === 'version') {
                            classes.push(browser.name + value);
                        } else {
                            classes.push(value);
                        }
                    }
                }
            }

            this.set('componentClass', classes.join(" "));
        },

        _getIOSVersion: function () {
            var iOSVersionRegexp = this.$window.navigator.userAgent.match(/OS ([0-9]+)(?:_[0-9])* like Mac OS/);

            if (iOSVersionRegexp && iOSVersionRegexp.length > 0) {
                return (iOSVersionRegexp[1]);
            } else {
                return '';
            }
        },

        /**
         * Creates one WindowManager as additional child
         *
         * @returns {Array}
         */
        createChildren: function () {

            this.$windowManager = this.createComponent(WindowManager);
            return [this.$windowManager];

        },
        /***
         * Renders into the given target
         *
         * @param {HTMLElement} target - the render target
         * @returns {HTMLElement}
         */
        render: function (target) {
            var dom = this.callBase(null);

            if (this.$externalInterface) {
                this.$externalInterface._stageRendered(dom);
            }

            if (target) {
                target.appendChild(dom);
                this.rendered = true;
                this.$bus.trigger('Stage.Rendered', target);
                this.trigger('dom:add', target);
            }
            return dom;
        },
        /***
         * Creates a window with an ID
         *
         * @param elementId
         * @returns {*}
         */
        createWindow: function (elementId) {
            return this.createHtmlElement(elementId, "windows");
        },
        /**
         * Creates a tooltip window for the stage with an ID
         *
         * @param tooltipId
         * @returns {*}
         */
        createTooltip: function (tooltipId) {
            return this.createHtmlElement(tooltipId, "tooltips");
        },
        /***
         * Creates an HTML element with inside a container with class {container-id}-container and
         * caches it with element and container id
         *
         * @param {String} elementId
         * @param {String} containerId
         * @returns {HTMLElement}
         */
        createHtmlElement: function (elementId, containerId) {
            var container = this.$containers[containerId];
            if (!container) {
                // TODO: remove this shit
                container = this.createContainer({'class': containerId + "-container"});
                this.$containers[containerId] = container;
                this.addChild(container);
                if (this.$containerOrder[containerId]) {
                    container.setChildIndex(this.$containerOrder[containerId]);
                }
            }

            var element = this.$elements[containerId + "_" + elementId];
            if (!element) {
                element = this.createContainer({});
                container.addChild(element);
            }

            return element.$el;
        },
        /***
         * Creates a container with a set of attributes
         *
         * @param {Object} attributes - the attribute set
         * @returns {js.core.HtmlElement}
         */
        createContainer: function (attributes) {
            attributes = attributes || {};
            return this.$stage.$applicationContext.createInstance(HtmlElement, [attributes, null, this.$stage, this, this]);
        },

        destroy: function () {
            this.callBase();

            this.$requirejsContext = null;
            this.$applicationContext = null;
            this.$applicationFactory = null;
            this.$document = null;
            this.$window = null;
            this.$bus = null;
            this.$externalInterface = null;

            this.$containers = null;
            this.$elements = null;

            this.$browser = null;

            this.$application.destroy();
        }

    });
});<commit_msg>rolled back #d1d3bd3<commit_after>define(["js/html/HtmlElement", "js/core/Bus", "js/core/WindowManager", "js/core/ExternalInterface", "js/core/ErrorProvider"], function (HtmlElement, Bus, WindowManager, ExternalInterface, ErrorProvider) {

    var browserClassMap = {
        "hasTouch": ["touch", "no-touch"],
        "supportsTransition": ["transition", "no-transition"],
        "isMobile": ["mobile", "desktop"],
        "supportViewPortRelativeSize": ["vs-support", "no-vs-support"]
    };

    /***
     * @summary The stage is the main container for each application.
     * It contains all root HTML elements like windows, tooltips or popups.
     * Usually an application is one window on the stage.
     *
     */
    return HtmlElement.inherit("js.core.Stage", {
        $containerOrder: {
            'windows': 0,
            'popups': 1,
            'tooltips': 2
        },

        defaults: {
            tagName: "div",
            componentClass: 'stage'
        },

        ctor: function (requireJsContext, applicationContext, document, window) {

            this.$requirejsContext = requireJsContext;
            this.$applicationContext = applicationContext;
            this.$applicationFactory = null;
            this.$document = document;
            this.$window = window;
            this.$bus = new Bus(this);
            this.$externalInterface = new ExternalInterface(this);

            this.$containers = {};
            this.$elements = {};

            this.callBase(null, false, this, null, this);

            this.$browser = this._createBrowserObject();

            this._annotateBrowserInformation(this.$browser);

        },

        _addInjectionFactories: function (injection) {
            injection.addFactory({
                type: "js.core.ErrorProvider",
                factory: ErrorProvider,
                singleton: true
            });
        },

        /**
         * Creates the browser object with the following information
         *
         * * isBrowser - false if runs on node
         * * hasTouch - true if touchend in window
         * * has3D - true if  'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()
         * * msPointerEnabled - true if  "msPointerEnabled" in window.navigator
         * * isMobile - true if userAgent is iphone, ipad, ipod blackberry, palm etc..
         * * isIOS - true if iPad, iPhone or iPod
         * * mobileType - the name of the mobile device
         * * os - the name of the operating system (windows, mac, linux, unix)
         * * name -  the name of the browser (safari, firefox, ie, chrome)
         * * supportsTransition
         *
         * @returns {Object}
         * @private
         */
        _createBrowserObject: function () {

            function getVendorPrefix() {
                if ('WebkitTransition' in s) return "webkit";
                if ('MozTransition' in s) return "Moz";
                if ('msTransition' in s) return "MS";
                if ('OTransition' in s) return "o";
                return "";
            }

            var browser = {};

            browser.isBrowser = this.runsInBrowser();

            if (this.runsInBrowser()) {
                var window = this.$window,
                    document = this.$document,
                    body = document.body || document.getElementsByTagName("body")[0],
                    navigator, s;

                if (window) {
                    browser.hasTouch = "ontouchend" in window;
                    browser.has3D = ('WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix());
                    browser.msPointerEnabled = "msPointerEnabled" in window.navigator;

                    navigator = window.navigator;

                    var div = document.createElement("div");
                    div.setAttribute("style", "position: absolute; height: 100vh; width: 100vw;");
                    body.appendChild(div);

                    browser.supportViewPortRelativeSize = (window.innerWidth === div.offsetWidth);

                    body.removeChild(div);

                    s = window.document.createElement('div').style;
                }

                if (navigator) {

                    var userAgent = navigator.userAgent || navigator.appVersion;
                    var mobile = /(iphone|ipad|ipod|android|blackberry|mini|windows\sce|palm)/i.exec(userAgent);
                    browser.isMobile = !!mobile;
                    browser.isIOS = !!userAgent.match(/(iPad|iPhone|iPod)/g);

                    if (mobile) {
                        browser.mobileType = mobile[1].toLowerCase();
                    }

                    var os = /win|mac|linux|x11/i.exec(userAgent);
                    if (os) {
                        os = {
                            win: "windows",
                            mac: "mac",
                            linux: "linux",
                            x11: "unix"
                        }[os[0].toLowerCase()];

                        browser.os = os;
                    }

                    var browserName = /(firefox)|(chrome)|(safari)/i.exec(userAgent);
                    if (browserName) {
                        browser.isFF = !!browserName[1];
                        browser.isChrome = !!browserName[2];
                        browser.isSafari = !!browserName[3];
                        browserName = browserName[0].toLowerCase();
                        browser.name = browserName;
                    }

                    browserName = /msie\s(\d+)/i.exec(userAgent);
                    if (browserName) {
                        browserName = "ie ie" + browserName[1];
                        browser.name = browserName;
                        browser.isIE = true;
                    } else if ("ActiveXObject" in window) {
                        browser.isIE = true;
                        browser.name = "ie ie11";
                    } else {
                        browser.isIE = false;
                    }

                    browser.name = browser.name || "";

                    var version = /(?:opera|chrome|safari|firefox|msie|trident)\/?\s*([\d]+)/i.exec(userAgent);
                    if (version && version [1]) {
                        browser.version = version[1];
                    }
                }

                if (s) {
                    browser.supportsTransition = 'transition' in s ||
                        'WebkitTransition' in s ||
                        'MozTransition' in s ||
                        'msTransition' in s ||
                        'OTransition' in s;

                    browser.vendorPrefix = getVendorPrefix();
                }

            }

            return browser;

        },
        /***
         * Goes through the browser object and adds classes for each entry to
         * the rendered stage element
         *
         * @param {Object} browser
         * @private
         */
        _annotateBrowserInformation: function (browser) {
            var classes = ["stage"], value;

            for (var key in browser) {
                if (browser.hasOwnProperty(key)) {

                    value = browser[key];

                    if (typeof(value) === "boolean") {
                        if (browserClassMap.hasOwnProperty(key)) {
                            classes.push(browserClassMap[key][value ? 0 : 1]);
                        } else if (key === 'isIOS' && value) {
                            classes.push('ios');
                            classes.push('ios' + this._getIOSVersion());
                        }
                    } else {
                        if (key === 'version') {
                            classes.push(browser.name + value);
                        } else {
                            classes.push(value);
                        }
                    }
                }
            }

            this.set('componentClass', classes.join(" "));
        },

        _getIOSVersion: function () {
            var iOSVersionRegexp = this.$window.navigator.userAgent.match(/OS ([0-9]+)(?:_[0-9])* like Mac OS/);

            if (iOSVersionRegexp && iOSVersionRegexp.length > 0) {
                return (iOSVersionRegexp[1]);
            } else {
                return '';
            }
        },

        /**
         * Creates one WindowManager as additional child
         *
         * @returns {Array}
         */
        createChildren: function () {

            this.$windowManager = this.createComponent(WindowManager);
            return [this.$windowManager];

        },
        /***
         * Renders into the given target
         *
         * @param {HTMLElement} target - the render target
         * @returns {HTMLElement}
         */
        render: function (target) {
            var dom = this.callBase(null);

            if (this.$externalInterface) {
                this.$externalInterface._stageRendered(dom);
            }

            if (target) {
                target.appendChild(dom);
                this.rendered = true;
                this.$bus.trigger('Stage.Rendered', target);
                this.trigger('dom:add', target);
            }
            return dom;
        },
        /***
         * Creates a window with an ID
         *
         * @param elementId
         * @returns {*}
         */
        createWindow: function (elementId) {
            return this.createHtmlElement(elementId, "windows");
        },
        /**
         * Creates a tooltip window for the stage with an ID
         *
         * @param tooltipId
         * @returns {*}
         */
        createTooltip: function (tooltipId) {
            return this.createHtmlElement(tooltipId, "tooltips");
        },
        /***
         * Creates an HTML element with inside a container with class {container-id}-container and
         * caches it with element and container id
         *
         * @param {String} elementId
         * @param {String} containerId
         * @returns {HTMLElement}
         */
        createHtmlElement: function (elementId, containerId) {
            var container = this.$containers[containerId];
            if (!container) {
                // TODO: remove this shit
                container = this.createContainer({'class': containerId + "-container"});
                this.$containers[containerId] = container;
                this.addChild(container);
                if (this.$containerOrder[containerId]) {
                    container.setChildIndex(this.$containerOrder[containerId]);
                }
            }

            var element = this.$elements[containerId + "_" + elementId];
            if (!element) {
                element = this.createContainer({});
                container.addChild(element);
            }

            return element.$el;
        },
        /***
         * Creates a container with a set of attributes
         *
         * @param {Object} attributes - the attribute set
         * @returns {js.core.HtmlElement}
         */
        createContainer: function (attributes) {
            attributes = attributes || {};
            return this.$stage.$applicationContext.createInstance(HtmlElement, [attributes, null, this.$stage, this, this]);
        },

        destroy: function () {
            this.callBase();

            this.$requirejsContext = null;
            this.$applicationContext = null;
            this.$applicationFactory = null;
            this.$document = null;
            this.$window = null;
            this.$bus = null;
            this.$externalInterface = null;

            this.$containers = null;
            this.$elements = null;

            this.$browser = null;

            this.$application.destroy();
        }

    });
});<|endoftext|>


<commit_before>var path = require('path')
var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var loadersByExtension = require('./config/loadersByExtension')
var booleanFromEnv = require('./config/booleanFromEnv')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var AutoprefixerCore = require('autoprefixer-core')

var plugins = [
  new webpack.PrefetchPlugin('react'),
  new HtmlWebpackPlugin({
    inject: true,
    template: 'app/index.html'
  }),
  new webpack.PrefetchPlugin('react/lib/ReactComponentBrowserEnvironment'),
  new ExtractTextPlugin('[name].css', {
    allChunks: true
  }),
  new webpack.NoErrorsPlugin()
]

if (booleanFromEnv('WEBPACK_MINIMIZE_DEV', false)) {
  plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false
      }
    }),
    new webpack.optimize.DedupePlugin(),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    })
  )
}

module.exports = {
  entry: booleanFromEnv('WEBPACK_HOT_COMPONENTS', false) ? [
    'webpack-dev-server/client?http://0.0.0.0:3000', // WebpackDevServer host and port
    'webpack/hot/dev-server',
    './app/main'
  ] : './app/main',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
    sourceMapFilename: '[name].map',
    pathinfo: process.env.WEBPACK_PATH_INFO
  },
  module: {
    loaders: loadersByExtension({
      'jsx': booleanFromEnv('WEBPACK_HOT_COMPONENTS', false) ? ['react-hot', 'babel-loader?stage=0'] : 'babel-loader?stage=0',
      'js': {
        loader: 'babel-loader?stage=0',
        include: path.join(__dirname, 'app')
      },
      'json': 'json-loader',
      'json5': 'json5-loader',
      'txt': 'raw-loader',
      'png|jpg|jpeg|gif|svg': 'url-loader?limit=10000',
      'woff|woff2': 'url-loader?limit=100000',
      'ttf|eot': 'file-loader',
      'wav|mp3': 'file-loader',
      'html': 'html-loader',
      'md|markdown': ['html-loader', 'markdown-loader'],
      'css': ExtractTextPlugin.extract('style-loader', 'css-loader?module&importLoaders=1!postcss-loader'),
      'less': ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader'),
      'styl': ExtractTextPlugin.extract('style-loader', 'css-loader!stylus-loader')

    }),
    preloaders: loadersByExtension({
      'js': 'source-map-loader'
    })
  },
  postcss: [AutoprefixerCore],
  devtool: process.env.WEBPACK_DEVTOOL,
  debug: booleanFromEnv('WEBPACK_DEBUG', false),
  resolve: {
    root: path.join(__dirname, 'app'),
    extensions: ['', '.web.js', '.js', '.jsx']
  },
  plugins: plugins,
  devServer: {
    port: 8080,
    host: '0.0.0.0',
    inline: true
  }
}
<commit_msg>caido: show path info on debug<commit_after>var path = require('path')
var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var loadersByExtension = require('./config/loadersByExtension')
var booleanFromEnv = require('./config/booleanFromEnv')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var AutoprefixerCore = require('autoprefixer-core')

var plugins = [
  new webpack.PrefetchPlugin('react'),
  new HtmlWebpackPlugin({
    inject: true,
    template: 'app/index.html'
  }),
  new webpack.PrefetchPlugin('react/lib/ReactComponentBrowserEnvironment'),
  new ExtractTextPlugin('[name].css', {
    allChunks: true
  }),
  new webpack.NoErrorsPlugin()
]

if (booleanFromEnv('WEBPACK_MINIMIZE_DEV', false)) {
  plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false
      }
    }),
    new webpack.optimize.DedupePlugin(),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    })
  )
}

module.exports = {
  entry: booleanFromEnv('WEBPACK_HOT_COMPONENTS', false) ? [
    'webpack-dev-server/client?http://0.0.0.0:3000', // WebpackDevServer host and port
    'webpack/hot/dev-server',
    './app/main'
  ] : './app/main',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
    sourceMapFilename: '[name].map',
    pathinfo: process.env.WEBPACK_DEBUG
  },
  module: {
    loaders: loadersByExtension({
      'jsx': booleanFromEnv('WEBPACK_HOT_COMPONENTS', false) ? ['react-hot', 'babel-loader?stage=0'] : 'babel-loader?stage=0',
      'js': {
        loader: 'babel-loader?stage=0',
        include: path.join(__dirname, 'app')
      },
      'json': 'json-loader',
      'json5': 'json5-loader',
      'txt': 'raw-loader',
      'png|jpg|jpeg|gif|svg': 'url-loader?limit=10000',
      'woff|woff2': 'url-loader?limit=100000',
      'ttf|eot': 'file-loader',
      'wav|mp3': 'file-loader',
      'html': 'html-loader',
      'md|markdown': ['html-loader', 'markdown-loader'],
      'css': ExtractTextPlugin.extract('style-loader', 'css-loader?module&importLoaders=1!postcss-loader'),
      'less': ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader'),
      'styl': ExtractTextPlugin.extract('style-loader', 'css-loader!stylus-loader')

    }),
    preloaders: loadersByExtension({
      'js': 'source-map-loader'
    })
  },
  postcss: [AutoprefixerCore],
  devtool: process.env.WEBPACK_DEVTOOL,
  debug: booleanFromEnv('WEBPACK_DEBUG', false),
  resolve: {
    root: path.join(__dirname, 'app'),
    extensions: ['', '.web.js', '.js', '.jsx']
  },
  plugins: plugins,
  devServer: {
    port: 8080,
    host: '0.0.0.0',
    inline: true
  }
}
<|endoftext|>


<commit_before>/*
 * Application state
 */
var World = {
  parser: PEG.buildParser(" \
    start = entity* \
    entity = (' ' / '\\n')* e:[^;\\n]+ (';' / '\\n' / !.) { return Query.parser.parse(e.join(''))[0] } \
  "),

  compile: function(state_source) {
    return World.parser.parse(state_source).map(function(predicates) {
      var entity = {}

      predicates.forEach(function(predicate) {
        if(predicate.simple) {
          entity[predicate.property] = true;

        } else if(predicate.infix) {
          switch(predicate.infix) {
            case '=': entity[predicate.property] = predicate.value; break;
            case '!=': if(predicate.value == undefined) entity[predicate.property] = true; break;
            default: console.warn("Ignoring unsupported infix operator '" + predicate.infix + "' in entity")
          }

        } else if(predicate.prefix) {
          console.warn("Ignoring unsupported prefix operator '" + predicate.prefix + "' in entity")

        }
      });

      return entity;
    });
  },

  state: [],

  add: function(state_source) {
    World.state = World.state.concat(World.compile(state_source));
  },

  remove: function(entity) {
    World.state.splice(World.state.indexOf(entity), 1)
  },

  clone: function(original) {
    var clone = World.spawn();
    for(var prop in original)
      clone[prop] = original[prop]
    return clone
  },

  spawn: function() {
    var newEntity = {};
    World.state.push(newEntity);
    return newEntity;
  },

  query: function(expression) {
    return World.state.filter(Query.compile(expression));
  },

  queryRaw: function(fn) {
    return World.state.filter(fn);
  }
}

var scripts = document.querySelectorAll("script[type='text/state']");
for (var i = 0; i < scripts.length; i++) {
  World.add( scripts[i].text.trim() )
};<commit_msg>Add World.queryFirst<commit_after>/*
 * Application state
 */
var World = {
  parser: PEG.buildParser(" \
    start = entity* \
    entity = (' ' / '\\n')* e:[^;\\n]+ (';' / '\\n' / !.) { return Query.parser.parse(e.join(''))[0] } \
  "),

  compile: function(state_source) {
    return World.parser.parse(state_source).map(function(predicates) {
      var entity = {}

      predicates.forEach(function(predicate) {
        if(predicate.simple) {
          entity[predicate.property] = true;

        } else if(predicate.infix) {
          switch(predicate.infix) {
            case '=': entity[predicate.property] = predicate.value; break;
            case '!=': if(predicate.value == undefined) entity[predicate.property] = true; break;
            default: console.warn("Ignoring unsupported infix operator '" + predicate.infix + "' in entity")
          }

        } else if(predicate.prefix) {
          console.warn("Ignoring unsupported prefix operator '" + predicate.prefix + "' in entity")

        }
      });

      return entity;
    });
  },

  state: [],

  add: function(state_source) {
    World.state = World.state.concat(World.compile(state_source));
  },

  remove: function(entity) {
    World.state.splice(World.state.indexOf(entity), 1)
  },

  clone: function(original) {
    var clone = World.spawn();
    for(var prop in original)
      clone[prop] = original[prop]
    return clone
  },

  spawn: function() {
    var newEntity = {};
    World.state.push(newEntity);
    return newEntity;
  },

  query: function(expression) {
    return World.state.filter(Query.compile(expression));
  },

  queryFirst: function(expression) {
    return World.query(expression)[0];
  },

  queryRaw: function(fn) {
    return World.state.filter(fn);
  }
}

var scripts = document.querySelectorAll("script[type='text/state']");
for (var i = 0; i < scripts.length; i++) {
  World.add( scripts[i].text.trim() )
};<|endoftext|>