<commit_before>QUnit.module("Loader Factory", {
    beforeEach: function() {
        console.log("Creating new test object");
        this.image_loader = new ImageLoader_0_5_x();
    }
});

QUnit.test("Confirm version number has been updated.", function(assert) {
    assert.equal(
        "0.5.0",
        this.image_loader.version_number()
    );
});

QUnit.test("Integration Test 1: Base functionality using the minimum setup and call", function(assert) {

    // TODO:
    // - Add environmental overrides so it doesn't matter what browser is being tested.
    // - Add something like `imageLoader.load_environment()` to Given for the standard setup
    // - Add something like `imageLoader.set_url_template()` to Given for the standard setup

    // Given
    var target_string = '';

    // When
    var result_string = this.image_loader.image_string_from_params( {
        filename: "horses.jpg",
        alt_text: "Photo of Horses",
        source_width: 1600,
        source_height: 1067
    });

    // Then
    assert.equal(result_string, target_string);

});

/******************************************************\
 * DEPRECATED TESTS
 *
 * The tests below are for the prior versoin. They
 * are being kept temporarirly to make sure new
 * versions testing the same things are completed
 * before they are removed.
 *
/******************************************************/

// QUnit.test("Integration Test: Verify environmental parameters load", function(assert) {
//
//     // It's a little weird to tests these params, but I
//     // want to make sure they work for integration purposes.
//
//     // Given
//     var target_dpr = window.devicePixelRatio ? window.devicePixelRatio : 1;
//
//     // When
//     var imageLoader = new ImageLoader_0_5_x();
//     imageLoader.load_environmental_params();
//
//     // Then
//     assert.equal(imageLoader._dpr, target_dpr);
//
// });
//
//
// QUnit.test("2x DPR Basic call stright to load_params", function(assert) {
//
//   // Given
// 	var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 2,
//     	image_name: "horses.jpg",
//       percent_of_viewport_width: 50,
//       quality: 80,
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then - Verify instance variables.
//   assert.equal(imageLoader._dpr, 2, "Device pixel resolution");
//   assert.equal(imageLoader._image_name, "horses.jpg", "Image name");
//   assert.equal(imageLoader._raw_height, 1067, "Raw image height");
//   assert.equal(imageLoader._raw_width, 1600, "Raw image width");
//   assert.equal(imageLoader._percent_of_viewport_width, 50, "Percent of viewport width");
//   assert.equal(imageLoader._quality, 80, "Quality level");
//   assert.equal(imageLoader._viewport_height, 680, "Viewport height");
//   assert.equal(imageLoader._viewport_width, 1024, "Viewport width");
//   assert.equal(imageLoader._url_template, "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]", "URL Template");
//
//   // And - Verify functions
//   assert.equal(imageLoader.url_to_call(), "http://res.cloudinary.com/demo/image/upload/w_1024,h_682,q_80/horses.jpg", "Final URL");
//   assert.equal(imageLoader.render_height(), 341, "Render height");
//   assert.equal(imageLoader.render_width(), 512, "Render width");
//   assert.equal(imageLoader.url_request_height(), 682, "Request height");
//   assert.equal(imageLoader.url_request_width(), 1024, "Request width");
//   assert.equal(imageLoader.img_tag(), '', "Image tag");
//
// });
//
// QUnit.test("2x DPR stright to load_params with max width enforced", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//       dpr: 2,
//       image_name: "horses.jpg",
//       percent_of_viewport_width: 50,
//       quality: 80,
//       raw_height: 1067,
//       raw_width: 1600,
//       max_render_width: 200,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader._max_render_width, 200, "Max render width");
//   assert.equal(imageLoader.render_width(), 200, "Verify render_width == max_render_width");
//   assert.equal(imageLoader.render_height(), 133, "Verify render_width == max_render_width");
//   assert.equal(imageLoader.url_request_width(), 400, "Request width");
//   assert.equal(imageLoader.url_request_height(), 266, "Request height");
//
// });
//
//
// QUnit.test("Verify defaults", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//       image_name: "horses.jpg",
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader._max_render_width, 1600, "Default Max Render Width == Raw Width");
//   assert.equal(imageLoader._dpr, 1, "Device pixel ratio default");
//   assert.equal(imageLoader._percent_of_viewport_width, 100, "Percent of viewport width default");
//   assert.equal(imageLoader._quality, 80, "Quality default");
//
// });
//
//
//
// QUnit.test("Ensure values are integers", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 2,
//       image_name: "horses.jpg",
//       percent_of_viewport_width: 25,
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1021,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader.render_height(), 170, "Render height");
//   assert.equal(imageLoader.render_width(), 255, "Render width is 255 and not 255.25");
//   assert.equal(imageLoader.url_request_height(), 340, "Request height");
//   assert.equal(imageLoader.url_request_width(), 510, "Request width is 510 and not 510.5");
//
// });
//
//
// QUnit.test("Restrict height", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 1,
//       image_name: "horses.jpg",
//       percent_of_viewport_height: 90,
//       raw_height: 2000,
//       raw_width: 2200,
//       viewport_height: 1000,
//       viewport_width: 1800,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   assert.equal(imageLoader.render_height(), 900, "Render height");
//
//
// });
//
//
<commit_msg>Moved creation of target_string into `//When`.<commit_after>QUnit.module("Loader Factory", {
    beforeEach: function() {
        console.log("Creating new test object");
        this.image_loader = new ImageLoader_0_5_x();
    }
});

QUnit.test("Confirm version number has been updated.", function(assert) {
    assert.equal(
        "0.5.0",
        this.image_loader.version_number()
    );
});

QUnit.test("Integration Test 1: Base functionality using the minimum setup and call", function(assert) {

    // TODO:
    // - Add environmental overrides so it doesn't matter what browser is being tested.
    // - Add something like `imageLoader.load_environment()` to Given for the standard setup
    // - Add something like `imageLoader.set_url_template()` to Given for the standard setup

    // Given

    // When
    var target_string = '';

    var result_string = this.image_loader.image_string_from_params( {
        filename: "horses.jpg",
        alt_text: "Photo of Horses",
        source_width: 1600,
        source_height: 1067
    });

    // Then
    assert.equal(result_string, target_string);

});

/******************************************************\
 * DEPRECATED TESTS
 *
 * The tests below are for the prior versoin. They
 * are being kept temporarirly to make sure new
 * versions testing the same things are completed
 * before they are removed.
 *
/******************************************************/

// QUnit.test("Integration Test: Verify environmental parameters load", function(assert) {
//
//     // It's a little weird to tests these params, but I
//     // want to make sure they work for integration purposes.
//
//     // Given
//     var target_dpr = window.devicePixelRatio ? window.devicePixelRatio : 1;
//
//     // When
//     var imageLoader = new ImageLoader_0_5_x();
//     imageLoader.load_environmental_params();
//
//     // Then
//     assert.equal(imageLoader._dpr, target_dpr);
//
// });
//
//
// QUnit.test("2x DPR Basic call stright to load_params", function(assert) {
//
//   // Given
// 	var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 2,
//     	image_name: "horses.jpg",
//       percent_of_viewport_width: 50,
//       quality: 80,
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then - Verify instance variables.
//   assert.equal(imageLoader._dpr, 2, "Device pixel resolution");
//   assert.equal(imageLoader._image_name, "horses.jpg", "Image name");
//   assert.equal(imageLoader._raw_height, 1067, "Raw image height");
//   assert.equal(imageLoader._raw_width, 1600, "Raw image width");
//   assert.equal(imageLoader._percent_of_viewport_width, 50, "Percent of viewport width");
//   assert.equal(imageLoader._quality, 80, "Quality level");
//   assert.equal(imageLoader._viewport_height, 680, "Viewport height");
//   assert.equal(imageLoader._viewport_width, 1024, "Viewport width");
//   assert.equal(imageLoader._url_template, "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]", "URL Template");
//
//   // And - Verify functions
//   assert.equal(imageLoader.url_to_call(), "http://res.cloudinary.com/demo/image/upload/w_1024,h_682,q_80/horses.jpg", "Final URL");
//   assert.equal(imageLoader.render_height(), 341, "Render height");
//   assert.equal(imageLoader.render_width(), 512, "Render width");
//   assert.equal(imageLoader.url_request_height(), 682, "Request height");
//   assert.equal(imageLoader.url_request_width(), 1024, "Request width");
//   assert.equal(imageLoader.img_tag(), '', "Image tag");
//
// });
//
// QUnit.test("2x DPR stright to load_params with max width enforced", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//       dpr: 2,
//       image_name: "horses.jpg",
//       percent_of_viewport_width: 50,
//       quality: 80,
//       raw_height: 1067,
//       raw_width: 1600,
//       max_render_width: 200,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader._max_render_width, 200, "Max render width");
//   assert.equal(imageLoader.render_width(), 200, "Verify render_width == max_render_width");
//   assert.equal(imageLoader.render_height(), 133, "Verify render_width == max_render_width");
//   assert.equal(imageLoader.url_request_width(), 400, "Request width");
//   assert.equal(imageLoader.url_request_height(), 266, "Request height");
//
// });
//
//
// QUnit.test("Verify defaults", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//       image_name: "horses.jpg",
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader._max_render_width, 1600, "Default Max Render Width == Raw Width");
//   assert.equal(imageLoader._dpr, 1, "Device pixel ratio default");
//   assert.equal(imageLoader._percent_of_viewport_width, 100, "Percent of viewport width default");
//   assert.equal(imageLoader._quality, 80, "Quality default");
//
// });
//
//
//
// QUnit.test("Ensure values are integers", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 2,
//       image_name: "horses.jpg",
//       percent_of_viewport_width: 25,
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1021,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader.render_height(), 170, "Render height");
//   assert.equal(imageLoader.render_width(), 255, "Render width is 255 and not 255.25");
//   assert.equal(imageLoader.url_request_height(), 340, "Request height");
//   assert.equal(imageLoader.url_request_width(), 510, "Request width is 510 and not 510.5");
//
// });
//
//
// QUnit.test("Restrict height", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 1,
//       image_name: "horses.jpg",
//       percent_of_viewport_height: 90,
//       raw_height: 2000,
//       raw_width: 2200,
//       viewport_height: 1000,
//       viewport_width: 1800,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   assert.equal(imageLoader.render_height(), 900, "Render height");
//
//
// });
//
//
<|endoftext|>


<commit_before>// core nodes
import PropertyNode from './core/PropertyNode.js';
import VarNode from './core/VarNode.js';
import AttributeNode from './core/AttributeNode.js';

// input nodes
import BoolNode from './inputs/BoolNode.js';
import ColorNode from './inputs/ColorNode.js';
import FloatNode from './inputs/FloatNode.js';
import IntNode from './inputs/IntNode.js';
import UintNode from './inputs/UintNode.js';
import Vector2Node from './inputs/Vector2Node.js';
import Vector3Node from './inputs/Vector3Node.js';
import Vector4Node from './inputs/Vector4Node.js';
import Matrix3Node from './inputs/Matrix3Node.js';
import Matrix4Node from './inputs/Matrix4Node.js';
import TextureNode from './inputs/TextureNode.js';

// accessor nodes
import PositionNode from './accessors/PositionNode.js';
import NormalNode from './accessors/NormalNode.js';
import UVNode from './accessors/UVNode.js';

// math nodes
import OperatorNode from './math/OperatorNode.js';
import CondNode from './math/CondNode.js';
import MathNode from './math/MathNode.js';

