<commit_before>const _ = require('lodash')
var Jexl = require('jexl')
const logging = require('homeautomation-js-lib/logging.js')
const metrics = require('homeautomation-js-lib/stats.js')
const moment = require('moment-timezone')
const feels = require('feels')

String.prototype.replaceAll = function(str1, str2) {
	return this.replace(new RegExp(str1, 'g'), str2)
//	return this.replace(new RegExp(str1.replace(/([/,!\\^${}[\]().*+?|<>\-&])/g, '\\$&'), (ignore?'gi':'g')), (typeof(str2)=='string')?str2.replace(/\$/g, '$$$$'):str2)
}

exports.jexl = function() {
	var jexl = new Jexl.Jexl()

	jexl.addTransform('split', function(val, char) {
		return val.split(char)
	})

	jexl.addTransform('words', function(val) {
		return _.words(val)
	})

	jexl.addTransform('lowercase', function(val) {
		return val.toLowerCase()
	})

	jexl.addTransform('uppercase', function(val) {
		return _.upperCase(val)
	})

	jexl.addTransform('uppercaseFirst', function(val) {
		return _.upperFirst(val)
	})

	jexl.addTransform('isNaN', function(val) {
		logging.info('is nan value: ' + val)
		return _.isNil(val) || val.toString().length == 0 || isNaN(val)
	})

	jexl.addTransform('camelCase', function(val) {
		return _.camelCase(val)
	})

	jexl.addTransform('startCase', function(val) {
		return _.startCase(val)
	})

	jexl.addTransform('feelsLikeAAT_C', function(temperature, humidity) {
		const config = {
			temp: Number(temperature),
			humidity: Number(humidity),
			units: {
				temp: 'c'
			}
		}

		let feelsLike
		try {
			feelsLike = new feels(config).like(['AAT'])
		} catch(err) {
			logging.error('err: ' + err)
		}

		return feelsLike
	})

	jexl.addTransform('trim', function(val) {
		return _.trim(val)
	})

	return jexl
}

exports.unique = function(list) {
	var result = []
	list.forEach(function(e) {
		if (!result.includes(e)) {
			result.push(e)
		}
	})
	return result
}

exports.update_topic_for_expression = function(topic) {
	if ( _.isNil(topic) ) {
		return 'empty_topic'
	}

	topic = topic.toString()

	topic = topic.replace(/(\/)(?=\w+)/g, '_')
	topic = topic.replace(/\b[-,+]\w+/g, '_')

	return topic
}

const replaceSpecialTokens = function(topic, expression, context) {
	if (_.isNil(context)) {
		logging.debug('no context for expression: ' + expression)
		return expression
	}

	var newExpression = expression

	if ( !_.isNil(context) && !_.isNil(context['TRIGGER_TOPIC']) ) {
		newExpression = newExpression.replaceAll('$TRIGGER_TOPIC', exports.update_topic_for_expression(context['TRIGGER_TOPIC']))
	}

	if ( !_.isNil(context) && !_.isNil(context['TRIGGER_TIME_UNIX']) ) {
		newExpression = newExpression.replaceAll('$TRIGGER_TIME_UNIX', exports.update_topic_for_expression(context['TRIGGER_TIME_UNIX']))
	}

	if ( !_.isNil(context) && !_.isNil(context['TRIGGER_TIME_STRING']) ) {
		newExpression = newExpression.replaceAll('$TRIGGER_TIME_STRING', exports.update_topic_for_expression(context['TRIGGER_TIME_STRING']))
	}

	if ( !_.isNil(context) && !_.isNil(context['TRIGGER_STRING']) ) {
		newExpression = newExpression.replaceAll('$TRIGGER_STRING', context['TRIGGER_STRING'])
	}

	return newExpression
}

exports.prepareExpression = function(topic, expression, context) {
	if (_.isNil(context)) {
		logging.debug('no context for expression: ' + expression)
		return expression
	}

	const variables = Object.keys(context).sort(function(a, b) {
		// ASC  -> a.length - b.length
		// DESC -> b.length - a.length
		return b.length - a.length
	})
	var newExpression = replaceSpecialTokens(topic, expression, context)

	variables.forEach(function(variable) {
		var value = context[variable]
		if (variable.length == 1) {
			return
		}

		if ( value == 'undefined' ) {
			value = 0
		}

		const numberValue = Number(value)

		if (_.isNumber(numberValue) && numberValue == value) {
			newExpression = newExpression.replaceAll(variable, value)
		}
	})

	return newExpression
}

exports.convertToNumberIfNeeded = function(value) {
	const numberValue = Number(value)
	if (!_.isNil(numberValue) && numberValue == value) {
		//logging.debug('converting string: ' + value + ' to number: ' + numberValue)
		if ( isFloat(numberValue) ) {
			return Number(numberValue.toFixed(2))
		}

		return numberValue
	}

	return value
}

exports.isValueAnExpression = function(value) {
	if ( _.isNil(value) ) {
		return false
	}

	value = value.toString()

	return (value.includes('/') || value.includes('?') || value.includes('+') || value.includes('-') || value.includes('*') || value.includes('$') || value.includes('|')  || value.includes('>')  || value.includes('<')  || value.includes('=') )
}

exports.getCurrentTimeZone = function() {
	var TIMEZONE = process.env.TIMEZONE

	if (_.isNil(TIMEZONE)) {
		TIMEZONE = process.env.TZ
	}

	if (_.isNil(TIMEZONE)) {
		TIMEZONE = moment.tz.guess()
	}

	if (_.isNil(TIMEZONE)) {
		TIMEZONE = 'UTC'
	}

	return TIMEZONE
}

const isFloat = function(n){
	return Number(n) === n && n % 1 !== 0
}

exports.resolveValueOrExpression = function(rule_name, topic, context, valueOrExpression, options){
	var quiet = false

	if ( !_.isNil(options) && !_.isNil(options.quiet) ) {
		quiet = options.quiet
	}

	return new Promise(function(resolve, reject){
		if (exports.isValueAnExpression(valueOrExpression)) {
			const publishExpression = exports.prepareExpression(topic, exports.update_topic_for_expression(valueOrExpression), context)

			if ( publishExpression[0] == '/') {
				resolve(replaceSpecialTokens(topic, valueOrExpression, context))
				return
			}

			var jexl = exports.jexl()
			const startTime = new Date()
			if ( !quiet && !global.isTestMode() ) {
				logging.debug('  start evaluating expression for =>(' + rule_name + ')' + '  context: ' + JSON.stringify({
					action: 'expression-evaluation-start',
					start_time: (startTime.getTime()),
					rule_name: rule_name,
					expression: publishExpression,
					topic: topic,
					value: valueOrExpression
				}))
			}

			jexl.eval(publishExpression, context).then(function(publishResult) {
				const evaluation_time = ((new Date().getTime()) - startTime.getTime())
				if ( isFloat(publishResult) ) {
					publishResult = Number(publishResult.toFixed(2))
				}

				if ( !quiet && !global.isTestMode() ) {
					logging.info('  => done evaluating: ' + rule_name + '  result: ' + publishResult + '  value: ' + valueOrExpression)

					logging.debug('  done evaluating expression for =>(' + rule_name + ')   context' +  JSON.stringify({
						action: 'expression-evaluation-done',
						evaluation_time: evaluation_time,
						rule_name: rule_name,
						expression: publishExpression,
						// context: context,
						result: publishResult,
						topic: topic,
						value: valueOrExpression
					}))
				}

				metrics.submit('jexl_evaluation_time', evaluation_time)
				resolve(publishResult)
			}).catch((catchError) => {
				logging.error(' *** Caught error while evaluating (' + rule_name + '): ' + catchError)
			})
		} else {
			resolve(valueOrExpression)
		}
	})
}
<commit_msg>Better replace again<commit_after>const _ = require('lodash')
var Jexl = require('jexl')
const logging = require('homeautomation-js-lib/logging.js')
const metrics = require('homeautomation-js-lib/stats.js')
const moment = require('moment-timezone')
const feels = require('feels')

String.prototype.replaceAll = function(searchStr, replaceStr) {
	var str = this

	// escape regexp special characters in search string
	searchStr = searchStr.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')

	return str.replace(new RegExp(searchStr, 'gi'), replaceStr)
}

// String.prototype.replaceAll = function(str1, str2) {
// 	return this.replace(new RegExp(str1, 'g'), str2)
// //	return this.replace(new RegExp(str1.replace(/([/,!\\^${}[\]().*+?|<>\-&])/g, '\\$&'), (ignore?'gi':'g')), (typeof(str2)=='string')?str2.replace(/\$/g, '$$$$'):str2)
// }

exports.jexl = function() {
	var jexl = new Jexl.Jexl()

	jexl.addTransform('split', function(val, char) {
		return val.split(char)
	})

	jexl.addTransform('words', function(val) {
		return _.words(val)
	})

	jexl.addTransform('lowercase', function(val) {
		return val.toLowerCase()
	})

	jexl.addTransform('uppercase', function(val) {
		return _.upperCase(val)
	})

	jexl.addTransform('uppercaseFirst', function(val) {
		return _.upperFirst(val)
	})

	jexl.addTransform('isNaN', function(val) {
		logging.info('is nan value: ' + val)
		return _.isNil(val) || val.toString().length == 0 || isNaN(val)
	})

	jexl.addTransform('camelCase', function(val) {
		return _.camelCase(val)
	})

	jexl.addTransform('startCase', function(val) {
		return _.startCase(val)
	})

	jexl.addTransform('feelsLikeAAT_C', function(temperature, humidity) {
		const config = {
			temp: Number(temperature),
			humidity: Number(humidity),
			units: {
				temp: 'c'
			}
		}

		let feelsLike
		try {
			feelsLike = new feels(config).like(['AAT'])
		} catch(err) {
			logging.error('err: ' + err)
		}

		return feelsLike
	})

	jexl.addTransform('trim', function(val) {
		return _.trim(val)
	})

	return jexl
}

exports.unique = function(list) {
	var result = []
	list.forEach(function(e) {
		if (!result.includes(e)) {
			result.push(e)
		}
	})
	return result
}

exports.update_topic_for_expression = function(topic) {
	if ( _.isNil(topic) ) {
		return 'empty_topic'
	}

	topic = topic.toString()

	topic = topic.replace(/(\/)(?=\w+)/g, '_')
	topic = topic.replace(/\b[-,+]\w+/g, '_')

	return topic
}

const replaceSpecialTokens = function(topic, expression, context) {
	if (_.isNil(context)) {
		logging.debug('no context for expression: ' + expression)
		return expression
	}

	var newExpression = expression

	if ( !_.isNil(context) && !_.isNil(context['TRIGGER_TOPIC']) ) {
		newExpression = newExpression.replaceAll('$TRIGGER_TOPIC', exports.update_topic_for_expression(context['TRIGGER_TOPIC']))
	}

	if ( !_.isNil(context) && !_.isNil(context['TRIGGER_TIME_UNIX']) ) {
		newExpression = newExpression.replaceAll('$TRIGGER_TIME_UNIX', exports.update_topic_for_expression(context['TRIGGER_TIME_UNIX']))
	}

	if ( !_.isNil(context) && !_.isNil(context['TRIGGER_TIME_STRING']) ) {
		newExpression = newExpression.replaceAll('$TRIGGER_TIME_STRING', exports.update_topic_for_expression(context['TRIGGER_TIME_STRING']))
	}

	if ( !_.isNil(context) && !_.isNil(context['TRIGGER_STRING']) ) {
		newExpression = newExpression.replaceAll('$TRIGGER_STRING', context['TRIGGER_STRING'])
	}

	return newExpression
}

exports.prepareExpression = function(topic, expression, context) {
	if (_.isNil(context)) {
		logging.debug('no context for expression: ' + expression)
		return expression
	}

	const variables = Object.keys(context).sort(function(a, b) {
		// ASC  -> a.length - b.length
		// DESC -> b.length - a.length
		return b.length - a.length
	})
	var newExpression = replaceSpecialTokens(topic, expression, context)

	variables.forEach(function(variable) {
		var value = context[variable]
		if (variable.length == 1) {
			return
		}

		if ( value == 'undefined' ) {
			value = 0
		}

		const numberValue = Number(value)

		if (_.isNumber(numberValue) && numberValue == value) {
			newExpression = newExpression.replaceAll(variable, value)
		}
	})

	return newExpression
}

exports.convertToNumberIfNeeded = function(value) {
	const numberValue = Number(value)
	if (!_.isNil(numberValue) && numberValue == value) {
		//logging.debug('converting string: ' + value + ' to number: ' + numberValue)
		if ( isFloat(numberValue) ) {
			return Number(numberValue.toFixed(2))
		}

		return numberValue
	}

	return value
}

exports.isValueAnExpression = function(value) {
	if ( _.isNil(value) ) {
		return false
	}

	value = value.toString()

	return (value.includes('/') || value.includes('?') || value.includes('+') || value.includes('-') || value.includes('*') || value.includes('$') || value.includes('|')  || value.includes('>')  || value.includes('<')  || value.includes('=') )
}

exports.getCurrentTimeZone = function() {
	var TIMEZONE = process.env.TIMEZONE

	if (_.isNil(TIMEZONE)) {
		TIMEZONE = process.env.TZ
	}

	if (_.isNil(TIMEZONE)) {
		TIMEZONE = moment.tz.guess()
	}

	if (_.isNil(TIMEZONE)) {
		TIMEZONE = 'UTC'
	}

	return TIMEZONE
}

const isFloat = function(n){
	return Number(n) === n && n % 1 !== 0
}

exports.resolveValueOrExpression = function(rule_name, topic, context, valueOrExpression, options){
	var quiet = false

	if ( !_.isNil(options) && !_.isNil(options.quiet) ) {
		quiet = options.quiet
	}

	return new Promise(function(resolve, reject){
		if (exports.isValueAnExpression(valueOrExpression)) {
			const publishExpression = exports.prepareExpression(topic, exports.update_topic_for_expression(valueOrExpression), context)

			if ( publishExpression[0] == '/') {
				resolve(replaceSpecialTokens(topic, valueOrExpression, context))
				return
			}

			var jexl = exports.jexl()
			const startTime = new Date()
			if ( !quiet && !global.isTestMode() ) {
				logging.debug('  start evaluating expression for =>(' + rule_name + ')' + '  context: ' + JSON.stringify({
					action: 'expression-evaluation-start',
					start_time: (startTime.getTime()),
					rule_name: rule_name,
					expression: publishExpression,
					topic: topic,
					value: valueOrExpression
				}))
			}

			jexl.eval(publishExpression, context).then(function(publishResult) {
				const evaluation_time = ((new Date().getTime()) - startTime.getTime())
				if ( isFloat(publishResult) ) {
					publishResult = Number(publishResult.toFixed(2))
				}

				if ( !quiet && !global.isTestMode() ) {
					logging.info('  => done evaluating: ' + rule_name + '  result: ' + publishResult + '  value: ' + valueOrExpression)

					logging.debug('  done evaluating expression for =>(' + rule_name + ')   context' +  JSON.stringify({
						action: 'expression-evaluation-done',
						evaluation_time: evaluation_time,
						rule_name: rule_name,
						expression: publishExpression,
						// context: context,
						result: publishResult,
						topic: topic,
						value: valueOrExpression
					}))
				}

				metrics.submit('jexl_evaluation_time', evaluation_time)
				resolve(publishResult)
			}).catch((catchError) => {
				logging.error(' *** Caught error while evaluating (' + rule_name + '): ' + catchError)
			})
		} else {
			resolve(valueOrExpression)
		}
	})
}
<|endoftext|>


<commit_before>import {Layout} from "./layout"

/**
 * The main Mesh class. The constructor will parse through the OBJ file data
 * and collect the vertex, vertex normal, texture, and face information. This
 * information can then be used later on when creating your VBOs. See
 * OBJ.initMeshBuffers for an example of how to use the newly created Mesh
 *
 * - Options
 *   - materials
 */
export class Mesh {
    /**
     * Create a Mesh
     * @param {String} objectData a string representation of an OBJ file with
     *        newlines preserved.
     * @param {Object} options a JS object containing valid options. See class
     *        documentation for options.
     */
    constructor(objectData, options) {
        options = options || {};
        options.materials = options.materials || [];

        let self = this;
        self.has_materials = !!options.materials;
        // maps material name to Material() instance
        self.materials = {};
        // the list of unique vertex, normal, texture, attributes
        self.vertices = [];
        self.vertexNormals = [];
        self.textures = [];
        // the indicies to draw the faces
        self.indices = [];

        /*
        The OBJ file format does a sort of compression when saving a model in a
        program like Blender. There are at least 3 sections (4 including textures)
        within the file. Each line in a section begins with the same string:
          * 'v': indicates vertex section
          * 'vn': indicates vertex normal section
          * 'f': indicates the faces section
          * 'vt': indicates vertex texture section (if textures were used on the model)
        Each of the above sections (except for the faces section) is a list/set of
        unique vertices.

        Each line of the faces section contains a list of
        (vertex, [texture], normal) groups.

        **Note:** The following documentation will use a capital "V" Vertex to
        denote the above (vertex, [texture], normal) groups whereas a lowercase
        "v" vertex is used to denote an X, Y, Z coordinate.

        Some examples:
            // the texture index is optional, both formats are possible for models
            // without a texture applied
            f 1/25 18/46 12/31
            f 1//25 18//46 12//31

            // A 3 vertex face with texture indices
            f 16/92/11 14/101/22 1/69/1

            // A 4 vertex face
            f 16/92/11 40/109/40 38/114/38 14/101/22

        The first two lines are examples of a 3 vertex face without a texture applied.
        The second is an example of a 3 vertex face with a texture applied.
        The third is an example of a 4 vertex face. Note: a face can contain N
        number of vertices.

        Each number that appears in one of the groups is a 1-based index
        corresponding to an item from the other sections (meaning that indexing
        starts at one and *not* zero).

        For example:
            `f 16/92/11` is saying to
              - take the 16th element from the [v] vertex array
              - take the 92nd element from the [vt] texture array
              - take the 11th element from the [vn] normal array
            and together they make a unique vertex.
        Using all 3+ unique Vertices from the face line will produce a polygon.

        Now, you could just go through the OBJ file and create a new vertex for
        each face line and WebGL will draw what appears to be the same model.
        However, vertices will be overlapped and duplicated all over the place.

        Consider a cube in 3D space centered about the origin and each side is
        2 units long. The front face (with the positive Z-axis pointing towards
        you) would have a Top Right vertex (looking orthogonal to its normal)
        mapped at (1,1,1) The right face would have a Top Left vertex (looking
        orthogonal to its normal) at (1,1,1) and the top face would have a Bottom
        Right vertex (looking orthogonal to its normal) at (1,1,1). Each face
        has a vertex at the same coordinates, however, three distinct vertices
        will be drawn at the same spot.

        To solve the issue of duplicate Vertices (the `(vertex, [texture], normal)`
        groups), while iterating through the face lines, when a group is encountered
        the whole group string ('16/92/11') is checked to see if it exists in the
        packed.hashindices object, and if it doesn't, the indices it specifies
        are used to look up each attribute in the corresponding attribute arrays
        already created. The values are then copied to the corresponding unpacked
        array (flattened to play nice with WebGL's ELEMENT_ARRAY_BUFFER indexing),
        the group string is added to the hashindices set and the current unpacked
        index is used as this hashindices value so that the group of elements can
        be reused. The unpacked index is incremented. If the group string already
        exists in the hashindices object, its corresponding value is the index of
        that group and is appended to the unpacked indices array.
       */
        const verts = [];
        const vertNormals = [];
        const textures = [];
        const unpacked = {};
        // keep track of what material we've seen last
        let currentMaterial;
        // unpacking stuff
        unpacked.verts = [];
        unpacked.norms = [];
        unpacked.textures = [];
        unpacked.hashindices = {};
        unpacked.indices = [];
        unpacked.index = 0;

        const VERTEX_RE = /^v\s/;
        const NORMAL_RE = /^vn\s/;
        const TEXTURE_RE = /^vt\s/;
        const FACE_RE = /^f\s/;
        const WHITESPACE_RE = /\s+/;
        const USE_MATERIAL_RE = /^usemtl/;

        // array of lines separated by the newline
        const lines = objectData.split('\n');

        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trim();
            const elements = line.split(WHITESPACE_RE);
            elements.shift();

            if (VERTEX_RE.test(line)) {
                // if this is a vertex
                verts.push(...elements);
            } else if (NORMAL_RE.test(line)) {
                // if this is a vertex normal
                vertNormals.push(...elements);
            } else if (TEXTURE_RE.test(line)) {
                // if this is a texture
                textures.push(...elements);
            } else if (USE_MATERIAL_RE.test(line)) {
                // keep track of the current material
                currentMaterial = elements[0];
            } else if (FACE_RE.test(line)) {
                // if this is a face
                /*
                split this face into an array of Vertex groups
                for example:
                   f 16/92/11 14/101/22 1/69/1
                becomes:
                  ['16/92/11', '14/101/22', '1/69/1'];
                */
                let quad = false;
                for (let j = 0, eleLen = elements.length; j < eleLen; j++) {
                    // Triangulating quads
                    // quad: 'f v0/t0/vn0 v1/t1/vn1 v2/t2/vn2 v3/t3/vn3/'
                    // corresponding triangles:
                    //      'f v0/t0/vn0 v1/t1/vn1 v2/t2/vn2'
                    //      'f v2/t2/vn2 v3/t3/vn3 v0/t0/vn0'
                    if (j === 3 && !quad) {
                        // add v2/t2/vn2 in again before continuing to 3
                        j = 2;
                        quad = true;
                    }
                    if (elements[j] in unpacked.hashindices) {
                        unpacked.indices.push(unpacked.hashindices[elements[j]]);
                    } else {
                        /*
                        Each element of the face line array is a Vertex which has its
                        attributes delimited by a forward slash. This will separate
                        each attribute into another array:
                            '19/92/11'
                        becomes:
                            Vertex = ['19', '92', '11'];
                        where
                            Vertex[0] is the vertex index
                            Vertex[1] is the texture index
                            Vertex[2] is the normal index
                         Think of faces having Vertices which are comprised of the
                         attributes location (v), texture (vt), and normal (vn).
                         */
                        let vertex = elements[j].split('/');
                        /*
                         The verts, textures, and vertNormals arrays each contain a
                         flattend array of coordinates.

                         Because it gets confusing by referring to Vertex and then
                         vertex (both are different in my descriptions) I will explain
                         what's going on using the vertexNormals array:

                         vertex[2] will contain the one-based index of the vertexNormals
                         section (vn). One is subtracted from this index number to play
                         nice with javascript's zero-based array indexing.

                         Because vertexNormal is a flattened array of x, y, z values,
                         simple pointer arithmetic is used to skip to the start of the
                         vertexNormal, then the offset is added to get the correct
                         component: +0 is x, +1 is y, +2 is z.

                         This same process is repeated for verts and textures.
                         */
                        // Vertex position
                        unpacked.verts.push(+verts[(vertex[0] - 1) * 3 + 0]);
                        unpacked.verts.push(+verts[(vertex[0] - 1) * 3 + 1]);
                        unpacked.verts.push(+verts[(vertex[0] - 1) * 3 + 2]);
                        // Vertex textures
                        if (textures.length) {
                            unpacked.textures.push(+textures[(vertex[1] - 1) * 2 + 0]);
                            unpacked.textures.push(+textures[(vertex[1] - 1) * 2 + 1]);
                        }
                        // Vertex normals
                        unpacked.norms.push(+vertNormals[(vertex[2] - 1) * 3 + 0]);
                        unpacked.norms.push(+vertNormals[(vertex[2] - 1) * 3 + 1]);
                        unpacked.norms.push(+vertNormals[(vertex[2] - 1) * 3 + 2]);
                        // add the newly created Vertex to the list of indices
                        unpacked.hashindices[elements[j]] = unpacked.index;
                        unpacked.indices.push(unpacked.index);
                        // increment the counter
                        unpacked.index += 1;
                    }
                    if (j === 3 && quad) {
                        // add v0/t0/vn0 onto the second triangle
                        unpacked.indices.push(unpacked.hashindices[elements[0]]);
                    }
                }
            }
        }
        self.vertices = unpacked.verts;
        self.vertexNormals = unpacked.norms;
        self.textures = unpacked.textures;
        self.indices = unpacked.indices;
    }

    /**
     * @param {Layout} layout - A {@link Layout} object that describes the
     * desired memory layout of the generated buffer
     * @return {ArrayBuffer} The packed array in the ... TODO
     */
    makeBufferData(layout) {
        const buffer = new ArrayBuffer(layout.stride * this.vertices.length/3);
        buffer.numItems = this.vertices.length / 3;
        const dataView = new DataView(buffer);
        for (let i = 0, offset = 0; i < buffer.numItems; i++) {
            // copy in the vertex data in the order and format given by the
            // layout param
            for (const attribute of layout.attributes) {
                switch (attribute.key) {
                    case Layout.POSITION.key:
                        dataView.setFloat32(offset, this.vertices[i * 3], true);
                        dataView.setFloat32(offset + 4, this.vertices[i * 3 + 1], true);
                        dataView.setFloat32(offset + 8, this.vertices[i * 3 + 2], true);
                        break;
                    case Layout.UV.key:
                        dataView.setFloat32(offset, this.textures[i * 2], true);
                        dataView.setFloat32(offset + 4, this.vertices[i * 2 + 1], true);
                        break;
                    case Layout.NORMAL.key:
                        dataView.setFloat32(offset, this.vertexNormals[i * 3], true);
                        dataView.setFloat32(offset + 4, this.vertexNormals[i * 3 + 1], true);
                        dataView.setFloat32(offset + 8, this.vertexNormals[i * 3 + 2], true);
                        break;
                }
                offset += attribute.sizeInBytes;
            }
        }
        return buffer;
    }
}
<commit_msg>Mesh: Add makeIndexBufferData method for indices<commit_after>import {Layout} from "./layout"