// util nodes
import ArrayElementNode from './utils/ArrayElementNode.js';
import ConvertNode from './utils/ConvertNode.js';
import JoinNode from './utils/JoinNode.js';
import SplitNode from './utils/SplitNode.js';

// core
import { Vector2, Vector3, Vector4, Matrix3, Matrix4, Color } from 'three';

const NodeHandler = {

	construct( NodeClosure, params ) {

		const inputs = params.shift();

		return NodeClosure( new ShaderNodeObjects( inputs ), ...params );

	},

	get: function ( node, prop ) {

		if ( typeof prop === 'string' && node[ prop ] === undefined ) {

			if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true ) {

				// accessing properties ( swizzle )

				prop = prop
					.replace( /r|s/g, 'x' )
					.replace( /g|t/g, 'y' )
					.replace( /b|p/g, 'z' )
					.replace( /a|q/g, 'w' );

				return new ShaderNodeObject( new SplitNode( node, prop ) );

			} else if ( /^\d+$/.test( prop ) === true ) {

				// accessing array

				return new ShaderNodeObject( new ArrayElementNode( node, new FloatNode( Number( prop ) ).setConst( true ) ) );

			}

		}

		return node[ prop ];

	}

};

const nodeObjects = new WeakMap();

const ShaderNodeObject = function( obj ) {

	const type = typeof obj;

	if ( type === 'number' ) {

		return new ShaderNodeObject( new FloatNode( obj ).setConst( true ) );

	} else if ( type === 'object' ) {

		if ( obj.isNode === true ) {

			let nodeObject = nodeObjects.get( obj );

			if ( nodeObject === undefined ) {

				nodeObject = new Proxy( obj, NodeHandler );
				nodeObjects.set( obj, nodeObject );
				nodeObjects.set( nodeObject, nodeObject );

			}

			return nodeObject;

		}

	}

	return obj;

};

const ShaderNodeObjects = function( objects ) {

	for ( const name in objects ) {

		objects[ name ] = new ShaderNodeObject( objects[ name ] );

	}

	return objects;

};

const getShaderNodeArray = ( array ) => {

	const len = array.length;

	for ( let i = 0; i < len; i ++ ) {

		array[ i ] = new ShaderNodeObject( array[ i ] );

	}

	return array;

};

const ShaderNodeProxy = function( NodeClass, scope = null, factor = null ) {

	if ( scope === null ) {

		return ( ...params ) => {

			return new ShaderNodeObject( new NodeClass( ...getShaderNodeArray( params ) ) );

		};

	} else if ( factor === null ) {

		return ( ...params ) => {

			return new ShaderNodeObject( new NodeClass( scope, ...getShaderNodeArray( params ) ) );

		};

	} else {

		factor = new ShaderNodeObject( factor );

		return ( ...params ) => {

			return new ShaderNodeObject( new NodeClass( scope, ...getShaderNodeArray( params ), factor ) );

		};

	}

};

const ShaderNodeScript = function ( jsFunc ) {

	return ( inputs, builder ) => {

		new ShaderNodeObjects( inputs );

		return new ShaderNodeObject( jsFunc( inputs, builder ) );

	};

};

export const ShaderNode = new Proxy( ShaderNodeScript, NodeHandler );

//
// Node Material Shader Syntax
//

export const uniform = new ShaderNode( ( inputNode ) => {

	inputNode.setConst( false );

	return inputNode;

} );

export const nodeObject = ( val ) => {

	return new ShaderNodeObject( val );

};

export const label = ( node, name ) => {

	node = nodeObject( node );

	if ( node.isVarNode === true ) {

		node.name = name;

		return node;

	}

	return nodeObject( new VarNode( node, name ) );

};

export const temp = ( node ) => nodeObject( new VarNode( nodeObject( node ), name ) );

const ConvertType = function ( nodeClass, type, valueClass = null, valueComponents = 1 ) {

	return ( ...params ) => {

		if ( params[ 0 ]?.isNode === true ) {

			return nodeObject( new ConvertNode( params[ 0 ], type ) );

		}

		if ( ( params.length === 1 ) && ( valueComponents !== 1 ) ) {

			// Providing one scalar value: This value is used for all components

			for ( let i = 1; i < valueComponents; i ++ ) {

				params[ i ] = params [ 0 ];

			}

		}

		const val = ( ( valueClass === null ) || ( params[ 0 ] instanceof valueClass ) ) ? params[ 0 ] : new valueClass().set( ...params );

		return nodeObject( new nodeClass( val ).setConst( true ) );

	};

};

export const float = new ConvertType( FloatNode, 'float' );
export const int = new ConvertType( IntNode, 'int' );
export const uint = new ConvertType( UintNode, 'uint' );
export const bool = new ConvertType( BoolNode, 'bool' );
export const color = new ConvertType( ColorNode, 'color', Color );

export const vec2 = new ConvertType( Vector2Node, 'vec2', Vector2, 2 );
export const vec3 = new ConvertType( Vector3Node, 'vec3', Vector3, 3 );
export const vec4 = new ConvertType( Vector4Node, 'vec4', Vector4, 4 );

export const mat3 = new ConvertType( Matrix3Node, 'mat3', Matrix3 );
export const mat4 = new ConvertType( Matrix4Node, 'mat4', Matrix4 );

export const join = ( ...params ) => {

	return nodeObject( new JoinNode( getShaderNodeArray( params ) ) );

};

export const cond = ( ...params ) => {

	return nodeObject( new CondNode( ...getShaderNodeArray( params ) ) );

};

export const addTo = ( varNode, ...params ) => {

	varNode.node = add( varNode.node, ...getShaderNodeArray( params ) );

	return nodeObject( varNode );

};

export const uv = new ShaderNodeProxy( UVNode );
export const attribute = new ShaderNodeProxy( AttributeNode );

export const texture = new ShaderNodeProxy( TextureNode );

export const add = new ShaderNodeProxy( OperatorNode, '+' );
export const sub = new ShaderNodeProxy( OperatorNode, '-' );
export const mul = new ShaderNodeProxy( OperatorNode, '*' );
export const div = new ShaderNodeProxy( OperatorNode, '/' );
export const remainder = new ShaderNodeProxy( OperatorNode, '%' );
export const equal = new ShaderNodeProxy( OperatorNode, '==' );
export const assign = new ShaderNodeProxy( OperatorNode, '=' );
export const lessThan = new ShaderNodeProxy( OperatorNode, '<' );
export const greaterThan = new ShaderNodeProxy( OperatorNode, '>' );
export const lessThanEqual = new ShaderNodeProxy( OperatorNode, '<=' );
export const greaterThanEqual = new ShaderNodeProxy( OperatorNode, '>=' );
export const and = new ShaderNodeProxy( OperatorNode, '&&' );
export const or = new ShaderNodeProxy( OperatorNode, '||' );
export const xor = new ShaderNodeProxy( OperatorNode, '^^' );
export const bitAnd = new ShaderNodeProxy( OperatorNode, '&' );
export const bitOr = new ShaderNodeProxy( OperatorNode, '|' );
export const bitXor = new ShaderNodeProxy( OperatorNode, '^' );
export const shiftLeft = new ShaderNodeProxy( OperatorNode, '<<' );
export const shiftRight = new ShaderNodeProxy( OperatorNode, '>>' );

export const element = new ShaderNodeProxy( ArrayElementNode );

export const normalGeometry = new ShaderNodeObject( new NormalNode( NormalNode.GEOMETRY ) );
export const normalLocal = new ShaderNodeObject( new NormalNode( NormalNode.LOCAL ) );
export const normalWorld = new ShaderNodeObject( new NormalNode( NormalNode.WORLD ) );
export const normalView = new ShaderNodeObject( new NormalNode( NormalNode.VIEW ) );
export const transformedNormalView = new ShaderNodeObject( new VarNode( new NormalNode( NormalNode.VIEW ), 'TransformedNormalView', 'vec3' ) );

export const positionLocal = new ShaderNodeObject( new PositionNode( PositionNode.LOCAL ) );
export const positionWorld = new ShaderNodeObject( new PositionNode( PositionNode.WORLD ) );
export const positionView = new ShaderNodeObject( new PositionNode( PositionNode.VIEW ) );
export const positionViewDirection = new ShaderNodeObject( new PositionNode( PositionNode.VIEW_DIRECTION ) );

export const PI = float( 3.141592653589793 );
export const PI2 = float( 6.283185307179586 );
export const PI_HALF = float( 1.5707963267948966 );
export const RECIPROCAL_PI = float( 0.3183098861837907 );
export const RECIPROCAL_PI2 = float( 0.15915494309189535 );
export const EPSILON = float( 1e-6 );

export const diffuseColor = new ShaderNodeObject( new PropertyNode( 'DiffuseColor', 'vec4' ) );
export const roughness = new ShaderNodeObject( new PropertyNode( 'Roughness', 'float' ) );
export const metalness = new ShaderNodeObject( new PropertyNode( 'Metalness', 'float' ) );
export const alphaTest = new ShaderNodeObject( new PropertyNode( 'AlphaTest', 'float' ) );
export const specularColor = new ShaderNodeObject( new PropertyNode( 'SpecularColor', 'color' ) );