/**
 * The main Mesh class. The constructor will parse through the OBJ file data
 * and collect the vertex, vertex normal, texture, and face information. This
 * information can then be used later on when creating your VBOs. See
 * OBJ.initMeshBuffers for an example of how to use the newly created Mesh
 *
 * - Options
 *   - materials
 */
export class Mesh {
    /**
     * Create a Mesh
     * @param {String} objectData a string representation of an OBJ file with
     *        newlines preserved.
     * @param {Object} options a JS object containing valid options. See class
     *        documentation for options.
     */
    constructor(objectData, options) {
        options = options || {};
        options.materials = options.materials || [];

        let self = this;
        self.has_materials = !!options.materials;
        // maps material name to Material() instance
        self.materials = {};
        // the list of unique vertex, normal, texture, attributes
        self.vertices = [];
        self.vertexNormals = [];
        self.textures = [];
        // the indicies to draw the faces
        self.indices = [];

        /*
        The OBJ file format does a sort of compression when saving a model in a
        program like Blender. There are at least 3 sections (4 including textures)
        within the file. Each line in a section begins with the same string:
          * 'v': indicates vertex section
          * 'vn': indicates vertex normal section
          * 'f': indicates the faces section
          * 'vt': indicates vertex texture section (if textures were used on the model)
        Each of the above sections (except for the faces section) is a list/set of
        unique vertices.

        Each line of the faces section contains a list of
        (vertex, [texture], normal) groups.

        **Note:** The following documentation will use a capital "V" Vertex to
        denote the above (vertex, [texture], normal) groups whereas a lowercase
        "v" vertex is used to denote an X, Y, Z coordinate.

        Some examples:
            // the texture index is optional, both formats are possible for models
            // without a texture applied
            f 1/25 18/46 12/31
            f 1//25 18//46 12//31

            // A 3 vertex face with texture indices
            f 16/92/11 14/101/22 1/69/1

            // A 4 vertex face
            f 16/92/11 40/109/40 38/114/38 14/101/22

        The first two lines are examples of a 3 vertex face without a texture applied.
        The second is an example of a 3 vertex face with a texture applied.
        The third is an example of a 4 vertex face. Note: a face can contain N
        number of vertices.

        Each number that appears in one of the groups is a 1-based index
        corresponding to an item from the other sections (meaning that indexing
        starts at one and *not* zero).

        For example:
            `f 16/92/11` is saying to
              - take the 16th element from the [v] vertex array
              - take the 92nd element from the [vt] texture array
              - take the 11th element from the [vn] normal array
            and together they make a unique vertex.
        Using all 3+ unique Vertices from the face line will produce a polygon.

        Now, you could just go through the OBJ file and create a new vertex for
        each face line and WebGL will draw what appears to be the same model.
        However, vertices will be overlapped and duplicated all over the place.

        Consider a cube in 3D space centered about the origin and each side is
        2 units long. The front face (with the positive Z-axis pointing towards
        you) would have a Top Right vertex (looking orthogonal to its normal)
        mapped at (1,1,1) The right face would have a Top Left vertex (looking
        orthogonal to its normal) at (1,1,1) and the top face would have a Bottom
        Right vertex (looking orthogonal to its normal) at (1,1,1). Each face
        has a vertex at the same coordinates, however, three distinct vertices
        will be drawn at the same spot.

        To solve the issue of duplicate Vertices (the `(vertex, [texture], normal)`
        groups), while iterating through the face lines, when a group is encountered
        the whole group string ('16/92/11') is checked to see if it exists in the
        packed.hashindices object, and if it doesn't, the indices it specifies
        are used to look up each attribute in the corresponding attribute arrays
        already created. The values are then copied to the corresponding unpacked
        array (flattened to play nice with WebGL's ELEMENT_ARRAY_BUFFER indexing),
        the group string is added to the hashindices set and the current unpacked
        index is used as this hashindices value so that the group of elements can
        be reused. The unpacked index is incremented. If the group string already
        exists in the hashindices object, its corresponding value is the index of
        that group and is appended to the unpacked indices array.
       */
        const verts = [];
        const vertNormals = [];
        const textures = [];
        const unpacked = {};
        // keep track of what material we've seen last
        let currentMaterial;
        // unpacking stuff
        unpacked.verts = [];
        unpacked.norms = [];
        unpacked.textures = [];
        unpacked.hashindices = {};
        unpacked.indices = [];
        unpacked.index = 0;

        const VERTEX_RE = /^v\s/;
        const NORMAL_RE = /^vn\s/;
        const TEXTURE_RE = /^vt\s/;
        const FACE_RE = /^f\s/;
        const WHITESPACE_RE = /\s+/;
        const USE_MATERIAL_RE = /^usemtl/;

        // array of lines separated by the newline
        const lines = objectData.split('\n');

        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trim();
            const elements = line.split(WHITESPACE_RE);
            elements.shift();

            if (VERTEX_RE.test(line)) {
                // if this is a vertex
                verts.push(...elements);
            } else if (NORMAL_RE.test(line)) {
                // if this is a vertex normal
                vertNormals.push(...elements);
            } else if (TEXTURE_RE.test(line)) {
                // if this is a texture
                textures.push(...elements);
            } else if (USE_MATERIAL_RE.test(line)) {
                // keep track of the current material
                currentMaterial = elements[0];
            } else if (FACE_RE.test(line)) {
                // if this is a face
                /*
                split this face into an array of Vertex groups
                for example:
                   f 16/92/11 14/101/22 1/69/1
                becomes:
                  ['16/92/11', '14/101/22', '1/69/1'];
                */
                let quad = false;
                for (let j = 0, eleLen = elements.length; j < eleLen; j++) {
                    // Triangulating quads
                    // quad: 'f v0/t0/vn0 v1/t1/vn1 v2/t2/vn2 v3/t3/vn3/'
                    // corresponding triangles:
                    //      'f v0/t0/vn0 v1/t1/vn1 v2/t2/vn2'
                    //      'f v2/t2/vn2 v3/t3/vn3 v0/t0/vn0'
                    if (j === 3 && !quad) {
                        // add v2/t2/vn2 in again before continuing to 3
                        j = 2;
                        quad = true;
                    }
                    if (elements[j] in unpacked.hashindices) {
                        unpacked.indices.push(unpacked.hashindices[elements[j]]);
                    } else {
                        /*
                        Each element of the face line array is a Vertex which has its
                        attributes delimited by a forward slash. This will separate
                        each attribute into another array:
                            '19/92/11'
                        becomes:
                            Vertex = ['19', '92', '11'];
                        where
                            Vertex[0] is the vertex index
                            Vertex[1] is the texture index
                            Vertex[2] is the normal index
                         Think of faces having Vertices which are comprised of the
                         attributes location (v), texture (vt), and normal (vn).
                         */
                        let vertex = elements[j].split('/');
                        /*
                         The verts, textures, and vertNormals arrays each contain a
                         flattend array of coordinates.

                         Because it gets confusing by referring to Vertex and then
                         vertex (both are different in my descriptions) I will explain
                         what's going on using the vertexNormals array:

                         vertex[2] will contain the one-based index of the vertexNormals
                         section (vn). One is subtracted from this index number to play
                         nice with javascript's zero-based array indexing.

                         Because vertexNormal is a flattened array of x, y, z values,
                         simple pointer arithmetic is used to skip to the start of the
                         vertexNormal, then the offset is added to get the correct
                         component: +0 is x, +1 is y, +2 is z.

                         This same process is repeated for verts and textures.
                         */
                        // Vertex position
                        unpacked.verts.push(+verts[(vertex[0] - 1) * 3 + 0]);
                        unpacked.verts.push(+verts[(vertex[0] - 1) * 3 + 1]);
                        unpacked.verts.push(+verts[(vertex[0] - 1) * 3 + 2]);
                        // Vertex textures
                        if (textures.length) {
                            unpacked.textures.push(+textures[(vertex[1] - 1) * 2 + 0]);
                            unpacked.textures.push(+textures[(vertex[1] - 1) * 2 + 1]);
                        }
                        // Vertex normals
                        unpacked.norms.push(+vertNormals[(vertex[2] - 1) * 3 + 0]);
                        unpacked.norms.push(+vertNormals[(vertex[2] - 1) * 3 + 1]);
                        unpacked.norms.push(+vertNormals[(vertex[2] - 1) * 3 + 2]);
                        // add the newly created Vertex to the list of indices
                        unpacked.hashindices[elements[j]] = unpacked.index;
                        unpacked.indices.push(unpacked.index);
                        // increment the counter
                        unpacked.index += 1;
                    }
                    if (j === 3 && quad) {
                        // add v0/t0/vn0 onto the second triangle
                        unpacked.indices.push(unpacked.hashindices[elements[0]]);
                    }
                }
            }
        }
        self.vertices = unpacked.verts;
        self.vertexNormals = unpacked.norms;
        self.textures = unpacked.textures;
        self.indices = unpacked.indices;
    }

    /**
     * @param {Layout} layout - A {@link Layout} object that describes the
     * desired memory layout of the generated buffer
     * @return {ArrayBuffer} The packed array in the ... TODO
     */
    makeBufferData(layout) {
        const buffer = new ArrayBuffer(layout.stride * this.vertices.length/3);
        buffer.numItems = this.vertices.length / 3;
        const dataView = new DataView(buffer);
        for (let i = 0, offset = 0; i < buffer.numItems; i++) {
            // copy in the vertex data in the order and format given by the
            // layout param
            for (const attribute of layout.attributes) {
                switch (attribute.key) {
                    case Layout.POSITION.key:
                        dataView.setFloat32(offset, this.vertices[i * 3], true);
                        dataView.setFloat32(offset + 4, this.vertices[i * 3 + 1], true);
                        dataView.setFloat32(offset + 8, this.vertices[i * 3 + 2], true);
                        break;
                    case Layout.UV.key:
                        dataView.setFloat32(offset, this.textures[i * 2], true);
                        dataView.setFloat32(offset + 4, this.vertices[i * 2 + 1], true);
                        break;
                    case Layout.NORMAL.key:
                        dataView.setFloat32(offset, this.vertexNormals[i * 3], true);
                        dataView.setFloat32(offset + 4, this.vertexNormals[i * 3 + 1], true);
                        dataView.setFloat32(offset + 8, this.vertexNormals[i * 3 + 2], true);
                        break;
                }
                offset += attribute.sizeInBytes;
            }
        }
        return buffer;
    }

    makeIndexBufferData() {
        const buffer = new Uint16Array(this.indices);
        buffer.numItems = this.indices.length;
        return buffer;
    }
}
<|endoftext|>