export const abs = new ShaderNodeProxy( MathNode, 'abs' );
export const acos = new ShaderNodeProxy( MathNode, 'acos' );
export const asin = new ShaderNodeProxy( MathNode, 'asin' );
export const atan = new ShaderNodeProxy( MathNode, 'atan' );
export const ceil = new ShaderNodeProxy( MathNode, 'ceil' );
export const clamp = new ShaderNodeProxy( MathNode, 'clamp' );
export const cos = new ShaderNodeProxy( MathNode, 'cos' );
export const cross = new ShaderNodeProxy( MathNode, 'cross' );
export const degrees = new ShaderNodeProxy( MathNode, 'degrees' );
export const dFdx = new ShaderNodeProxy( MathNode, 'dFdx' );
export const dFdy = new ShaderNodeProxy( MathNode, 'dFdy' );
export const distance = new ShaderNodeProxy( MathNode, 'distance' );
export const dot = new ShaderNodeProxy( MathNode, 'dot' );
export const exp = new ShaderNodeProxy( MathNode, 'exp' );
export const exp2 = new ShaderNodeProxy( MathNode, 'exp2' );
export const faceforward = new ShaderNodeProxy( MathNode, 'faceforward' );
export const floor = new ShaderNodeProxy( MathNode, 'floor' );
export const fract = new ShaderNodeProxy( MathNode, 'fract' );
export const invert = new ShaderNodeProxy( MathNode, 'invert' );
export const inversesqrt = new ShaderNodeProxy( MathNode, 'inversesqrt' );
export const length = new ShaderNodeProxy( MathNode, 'length' );
export const log = new ShaderNodeProxy( MathNode, 'log' );
export const log2 = new ShaderNodeProxy( MathNode, 'log2' );
export const max = new ShaderNodeProxy( MathNode, 'max' );
export const min = new ShaderNodeProxy( MathNode, 'min' );
export const mix = new ShaderNodeProxy( MathNode, 'mix' );
export const mod = new ShaderNodeProxy( MathNode, 'mod' );
export const negate = new ShaderNodeProxy( MathNode, 'negate' );
export const normalize = new ShaderNodeProxy( MathNode, 'normalize' );
export const pow = new ShaderNodeProxy( MathNode, 'pow' );
export const pow2 = new ShaderNodeProxy( MathNode, 'pow', 2 );
export const pow3 = new ShaderNodeProxy( MathNode, 'pow', 3 );
export const pow4 = new ShaderNodeProxy( MathNode, 'pow', 4 );
export const radians = new ShaderNodeProxy( MathNode, 'radians' );
export const reflect = new ShaderNodeProxy( MathNode, 'reflect' );
export const refract = new ShaderNodeProxy( MathNode, 'refract' );
export const round = new ShaderNodeProxy( MathNode, 'round' );
export const saturate = new ShaderNodeProxy( MathNode, 'saturate' );
export const sign = new ShaderNodeProxy( MathNode, 'sign' );
export const sin = new ShaderNodeProxy( MathNode, 'sin' );
export const smoothstep = new ShaderNodeProxy( MathNode, 'smoothstep' );
export const sqrt = new ShaderNodeProxy( MathNode, 'sqrt' );
export const step = new ShaderNodeProxy( MathNode, 'step' );
export const tan = new ShaderNodeProxy( MathNode, 'tan' );
export const transformDirection = new ShaderNodeProxy( MathNode, 'transformDirection' );
<commit_msg>Nodes: fixes and revision(2) (#23648)<commit_after>// core nodes
import PropertyNode from './core/PropertyNode.js';
import VarNode from './core/VarNode.js';
import AttributeNode from './core/AttributeNode.js';

// input nodes
import BoolNode from './inputs/BoolNode.js';
import ColorNode from './inputs/ColorNode.js';
import FloatNode from './inputs/FloatNode.js';
import IntNode from './inputs/IntNode.js';
import UintNode from './inputs/UintNode.js';
import Vector2Node from './inputs/Vector2Node.js';
import Vector3Node from './inputs/Vector3Node.js';
import Vector4Node from './inputs/Vector4Node.js';
import Matrix3Node from './inputs/Matrix3Node.js';
import Matrix4Node from './inputs/Matrix4Node.js';
import TextureNode from './inputs/TextureNode.js';

// accessor nodes
import PositionNode from './accessors/PositionNode.js';
import NormalNode from './accessors/NormalNode.js';
import UVNode from './accessors/UVNode.js';

// math nodes
import OperatorNode from './math/OperatorNode.js';
import CondNode from './math/CondNode.js';
import MathNode from './math/MathNode.js';

// util nodes
import ArrayElementNode from './utils/ArrayElementNode.js';
import ConvertNode from './utils/ConvertNode.js';
import JoinNode from './utils/JoinNode.js';
import SplitNode from './utils/SplitNode.js';

// core
import { Vector2, Vector3, Vector4, Matrix3, Matrix4, Color } from 'three';

const NodeHandler = {

	construct( NodeClosure, params ) {

		const inputs = params.shift();

		return NodeClosure( new ShaderNodeObjects( inputs ), ...params );

	},

	get: function ( node, prop ) {

		if ( typeof prop === 'string' && node[ prop ] === undefined ) {

			if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true ) {

				// accessing properties ( swizzle )

				prop = prop
					.replace( /r|s/g, 'x' )
					.replace( /g|t/g, 'y' )
					.replace( /b|p/g, 'z' )
					.replace( /a|q/g, 'w' );

				return new ShaderNodeObject( new SplitNode( node, prop ) );

			} else if ( /^\d+$/.test( prop ) === true ) {

				// accessing array

				return new ShaderNodeObject( new ArrayElementNode( node, new FloatNode( Number( prop ) ).setConst( true ) ) );

			}

		}

		return node[ prop ];

	}

};

const nodeObjects = new WeakMap();

const ShaderNodeObject = function( obj ) {

	const type = typeof obj;

	if ( type === 'number' ) {

		return new ShaderNodeObject( new FloatNode( obj ).setConst( true ) );

	} else if ( type === 'object' ) {

		if ( obj.isNode === true ) {

			let nodeObject = nodeObjects.get( obj );

			if ( nodeObject === undefined ) {

				nodeObject = new Proxy( obj, NodeHandler );
				nodeObjects.set( obj, nodeObject );
				nodeObjects.set( nodeObject, nodeObject );

			}

			return nodeObject;

		}

	}

	return obj;

};

const ShaderNodeObjects = function( objects ) {

	for ( const name in objects ) {

		objects[ name ] = new ShaderNodeObject( objects[ name ] );

	}

	return objects;

};

const getShaderNodeArray = ( array ) => {

	const len = array.length;

	for ( let i = 0; i < len; i ++ ) {

		array[ i ] = new ShaderNodeObject( array[ i ] );

	}

	return array;

};

const ShaderNodeProxy = function( NodeClass, scope = null, factor = null ) {

	if ( scope === null ) {

		return ( ...params ) => {

			return new ShaderNodeObject( new NodeClass( ...getShaderNodeArray( params ) ) );

		};

	} else if ( factor === null ) {

		return ( ...params ) => {

			return new ShaderNodeObject( new NodeClass( scope, ...getShaderNodeArray( params ) ) );

		};

	} else {

		factor = new ShaderNodeObject( factor );

		return ( ...params ) => {

			return new ShaderNodeObject( new NodeClass( scope, ...getShaderNodeArray( params ), factor ) );

		};

	}

};

const ShaderNodeScript = function ( jsFunc ) {

	return ( inputs, builder ) => {

		new ShaderNodeObjects( inputs );

		return new ShaderNodeObject( jsFunc( inputs, builder ) );

	};

};

export const ShaderNode = new Proxy( ShaderNodeScript, NodeHandler );

//
// Node Material Shader Syntax
//

export const uniform = new ShaderNode( ( inputNode ) => {

	inputNode.setConst( false );

	return inputNode;

} );

export const nodeObject = ( val ) => {

	return new ShaderNodeObject( val );

};

export const label = ( node, name ) => {

	node = nodeObject( node );

	if ( node.isVarNode === true ) {

		node.name = name;

		return node;

	}

	return nodeObject( new VarNode( node, name ) );

};

export const temp = ( node ) => nodeObject( new VarNode( nodeObject( node ) ) );

const ConvertType = function ( nodeClass, type, valueClass = null, valueComponents = 1 ) {

	return ( ...params ) => {

		if ( params[ 0 ]?.isNode === true ) {

			return nodeObject( new ConvertNode( params[ 0 ], type ) );

		}

		if ( ( params.length === 1 ) && ( valueComponents !== 1 ) ) {

			// Providing one scalar value: This value is used for all components

			for ( let i = 1; i < valueComponents; i ++ ) {

				params[ i ] = params [ 0 ];

			}

		}

		const val = ( ( valueClass === null ) || ( params[ 0 ] instanceof valueClass ) ) ? params[ 0 ] : new valueClass().set( ...params );

		return nodeObject( new nodeClass( val ).setConst( true ) );

	};

};

export const float = new ConvertType( FloatNode, 'float' );
export const int = new ConvertType( IntNode, 'int' );
export const uint = new ConvertType( UintNode, 'uint' );
export const bool = new ConvertType( BoolNode, 'bool' );
export const color = new ConvertType( ColorNode, 'color', Color );

export const vec2 = new ConvertType( Vector2Node, 'vec2', Vector2, 2 );
export const vec3 = new ConvertType( Vector3Node, 'vec3', Vector3, 3 );
export const vec4 = new ConvertType( Vector4Node, 'vec4', Vector4, 4 );

export const mat3 = new ConvertType( Matrix3Node, 'mat3', Matrix3 );
export const mat4 = new ConvertType( Matrix4Node, 'mat4', Matrix4 );

export const join = ( ...params ) => {

	return nodeObject( new JoinNode( getShaderNodeArray( params ) ) );

};

export const cond = ( ...params ) => {

	return nodeObject( new CondNode( ...getShaderNodeArray( params ) ) );

};

export const addTo = ( varNode, ...params ) => {

	varNode.node = add( varNode.node, ...getShaderNodeArray( params ) );

	return nodeObject( varNode );

};

export const uv = new ShaderNodeProxy( UVNode );
export const attribute = new ShaderNodeProxy( AttributeNode );

export const texture = new ShaderNodeProxy( TextureNode );

export const add = new ShaderNodeProxy( OperatorNode, '+' );
export const sub = new ShaderNodeProxy( OperatorNode, '-' );
export const mul = new ShaderNodeProxy( OperatorNode, '*' );
export const div = new ShaderNodeProxy( OperatorNode, '/' );
export const remainder = new ShaderNodeProxy( OperatorNode, '%' );
export const equal = new ShaderNodeProxy( OperatorNode, '==' );
export const assign = new ShaderNodeProxy( OperatorNode, '=' );
export const lessThan = new ShaderNodeProxy( OperatorNode, '<' );
export const greaterThan = new ShaderNodeProxy( OperatorNode, '>' );
export const lessThanEqual = new ShaderNodeProxy( OperatorNode, '<=' );
export const greaterThanEqual = new ShaderNodeProxy( OperatorNode, '>=' );
export const and = new ShaderNodeProxy( OperatorNode, '&&' );
export const or = new ShaderNodeProxy( OperatorNode, '||' );
export const xor = new ShaderNodeProxy( OperatorNode, '^^' );
export const bitAnd = new ShaderNodeProxy( OperatorNode, '&' );
export const bitOr = new ShaderNodeProxy( OperatorNode, '|' );
export const bitXor = new ShaderNodeProxy( OperatorNode, '^' );
export const shiftLeft = new ShaderNodeProxy( OperatorNode, '<<' );
export const shiftRight = new ShaderNodeProxy( OperatorNode, '>>' );

export const element = new ShaderNodeProxy( ArrayElementNode );

export const normalGeometry = new ShaderNodeObject( new NormalNode( NormalNode.GEOMETRY ) );
export const normalLocal = new ShaderNodeObject( new NormalNode( NormalNode.LOCAL ) );
export const normalWorld = new ShaderNodeObject( new NormalNode( NormalNode.WORLD ) );
export const normalView = new ShaderNodeObject( new NormalNode( NormalNode.VIEW ) );
export const transformedNormalView = new ShaderNodeObject( new VarNode( new NormalNode( NormalNode.VIEW ), 'TransformedNormalView', 'vec3' ) );

export const positionLocal = new ShaderNodeObject( new PositionNode( PositionNode.LOCAL ) );
export const positionWorld = new ShaderNodeObject( new PositionNode( PositionNode.WORLD ) );
export const positionView = new ShaderNodeObject( new PositionNode( PositionNode.VIEW ) );
export const positionViewDirection = new ShaderNodeObject( new PositionNode( PositionNode.VIEW_DIRECTION ) );

export const PI = float( 3.141592653589793 );
export const PI2 = float( 6.283185307179586 );
export const PI_HALF = float( 1.5707963267948966 );
export const RECIPROCAL_PI = float( 0.3183098861837907 );
export const RECIPROCAL_PI2 = float( 0.15915494309189535 );
export const EPSILON = float( 1e-6 );

export const diffuseColor = new ShaderNodeObject( new PropertyNode( 'DiffuseColor', 'vec4' ) );
export const roughness = new ShaderNodeObject( new PropertyNode( 'Roughness', 'float' ) );
export const metalness = new ShaderNodeObject( new PropertyNode( 'Metalness', 'float' ) );
export const alphaTest = new ShaderNodeObject( new PropertyNode( 'AlphaTest', 'float' ) );
export const specularColor = new ShaderNodeObject( new PropertyNode( 'SpecularColor', 'color' ) );