<commit_before>/**
 * Controllers setups up routers for Express
 */

/**
 * Dependencies
 */
var fs = require('fs');
var path = require('path');

/**
 * Loads any matching modules in a directory
 *
 * @param     {String}    dir    path to load from
 * @param     {String}    search (optional) search
 *
* @return    {Object}
 */
module.exports.loadModules = function(dir, search) {
  /**
   * Resolve any relative paths
   *
   * @type    {String}
   */
  dir = path.resolve(dir);

  /**
   * Interal collection of modules loaded
   *
   * @type    {Object}
   */
  var modules = {};

  /**
   * Get a list of items in a directory. This is synchronous since we're
   * only doing this once on application load
   *
   * @type    {Array}
   */
  var list = fs.readdirSync(dir);

  /**
   * The search suffic is optional
   */
  if (typeof search !== 'string') {
    search = '';
  }

  /**
   * Setup the Regex to filter out *.js files and optionall the {search}
   *
   * @type    {RegExp}
   */
  search = new RegExp('(.*' + search + ')\\.js');

  /**
   * Cycle through each item in the directory
   */
  list.forEach(function(module) {

    /**
     * Check to see if with have a match and if we split it apartment
     *
     * @type    {Array}
     */
    module = module.match(search);
    if (module) {
      /**
       * If we find a match try to load and save it. Otherwise log an error but
       * don't crash the application.
       */
      try {
        modules[module[1]] = require(dir + '/' + module[1]);
      } catch (err) {
        console.error('Unable to load ' + dir + '/' + module[1], err);
      }
    }
  });

  return modules;
};

/**
 * Match JS Comments
 *
 * @type    {RegExp}
 */
var jsComments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

/**
 * Match function arguments
 *
 * @type    {RegExp}
 */
var functionArguments = /function.*\((.*)\)/;

/**
 * Extract Argument Names
 *
 * @param     {Function}    fn    function to get argument names of
 *
 * @return    {Array}
 */
module.exports.getArgNames = function(fn) {
  if (typeof fn !== 'function') {
    throw 'fn is not a function';
  }

  // Convert to string
  var fnString = fn.toString();

  // Strip comments
  fnString = fnString.replace(jsComments, '');

  // Find args
  var args = fnString.match(functionArguments);

  // Default result
  var params = [];

  // Split arguments into array
  if (args && args[1]) {
    params = args[1].split(',');
  }

  return params.map(function(arg) {
    return arg.trim(); // Trim extra space
  });
};
<commit_msg>added a check if a dir exists and adjusted formatting<commit_after>/**
 * Utilities
 */

/**
 * Dependencies
 */
var fs = require('fs');
var path = require('path');

/**
 * Loads any matching modules in a directory
 *
 * @param     {String}    dir    path to load from
 * @param     {String}    search (optional) search
 *
* @return    {Object}
 */
module.exports.loadModules = function(dir, search) {
  /**
   * Resolve any relative paths
   *
   * @type    {String}
   */
  dir = path.resolve(dir);

  /**
   * Interal collection of modules loaded
   *
   * @type    {Object}
   */
  var modules = {};

  /**
   * Check to see if the directory exists
   */
  if (fs.existsSync(dir) !== true) { return modules; }

  /**
   * Get a list of items in a directory. This is synchronous since we're
   * only doing this once on application load
   *
   * @type    {Array}
   */
  var list = fs.readdirSync(dir);

  /**
   * The search suffic is optional
   */
  if (typeof search !== 'string') { search = ''; }

  /**
   * Setup the Regex to filter out *.js files and optionall the {search}
   *
   * @type    {RegExp}
   */
  search = new RegExp('(.*' + search + ')\\.js');

  /**
   * Cycle through each item in the directory
   */
  list.forEach(function(module) {

    /**
     * Check to see if with have a match and if we split it apartment
     *
     * @type    {Array}
     */
    module = module.match(search);
    if (module) {
      /**
       * If we find a match try to load and save it. Otherwise log an error but
       * don't crash the application.
       */
      try {
        modules[module[1]] = require(dir + '/' + module[1]);
      } catch (err) {
        console.error('Unable to load ' + dir + '/' + module[1], err);
      }
    }
  });

  return modules;
};

/**
 * Match JS Comments
 *
 * @type    {RegExp}
 */
var jsComments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

/**
 * Match function arguments
 *
 * @type    {RegExp}
 */
var functionArguments = /function.*\((.*)\)/;

/**
 * Extract Argument Names
 *
 * @param     {Function}    fn    function to get argument names of
 *
 * @return    {Array}
 */
module.exports.getArgNames = function(fn) {
  if (typeof fn !== 'function') {
    throw 'fn is not a function';
  }

  // Convert to string
  var fnString = fn.toString();

  // Strip comments
  fnString = fnString.replace(jsComments, '');

  // Find args
  var args = fnString.match(functionArguments);

  // Default result
  var params = [];

  // Split arguments into array
  if (args && args[1]) {
    params = args[1].split(',');
  }

  return params.map(function(arg) {
    return arg.trim(); // Trim extra space
  });
};
<|endoftext|>


<commit_before>var log4js = require('log4js');
var logger = log4js.getLogger();

module.exports = {
  getChats,
  addUserMessage,
  addMachineMessage,
  addVideoMessage
};

function getChats() {
  return Chat.find();
}

function addUserMessage(chat) {
  chat.time = new Date(chat.time).toISOString();
  addMessage(chat);
}

function addMachineMessage(message, username) {
  var chat = {
    message: message,
    type: 'machine',
    username: username,
    time: new Date().toISOString()
  };
  addMessage(chat);
}

function addVideoMessage(message) {
  var chat = {
    message: message,
    type: 'video',
    time: new Date().toISOString()
  };
  addMessage(chat);
}

function addMessage(chat) {
  Chat.create(chat)
    .then((c) => {
      sails.io.sockets.in('chatting').emit('chat', c);
    })
    .catch((e) => logger.warning(e));
}
<commit_msg>Fix incorrect logging method<commit_after>var log4js = require('log4js');
var logger = log4js.getLogger();

module.exports = {
  getChats,
  addUserMessage,
  addMachineMessage,
  addVideoMessage
};

function getChats() {
  return Chat.find();
}

function addUserMessage(chat) {
  chat.time = new Date(chat.time).toISOString();
  addMessage(chat);
}

function addMachineMessage(message, username) {
  var chat = {
    message: message,
    type: 'machine',
    username: username,
    time: new Date().toISOString()
  };
  addMessage(chat);
}

function addVideoMessage(message) {
  var chat = {
    message: message,
    type: 'video',
    time: new Date().toISOString()
  };
  addMessage(chat);
}

function addMessage(chat) {
  Chat.create(chat)
    .then((c) => {
      sails.io.sockets.in('chatting').emit('chat', c);
    })
    .catch((e) => logger.warn(e));
}
<|endoftext|>


<commit_before>'use strict';

const Handler = require('../models/BaseEventHandler');

let guildCheck = null; // I don't know where to put this

/**
 * Sends a DM to the owners of all guilds over the ratio
 * @param {Bot} self the bot
 */
async function guildLeave(self) {
  const [results] = await self.settings.getGuildRatios(self.bot.shards) || [];
  if (!results) return;

  const guilds = results.slice(0, 5);
  const owners = {};
  try {
    guilds.forEach((row) => {
      if (self.client.guilds.cache.has(row.guild_id)) {
        if (owners[row.owner_id]) {
          owners[row.owner_id].message += `, **${self.client.guilds.cache.get(row.guild_id).name}**`;
          owners[row.owner_id].guilds.push(row.guild_id);
        } else {
          owners[row.owner_id] = {
            message: `**${self.client.guilds.cache.get(row.guild_id).name}**`,
            guilds: [row.guild_id],
          };
        }
      }
    });
    Object.keys(owners).forEach(async (id) => {
      const user = await self.client.users.fetch(id);
      if (user) {
        try {
          await user.send(`Your guild${owners[id].guilds.length > 1 ? 's' : ''}
          ${owners[id].message} are over the bot-to-user ratio.
          ${self.client.user.username} will now leave.
          If you want to keep using Genesis please invite more people or kick some bots.`);
        } catch (e) {
          // swallow, it's not an important error
        }
      }
      owners[id].guilds.forEach((guild) => {
        if (guild && self.client.guilds.has(guild)) {
          self.client.guilds.get(guild).leave();
        }
      });
    });
    if ((results.length - 5) <= 0) {
      self.logger.silly('No more guilds in "guild_ratio" clearing interval.');
      clearInterval(guildCheck);
    }
  } catch (e) {
    self.bot.logger.error(e);
  }
}

/**
 * Checks the ratio of all guilds on the shard
 * @param {Bot} self the bot
 */
function guildRatioCheck(self) {
  const guilds = self.client.guilds.cache.filter((guild) => {
    self.logger.silly(`Checking Guild: ${guild.name} (${guild.id}) Owner: ${guild.ownerID}`);
    const bots = guild.members.cache.filter(user => user.user.bot);
    return ((bots.size / guild.memberCount) * 100) >= 80;
  });

  guilds.forEach((guild) => {
    self.settings.addGuildRatio(guild.shard, guild);
  });

  guildCheck = setInterval(guildLeave, 10000, self);
}

/**
 * Describes a handler
 */
class OnReadyHandle extends Handler {
  /**
   * Base class for bot commands
   * @param {Genesis} bot  The bot object
   * @param {string}  id   The command's unique id
   * @param {string}  event Event to trigger this handler
   */
  constructor(bot) {
    super(bot, 'handlers.ready.ratio', 'ready');
  }

  /**
   * Run the ready handle
   */
  async execute() {
    this.logger.silly(`Running ${this.id} for ${this.event}`);
    setTimeout(guildRatioCheck, 4000, this);
  }
}

module.exports = OnReadyHandle;
<commit_msg>chore: more djs12 cache fixes<commit_after>'use strict';

const Handler = require('../models/BaseEventHandler');

let guildCheck = null; // I don't know where to put this

/**
 * Sends a DM to the owners of all guilds over the ratio
 * @param {Bot} self the bot
 */
async function guildLeave(self) {
  const [results] = await self.settings.getGuildRatios(self.bot.shards) || [];
  if (!results) return;

  const guilds = results.slice(0, 5);
  const owners = {};
  try {
    guilds.forEach((row) => {
      if (self.client.guilds.cache.has(row.guild_id)) {
        if (owners[row.owner_id]) {
          owners[row.owner_id].message += `, **${self.client.guilds.cache.get(row.guild_id).name}**`;
          owners[row.owner_id].guilds.push(row.guild_id);
        } else {
          owners[row.owner_id] = {
            message: `**${self.client.guilds.cache.get(row.guild_id).name}**`,
            guilds: [row.guild_id],
          };
        }
      }
    });
    Object.keys(owners).forEach(async (id) => {
      const user = await self.client.users.cache.fetch(id);
      if (user) {
        try {
          await user.send(`Your guild${owners[id].guilds.length > 1 ? 's' : ''}
          ${owners[id].message} are over the bot-to-user ratio.
          ${self.client.user.username} will now leave.
          If you want to keep using Genesis please invite more people or kick some bots.`);
        } catch (e) {
          // swallow, it's not an important error
        }
      }
      owners[id].guilds.forEach((guild) => {
        if (guild && self.client.guilds.cache.has(guild)) {
          self.client.guilds.cache.get(guild).leave();
        }
      });
    });
    if ((results.length - 5) <= 0) {
      self.logger.silly('No more guilds in "guild_ratio" clearing interval.');
      clearInterval(guildCheck);
    }
  } catch (e) {
    self.bot.logger.error(e);
  }
}

/**
 * Checks the ratio of all guilds on the shard
 * @param {Bot} self the bot
 */
function guildRatioCheck(self) {
  const guilds = self.client.guilds.cache.filter((guild) => {
    self.logger.silly(`Checking Guild: ${guild.name} (${guild.id}) Owner: ${guild.ownerID}`);
    const bots = guild.members.cache.filter(user => user.user.bot);
    return ((bots.size / guild.memberCount) * 100) >= 80;
  });

  guilds.forEach((guild) => {
    self.settings.addGuildRatio(guild.shard, guild);
  });

  guildCheck = setInterval(guildLeave, 10000, self);
}

/**
 * Describes a handler
 */
class OnReadyHandle extends Handler {
  /**
   * Base class for bot commands
   * @param {Genesis} bot  The bot object
   * @param {string}  id   The command's unique id
   * @param {string}  event Event to trigger this handler
   */
  constructor(bot) {
    super(bot, 'handlers.ready.ratio', 'ready');
  }

  /**
   * Run the ready handle
   */
  async execute() {
    this.logger.silly(`Running ${this.id} for ${this.event}`);
    setTimeout(guildRatioCheck, 4000, this);
  }
}

module.exports = OnReadyHandle;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Alert, Icon, Tabs } from 'antd';
import history from '../../common/history';
import { ElementDiagram } from '../diagram';
import { colors } from '../common';
import { CodeEditor, ElementTabs } from './';

const TabPane = Tabs.TabPane;

export class ElementPage extends Component {
  static propTypes = {
    home: PropTypes.object.isRequired,
    router: PropTypes.object.isRequired,
    match: PropTypes.object.isRequired,
    dispatch: PropTypes.func.isRequired,
  };

  static defaultProps = {
  };

  constructor(props) {
    super(props);
    this.state.editAreaSize = this.getEditAreaSize();
  }

  state = {
    codeChanged: false,
    editAreaSize: null,
  };

  componentWillMount() {
    // Ensure the tab is opened.
    window.addEventListener('resize', this.handleWindowResize);

    // This is used to show tab when refresh the element page.
    this.props.dispatch({
      type: '@@router/LOCATION_CHANGE',
      payload: this.props.router.location,
    });
  }

  componentDidMount() {
    document.getElementById('page-container').className = 'page-container full-screen';
  }

  componentWillUnmount() {
    document.getElementById('page-container').className = 'page-container';
    window.removeEventListener('resize', this.handleWindowResize);
  }

  getPageContainer() {
    return document.getElementById('page-container');
  }

  getElementData() {
    const { elementById, projectRoot } = this.props.home;
    let { file } = this.props.match.params;
    if (!file) return null;
    file = decodeURIComponent(file);
    const fullPath = projectRoot + file;
    const arr = fullPath.split('.');
    const ext = arr.length > 1 ? arr.pop() : null;
    const ele = elementById[file];

    if (!ele) return null;

    return {
      ...ele,
      hasDiagram: /^(js|jsx)$/.test(ext),
      hasTest: ele.feature && /^(js|jsx)$/.test(ext),
      hasCode: /^(js|jsx|html|css|less|scss|txt|json|sass|md|log|pl|py|sh|cmd)$/.test(ext),
      isPic: /^(jpe?g|png|gif|bmp)$/.test(ext),
    };
    // return _.find(featureById[feature].components, { name: ele.name });
  }

  getEditAreaSize() {
    return {
      width: document.body.offsetWidth,
      height: document.body.offsetHeight,
    };
  }

  handleWindowResize = () => {
    this.setState({
      editAreaSize: this.getEditAreaSize(),
    });
  }

  handleCodeChange = (args) => {
    this.setState({
      codeChanged: args.hasChange,
    });
  }

  handleTabChange = (tabKey) => {
    const file = decodeURIComponent(this.props.match.params.file);
    history.push(`/element/${encodeURIComponent(file)}/${tabKey}`);
  }

  handleRunTest = () => {
    const { file } = this.props.match.params;
    history.push(`/tools/tests/${encodeURIComponent(file)}`);
  }

  renderNotFound() {
    return (

Element not found, please check the URL or if element exists.

    );
  }

  renderMarks() {
    const data = this.getElementData();
    if (!data.feature) return null;
    const { featureById } = this.props.home;
    const markDescription = {
      a: 'Async action',
      c: 'Connected to Redux store',
      r: 'Mapped to an URL path',
    };
    const marks = [];
    switch (data.type) {
      case 'component':
        if (data.connectToStore) marks.push('C');
        if (_.find(featureById[data.feature].routes, { component: data.name })) marks.push('R');
        break;
      case 'action':
        if (data.isAsync) marks.push('A');
        break;
      default:
        break;
    }
    return marks.map(mark => (
      <span
        key={mark}
        title={markDescription[mark.toLowerCase()]}
        className={`mark mark-${mark.toLowerCase()}`}
      >{mark}
    ));
  }

  render() {
    const data = this.getElementData();
    if (!data) {
      return this.renderNotFound();
    }

    const { home } = this.props;
    const onlyCode = data.hasCode && !data.hasDiagram && !data.hasTest;

    let codeFile;
    let tabKey = this.props.match.params.type || (onlyCode ? 'code' : 'diagram');

    if (!data.hasCode) tabKey = 'diagram';
    if (onlyCode) tabKey = 'code';
    if (tabKey === 'style' && (data.type !== 'component' || !data.feature)) tabKey = 'diagram';
    switch (tabKey) {
      case 'code':
        codeFile = data.file;
        break;
      case 'style':
        codeFile = `src/features/${data.feature}/${data.name}.${home.cssExt}`;
        break;
      case 'test':
        codeFile = `tests/${decodeURIComponent(this.props.match.params.file).replace(/^src\//, '').replace('.js', '')}.test.js`;
        break;
      default:
        codeFile = data.file;
        break;
    }

    const iconTypes = {
      component: 'appstore-o',
      action: 'notification',
      misc: 'file',
    };

    const arr = data.file.split('.');
    const ext = arr.length > 1 ? arr.pop() : null;

    // const title = data.feature ? `${data.feature} / ${data.name}` : data.file;
    const codeChangeMark = this.state.codeChanged ? ' *' : '';
    return (

        {data.isPic &&

          <img src={`/${codeFile}`} alt={codeFile} />

}
        {!onlyCode && data.hasCode && !data.isPic &&
        <Tabs activeKey={tabKey} animated={false} onChange={this.handleTabChange}>
          {data.hasDiagram &&
          <TabPane tab="Diagram" key="diagram">
            <ElementDiagram homeStore={this.props.home} elementId={data.file} size={this.state.editAreaSize} />
          </TabPane>}
          {data.hasCode && <TabPane tab={`Code${tabKey === 'code' ? codeChangeMark : ''}`} key="code" />}
          {(data.type === 'component' && data.feature) && <TabPane tab={`Style${tabKey === 'style' ? codeChangeMark : ''}`} key="style" />}
          {data.hasTest && <TabPane tab={`Test${tabKey === 'test' ? codeChangeMark : ''}`} key="test" />}
        </Tabs>}
        {tabKey !== 'diagram' && data.hasCode && <CodeEditor file={codeFile} onStateChange={this.handleCodeChange} onRunTest={data.hasTest && tabKey === 'test' ? this.handleRunTest : null} />}
        {!data.hasCode && !data.isPic && <Alert type="error" showIcon message={`The file with extension ".${ext}" will not be displayed because it is either binary, very large or uses an unsupported text encoding.`} />}

    );
  }
}

/* istanbul ignore next */
function mapStateToProps(state) {
  return {
    home: state.home,
    router: state.router,
  };
}