export const abs = new ShaderNodeProxy( MathNode, 'abs' );
export const acos = new ShaderNodeProxy( MathNode, 'acos' );
export const asin = new ShaderNodeProxy( MathNode, 'asin' );
export const atan = new ShaderNodeProxy( MathNode, 'atan' );
export const ceil = new ShaderNodeProxy( MathNode, 'ceil' );
export const clamp = new ShaderNodeProxy( MathNode, 'clamp' );
export const cos = new ShaderNodeProxy( MathNode, 'cos' );
export const cross = new ShaderNodeProxy( MathNode, 'cross' );
export const degrees = new ShaderNodeProxy( MathNode, 'degrees' );
export const dFdx = new ShaderNodeProxy( MathNode, 'dFdx' );
export const dFdy = new ShaderNodeProxy( MathNode, 'dFdy' );
export const distance = new ShaderNodeProxy( MathNode, 'distance' );
export const dot = new ShaderNodeProxy( MathNode, 'dot' );
export const exp = new ShaderNodeProxy( MathNode, 'exp' );
export const exp2 = new ShaderNodeProxy( MathNode, 'exp2' );
export const faceforward = new ShaderNodeProxy( MathNode, 'faceforward' );
export const floor = new ShaderNodeProxy( MathNode, 'floor' );
export const fract = new ShaderNodeProxy( MathNode, 'fract' );
export const invert = new ShaderNodeProxy( MathNode, 'invert' );
export const inversesqrt = new ShaderNodeProxy( MathNode, 'inversesqrt' );
export const length = new ShaderNodeProxy( MathNode, 'length' );
export const log = new ShaderNodeProxy( MathNode, 'log' );
export const log2 = new ShaderNodeProxy( MathNode, 'log2' );
export const max = new ShaderNodeProxy( MathNode, 'max' );
export const min = new ShaderNodeProxy( MathNode, 'min' );
export const mix = new ShaderNodeProxy( MathNode, 'mix' );
export const mod = new ShaderNodeProxy( MathNode, 'mod' );
export const negate = new ShaderNodeProxy( MathNode, 'negate' );
export const normalize = new ShaderNodeProxy( MathNode, 'normalize' );
export const pow = new ShaderNodeProxy( MathNode, 'pow' );
export const pow2 = new ShaderNodeProxy( MathNode, 'pow', 2 );
export const pow3 = new ShaderNodeProxy( MathNode, 'pow', 3 );
export const pow4 = new ShaderNodeProxy( MathNode, 'pow', 4 );
export const radians = new ShaderNodeProxy( MathNode, 'radians' );
export const reflect = new ShaderNodeProxy( MathNode, 'reflect' );
export const refract = new ShaderNodeProxy( MathNode, 'refract' );
export const round = new ShaderNodeProxy( MathNode, 'round' );
export const saturate = new ShaderNodeProxy( MathNode, 'saturate' );
export const sign = new ShaderNodeProxy( MathNode, 'sign' );
export const sin = new ShaderNodeProxy( MathNode, 'sin' );
export const smoothstep = new ShaderNodeProxy( MathNode, 'smoothstep' );
export const sqrt = new ShaderNodeProxy( MathNode, 'sqrt' );
export const step = new ShaderNodeProxy( MathNode, 'step' );
export const tan = new ShaderNodeProxy( MathNode, 'tan' );
export const transformDirection = new ShaderNodeProxy( MathNode, 'transformDirection' );
<|endoftext|>


<commit_before>var Message = require("./message.js").Message,
    Upload = require("./upload.js").Upload,
    User = require("./user.js").User;

/**
 * A chat room.
 * @constructor
 * @param {Connection} connection A server connection.
 * @param {Object} attrs The initial attributes of this room.
 */
exports.Room = function (connection, attrs) {
  var self, resultToMessages, urlFor, get, post, put, sendMessage, listenConnection;

  self = this;
  this.id = attrs.id;
  this.name = attrs.name;
  this.topic = attrs.topic;
  this.membershipLimit = attrs.membership_limit;
  this.locked = attrs.locked;
  this.openToGuests = attrs.open_to_guests;
  this.guestToken = attrs.active_token_value;
  this.createdAt = new Date(attrs.created_at);
  this.updatedAt = new Date(attrs.updated_at);

  /**
   * Returns an array of message objects given the server response.
   * @param {Object} result The server result.
   * @return {Array.<Message>} The resulting messages.
   */
  resultToMessages = function (result) {
    var i, messages = [];
    for (i = 0; i < result.messages.length; i++) {
      messages.push(new Message(connection, result.messages[i]));
    }

    return messages;
  }

  /**
   * Returns the url for the specified action.
   * @param {string} action The action to get the url for.
   * @return {string} The url for the specified action.
   */
  urlFor = function (action) {
    var url = "/room/" + self.id;
    if (action) {
      url += "/" + action;
    }

    return url + ".json";
  };

  get = function (action, params, callback) {
    connection.get(urlFor(action), params, callback);
  };

  post = function (action, body, callback) {
    connection.post(urlFor(action), body, callback);
  };

  put = function (body, callback) {
    connection.put("/room/" + self.id + ".json", body, callback);
  };

  /**
   * Sends the specified message.
   * @param {string} message The message to send.
   * @param {string} type The type of message.
   * @param {function()=} callback The optional callback to call when complete.
   */
  sendMessage = function (message, type, callback) {
    post("speak", {
      message: {
        body: message,
        type: type
      }
    }, callback);
  };

  /**
   * Sends the specified message.
   * @param {string} message The message to send.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.speak = function (message, callback) {
    sendMessage(message, "TextMessage", callback);
  };

  /**
   * Sends the specified pre-formatted message.
   * @param {string} message The message to send.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.paste = function (message, callback) {
    sendMessage(message, "PasteMessage", callback);
  };

  /**
   * Plays the sound with the specified name.
   * @param {string} name The name of the sound to play.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.play = function (name, callback) {
    sendMessage(name, "SoundMessage", callback);
  };

  /**
   * Updates the specified room attributes.
   * @param {Object} attributes The attributes to update.
   * @param {function()=} callback The optional callback to call when comlete.
   */
  this.update = function (attributes, callback) {
    // TODO: Update the name and topic of the room after complete
    put({ room: attributes }, callback);
  };

  /**
   * Join this room.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.join = function (callback) {
    post("join", callback);
  };

  /**
   * Leave this room.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.leave = function (callback) {
    post("leave", callback);
  };

  /**
   * Lock this room. You must be in a room to lock it.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.lock = function (callback) {
    post("lock", callback);
  };

  /**
   * Unlock this room.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.unlock = function (callback) {
    post("unlock", callback);
  };

  /**
   * Gets an array of users currently in this room.
   * @param {function(Array.<User>)} callback The callback to call with the array of users.
   */
  this.users = function (callback) {
    get(null, function (data) {
      var i, users = [];
      for (i = 0; i < data.room.users.length; i++) {
        users.push(new User(connection, data.room.users[i]));
      }

      callback(users);
    });
  };

  /**
   * Gets up to 5 recent file uploads in the room.
   * @param {function(Array.<Upload>)} callback The callback to call with the array of uploads.
   */
  this.recentUploads = function (callback) {
    get("uploads", function (data) {
      var i, uploads = [];
      for (i = 0; i < data.uploads.length; i++) {
        uploads.push(new Upload(connection, data.uploads[i]));
      }

      callback(uploads);
    });
  };

  /**
   * Gets up to 100 recent messages in the room.
   * The following options are valid:
   * limit - Limits the number of results (a max of 100 will be returned).
   * sinceMessageId - Only retrieve messages after this message id.
   * @param {Object=} options The optional options to use.
   * @param {function(Array.<Message>)} callback The callback to call with the array of messages.
   */
  this.recentMessages = function (options, callback) {
    // Change case to what Campfire expects
    if (options && options.sinceMessageId) {
      options.since_message_id = options.sinceMessageId;
      delete options.sinceMessageId;
    }

    get("recent", options, function (data) {
      callback(resultToMessages(data));
    });
  };

  /**
   * Listen for messages in the room. Currently only one listener is supported,
   * subsequent listeners will be ignored.
   * @param {function(Message)} callback The callback to call with each message.
   */
  this.listen = function (callback) {
    if (self.isListening()) {
      return;
    }

    connection.stream(urlFor("live"), function (streamConnection) {
      listenConnection = streamConnection;
    }, function (data) {
      callback(new Message(connection, data));
    });
  };

  /**
   * Stops listening for messages in this room.
   */
  this.stopListening = function () {
    if (self.isListening()) {
      listenConnection.destroy();
      listenConnection = null;
    }
  };

  /**
   * Returns whether there is currently a connection listening on this room.
   * @return {boolean} Whether there is currently a connection listening.
   */
  this.isListening = function () {
    return !!(listenConnection);
  };

  /**
   * Gets all messages for a specified date.
   * @param {Date=} date The date to get messages for (defaults to today).
   * @param {function(Array.<Message>)} callback The callback to call with the array of messages.
   */
  this.transcript = function (date, callback) {
    var action = "transcript";

    if (typeof date === "function") {
      callback = date;
      date = null;
    }

    if (date) {
      action += "/" + date.getFullYear();
      action += "/" + (date.getMonth() + 1);
      action += "/" + date.getDate();
    }

    get(action, function (data) {
      callback(resultToMessages(data));
    });
  };
};
<commit_msg>Missing semicolon<commit_after>var Message = require("./message.js").Message,
    Upload = require("./upload.js").Upload,
    User = require("./user.js").User;

/**
 * A chat room.
 * @constructor
 * @param {Connection} connection A server connection.
 * @param {Object} attrs The initial attributes of this room.
 */
exports.Room = function (connection, attrs) {
  var self, resultToMessages, urlFor, get, post, put, sendMessage, listenConnection;

  self = this;
  this.id = attrs.id;
  this.name = attrs.name;
  this.topic = attrs.topic;
  this.membershipLimit = attrs.membership_limit;
  this.locked = attrs.locked;
  this.openToGuests = attrs.open_to_guests;
  this.guestToken = attrs.active_token_value;
  this.createdAt = new Date(attrs.created_at);
  this.updatedAt = new Date(attrs.updated_at);

  /**
   * Returns an array of message objects given the server response.
   * @param {Object} result The server result.
   * @return {Array.<Message>} The resulting messages.
   */
  resultToMessages = function (result) {
    var i, messages = [];
    for (i = 0; i < result.messages.length; i++) {
      messages.push(new Message(connection, result.messages[i]));
    }

    return messages;
  };

  /**
   * Returns the url for the specified action.
   * @param {string} action The action to get the url for.
   * @return {string} The url for the specified action.
   */
  urlFor = function (action) {
    var url = "/room/" + self.id;
    if (action) {
      url += "/" + action;
    }

    return url + ".json";
  };

  get = function (action, params, callback) {
    connection.get(urlFor(action), params, callback);
  };

  post = function (action, body, callback) {
    connection.post(urlFor(action), body, callback);
  };

  put = function (body, callback) {
    connection.put("/room/" + self.id + ".json", body, callback);
  };

  /**
   * Sends the specified message.
   * @param {string} message The message to send.
   * @param {string} type The type of message.
   * @param {function()=} callback The optional callback to call when complete.
   */
  sendMessage = function (message, type, callback) {
    post("speak", {
      message: {
        body: message,
        type: type
      }
    }, callback);
  };

  /**
   * Sends the specified message.
   * @param {string} message The message to send.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.speak = function (message, callback) {
    sendMessage(message, "TextMessage", callback);
  };

  /**
   * Sends the specified pre-formatted message.
   * @param {string} message The message to send.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.paste = function (message, callback) {
    sendMessage(message, "PasteMessage", callback);
  };

  /**
   * Plays the sound with the specified name.
   * @param {string} name The name of the sound to play.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.play = function (name, callback) {
    sendMessage(name, "SoundMessage", callback);
  };

  /**
   * Updates the specified room attributes.
   * @param {Object} attributes The attributes to update.
   * @param {function()=} callback The optional callback to call when comlete.
   */
  this.update = function (attributes, callback) {
    // TODO: Update the name and topic of the room after complete
    put({ room: attributes }, callback);
  };

  /**
   * Join this room.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.join = function (callback) {
    post("join", callback);
  };

  /**
   * Leave this room.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.leave = function (callback) {
    post("leave", callback);
  };

  /**
   * Lock this room. You must be in a room to lock it.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.lock = function (callback) {
    post("lock", callback);
  };

  /**
   * Unlock this room.
   * @param {function()=} callback The optional callback to call when complete.
   */
  this.unlock = function (callback) {
    post("unlock", callback);
  };

  /**
   * Gets an array of users currently in this room.
   * @param {function(Array.<User>)} callback The callback to call with the array of users.
   */
  this.users = function (callback) {
    get(null, function (data) {
      var i, users = [];
      for (i = 0; i < data.room.users.length; i++) {
        users.push(new User(connection, data.room.users[i]));
      }

      callback(users);
    });
  };

  /**
   * Gets up to 5 recent file uploads in the room.
   * @param {function(Array.<Upload>)} callback The callback to call with the array of uploads.
   */
  this.recentUploads = function (callback) {
    get("uploads", function (data) {
      var i, uploads = [];
      for (i = 0; i < data.uploads.length; i++) {
        uploads.push(new Upload(connection, data.uploads[i]));
      }

      callback(uploads);
    });
  };

  /**
   * Gets up to 100 recent messages in the room.
   * The following options are valid:
   * limit - Limits the number of results (a max of 100 will be returned).
   * sinceMessageId - Only retrieve messages after this message id.
   * @param {Object=} options The optional options to use.
   * @param {function(Array.<Message>)} callback The callback to call with the array of messages.
   */
  this.recentMessages = function (options, callback) {
    // Change case to what Campfire expects
    if (options && options.sinceMessageId) {
      options.since_message_id = options.sinceMessageId;
      delete options.sinceMessageId;
    }

    get("recent", options, function (data) {
      callback(resultToMessages(data));
    });
  };

  /**
   * Listen for messages in the room. Currently only one listener is supported,
   * subsequent listeners will be ignored.
   * @param {function(Message)} callback The callback to call with each message.
   */
  this.listen = function (callback) {
    if (self.isListening()) {
      return;
    }

    connection.stream(urlFor("live"), function (streamConnection) {
      listenConnection = streamConnection;
    }, function (data) {
      callback(new Message(connection, data));
    });
  };

  /**
   * Stops listening for messages in this room.
   */
  this.stopListening = function () {
    if (self.isListening()) {
      listenConnection.destroy();
      listenConnection = null;
    }
  };

  /**
   * Returns whether there is currently a connection listening on this room.
   * @return {boolean} Whether there is currently a connection listening.
   */
  this.isListening = function () {
    return !!(listenConnection);
  };

  /**
   * Gets all messages for a specified date.
   * @param {Date=} date The date to get messages for (defaults to today).
   * @param {function(Array.<Message>)} callback The callback to call with the array of messages.
   */
  this.transcript = function (date, callback) {
    var action = "transcript";

    if (typeof date === "function") {
      callback = date;
      date = null;
    }

    if (date) {
      action += "/" + date.getFullYear();
      action += "/" + (date.getMonth() + 1);
      action += "/" + date.getDate();
    }

    get(action, function (data) {
      callback(resultToMessages(data));
    });
  };
};
<|endoftext|>


<commit_before>// @flow
import * as Constants from '../../constants/chat'
import * as Creators from '../../actions/chat/creators'
import Conversation from './index'
import HiddenString from '../../util/hidden-string'
import React, {Component} from 'react'
import {Box} from '../../common-adapters'
import {List, Map} from 'immutable'
import {connect} from 'react-redux'
import {downloadFilePath} from '../../util/file'
import {getProfile} from '../../actions/tracker'
import {navigateAppend} from '../../actions/route-tree'
import {onUserClick} from '../../actions/profile'
import {openDialog as openRekeyDialog} from '../../actions/unlock-folders'
import {pick} from 'lodash'
import {isMobile} from '../../constants/platform'

import type {TypedState} from '../../constants/reducer'
import type {OpenInFileUI} from '../../constants/kbfs'
import type {Props} from '.'

type OwnProps = {}
type State = {
  listScrollDownCounter: number, // count goes up when this mutates, causing the list to scroll down
  sidePanelOpen: boolean,
}

class ConversationContainer extends Component<void, Props, State> {
  state: State

  constructor (props: Props) {
    super(props)
    this.state = {
      listScrollDownCounter: 0,
      sidePanelOpen: false,
    }
  }

  componentWillReceiveProps (nextProps: Props) {
    if (this.props.selectedConversation !== nextProps.selectedConversation) {
      this.setState({
        sidePanelOpen: false,
      })
    }
  }

  _onToggleSidePanel = () => {
    this.setState({sidePanelOpen: !this.state.sidePanelOpen})
  }

  _onTriggerScrollDown = () => {
    this.setState({listScrollDownCounter: this.state.listScrollDownCounter + 1})
  }

  render () {
    if (!this.props.selectedConversation) {
      return <Box style={{flex: 1}} />
    }

    return <Conversation
      {...this.props}
      sidePanelOpen={this.state.sidePanelOpen}
      onToggleSidePanel={this._onToggleSidePanel}
      onPostMessage={(...args) => {
        this._onTriggerScrollDown()
        this.props.onPostMessage(...args)
      }}
      listScrollDownState={this.state.listScrollDownCounter}
    />
  }
}

export default connect(
  (state: TypedState, {routePath, routeState}) => {
    const selectedConversation = routePath.last()

    const you = state.config.username || ''
    const followingMap = state.config.following
    const metaDataMap = state.chat.get('metaData')

    if (Constants.isPendingConversationIDKey(selectedConversation)) {
      const tlfName = Constants.pendingConversationIDKeyToTlfName(selectedConversation)
      if (tlfName) {
        const participants = List(tlfName.split(','))

        return {
          bannerMessage: null,
          emojiPickerOpen: false,
          followingMap: pick(followingMap, participants.toArray()),
          inputText: routeState.inputText && routeState.inputText.stringValue(),
          isLoading: false,
          messages: List(),
          metaDataMap: metaDataMap.filter((k, v) => participants.contains(v)),
          moreToLoad: false,
          muted: false,
          participants,
          rekeyInfo: null,
          selectedConversation,
          validated: true,
          you,
        }
      }
    }

    if (selectedConversation !== Constants.nothingSelected) {
      const conversationState = state.chat.get('conversationStates').get(selectedConversation)
      if (conversationState) {
        const inbox = state.chat.get('inbox')
        const selected = inbox && inbox.find(inbox => inbox.get('conversationIDKey') === selectedConversation)
        const muted = selected && selected.get('status') === 'muted'
        const participants = selected && selected.participants || List()
        const rekeyInfo = state.chat.get('rekeyInfos').get(selectedConversation)

        const supersedes = Constants.convSupersedesInfo(selectedConversation, state.chat)
        const supersededBy = Constants.convSupersededByInfo(selectedConversation, state.chat)
        const finalizeInfo = state.chat.get('finalizedState').get(selectedConversation)

        return {
          bannerMessage: null,
          emojiPickerOpen: false,
          firstNewMessageID: conversationState.firstNewMessageID,
          followingMap: pick(followingMap, participants.toArray()),
          inputText: routeState.inputText && routeState.inputText.stringValue(),
          isLoading: conversationState.isLoading,
          messages: conversationState.messages,
          metaDataMap: metaDataMap.filter((k, v) => participants.contains(v)),
          moreToLoad: conversationState.moreToLoad,
          muted,
          participants,
          rekeyInfo,
          selectedConversation,
          validated: selected && selected.validated,
          you,
          supersedes,
          supersededBy,
          finalizeInfo,
          editingMessage: state.chat.get('editingMessage'),
        }
      }
    }

    return {
      bannerMessage: null,
      followingMap,
      isLoading: false,
      messages: List(),
      metaDataMap: Map(),
      moreToLoad: false,
      participants: List(),
      rekeyInfo: null,
      selectedConversation,
      validated: false,
      you,
      supersedes: null,
      supersededBy: null,
    }
  },
  (dispatch: Dispatch, {setRouteState, navigateUp}) => ({
    onAddParticipant: (participants: Array<string>) => dispatch(Creators.newChat(participants)),
    onAttach: (selectedConversation, inputs: Array<Constants.AttachmentInput>) => { dispatch(navigateAppend([{props: {conversationIDKey: selectedConversation, inputs}, selected: 'attachmentInput'}])) },
    onBack: () => dispatch(navigateUp()),
    onDeleteMessage: (message: Constants.Message) => { dispatch(Creators.deleteMessage(message)) },
    onEditMessage: (message: Constants.Message, body: string) => { dispatch(Creators.editMessage(message, new HiddenString(body))) },
    onBannerWarning: (username: string) => { isMobile ? dispatch(Creators.onShowProfile(username)) : dispatch(Creators.onShowTracker(username)) },
    onShowBlockConversationDialog: (selectedConversation, participants) => { dispatch(navigateAppend([{props: {conversationIDKey: selectedConversation, participants}, selected: 'showBlockConversationDialog'}])) },
    onShowEditor: (message: Constants.Message) => { dispatch(Creators.showEditor(message)) },
    onLoadAttachment: (selectedConversation, messageID, filename) => dispatch(Creators.loadAttachment(selectedConversation, messageID, downloadFilePath(filename), false, false)),
    onLoadMoreMessages: (conversationIDKey: Constants.ConversationIDKey) => dispatch(Creators.loadMoreMessages(conversationIDKey, false)),
    onMessageAction: (message: Constants.ServerMessage) => dispatch(navigateAppend([{props: {message}, selected: 'messageAction'}])),
    onMuteConversation: (conversationIDKey: Constants.ConversationIDKey, muted: boolean) => { dispatch(Creators.muteConversation(conversationIDKey, muted)) },
    onOpenFolder: () => dispatch(Creators.openFolder()),
    onOpenConversation: (conversationIDKey: Constants.ConversationIDKey) => dispatch(Creators.openConversation(conversationIDKey)),
    onOpenInFileUI: (path: string) => dispatch(({payload: {path}, type: 'fs:openInFileUI'}: OpenInFileUI)),
    onOpenInPopup: (message: Constants.AttachmentMessage) => dispatch(Creators.openAttachmentPopup(message)),
    onPostMessage: (selectedConversation, text) => dispatch(Creators.postMessage(selectedConversation, new HiddenString(text))),
    onRetryAttachment: (message: Constants.AttachmentMessage) => dispatch(Creators.retryAttachment(message)),
    onRetryMessage: (conversationIDKey: Constants.ConversationIDKey, outboxID: Constants.OutboxIDKey) => dispatch(Creators.retryMessage(conversationIDKey, outboxID)),
    onSelectAttachment: (conversationIDKey: Constants.ConversationIDKey, input: Constants.AttachmentInput) => dispatch(Creators.selectAttachment(input)),
    startConversation: (users: Array<string>) => dispatch(Creators.startConversation(users, true)),
    onStoreInputText: (inputText: string) => setRouteState({inputText: new HiddenString(inputText)}),
    onShowProfile: (username: string) => dispatch(onUserClick(username, '')),
    onShowTracker: (username: string) => dispatch(getProfile(username, true, true)),
    onRekey: () => dispatch(openRekeyDialog()),
    onEnterPaperkey: () => dispatch(navigateAppend(['enterPaperkey'])),
  }),
  (stateProps, dispatchProps, ownProps: OwnProps) => {
    let bannerMessage

    const brokenUsers = Constants.getBrokenUsers(stateProps.participants.toArray(), stateProps.you, stateProps.metaDataMap)
    if (brokenUsers.length) {
      bannerMessage = {
        onClick: (user: string) => dispatchProps.onBannerWarning(user),
        type: 'BrokenTracker',
        users: brokenUsers,
      }
    }

    if (!bannerMessage) {
      const sbsUsers = stateProps.participants.filter(p => p.includes('@')).toArray()
      if (sbsUsers.length) {
        bannerMessage = {
          type: 'Invite',
          users: sbsUsers,
        }
      }
    }

    return {
      ...stateProps,
      ...dispatchProps,
      ...ownProps,
      bannerMessage,
      onAddParticipant: () => dispatchProps.onAddParticipant(stateProps.participants.filter(p => p !== stateProps.you).toArray()),
      onAttach: (inputs: Array<Constants.AttachmentInput>) => dispatchProps.onAttach(stateProps.selectedConversation, inputs),
      onLoadAttachment: (messageID, filename) => dispatchProps.onLoadAttachment(stateProps.selectedConversation, messageID, filename),
      onLoadMoreMessages: () => dispatchProps.onLoadMoreMessages(stateProps.selectedConversation),
      onMuteConversation: (muted: boolean) => dispatchProps.onMuteConversation(stateProps.selectedConversation, muted),
      onPostMessage: text => dispatchProps.onPostMessage(stateProps.selectedConversation, text),
      onRetryMessage: (outboxID: Constants.OutboxIDKey) => dispatchProps.onRetryMessage(stateProps.selectedConversation, outboxID),
      onSelectAttachment: (input) => dispatchProps.onSelectAttachment(stateProps.selectedConversation, input),
      onShowBlockConversationDialog: () => dispatchProps.onShowBlockConversationDialog(stateProps.selectedConversation, stateProps.participants.toArray().join(',')),
      restartConversation: () => dispatchProps.startConversation(stateProps.participants.toArray()),
    }
  },
)(ConversationContainer)
<commit_msg>avoid referencing dispatchProps inside dispatchProps<commit_after>// @flow
import * as Constants from '../../constants/chat'
import * as Creators from '../../actions/chat/creators'
import Conversation from './index'
import HiddenString from '../../util/hidden-string'
import React, {Component} from 'react'
import {Box} from '../../common-adapters'
import {List, Map} from 'immutable'
import {connect} from 'react-redux'
import {downloadFilePath} from '../../util/file'
import {getProfile} from '../../actions/tracker'
import {navigateAppend} from '../../actions/route-tree'
import {onUserClick} from '../../actions/profile'
import {openDialog as openRekeyDialog} from '../../actions/unlock-folders'
import {pick} from 'lodash'
import {isMobile} from '../../constants/platform'