/* istanbul ignore next */
function mapDispatchToProps(dispatch) {
  return {
    dispatch,
    actions: bindActionCreators({ }, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ElementPage);
<commit_msg>Hide test tab when element has no test.<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Alert, Icon, Tabs } from 'antd';
import history from '../../common/history';
import { ElementDiagram } from '../diagram';
import { colors } from '../common';
import { CodeEditor, ElementTabs } from './';

const TabPane = Tabs.TabPane;

export class ElementPage extends Component {
  static propTypes = {
    home: PropTypes.object.isRequired,
    router: PropTypes.object.isRequired,
    match: PropTypes.object.isRequired,
    dispatch: PropTypes.func.isRequired,
  };

  static defaultProps = {
  };

  constructor(props) {
    super(props);
    this.state.editAreaSize = this.getEditAreaSize();
  }

  state = {
    codeChanged: false,
    editAreaSize: null,
  };

  componentWillMount() {
    // Ensure the tab is opened.
    window.addEventListener('resize', this.handleWindowResize);

    // This is used to show tab when refresh the element page.
    this.props.dispatch({
      type: '@@router/LOCATION_CHANGE',
      payload: this.props.router.location,
    });
  }

  componentDidMount() {
    document.getElementById('page-container').className = 'page-container full-screen';
  }

  componentWillUnmount() {
    document.getElementById('page-container').className = 'page-container';
    window.removeEventListener('resize', this.handleWindowResize);
  }

  getPageContainer() {
    return document.getElementById('page-container');
  }

  getElementData() {
    const { elementById, projectRoot } = this.props.home;
    let { file } = this.props.match.params;
    if (!file) return null;
    file = decodeURIComponent(file);
    const fullPath = projectRoot + file;
    const arr = fullPath.split('.');
    const ext = arr.length > 1 ? arr.pop() : null;
    const ele = elementById[file];

    if (!ele) return null;

    return {
      ...ele,
      hasDiagram: /^(js|jsx)$/.test(ext),
      hasTest: ele.hasTest,
      hasCode: /^(js|jsx|html|css|less|scss|txt|json|sass|md|log|pl|py|sh|cmd)$/.test(ext),
      isPic: /^(jpe?g|png|gif|bmp)$/.test(ext),
    };
    // return _.find(featureById[feature].components, { name: ele.name });
  }

  getEditAreaSize() {
    return {
      width: document.body.offsetWidth,
      height: document.body.offsetHeight,
    };
  }

  handleWindowResize = () => {
    this.setState({
      editAreaSize: this.getEditAreaSize(),
    });
  }

  handleCodeChange = (args) => {
    this.setState({
      codeChanged: args.hasChange,
    });
  }

  handleTabChange = (tabKey) => {
    const file = decodeURIComponent(this.props.match.params.file);
    history.push(`/element/${encodeURIComponent(file)}/${tabKey}`);
  }

  handleRunTest = () => {
    const { file } = this.props.match.params;
    history.push(`/tools/tests/${encodeURIComponent(file)}`);
  }

  renderNotFound() {
    return (

Element not found, please check the URL or if element exists.

    );
  }

  renderMarks() {
    const data = this.getElementData();
    if (!data.feature) return null;
    const { featureById } = this.props.home;
    const markDescription = {
      a: 'Async action',
      c: 'Connected to Redux store',
      r: 'Mapped to an URL path',
    };
    const marks = [];
    switch (data.type) {
      case 'component':
        if (data.connectToStore) marks.push('C');
        if (_.find(featureById[data.feature].routes, { component: data.name })) marks.push('R');
        break;
      case 'action':
        if (data.isAsync) marks.push('A');
        break;
      default:
        break;
    }
    return marks.map(mark => (
      <span
        key={mark}
        title={markDescription[mark.toLowerCase()]}
        className={`mark mark-${mark.toLowerCase()}`}
      >{mark}
    ));
  }

  render() {
    const data = this.getElementData();
    if (!data) {
      return this.renderNotFound();
    }

    const { home } = this.props;
    const onlyCode = data.hasCode && !data.hasDiagram && !data.hasTest;

    let codeFile;
    let tabKey = this.props.match.params.type || (onlyCode ? 'code' : 'diagram');

    if (!data.hasCode) tabKey = 'diagram';
    if (onlyCode) tabKey = 'code';
    if (tabKey === 'style' && (data.type !== 'component' || !data.feature)) tabKey = 'diagram';
    switch (tabKey) {
      case 'code':
        codeFile = data.file;
        break;
      case 'style':
        codeFile = `src/features/${data.feature}/${data.name}.${home.cssExt}`;
        break;
      case 'test':
        codeFile = `tests/${decodeURIComponent(this.props.match.params.file).replace(/^src\//, '').replace('.js', '')}.test.js`;
        break;
      default:
        codeFile = data.file;
        break;
    }

    const iconTypes = {
      component: 'appstore-o',
      action: 'notification',
      misc: 'file',
    };

    const arr = data.file.split('.');
    const ext = arr.length > 1 ? arr.pop() : null;

    // const title = data.feature ? `${data.feature} / ${data.name}` : data.file;
    const codeChangeMark = this.state.codeChanged ? ' *' : '';
    return (

        {data.isPic &&

          <img src={`/${codeFile}`} alt={codeFile} />

}
        {!onlyCode && data.hasCode && !data.isPic &&
        <Tabs activeKey={tabKey} animated={false} onChange={this.handleTabChange}>
          {data.hasDiagram &&
          <TabPane tab="Diagram" key="diagram">
            <ElementDiagram homeStore={this.props.home} elementId={data.file} size={this.state.editAreaSize} />
          </TabPane>}
          {data.hasCode && <TabPane tab={`Code${tabKey === 'code' ? codeChangeMark : ''}`} key="code" />}
          {(data.type === 'component' && data.feature) && <TabPane tab={`Style${tabKey === 'style' ? codeChangeMark : ''}`} key="style" />}
          {data.hasTest && <TabPane tab={`Test${tabKey === 'test' ? codeChangeMark : ''}`} key="test" />}
        </Tabs>}
        {tabKey !== 'diagram' && data.hasCode && <CodeEditor file={codeFile} onStateChange={this.handleCodeChange} onRunTest={data.hasTest && tabKey === 'test' ? this.handleRunTest : null} />}
        {!data.hasCode && !data.isPic && <Alert type="error" showIcon message={`The file with extension ".${ext}" will not be displayed because it is either binary, very large or uses an unsupported text encoding.`} />}

    );
  }
}

/* istanbul ignore next */
function mapStateToProps(state) {
  return {
    home: state.home,
    router: state.router,
  };
}

/* istanbul ignore next */
function mapDispatchToProps(dispatch) {
  return {
    dispatch,
    actions: bindActionCreators({ }, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ElementPage);
<|endoftext|>


<commit_before>/*
 * match.js -- utility functions for matches and match lists
 * requires jquery
 **/

var courier = courier || {};

courier.match = (function() { // begin courier namespace

/* ------------------ Robots --------------------- */
function Robot () {
  var r = arguments[0];
  if (typeof r == 'object' && r !== null) {
    this.name = r.name;
    this.armor = r.armor;
    this.heat = r.heat;
  }
  this.jq = $("<div>");
}

/* ------------------ Match --------------------- */
function Match(id) {
  // Represents a match.
  this.mid = id;
  this.url = "/matches/" + id;
  this.populating = false;
}
Match.prototype.populate = function(stream, cb) {
  // get information about this match and run the callback.
  if (this.populating) {
    return false;
  }
  this.populating = true;
  var self = this;
  courier.core.ajaxRequest(this.url, {info: true},
    function(minfo){
      self.initTime = minfo.init_time;
      self.started = minfo.started;
      self['private'] = minfo['private'];
      self.robots = [];
      for (var i = 0,l = minfo.robots.length; i < l; i++) {
        self.robots[i] = new Robot(minfo.robots[i]);
      }
      if (typeof cb == 'function') {
        cb(self);
      }
      if (stream) {
        self.beginStream(minfo.history);
      }
      self.populating = false;
    });
};
Match.prototype.beginStream = function(time) {
  var self = this;
  this.sh = new courier.core.StreamingHistory(this.url + "?history=t",
      time,
      function(action) {
        if (action.hasOwnProperty('field') &&
            typeof self.onFieldUpdateCb == 'function') {
          self.onFieldUpdateCb(action.field);
        } else if (action.hasOwnProperty('hit') &&
                   typeof self.onHitCb == 'function') {
          self.onHitCb(action.hit.obj, action.hit.location);
        } else if (action.hasOwnProperty('splash_damage') &&
                   typeof self.onSplashCb == 'function') {
          self.onSplashCb(action.splash_damage.objects,
                          action.splash_damage.location,
                          action.splash_damage.damage);
        } else if (action.hasOwnProperty('remove_robot') &&
                   typeof self.onRemoveRobotCb == 'function') {
          self.onRemoveRobotCb(new Robot(action.remove_robot));
        } else if (action.hasOwnProperty('disconnect_robot') &&
                   typeof self.onDisconnectRobotCb == 'function') {
          self.onDisconnectRobotCb(new Robot(action.disconnect_robot));
        } else if (action.hasOwnProperty('connected_robot') &&
                   typeof self.onConnectedRobotCb == 'function') {
          self.onConnectedRobotCb(new Robot(action.connected_robot));
        } else if (action.hasOwnProperty('new_slot') &&
                   typeof self.onNewSlotCb == 'function') {
          self.onNewSlotCb(new Robot(action.new_slot));
        } else if (action.hasOwnProperty('match_started') &&
                   typeof self.onMatchStarted == 'function') {
          self.onMatchStarted();
        }
      });
};
courier.core.createPropertySetters(Match, "Cb",
    ["onFieldUpdate",
     "onHit",
     "onSplash",
     "onRemoveRobot",
     "onNewSlot",
     "onDisconnectRobot",
     "onConnectedRobot",
     "onMatchStarted"]);

// public methods
return {
  Robot: Robot,
  Match: Match
};

})();  // end courier namespace
<commit_msg>started the match if necessary.<commit_after>/*
 * match.js -- utility functions for matches and match lists
 * requires jquery
 **/

var courier = courier || {};

courier.match = (function() { // begin courier namespace

/* ------------------ Robots --------------------- */
function Robot () {
  var r = arguments[0];
  if (typeof r == 'object' && r !== null) {
    this.name = r.name;
    this.armor = r.armor;
    this.heat = r.heat;
  }
  this.jq = $("<div>");
}

/* ------------------ Match --------------------- */
function Match(id) {
  // Represents a match.
  this.mid = id;
  this.url = "/matches/" + id;
  this.populating = false;
}
Match.prototype.populate = function(stream, cb) {
  // get information about this match and run the callback.
  if (this.populating) {
    return false;
  }
  this.populating = true;
  var self = this;
  courier.core.ajaxRequest(this.url, {info: true},
    function(minfo){
      self.initTime = minfo.init_time;
      self.started = minfo.started;
      self['private'] = minfo['private'];
      self.robots = [];
      for (var i = 0,l = minfo.robots.length; i < l; i++) {
        self.robots[i] = new Robot(minfo.robots[i]);
      }
      if (typeof cb == 'function') {
        cb(self);
      }
      if (typeof self.onMatchStartedCb == 'function') {
        self.onMatchStartedCb();
      }
      if (stream) {
        self.beginStream(minfo.history);
      }
      self.populating = false;
    });
};
Match.prototype.beginStream = function(time) {
  var self = this;
  this.sh = new courier.core.StreamingHistory(this.url + "?history=t",
      time,
      function(action) {
        if (action.hasOwnProperty('field') &&
            typeof self.onFieldUpdateCb == 'function') {
          self.onFieldUpdateCb(action.field);
        } else if (action.hasOwnProperty('hit') &&
                   typeof self.onHitCb == 'function') {
          self.onHitCb(action.hit.obj, action.hit.location);
        } else if (action.hasOwnProperty('splash_damage') &&
                   typeof self.onSplashCb == 'function') {
          self.onSplashCb(action.splash_damage.objects,
                          action.splash_damage.location,
                          action.splash_damage.damage);
        } else if (action.hasOwnProperty('remove_robot') &&
                   typeof self.onRemoveRobotCb == 'function') {
          self.onRemoveRobotCb(new Robot(action.remove_robot));
        } else if (action.hasOwnProperty('disconnect_robot') &&
                   typeof self.onDisconnectRobotCb == 'function') {
          self.onDisconnectRobotCb(new Robot(action.disconnect_robot));
        } else if (action.hasOwnProperty('connected_robot') &&
                   typeof self.onConnectedRobotCb == 'function') {
          self.onConnectedRobotCb(new Robot(action.connected_robot));
        } else if (action.hasOwnProperty('new_slot') &&
                   typeof self.onNewSlotCb == 'function') {
          self.onNewSlotCb(new Robot(action.new_slot));
        } else if (action.hasOwnProperty('match_started') &&
                   typeof self.onMatchStarted == 'function') {
          self.onMatchStarted();
        }
      });
};
courier.core.createPropertySetters(Match, "Cb",
    ["onFieldUpdate",
     "onHit",
     "onSplash",
     "onRemoveRobot",
     "onNewSlot",
     "onDisconnectRobot",
     "onConnectedRobot",
     "onMatchStarted"]);

// public methods
return {
  Robot: Robot,
  Match: Match
};

})();  // end courier namespace
<|endoftext|>


<commit_before>'use strict'

const MLB_FEED = require('../../config/mlbFeed.js');

var request = require('request');
var Game = require('../models/Game');
var TeamService = require('./TeamService');

module.exports = {
	create,
	findDay,
	findMonth,
	findOrCreate,
	findTeamDay,
	findTeamMonth,
	findTeamYear,
	findYear
}

function create(game) {

	var homeTeam;

	return TeamService.findOrCreate(game.home_name_abbrev, game.home_team_city, game.home_team_name)
	.then((team) => {

		homeTeam = team;

		return TeamService.findOrCreate(game.away_name_abbrev, game.away_team_city, game.away_team_name);

	})
	.then((awayTeam) => {

		var dateSplit = game.original_date.split('/');
		var dateString = `${dateSplit[1]}/${dateSplit[2]}/${dateSplit[0]}`;

		var newGame = {
			date: new Date(`${dateString} ${game.time} ${game.ampm}`),
			home_team: homeTeam._id,
			away_team: awayTeam._id,
			home_runs: 0,
			away_runs: 0,
			status: game.status.status || 'Preview',
			inning: game.status.inning || 0,
			top_inning: game.status.top_inning === 'Y',
			gd2_id: game.id
		}

		if (game.linescore) {
			newGame.home_runs = Number(game.linescore.r.home);
			newGame.away_runs = Number(game.linescore.r.away);
		}

		return Game.create(newGame)

	});

}

function findDay(year, month, day) {

	var startDate = new Date(year,month-1,day-1,23,59,59,999);
	var endDate = new Date(year,month-1,day+1,0,0,0,0);

	var range = {
		$gt: startDate,
		$lt: endDate
	}

	return getGames(range);

}

function findTeamDay(teamAbbrev, year, month, day) {

	var startDate = new Date(year,month-1,day-1,23,59,59,999);
	var endDate = new Date(year,month-1,day+1,0,0,0,0);

	var dateRange = {
		$gt: startDate,
		$lt: endDate
	}

	return getGamesByTeam(teamAbbrev, dateRange);

}

function findMonth(year, month) {

	var startDate = new Date(year,month-1,0,23,59,59,999);
	var endDate = new Date(year,month,1,0,0,0,0);

	var range = {
		$gt: startDate,
		$lt: endDate
	}

	return getGames(range);

}

function findOrCreate(game) {

	return Game.findOne({ gd2_id: game.id })
	.then((data) => {

		if (!data) {

			return create(game);

		}

		return update(game);

	});

}

function findTeamMonth(teamAbbrev, year, month) {

	var startDate = new Date(year,month-1,0,23,59,59,999);
	var endDate = new Date(year,month,1,0,0,0,0);

	var dateRange = {
		$gt: startDate,
		$lt: endDate
	}

	return getGamesByTeam(teamAbbrev, dateRange);

}

function findTeamYear(teamAbbrev, year) {

	if (!MLB_FEED.years[year]) {
		var error = new Error('No data for given year');
		error.status = 404;
		throw error;
	}

	var startDate = new Date(MLB_FEED.years[year].startDate);
	startDate.setDate(startDate.getDate()-1);
	startDate.setHours(23);
	startDate.setMinutes(59);
	startDate.setSeconds(59);
	startDate.setMilliseconds(999);

	var endDate = new Date(MLB_FEED.years[year].endDate);
	endDate.setDate(endDate.getDate()+1);
	endDate.setHours(0);
	endDate.setMinutes(0);
	endDate.setSeconds(0);
	endDate.setMilliseconds(0);

	var dateRange = {
		$gt: startDate,
		$lt: endDate
	}

	return getGamesByTeam(teamAbbrev, dateRange);

}

function findYear(year) {

	var startDate = new Date(MLB_FEED.years[year].startDate);
	startDate.setDate(startDate.getDate()-1);
	startDate.setHours(23);
	startDate.setMinutes(59);
	startDate.setSeconds(59);
	startDate.setMilliseconds(999);

	var endDate = new Date(MLB_FEED.years[year].endDate);
	endDate.setDate(endDate.getDate()+1);
	endDate.setHours(0);
	endDate.setMinutes(0);
	endDate.setSeconds(0);
	endDate.setMilliseconds(0);

	var range = {
		$gt: startDate,
		$lt: endDate
	}

	return getGames(range);

}

function getGames(dateRange) {

	var query = {
		date: dateRange
	}

	return Game.find(query).populate('home_team away_team')
	.then((gameList) => {

		return gameList;

	});

}

function getGamesByTeam(teamAbbrev, dateRange) {

	return TeamService.findOne({ abbrev: teamAbbrev })
	.then((team) => {

		var query = {
			$or: [{ home_team: team._id }, { away_team: team._id }],
			date: dateRange
		}

		return Game.find(query).populate('home_team away_team')

	})
	.then((gameList) => {

		return gameList;

	});

}

function update(game) {

	return Game.findOne({ gd2_id: game.id })
	.then((gameData) => {

		var home_runs, away_runs;

		if (game.linescore) {

			gameData.home_runs = Number(game.linescore.r.home);
			gameData.away_runs = Number(game.linescore.r.away);

		}

		gameData.status = game.status.status || 'Preview';
		gameData.inning = game.status.inning || 0,
		gameData.top_inning = game.status.top_inning === 'Y',

		return gameData.save();

	});

}
<commit_msg>Fixing comma typos<commit_after>'use strict'

const MLB_FEED = require('../../config/mlbFeed.js');

var request = require('request');
var Game = require('../models/Game');
var TeamService = require('./TeamService');

module.exports = {
	create,
	findDay,
	findMonth,
	findOrCreate,
	findTeamDay,
	findTeamMonth,
	findTeamYear,
	findYear
}

function create(game) {

	var homeTeam;

	return TeamService.findOrCreate(game.home_name_abbrev, game.home_team_city, game.home_team_name)
	.then((team) => {

		homeTeam = team;

		return TeamService.findOrCreate(game.away_name_abbrev, game.away_team_city, game.away_team_name);

	})
	.then((awayTeam) => {

		var dateSplit = game.original_date.split('/');
		var dateString = `${dateSplit[1]}/${dateSplit[2]}/${dateSplit[0]}`;

		var newGame = {
			date: new Date(`${dateString} ${game.time} ${game.ampm}`),
			home_team: homeTeam._id,
			away_team: awayTeam._id,
			home_runs: 0,
			away_runs: 0,
			status: game.status.status || 'Preview',
			inning: game.status.inning || 0,
			top_inning: game.status.top_inning === 'Y',
			gd2_id: game.id
		}

		if (game.linescore) {
			newGame.home_runs = Number(game.linescore.r.home);
			newGame.away_runs = Number(game.linescore.r.away);
		}

		return Game.create(newGame)

	});

}

function findDay(year, month, day) {

	var startDate = new Date(year,month-1,day-1,23,59,59,999);
	var endDate = new Date(year,month-1,day+1,0,0,0,0);

	var range = {
		$gt: startDate,
		$lt: endDate
	}

	return getGames(range);

}

function findTeamDay(teamAbbrev, year, month, day) {

	var startDate = new Date(year,month-1,day-1,23,59,59,999);
	var endDate = new Date(year,month-1,day+1,0,0,0,0);

	var dateRange = {
		$gt: startDate,
		$lt: endDate
	}

	return getGamesByTeam(teamAbbrev, dateRange);

}

function findMonth(year, month) {

	var startDate = new Date(year,month-1,0,23,59,59,999);
	var endDate = new Date(year,month,1,0,0,0,0);

	var range = {
		$gt: startDate,
		$lt: endDate
	}

	return getGames(range);

}

function findOrCreate(game) {

	return Game.findOne({ gd2_id: game.id })
	.then((data) => {

		if (!data) {

			return create(game);

		}

		return update(game);

	});

}

function findTeamMonth(teamAbbrev, year, month) {

	var startDate = new Date(year,month-1,0,23,59,59,999);
	var endDate = new Date(year,month,1,0,0,0,0);

	var dateRange = {
		$gt: startDate,
		$lt: endDate
	}

	return getGamesByTeam(teamAbbrev, dateRange);

}

function findTeamYear(teamAbbrev, year) {

	if (!MLB_FEED.years[year]) {
		var error = new Error('No data for given year');
		error.status = 404;
		throw error;
	}

	var startDate = new Date(MLB_FEED.years[year].startDate);
	startDate.setDate(startDate.getDate()-1);
	startDate.setHours(23);
	startDate.setMinutes(59);
	startDate.setSeconds(59);
	startDate.setMilliseconds(999);

	var endDate = new Date(MLB_FEED.years[year].endDate);
	endDate.setDate(endDate.getDate()+1);
	endDate.setHours(0);
	endDate.setMinutes(0);
	endDate.setSeconds(0);
	endDate.setMilliseconds(0);

	var dateRange = {
		$gt: startDate,
		$lt: endDate
	}

	return getGamesByTeam(teamAbbrev, dateRange);

}

function findYear(year) {

	var startDate = new Date(MLB_FEED.years[year].startDate);
	startDate.setDate(startDate.getDate()-1);
	startDate.setHours(23);
	startDate.setMinutes(59);
	startDate.setSeconds(59);
	startDate.setMilliseconds(999);

	var endDate = new Date(MLB_FEED.years[year].endDate);
	endDate.setDate(endDate.getDate()+1);
	endDate.setHours(0);
	endDate.setMinutes(0);
	endDate.setSeconds(0);
	endDate.setMilliseconds(0);

	var range = {
		$gt: startDate,
		$lt: endDate
	}

	return getGames(range);

}

function getGames(dateRange) {

	var query = {
		date: dateRange
	}

	return Game.find(query).populate('home_team away_team')
	.then((gameList) => {

		return gameList;

	});

}

function getGamesByTeam(teamAbbrev, dateRange) {

	return TeamService.findOne({ abbrev: teamAbbrev })
	.then((team) => {

		var query = {
			$or: [{ home_team: team._id }, { away_team: team._id }],
			date: dateRange
		}

		return Game.find(query).populate('home_team away_team')

	})
	.then((gameList) => {

		return gameList;

	});

}

function update(game) {

	return Game.findOne({ gd2_id: game.id })
	.then((gameData) => {

		var home_runs, away_runs;

		if (game.linescore) {

			gameData.home_runs = Number(game.linescore.r.home);
			gameData.away_runs = Number(game.linescore.r.away);

		}

		gameData.status = game.status.status || 'Preview';
		gameData.inning = game.status.inning || 0;
		gameData.top_inning = game.status.top_inning === 'Y';

		return gameData.save();

	});

}
<|endoftext|>


<commit_before>import * as caret from './caret';
import * as componentElements from './component-elements';
import * as headComponents from './head-components';
import icon from './icon.vue';
import interpolate from './interpolate';
import label from './label';
import * as promises from './promises';
import * as references from './references';
import * as urls from './urls';

const api = {
  componentElements,
  caret,
  headComponents,
  icon,
  interpolate,
  label,
  promises,
  references,
  urls
};

export default api;
<commit_msg>comment<commit_after>import * as caret from './caret';
import * as componentElements from './component-elements';
import * as headComponents from './head-components';
import icon from './icon.vue';
import interpolate from './interpolate';
import label from './label';
import * as promises from './promises';
import * as references from './references';
import * as urls from './urls';

// these utilities are exported so 3rd party plugins/validators/behaviors/panes can access them.
// note: the store is passed into those things automatically, so don't export it here
const api = {
  componentElements,
  caret,
  headComponents,
  icon,
  interpolate,
  label,
  promises,
  references,
  urls
};

export default api;
<|endoftext|>


<commit_before>/* eslint-disable */

import mocha from 'mocha';
import app from '../server';
import chai from 'chai';
import request from 'supertest';
import mongoose from 'mongoose';
import Post from '../models/post';

const expect = chai.expect;

function connectDB(done) {
  if (mongoose.connection.name !== 'mern-test') {
    return done();
  }

  mongoose.connect((process.env.MONGO_URL || 'mongodb://localhost:27017/mern-test'), function (err) {
    if (err) return done(err);
    done();
  });
}

function dropDB(done) {
  if (mongoose.connection.name !== 'mern-test') {
    return done();
  }

  mongoose.connection.db.dropDatabase(function (err) {
    mongoose.connection.close(done);
  });
}

describe('GET /api/getPosts', function () {

  beforeEach('connect and add two post entries', function (done) {

    connectDB(function () {
      var post1 = new Post({name: 'Prashant', title: 'Hello Mern', content: "All cats meow 'mern!'"});
      var post2 = new Post({name: 'Mayank', title: 'Hi Mern', content: "All dogs bark 'mern!'"});

      Post.create([post1, post2], function (err, saved) {
        done();
      });
    });
  });

  afterEach(function (done) {
    dropDB(done);
  });

  it('Should correctly give number of Posts', function (done) {

    request(app)
      .get('/api/getPosts')
      .set('Accept', 'application/json')
      .end(function (err, res) {
        Post.find().exec(function (err, posts) {
          expect(posts.length).to.equal(res.body.posts.length);
          done();
        });
      });
  });
});

describe('GET /api/getPost', function () {

  beforeEach('connect and add one Post entry', function(done){

    connectDB(function () {
      var post = new Post({ name: 'Foo', title: 'bar', slug: 'bar', cuid: 'f34gb2bh24b24b2', content: 'Hello Mern says Foo' });

      post.save(function (err, saved) {
        done();
      });
    });
  });

  afterEach(function (done) {
    dropDB(done);
  });

  it('Should send correct data when queried against a title', function (done) {

    request(app)
      .get('/api/getPost?slug=bar-f34gb2bh24b24b2')
      .set('Accept', 'application/json')
      .end(function (err, res) {
        Post.findOne({ cuid: 'f34gb2bh24b24b2' }).exec(function (err, post) {
          expect(post.name).to.equal('Foo');
          done();
        });
      });
  });

});

describe('POST /api/addPost', function () {

  beforeEach('connect and add a post', function (done) {

    connectDB(function () {
      done();
    });
  });

  afterEach(function (done) {
    dropDB(done);
  });

  it('Should send correctly add a post', function (done) {

    request(app)
      .post('/api/addPost')
      .send({ post: { name: 'Foo', title: 'bar', content: 'Hello Mern says Foo' } })
      .set('Accept', 'application/json')
      .end(function (err, res) {
        Post.findOne({ title: 'bar' }).exec(function (err, post) {
          expect(post.name).to.equal('Foo');
          done();
        });
      });
  });

});

describe('POST /api/deletePost', function () {
  var postId;

  beforeEach('connect and add one Post entry', function(done){

    connectDB(function () {
      var post = new Post({ name: 'Foo', title: 'bar', slug: 'bar', cuid: 'f34gb2bh24b24b2', content: 'Hello Mern says Foo' });

      post.save(function (err, saved) {
        postId = saved._id;
        done();
      });
    });
  });

  afterEach(function (done) {
    dropDB(done);
  });

  it('Should connect and delete a post', function () {

    // Check if post is saved in DB
    Post.findById(postId).exec(function (err, post) {
      expect(post.name).to.equal('Foo')
    });

    request(app)
      .post('/api/deletePost')
      .send({ postId: postId})
      .set('Accept', 'application/json')
      .end(function () {

        // Check if post is removed from DB
        Post.findById(postId).exec(function (err, post) {
          expect(post).to.equal(null);
          done();
        });
      });
  })
});
<commit_msg>set the done state on the delete post test<commit_after>/* eslint-disable */

import mocha from 'mocha';
import app from '../server';
import chai from 'chai';
import request from 'supertest';
import mongoose from 'mongoose';
import Post from '../models/post';

const expect = chai.expect;

function connectDB(done) {
  if (mongoose.connection.name !== 'mern-test') {
    return done();
  }

  mongoose.connect((process.env.MONGO_URL || 'mongodb://localhost:27017/mern-test'), function (err) {
    if (err) return done(err);
    done();
  });
}

function dropDB(done) {
  if (mongoose.connection.name !== 'mern-test') {
    return done();
  }

  mongoose.connection.db.dropDatabase(function (err) {
    mongoose.connection.close(done);
  });
}

describe('GET /api/getPosts', function () {

  beforeEach('connect and add two post entries', function (done) {

    connectDB(function () {
      var post1 = new Post({name: 'Prashant', title: 'Hello Mern', content: "All cats meow 'mern!'"});
      var post2 = new Post({name: 'Mayank', title: 'Hi Mern', content: "All dogs bark 'mern!'"});

      Post.create([post1, post2], function (err, saved) {
        done();
      });
    });
  });

  afterEach(function (done) {
    dropDB(done);
  });

  it('Should correctly give number of Posts', function (done) {

    request(app)
      .get('/api/getPosts')
      .set('Accept', 'application/json')
      .end(function (err, res) {
        Post.find().exec(function (err, posts) {
          expect(posts.length).to.equal(res.body.posts.length);
          done();
        });
      });
  });
});

describe('GET /api/getPost', function () {

  beforeEach('connect and add one Post entry', function(done){

    connectDB(function () {
      var post = new Post({ name: 'Foo', title: 'bar', slug: 'bar', cuid: 'f34gb2bh24b24b2', content: 'Hello Mern says Foo' });

      post.save(function (err, saved) {
        done();
      });
    });
  });

  afterEach(function (done) {
    dropDB(done);
  });

  it('Should send correct data when queried against a title', function (done) {

    request(app)
      .get('/api/getPost?slug=bar-f34gb2bh24b24b2')
      .set('Accept', 'application/json')
      .end(function (err, res) {
        Post.findOne({ cuid: 'f34gb2bh24b24b2' }).exec(function (err, post) {
          expect(post.name).to.equal('Foo');
          done();
        });
      });
  });

});

describe('POST /api/addPost', function () {

  beforeEach('connect and add a post', function (done) {

    connectDB(function () {
      done();
    });
  });

  afterEach(function (done) {
    dropDB(done);
  });

  it('Should send correctly add a post', function (done) {

    request(app)
      .post('/api/addPost')
      .send({ post: { name: 'Foo', title: 'bar', content: 'Hello Mern says Foo' } })
      .set('Accept', 'application/json')
      .end(function (err, res) {
        Post.findOne({ title: 'bar' }).exec(function (err, post) {
          expect(post.name).to.equal('Foo');
          done();
        });
      });
  });

});

describe('POST /api/deletePost', function () {
  var postId;

  beforeEach('connect and add one Post entry', function(done){

    connectDB(function () {
      var post = new Post({ name: 'Foo', title: 'bar', slug: 'bar', cuid: 'f34gb2bh24b24b2', content: 'Hello Mern says Foo' });

      post.save(function (err, saved) {
        postId = saved._id;
        done();
      });
    });
  });

  afterEach(function (done) {
    dropDB(done);
  });

  it('Should connect and delete a post', function (done) {

    // Check if post is saved in DB
    Post.findById(postId).exec(function (err, post) {
      expect(post.name).to.equal('Foo')
    });

    request(app)
      .post('/api/deletePost')
      .send({ postId: postId})
      .set('Accept', 'application/json')
      .end(function () {

        // Check if post is removed from DB
        Post.findById(postId).exec(function (err, post) {
          expect(post).to.equal(null);
          done();
        });
      });
  })
});
<|endoftext|>


<commit_before>import { Feed } from 'feed';
import compareDesc from 'date-fns/compareDesc';

// App Imports
import { languages } from './utils';
import { getAllRSS } from '../modules/chapter/resolvers';
import { API_URL, APP_URL, REACT_APP_APP_TITLE } from '../config/env';

// File upload configurations and route
export default function (server) {
  console.info('SETUP - Feeds...');

  // Thumbnail route
  server.get('/feed/:feed/:lang', async (request, response) => {
    const reqLang = request.params.lang;
    let language = -1;
    if (isNaN(parseInt(reqLang, 0))) {
      var languageFound = languages.find(ln => ln.name === reqLang);
      if (languageFound) {
        language = languageFound.id;
      }
    } else {
      language = reqLang;
    }

    const feed = request.params.feed;
    let chapters = await getAllRSS({
      language,
      orderBy: 'DESC'
    });

    chapters = await chapters.sort((ch1, ch2) =>
      compareDesc(ch1.releaseDate, ch2.releaseDate)
    );
    const feedConfig = await generateFeed(chapters);

    response.type(feed);
    if (feed === 'atom') {
      response.send(feedConfig.atom1());
    } else if (feed === 'json') {
      response.send(feedConfig.json1());
    } else {
      response.send(feedConfig.rss2());
    }
  });
}

async function generateFeed(chapters) {
  const feedConfig = new Feed({
    title: REACT_APP_APP_TITLE,
    id: APP_URL,
    link: APP_URL,
    updated: chapters[0] ? chapters[0].releaseDate : new Date(),
    generator: 'ReaderFront',
    author: {
      name: 'dvaJi',
      link: 'https://github.com/dvaJi'
    }
  });

  for (const chapter of chapters) {
    const title = generateChapterTitle(chapter, chapter.language);
    const url = generateChapterUrl(chapter, APP_URL);
    const thumb = generateThumbnailUrl(chapter, API_URL);
    await feedConfig.addItem({
      title: title,
      id: url,
      link: url,
      description: chapter.description,
      date: chapter.releaseDate,
      image: thumb
    });
  }

  return feedConfig;
}

const translation = {
  '1': {
    chapter: 'Capítulo ',
    volume: 'Tomo'
  },
  '2': {
    chapter: 'Chapter ',
    volume: 'Volume'
  }
};

function generateChapterTitle(chapter, lang) {
  let title =
    chapter.work.name + ' - ' + translation[lang].chapter + chapter.chapter;

  if (chapter.subchapter !== 0) {
    title += '.' + chapter.subchapter;
  }

  if (chapter.name !== null && chapter.name !== '') {
    title += ': ' + chapter.name;
  }

  return title;
}

function generateChapterUrl(chapter, frontendBaseUrl) {
  const lang = languages.find(ln => ln.id === chapter.language);
  return (
    frontendBaseUrl +
    '/read/' +
    chapter.work.stub +
    '/' +
    lang.name +
    '/' +
    chapter.volume +
    '/' +
    chapter.chapter +
    '.' +
    chapter.subchapter
  );
}

function generateThumbnailUrl(chapter, baseUrl) {
  const { work, uniqid, thumbnail } = chapter;
  return `${baseUrl}/works/${work.uniqid}/${uniqid}/${thumbnail}`;
}
<commit_msg>fix(api): update 'feed' to languages array<commit_after>import { Feed } from 'feed';
import compareDesc from 'date-fns/compareDesc';

// App Imports
import { languages } from './utils';
import { getAllRSS } from '../modules/chapter/resolvers';
import { API_URL, APP_URL, REACT_APP_APP_TITLE } from '../config/env';

// File upload configurations and route
export default function (server) {
  console.info('SETUP - Feeds...');

  // Thumbnail route
  server.get('/feed/:feed/:lang', async (request, response) => {
    const reqLang = request.params.lang;
    let languages = [];
    if (isNaN(parseInt(reqLang, 0))) {
      var languageFound = languages.find(ln => ln.name === reqLang);
      if (languageFound) {
        languages = [languageFound.id];
      }
    } else {
      languages = [reqLang];
    }

    const feed = request.params.feed;
    let chapters = await getAllRSS({
      languages,
      orderBy: 'DESC'
    });

    chapters = await chapters.sort((ch1, ch2) =>
      compareDesc(ch1.releaseDate, ch2.releaseDate)
    );
    const feedConfig = await generateFeed(chapters);

    response.type(feed);
    if (feed === 'atom') {
      response.send(feedConfig.atom1());
    } else if (feed === 'json') {
      response.send(feedConfig.json1());
    } else {
      response.send(feedConfig.rss2());
    }
  });
}

async function generateFeed(chapters) {
  const feedConfig = new Feed({
    title: REACT_APP_APP_TITLE,
    id: APP_URL,
    link: APP_URL,
    updated: chapters[0] ? chapters[0].releaseDate : new Date(),
    generator: 'ReaderFront',
    author: {
      name: 'dvaJi',
      link: 'https://github.com/dvaJi'
    }
  });

  for (const chapter of chapters) {
    const title = generateChapterTitle(chapter, chapter.language);
    const url = generateChapterUrl(chapter, APP_URL);
    const thumb = generateThumbnailUrl(chapter, API_URL);
    await feedConfig.addItem({
      title: title,
      id: url,
      link: url,
      description: chapter.description,
      date: chapter.releaseDate,
      image: thumb
    });
  }

  return feedConfig;
}

const translation = {
  '1': {
    chapter: 'Capítulo ',
    volume: 'Tomo'
  },
  '2': {
    chapter: 'Chapter ',
    volume: 'Volume'
  }
};

function generateChapterTitle(chapter, lang) {
  let title =
    chapter.work.name + ' - ' + translation[lang].chapter + chapter.chapter;

  if (chapter.subchapter !== 0) {
    title += '.' + chapter.subchapter;
  }

  if (chapter.name !== null && chapter.name !== '') {
    title += ': ' + chapter.name;
  }

  return title;
}

function generateChapterUrl(chapter, frontendBaseUrl) {
  const lang = languages.find(ln => ln.id === chapter.language);
  return (
    frontendBaseUrl +
    '/read/' +
    chapter.work.stub +
    '/' +
    lang.name +
    '/' +
    chapter.volume +
    '/' +
    chapter.chapter +
    '.' +
    chapter.subchapter
  );
}

function generateThumbnailUrl(chapter, baseUrl) {
  const { work, uniqid, thumbnail } = chapter;
  return `${baseUrl}/works/${work.uniqid}/${uniqid}/${thumbnail}`;
}
<|endoftext|>