import type {TypedState} from '../../constants/reducer'
import type {OpenInFileUI} from '../../constants/kbfs'
import type {Props} from '.'

type OwnProps = {}
type State = {
  listScrollDownCounter: number, // count goes up when this mutates, causing the list to scroll down
  sidePanelOpen: boolean,
}

class ConversationContainer extends Component<void, Props, State> {
  state: State

  constructor (props: Props) {
    super(props)
    this.state = {
      listScrollDownCounter: 0,
      sidePanelOpen: false,
    }
  }

  componentWillReceiveProps (nextProps: Props) {
    if (this.props.selectedConversation !== nextProps.selectedConversation) {
      this.setState({
        sidePanelOpen: false,
      })
    }
  }

  _onToggleSidePanel = () => {
    this.setState({sidePanelOpen: !this.state.sidePanelOpen})
  }

  _onTriggerScrollDown = () => {
    this.setState({listScrollDownCounter: this.state.listScrollDownCounter + 1})
  }

  render () {
    if (!this.props.selectedConversation) {
      return <Box style={{flex: 1}} />
    }

    return <Conversation
      {...this.props}
      sidePanelOpen={this.state.sidePanelOpen}
      onToggleSidePanel={this._onToggleSidePanel}
      onPostMessage={(...args) => {
        this._onTriggerScrollDown()
        this.props.onPostMessage(...args)
      }}
      listScrollDownState={this.state.listScrollDownCounter}
    />
  }
}

export default connect(
  (state: TypedState, {routePath, routeState}) => {
    const selectedConversation = routePath.last()

    const you = state.config.username || ''
    const followingMap = state.config.following
    const metaDataMap = state.chat.get('metaData')

    if (Constants.isPendingConversationIDKey(selectedConversation)) {
      const tlfName = Constants.pendingConversationIDKeyToTlfName(selectedConversation)
      if (tlfName) {
        const participants = List(tlfName.split(','))

        return {
          bannerMessage: null,
          emojiPickerOpen: false,
          followingMap: pick(followingMap, participants.toArray()),
          inputText: routeState.inputText && routeState.inputText.stringValue(),
          isLoading: false,
          messages: List(),
          metaDataMap: metaDataMap.filter((k, v) => participants.contains(v)),
          moreToLoad: false,
          muted: false,
          participants,
          rekeyInfo: null,
          selectedConversation,
          validated: true,
          you,
        }
      }
    }

    if (selectedConversation !== Constants.nothingSelected) {
      const conversationState = state.chat.get('conversationStates').get(selectedConversation)
      if (conversationState) {
        const inbox = state.chat.get('inbox')
        const selected = inbox && inbox.find(inbox => inbox.get('conversationIDKey') === selectedConversation)
        const muted = selected && selected.get('status') === 'muted'
        const participants = selected && selected.participants || List()
        const rekeyInfo = state.chat.get('rekeyInfos').get(selectedConversation)

        const supersedes = Constants.convSupersedesInfo(selectedConversation, state.chat)
        const supersededBy = Constants.convSupersededByInfo(selectedConversation, state.chat)
        const finalizeInfo = state.chat.get('finalizedState').get(selectedConversation)

        return {
          bannerMessage: null,
          emojiPickerOpen: false,
          firstNewMessageID: conversationState.firstNewMessageID,
          followingMap: pick(followingMap, participants.toArray()),
          inputText: routeState.inputText && routeState.inputText.stringValue(),
          isLoading: conversationState.isLoading,
          messages: conversationState.messages,
          metaDataMap: metaDataMap.filter((k, v) => participants.contains(v)),
          moreToLoad: conversationState.moreToLoad,
          muted,
          participants,
          rekeyInfo,
          selectedConversation,
          validated: selected && selected.validated,
          you,
          supersedes,
          supersededBy,
          finalizeInfo,
          editingMessage: state.chat.get('editingMessage'),
        }
      }
    }

    return {
      bannerMessage: null,
      followingMap,
      isLoading: false,
      messages: List(),
      metaDataMap: Map(),
      moreToLoad: false,
      participants: List(),
      rekeyInfo: null,
      selectedConversation,
      validated: false,
      you,
      supersedes: null,
      supersededBy: null,
    }
  },
  (dispatch: Dispatch, {setRouteState, navigateUp}) => ({
    onAddParticipant: (participants: Array<string>) => dispatch(Creators.newChat(participants)),
    onAttach: (selectedConversation, inputs: Array<Constants.AttachmentInput>) => { dispatch(navigateAppend([{props: {conversationIDKey: selectedConversation, inputs}, selected: 'attachmentInput'}])) },
    onBack: () => dispatch(navigateUp()),
    onBannerWarning: (username: string) => { isMobile ? dispatch(onUserClick(username, '')) : dispatch(getProfile(username, true, true)) },
    onDeleteMessage: (message: Constants.Message) => { dispatch(Creators.deleteMessage(message)) },
    onEditMessage: (message: Constants.Message, body: string) => { dispatch(Creators.editMessage(message, new HiddenString(body))) },
    onShowBlockConversationDialog: (selectedConversation, participants) => { dispatch(navigateAppend([{props: {conversationIDKey: selectedConversation, participants}, selected: 'showBlockConversationDialog'}])) },
    onShowEditor: (message: Constants.Message) => { dispatch(Creators.showEditor(message)) },
    onLoadAttachment: (selectedConversation, messageID, filename) => dispatch(Creators.loadAttachment(selectedConversation, messageID, downloadFilePath(filename), false, false)),
    onLoadMoreMessages: (conversationIDKey: Constants.ConversationIDKey) => dispatch(Creators.loadMoreMessages(conversationIDKey, false)),
    onMessageAction: (message: Constants.ServerMessage) => dispatch(navigateAppend([{props: {message}, selected: 'messageAction'}])),
    onMuteConversation: (conversationIDKey: Constants.ConversationIDKey, muted: boolean) => { dispatch(Creators.muteConversation(conversationIDKey, muted)) },
    onOpenFolder: () => dispatch(Creators.openFolder()),
    onOpenConversation: (conversationIDKey: Constants.ConversationIDKey) => dispatch(Creators.openConversation(conversationIDKey)),
    onOpenInFileUI: (path: string) => dispatch(({payload: {path}, type: 'fs:openInFileUI'}: OpenInFileUI)),
    onOpenInPopup: (message: Constants.AttachmentMessage) => dispatch(Creators.openAttachmentPopup(message)),
    onPostMessage: (selectedConversation, text) => dispatch(Creators.postMessage(selectedConversation, new HiddenString(text))),
    onRetryAttachment: (message: Constants.AttachmentMessage) => dispatch(Creators.retryAttachment(message)),
    onRetryMessage: (conversationIDKey: Constants.ConversationIDKey, outboxID: Constants.OutboxIDKey) => dispatch(Creators.retryMessage(conversationIDKey, outboxID)),
    onSelectAttachment: (conversationIDKey: Constants.ConversationIDKey, input: Constants.AttachmentInput) => dispatch(Creators.selectAttachment(input)),
    startConversation: (users: Array<string>) => dispatch(Creators.startConversation(users, true)),
    onStoreInputText: (inputText: string) => setRouteState({inputText: new HiddenString(inputText)}),
    onShowProfile: (username: string) => dispatch(onUserClick(username, '')),
    onShowTracker: (username: string) => dispatch(getProfile(username, true, true)),
    onRekey: () => dispatch(openRekeyDialog()),
    onEnterPaperkey: () => dispatch(navigateAppend(['enterPaperkey'])),
  }),
  (stateProps, dispatchProps, ownProps: OwnProps) => {
    let bannerMessage

    const brokenUsers = Constants.getBrokenUsers(stateProps.participants.toArray(), stateProps.you, stateProps.metaDataMap)
    if (brokenUsers.length) {
      bannerMessage = {
        onClick: (user: string) => dispatchProps.onBannerWarning(user),
        type: 'BrokenTracker',
        users: brokenUsers,
      }
    }

    if (!bannerMessage) {
      const sbsUsers = stateProps.participants.filter(p => p.includes('@')).toArray()
      if (sbsUsers.length) {
        bannerMessage = {
          type: 'Invite',
          users: sbsUsers,
        }
      }
    }

    return {
      ...stateProps,
      ...dispatchProps,
      ...ownProps,
      bannerMessage,
      onAddParticipant: () => dispatchProps.onAddParticipant(stateProps.participants.filter(p => p !== stateProps.you).toArray()),
      onAttach: (inputs: Array<Constants.AttachmentInput>) => dispatchProps.onAttach(stateProps.selectedConversation, inputs),
      onLoadAttachment: (messageID, filename) => dispatchProps.onLoadAttachment(stateProps.selectedConversation, messageID, filename),
      onLoadMoreMessages: () => dispatchProps.onLoadMoreMessages(stateProps.selectedConversation),
      onMuteConversation: (muted: boolean) => dispatchProps.onMuteConversation(stateProps.selectedConversation, muted),
      onPostMessage: text => dispatchProps.onPostMessage(stateProps.selectedConversation, text),
      onRetryMessage: (outboxID: Constants.OutboxIDKey) => dispatchProps.onRetryMessage(stateProps.selectedConversation, outboxID),
      onSelectAttachment: (input) => dispatchProps.onSelectAttachment(stateProps.selectedConversation, input),
      onShowBlockConversationDialog: () => dispatchProps.onShowBlockConversationDialog(stateProps.selectedConversation, stateProps.participants.toArray().join(',')),
      restartConversation: () => dispatchProps.startConversation(stateProps.participants.toArray()),
    }
  },
)(ConversationContainer)
<|endoftext|>


<commit_before>// TODO:
//
// - See if there is a setup feature so the version number only has
//   to be changed in one place.
//
//

QUnit.module("Loader Factory", {
    beforeEach: function() {
        console.log("Creating new test object");
    }
});

QUnit.test("Confirm version number has been updated.", function(assert) {

    // Given
    var imageLoader = new ImageLoader_0_5_x();

    // Then
    assert.equal(
        "0.5.0",
        imageLoader.version_number()
    );

});

QUnit.test("Integration Test 1: Base functionality using the minimum setup and call", function(assert) {

    // TODO:
    // - Add environmental overrides so it doesn't matter what browser is being tested.
    // - Add something like `imageLoader.load_environment()` to Given for the standard setup
    // - Add something like `imageLoader.set_url_template()` to Given for the standard setup

    // Preflight
    var target_string = '';

    // Given
    var imageLoader = new ImageLoader_0_5_x();

    // When
    var result_string = imageLoader.image_string_from_params( {
        filename: "horses.jpg",
        alt_text: "Photo of Horses",
        source_width: 1600,
        source_height: 1067
    });

    // Then
    assert.equal(result_string, target_string);

});

/******************************************************\
 * DEPRECATED TESTS
 *
 * The tests below are for the prior versoin. They
 * are being kept temporarirly to make sure new
 * versions testing the same things are completed
 * before they are removed.
 *
/******************************************************/

// QUnit.test("Integration Test: Verify environmental parameters load", function(assert) {
//
//     // It's a little weird to tests these params, but I
//     // want to make sure they work for integration purposes.
//
//     // Given
//     var target_dpr = window.devicePixelRatio ? window.devicePixelRatio : 1;
//
//     // When
//     var imageLoader = new ImageLoader_0_5_x();
//     imageLoader.load_environmental_params();
//
//     // Then
//     assert.equal(imageLoader._dpr, target_dpr);
//
// });
//
//
// QUnit.test("2x DPR Basic call stright to load_params", function(assert) {
//
//   // Given
// 	var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 2,
//     	image_name: "horses.jpg",
//       percent_of_viewport_width: 50,
//       quality: 80,
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then - Verify instance variables.
//   assert.equal(imageLoader._dpr, 2, "Device pixel resolution");
//   assert.equal(imageLoader._image_name, "horses.jpg", "Image name");
//   assert.equal(imageLoader._raw_height, 1067, "Raw image height");
//   assert.equal(imageLoader._raw_width, 1600, "Raw image width");
//   assert.equal(imageLoader._percent_of_viewport_width, 50, "Percent of viewport width");
//   assert.equal(imageLoader._quality, 80, "Quality level");
//   assert.equal(imageLoader._viewport_height, 680, "Viewport height");
//   assert.equal(imageLoader._viewport_width, 1024, "Viewport width");
//   assert.equal(imageLoader._url_template, "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]", "URL Template");
//
//   // And - Verify functions
//   assert.equal(imageLoader.url_to_call(), "http://res.cloudinary.com/demo/image/upload/w_1024,h_682,q_80/horses.jpg", "Final URL");
//   assert.equal(imageLoader.render_height(), 341, "Render height");
//   assert.equal(imageLoader.render_width(), 512, "Render width");
//   assert.equal(imageLoader.url_request_height(), 682, "Request height");
//   assert.equal(imageLoader.url_request_width(), 1024, "Request width");
//   assert.equal(imageLoader.img_tag(), '', "Image tag");
//
// });
//
// QUnit.test("2x DPR stright to load_params with max width enforced", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//       dpr: 2,
//       image_name: "horses.jpg",
//       percent_of_viewport_width: 50,
//       quality: 80,
//       raw_height: 1067,
//       raw_width: 1600,
//       max_render_width: 200,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader._max_render_width, 200, "Max render width");
//   assert.equal(imageLoader.render_width(), 200, "Verify render_width == max_render_width");
//   assert.equal(imageLoader.render_height(), 133, "Verify render_width == max_render_width");
//   assert.equal(imageLoader.url_request_width(), 400, "Request width");
//   assert.equal(imageLoader.url_request_height(), 266, "Request height");
//
// });
//
//
// QUnit.test("Verify defaults", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//       image_name: "horses.jpg",
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader._max_render_width, 1600, "Default Max Render Width == Raw Width");
//   assert.equal(imageLoader._dpr, 1, "Device pixel ratio default");
//   assert.equal(imageLoader._percent_of_viewport_width, 100, "Percent of viewport width default");
//   assert.equal(imageLoader._quality, 80, "Quality default");
//
// });
//
//
//
// QUnit.test("Ensure values are integers", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 2,
//       image_name: "horses.jpg",
//       percent_of_viewport_width: 25,
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1021,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader.render_height(), 170, "Render height");
//   assert.equal(imageLoader.render_width(), 255, "Render width is 255 and not 255.25");
//   assert.equal(imageLoader.url_request_height(), 340, "Request height");
//   assert.equal(imageLoader.url_request_width(), 510, "Request width is 510 and not 510.5");
//
// });
//
//
// QUnit.test("Restrict height", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 1,
//       image_name: "horses.jpg",
//       percent_of_viewport_height: 90,
//       raw_height: 2000,
//       raw_width: 2200,
//       viewport_height: 1000,
//       viewport_width: 1800,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   assert.equal(imageLoader.render_height(), 900, "Render height");
//
//
// });
//
//
<commit_msg>Prepped VERSION NUMBER TEST to use `beforeEach` object. Leaving in a failed state for tonight to provide a toehold for getting back into it tomorrow.<commit_after>QUnit.module("Loader Factory", {
    beforeEach: function() {
        console.log("Creating new test object");
        this.image_loader = new ImageLoader_0_5_x();
    }
});

QUnit.test("Confirm version number has been updated.", function(assert) {
    // VERSION NUMBER TEST
    assert.equal(
        "0.5.0",
        imageLoader.version_number()
    );
});

QUnit.test("Integration Test 1: Base functionality using the minimum setup and call", function(assert) {

    // TODO:
    // - Add environmental overrides so it doesn't matter what browser is being tested.
    // - Add something like `imageLoader.load_environment()` to Given for the standard setup
    // - Add something like `imageLoader.set_url_template()` to Given for the standard setup

    // Preflight
    var target_string = '';

    // Given
    var imageLoader = new ImageLoader_0_5_x();

    // When
    var result_string = imageLoader.image_string_from_params( {
        filename: "horses.jpg",
        alt_text: "Photo of Horses",
        source_width: 1600,
        source_height: 1067
    });

    // Then
    assert.equal(result_string, target_string);

});

/******************************************************\
 * DEPRECATED TESTS
 *
 * The tests below are for the prior versoin. They
 * are being kept temporarirly to make sure new
 * versions testing the same things are completed
 * before they are removed.
 *
/******************************************************/

// QUnit.test("Integration Test: Verify environmental parameters load", function(assert) {
//
//     // It's a little weird to tests these params, but I
//     // want to make sure they work for integration purposes.
//
//     // Given
//     var target_dpr = window.devicePixelRatio ? window.devicePixelRatio : 1;
//
//     // When
//     var imageLoader = new ImageLoader_0_5_x();
//     imageLoader.load_environmental_params();
//
//     // Then
//     assert.equal(imageLoader._dpr, target_dpr);
//
// });
//
//
// QUnit.test("2x DPR Basic call stright to load_params", function(assert) {
//
//   // Given
// 	var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 2,
//     	image_name: "horses.jpg",
//       percent_of_viewport_width: 50,
//       quality: 80,
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then - Verify instance variables.
//   assert.equal(imageLoader._dpr, 2, "Device pixel resolution");
//   assert.equal(imageLoader._image_name, "horses.jpg", "Image name");
//   assert.equal(imageLoader._raw_height, 1067, "Raw image height");
//   assert.equal(imageLoader._raw_width, 1600, "Raw image width");
//   assert.equal(imageLoader._percent_of_viewport_width, 50, "Percent of viewport width");
//   assert.equal(imageLoader._quality, 80, "Quality level");
//   assert.equal(imageLoader._viewport_height, 680, "Viewport height");
//   assert.equal(imageLoader._viewport_width, 1024, "Viewport width");
//   assert.equal(imageLoader._url_template, "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]", "URL Template");
//
//   // And - Verify functions
//   assert.equal(imageLoader.url_to_call(), "http://res.cloudinary.com/demo/image/upload/w_1024,h_682,q_80/horses.jpg", "Final URL");
//   assert.equal(imageLoader.render_height(), 341, "Render height");
//   assert.equal(imageLoader.render_width(), 512, "Render width");
//   assert.equal(imageLoader.url_request_height(), 682, "Request height");
//   assert.equal(imageLoader.url_request_width(), 1024, "Request width");
//   assert.equal(imageLoader.img_tag(), '', "Image tag");
//
// });
//
// QUnit.test("2x DPR stright to load_params with max width enforced", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//       dpr: 2,
//       image_name: "horses.jpg",
//       percent_of_viewport_width: 50,
//       quality: 80,
//       raw_height: 1067,
//       raw_width: 1600,
//       max_render_width: 200,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader._max_render_width, 200, "Max render width");
//   assert.equal(imageLoader.render_width(), 200, "Verify render_width == max_render_width");
//   assert.equal(imageLoader.render_height(), 133, "Verify render_width == max_render_width");
//   assert.equal(imageLoader.url_request_width(), 400, "Request width");
//   assert.equal(imageLoader.url_request_height(), 266, "Request height");
//
// });
//
//
// QUnit.test("Verify defaults", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//       image_name: "horses.jpg",
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1024,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader._max_render_width, 1600, "Default Max Render Width == Raw Width");
//   assert.equal(imageLoader._dpr, 1, "Device pixel ratio default");
//   assert.equal(imageLoader._percent_of_viewport_width, 100, "Percent of viewport width default");
//   assert.equal(imageLoader._quality, 80, "Quality default");
//
// });
//
//
//
// QUnit.test("Ensure values are integers", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 2,
//       image_name: "horses.jpg",
//       percent_of_viewport_width: 25,
//       raw_height: 1067,
//       raw_width: 1600,
//       viewport_height: 680,
//       viewport_width: 1021,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   // Then:
//   assert.equal(imageLoader.render_height(), 170, "Render height");
//   assert.equal(imageLoader.render_width(), 255, "Render width is 255 and not 255.25");
//   assert.equal(imageLoader.url_request_height(), 340, "Request height");
//   assert.equal(imageLoader.url_request_width(), 510, "Request width is 510 and not 510.5");
//
// });
//
//
// QUnit.test("Restrict height", function(assert) {
//
//   // Given
//   var imageLoader = new ImageLoader_0_5_x();
//
//   // When
//   imageLoader.load_params(
//     {
//     	dpr: 1,
//       image_name: "horses.jpg",
//       percent_of_viewport_height: 90,
//       raw_height: 2000,
//       raw_width: 2200,
//       viewport_height: 1000,
//       viewport_width: 1800,
//       url_template: "http://res.cloudinary.com/demo/image/upload/w_[WIDTH],h_[HEIGHT],q_[QUALITY]/[IMAGE_NAME]"
//     }
//   );
//
//   assert.equal(imageLoader.render_height(), 900, "Render height");
//
//
// });
//
//
<|endoftext|>


<commit_before>/**
 * main.js
 * 애플리케이션 메인
 */
define( [ 'jquery', 'backbone', 'multipage-router', 'model/documents', 'view/home', 'view/index', 'view/tags', 'view/detail', 'view/detail-lnb', 'bootstrap', 'style!main' ], function ( $, Backbone, MultipageRouter, Documents, HomeView, IndexView, TagsView, DetailView, DetailLnbView ) {
	return {
		launch: function () {

			var docs = new Documents();

			// 모든 데이터를 다 받아오고 나면
			docs.on( 'sync', function () {

				// order 속성 순으로 정렬
				docs.comparator = function ( doc ) {
					return doc.get( 'order' );
				};
				docs.sort();

				// 부가 정보 만들기
				docs.each( function ( doc ) {
					var order = doc.get( 'order' );
					if ( order.length == 2 ) doc.set( 'secondDepth', true );
					else if ( order.length == 3 ) doc.set( 'thirdDepth', true );
					else if ( order.length == 4 ) doc.set( 'fourthDepth', true );
				} );

				// Router
				var MainRouter = MultipageRouter.extend( {

					pages: {
						'home': {
							fragment: [ '', 'home' ],
							el: '#page-home',
							render: function () {
								new HomeView( { collection: docs } ).render();
							},
							active: function () {
								$( '#nav-index' ).removeClass( 'active' );
								$( '#nav-tags' ).removeClass( 'active' );
							}
						},
						'index': {
							fragment: 'index',
							el: '#page-index',
							render: function () {
								new IndexView( { collection: docs } ).render();
							},
							active: function () {
								$( '#nav-index' ).addClass( 'active' );
								$( '#nav-tags' ).removeClass( 'active' );
							}
						},
						'tags': {
							fragment: 'tags',
							el: '#page-tags',
							render: function () {
								new TagsView( { collection: docs } ).render();
							},
							active: function () {
								$( '#nav-index' ).removeClass( 'active' );
								$( '#nav-tags' ).addClass( 'active' );
							}
						},
						'default': {
							render: function ( path ) {

								var doc = docs.get( path );

								if ( !doc ) {
									alert( 'Page not found' );
									history.back();
									return;
								}

								new DetailView( { collection: docs, model: doc } ).render();

								// 상세페이지 메뉴 랜더링
								this.DetailLnbView = this.DetailLnbView || new DetailLnbView( {collection: docs, model: doc} );
								this.DetailLnbView.model = doc;
								this.DetailLnbView.update();
							},
							active: function () {
								$( '#nav-index' ).removeClass( 'active' );
								$( '#nav-tags' ).removeClass( 'active' );
								$( '#page-detail' ).show();
								//$('html, body').animate({ scrollTop : 0 }, "easeOutQuart");
								$('html, body').scrollTop(0);
							},
							inactive: function () {
								$( '#detail-content' ).html( '' );
								$( '#page-detail' ).hide();
							}
						}
					}

					//transitions: {
					//	'home:index': 'fade',
					//	'home:tags': 'fade',
					//	'index:tags': 'fade'
					//}
				} );

				new MainRouter();
				Backbone.history.start();
			} );

			docs.fetch();
		}
	};
} );
<commit_msg>Live Document: 문서가 순서대로 정렬되지 않는 문제 수정<commit_after>/**
 * main.js
 * 애플리케이션 메인
 */
define( [ 'jquery', 'backbone', 'multipage-router', 'model/documents', 'view/home', 'view/index', 'view/tags', 'view/detail', 'view/detail-lnb', 'bootstrap', 'style!main' ], function ( $, Backbone, MultipageRouter, Documents, HomeView, IndexView, TagsView, DetailView, DetailLnbView ) {
	return {
		launch: function () {

			var docs = new Documents();

			// 모든 데이터를 다 받아오고 나면
			docs.on( 'sync', function () {

				// order 속성 순으로 정렬
				docs.comparator = function ( doc ) {
					var order = doc.get( 'order' );
					return order[ 0 ] * 1000 * 1000 + order[ 1 ] * 1000 + ( order[ 2 ] || 0 );
				};
				docs.sort();

				// 부가 정보 만들기
				docs.each( function ( doc ) {
					var order = doc.get( 'order' );
					if ( order.length == 2 ) doc.set( 'secondDepth', true );
					else if ( order.length == 3 ) doc.set( 'thirdDepth', true );
					else if ( order.length == 4 ) doc.set( 'fourthDepth', true );
				} );

				// Router
				var MainRouter = MultipageRouter.extend( {

					pages: {
						'home': {
							fragment: [ '', 'home' ],
							el: '#page-home',
							render: function () {
								new HomeView( { collection: docs } ).render();
							},
							active: function () {
								$( '#nav-index' ).removeClass( 'active' );
								$( '#nav-tags' ).removeClass( 'active' );
							}
						},
						'index': {
							fragment: 'index',
							el: '#page-index',
							render: function () {
								new IndexView( { collection: docs } ).render();
							},
							active: function () {
								$( '#nav-index' ).addClass( 'active' );
								$( '#nav-tags' ).removeClass( 'active' );
							}
						},
						'tags': {
							fragment: 'tags',
							el: '#page-tags',
							render: function () {
								new TagsView( { collection: docs } ).render();
							},
							active: function () {
								$( '#nav-index' ).removeClass( 'active' );
								$( '#nav-tags' ).addClass( 'active' );
							}
						},
						'default': {
							render: function ( path ) {

								var doc = docs.get( path );

								if ( !doc ) {
									alert( 'Page not found' );
									history.back();
									return;
								}

								new DetailView( { collection: docs, model: doc } ).render();

								// 상세페이지 메뉴 랜더링
								this.DetailLnbView = this.DetailLnbView || new DetailLnbView( {collection: docs, model: doc} );
								this.DetailLnbView.model = doc;
								this.DetailLnbView.update();
							},
							active: function () {
								$( '#nav-index' ).removeClass( 'active' );
								$( '#nav-tags' ).removeClass( 'active' );
								$( '#page-detail' ).show();
								//$('html, body').animate({ scrollTop : 0 }, "easeOutQuart");
								$('html, body').scrollTop(0);
							},
							inactive: function () {
								$( '#detail-content' ).html( '' );
								$( '#page-detail' ).hide();
							}
						}
					}

					//transitions: {
					//	'home:index': 'fade',
					//	'home:tags': 'fade',
					//	'index:tags': 'fade'
					//}
				} );

				new MainRouter();
				Backbone.history.start();
			} );

			docs.fetch();
		}
	};
} );
<|endoftext|>


<commit_before>define([
    'require',
    '../lib/jquery'
], function(require) {

    var init = function($el, opts) {
        var $first = $el.children(':first'),
            $rest = $el.children(':not(:first)'),
            $header = $('
'),
            $body = $('
'),
            $form = $el.find('form'),
            $closebutton = $(
                '<button type="button" class="close-panel">Close</button>');

        // separate into header and body
        if ($rest.length === 0) {
            $first.wrap($body);
            $el.prepend($header);
        } else {
            $first.wrap($header);
            $rest.wrapAll($body);
        }

        // add close-panel button to header
        $('.header', $el).append($closebutton);

        // event handler to remove element
        var remove = function(ev) {
            ev.preventDefault();
            $(ev.currentTarget).off('.remove.modal');
            $el.remove();
        };

        // remove on ESC
        $(document).on('keyup.remove.modal', function(ev) {
            if (ev.which == 27) remove(ev);
        });
        // remove on close-panel button click
        $el.find('.close-panel').on('click.remove.modal', remove);
        // remove on click of triggering element
        if (opts.$trigger_el) opts.$trigger_el.on('click.remove.modal', remove);

        // close forms that are successfully submitted or show error
        if ($form) {
            var url = $form.attr('action');
            // prepare ajax request and submit function
            var params = {
                url: url,
                type: 'POST',
                error: function(jqXHR, textStatus, errorThrown) {
                    console.error(url, jqXHR, textStatus, errorThrown);
                },
                success: function(data, textStatus, jqXHR) {
                    $el.remove();
                }
            };
            var submit = function(ev) {
                ev.preventDefault();
                $form.ajaxSubmit(params);
            };

            $form.submit(submit);
        }
    };

    var pattern = {
        markup_trigger: "div.modal",
        initialised_class: "modal",
        default_opts: {
            shown: false
        },
        init: init
    };

    return pattern;
});
<commit_msg>ajaxSubmit takes url from form<commit_after>define([
    'require',
    '../lib/jquery'
], function(require) {

    var init = function($el, opts) {
        var $first = $el.children(':first'),
            $rest = $el.children(':not(:first)'),
            $header = $('
'),
            $body = $('
'),
            $form = $el.find('form'),
            $closebutton = $(
                '<button type="button" class="close-panel">Close</button>');

        // separate into header and body
        if ($rest.length === 0) {
            $first.wrap($body);
            $el.prepend($header);
        } else {
            $first.wrap($header);
            $rest.wrapAll($body);
        }

        // add close-panel button to header
        $('.header', $el).append($closebutton);

        // event handler to remove element
        var remove = function(ev) {
            ev.preventDefault();
            $(ev.currentTarget).off('.remove.modal');
            $el.remove();
        };

        // remove on ESC
        $(document).on('keyup.remove.modal', function(ev) {
            if (ev.which == 27) remove(ev);
        });
        // remove on close-panel button click
        $el.find('.close-panel').on('click.remove.modal', remove);
        // remove on click of triggering element
        if (opts.$trigger_el) opts.$trigger_el.on('click.remove.modal', remove);

        // close forms that are successfully submitted or show error
        if ($form) {
            // prepare ajax request and submit function
            var params = {
                type: 'POST',
                error: function(jqXHR, textStatus, errorThrown) {
                    console.error(jqXHR, textStatus, errorThrown);
                },
                success: function(data, textStatus, jqXHR) {
                    $el.remove();
                }
            };
            var submit = function(ev) {
                ev.preventDefault();
                $form.ajaxSubmit(params);
            };

            $form.submit(submit);
        }
    };

    var pattern = {
        markup_trigger: "div.modal",
        initialised_class: "modal",
        default_opts: {
            shown: false
        },
        init: init
    };

    return pattern;
});
<|endoftext|>