<commit_before>/**
 * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs
 * @author mrdoob / http://mrdoob.com/
 * @author yomotsu / https://yomotsu.net/
 */

THREE.CSS3DObject = function ( element ) {

	THREE.Object3D.call( this );

	this.element = element;
	this.element.style.position = 'absolute';
	this.element.style.pointerEvents = 'auto';

	this.addEventListener( 'removed', function () {

		this.traverse( function ( object ) {

			if ( object.element instanceof Element && object.element.parentNode !== null ) {

				object.element.parentNode.removeChild( object.element );

			}

		} );

	} );

};

THREE.CSS3DObject.prototype = Object.create( THREE.Object3D.prototype );
THREE.CSS3DObject.prototype.constructor = THREE.CSS3DObject;

THREE.CSS3DSprite = function ( element ) {

	THREE.CSS3DObject.call( this, element );

};

THREE.CSS3DSprite.prototype = Object.create( THREE.CSS3DObject.prototype );
THREE.CSS3DSprite.prototype.constructor = THREE.CSS3DSprite;

//

THREE.CSS3DRenderer = function () {

	var _width, _height;
	var _widthHalf, _heightHalf;

	var matrix = new THREE.Matrix4();

	var cache = {
		camera: { fov: 0, style: '' },
		objects: new WeakMap()
	};

	var domElement = document.createElement( 'div' );
	domElement.style.overflow = 'hidden';

	this.domElement = domElement;

	var cameraElement = document.createElement( 'div' );

	cameraElement.style.WebkitTransformStyle = 'preserve-3d';
	cameraElement.style.transformStyle = 'preserve-3d';
	cameraElement.style.pointerEvents = 'none';

	domElement.appendChild( cameraElement );

	var isIE = /Trident/i.test( navigator.userAgent );

	this.getSize = function () {

		return {
			width: _width,
			height: _height
		};

	};

	this.setSize = function ( width, height ) {

		_width = width;
		_height = height;
		_widthHalf = _width / 2;
		_heightHalf = _height / 2;

		domElement.style.width = width + 'px';
		domElement.style.height = height + 'px';

		cameraElement.style.width = width + 'px';
		cameraElement.style.height = height + 'px';

	};

	function epsilon( value ) {

		return Math.abs( value ) < 1e-10 ? 0 : value;

	}

	function getCameraCSSMatrix( matrix ) {

		var elements = matrix.elements;

		return 'matrix3d(' +
			epsilon( elements[ 0 ] ) + ',' +
			epsilon( - elements[ 1 ] ) + ',' +
			epsilon( elements[ 2 ] ) + ',' +
			epsilon( elements[ 3 ] ) + ',' +
			epsilon( elements[ 4 ] ) + ',' +
			epsilon( - elements[ 5 ] ) + ',' +
			epsilon( elements[ 6 ] ) + ',' +
			epsilon( elements[ 7 ] ) + ',' +
			epsilon( elements[ 8 ] ) + ',' +
			epsilon( - elements[ 9 ] ) + ',' +
			epsilon( elements[ 10 ] ) + ',' +
			epsilon( elements[ 11 ] ) + ',' +
			epsilon( elements[ 12 ] ) + ',' +
			epsilon( - elements[ 13 ] ) + ',' +
			epsilon( elements[ 14 ] ) + ',' +
			epsilon( elements[ 15 ] ) +
		')';

	}

	function getObjectCSSMatrix( matrix, cameraCSSMatrix ) {

		var elements = matrix.elements;
		var matrix3d = 'matrix3d(' +
			epsilon( elements[ 0 ] ) + ',' +
			epsilon( elements[ 1 ] ) + ',' +
			epsilon( elements[ 2 ] ) + ',' +
			epsilon( elements[ 3 ] ) + ',' +
			epsilon( - elements[ 4 ] ) + ',' +
			epsilon( - elements[ 5 ] ) + ',' +
			epsilon( - elements[ 6 ] ) + ',' +
			epsilon( - elements[ 7 ] ) + ',' +
			epsilon( elements[ 8 ] ) + ',' +
			epsilon( elements[ 9 ] ) + ',' +
			epsilon( elements[ 10 ] ) + ',' +
			epsilon( elements[ 11 ] ) + ',' +
			epsilon( elements[ 12 ] ) + ',' +
			epsilon( elements[ 13 ] ) + ',' +
			epsilon( elements[ 14 ] ) + ',' +
			epsilon( elements[ 15 ] ) +
		')';

		if ( isIE ) {

			return 'translate(-50%,-50%)' +
				'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)' +
				cameraCSSMatrix +
				matrix3d;

		}

		return 'translate(-50%,-50%)' + matrix3d;

	}

	function renderObject( object, camera, cameraCSSMatrix ) {

		if ( object instanceof THREE.CSS3DObject ) {

			var style;

			if ( object instanceof THREE.CSS3DSprite ) {

				// http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/

				matrix.copy( camera.matrixWorldInverse );
				matrix.transpose();
				matrix.copyPosition( object.matrixWorld );
				matrix.scale( object.scale );

				matrix.elements[ 3 ] = 0;
				matrix.elements[ 7 ] = 0;
				matrix.elements[ 11 ] = 0;
				matrix.elements[ 15 ] = 1;

				style = getObjectCSSMatrix( matrix, cameraCSSMatrix );

			} else {

				style = getObjectCSSMatrix( object.matrixWorld, cameraCSSMatrix );

			}

			var element = object.element;
			var cachedObject = cache.objects.get( object );

			if ( cachedObject === undefined || cachedObject.style !== style ) {

				element.style.WebkitTransform = style;
				element.style.transform = style;

				var objectData = { style: style };

				if ( isIE ) {

					objectData.distanceToCameraSquared = getDistanceToSquared( camera, object );

				}

				cache.objects.set( object, objectData );

			}

			if ( element.parentNode !== cameraElement ) {

				cameraElement.appendChild( element );

			}

		}

		for ( var i = 0, l = object.children.length; i < l; i ++ ) {

			renderObject( object.children[ i ], camera, cameraCSSMatrix );

		}

	}

	var getDistanceToSquared = function () {

		var a = new THREE.Vector3();
		var b = new THREE.Vector3();

		return function ( object1, object2 ) {

			a.setFromMatrixPosition( object1.matrixWorld );
			b.setFromMatrixPosition( object2.matrixWorld );

			return a.distanceToSquared( b );

		};

	}();

	function filterAndFlatten( scene ) {

		var result = [];

		scene.traverse( function ( object ) {

			if ( object instanceof THREE.CSS3DObject ) result.push( object );

		} );

		return result;

	}

	function zOrder( scene ) {

		var sorted = filterAndFlatten( scene ).sort( function ( a, b ) {

			var distanceA = cache.objects.get( a ).distanceToCameraSquared;
			var distanceB = cache.objects.get( b ).distanceToCameraSquared;

			return distanceA - distanceB;

		} );

		var zMax = sorted.length;

		for ( var i = 0, l = sorted.length; i < l; i ++ ) {

			sorted[ i ].element.style.zIndex = zMax - i;

		}

	}

	this.render = function ( scene, camera ) {

		var fov = camera.projectionMatrix.elements[ 5 ] * _heightHalf;

		if ( cache.camera.fov !== fov ) {

			if ( camera.isPerspectiveCamera ) {

				domElement.style.WebkitPerspective = fov + 'px';
				domElement.style.perspective = fov + 'px';

			} else {

				domElement.style.WebkitPerspective = '';
				domElement.style.perspective = '';

			}

			cache.camera.fov = fov;

		}

		if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
		if ( camera.parent === null ) camera.updateMatrixWorld();

		if ( camera.isOrthographicCamera ) {

			var tx = - ( camera.right + camera.left ) / 2;
			var ty = ( camera.top + camera.bottom ) / 2;

		}

		var cameraCSSMatrix = camera.isOrthographicCamera ?
			'scale(' + fov + ')' + 'translate(' + epsilon( tx ) + 'px,' + epsilon( ty ) + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse ) :
			'translateZ(' + fov + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse );

		var style = cameraCSSMatrix +
			'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)';

		if ( cache.camera.style !== style && ! isIE ) {

			cameraElement.style.WebkitTransform = style;
			cameraElement.style.transform = style;

			cache.camera.style = style;

		}

		renderObject( scene, camera, cameraCSSMatrix );

		if ( isIE ) {

			// IE10 and 11 does not support 'preserve-3d'.
			// Thus, z-order in 3D will not work.
			// We have to calc z-order manually and set CSS z-index for IE.
			// FYI: z-index can't handle object intersection
			zOrder( scene );

		}

	};

};
<commit_msg>Call onBeforeRender and onAfterRender callbacks in CSS3DRenderer<commit_after>/**
 * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs
 * @author mrdoob / http://mrdoob.com/
 * @author yomotsu / https://yomotsu.net/
 */

THREE.CSS3DObject = function ( element ) {

	THREE.Object3D.call( this );

	this.element = element;
	this.element.style.position = 'absolute';
	this.element.style.pointerEvents = 'auto';

	this.addEventListener( 'removed', function () {

		this.traverse( function ( object ) {

			if ( object.element instanceof Element && object.element.parentNode !== null ) {

				object.element.parentNode.removeChild( object.element );

			}

		} );

	} );

};

THREE.CSS3DObject.prototype = Object.create( THREE.Object3D.prototype );
THREE.CSS3DObject.prototype.constructor = THREE.CSS3DObject;

THREE.CSS3DSprite = function ( element ) {

	THREE.CSS3DObject.call( this, element );

};

THREE.CSS3DSprite.prototype = Object.create( THREE.CSS3DObject.prototype );
THREE.CSS3DSprite.prototype.constructor = THREE.CSS3DSprite;

//

THREE.CSS3DRenderer = function () {

	var _this = this

	var _width, _height;
	var _widthHalf, _heightHalf;

	var matrix = new THREE.Matrix4();

	var cache = {
		camera: { fov: 0, style: '' },
		objects: new WeakMap()
	};

	var domElement = document.createElement( 'div' );
	domElement.style.overflow = 'hidden';

	this.domElement = domElement;

	var cameraElement = document.createElement( 'div' );

	cameraElement.style.WebkitTransformStyle = 'preserve-3d';
	cameraElement.style.transformStyle = 'preserve-3d';
	cameraElement.style.pointerEvents = 'none';

	domElement.appendChild( cameraElement );

	var isIE = /Trident/i.test( navigator.userAgent );

	this.getSize = function () {

		return {
			width: _width,
			height: _height
		};

	};

	this.setSize = function ( width, height ) {

		_width = width;
		_height = height;
		_widthHalf = _width / 2;
		_heightHalf = _height / 2;

		domElement.style.width = width + 'px';
		domElement.style.height = height + 'px';

		cameraElement.style.width = width + 'px';
		cameraElement.style.height = height + 'px';

	};

	function epsilon( value ) {

		return Math.abs( value ) < 1e-10 ? 0 : value;

	}

	function getCameraCSSMatrix( matrix ) {

		var elements = matrix.elements;

		return 'matrix3d(' +
			epsilon( elements[ 0 ] ) + ',' +
			epsilon( - elements[ 1 ] ) + ',' +
			epsilon( elements[ 2 ] ) + ',' +
			epsilon( elements[ 3 ] ) + ',' +
			epsilon( elements[ 4 ] ) + ',' +
			epsilon( - elements[ 5 ] ) + ',' +
			epsilon( elements[ 6 ] ) + ',' +
			epsilon( elements[ 7 ] ) + ',' +
			epsilon( elements[ 8 ] ) + ',' +
			epsilon( - elements[ 9 ] ) + ',' +
			epsilon( elements[ 10 ] ) + ',' +
			epsilon( elements[ 11 ] ) + ',' +
			epsilon( elements[ 12 ] ) + ',' +
			epsilon( - elements[ 13 ] ) + ',' +
			epsilon( elements[ 14 ] ) + ',' +
			epsilon( elements[ 15 ] ) +
		')';

	}

	function getObjectCSSMatrix( matrix, cameraCSSMatrix ) {

		var elements = matrix.elements;
		var matrix3d = 'matrix3d(' +
			epsilon( elements[ 0 ] ) + ',' +
			epsilon( elements[ 1 ] ) + ',' +
			epsilon( elements[ 2 ] ) + ',' +
			epsilon( elements[ 3 ] ) + ',' +
			epsilon( - elements[ 4 ] ) + ',' +
			epsilon( - elements[ 5 ] ) + ',' +
			epsilon( - elements[ 6 ] ) + ',' +
			epsilon( - elements[ 7 ] ) + ',' +
			epsilon( elements[ 8 ] ) + ',' +
			epsilon( elements[ 9 ] ) + ',' +
			epsilon( elements[ 10 ] ) + ',' +
			epsilon( elements[ 11 ] ) + ',' +
			epsilon( elements[ 12 ] ) + ',' +
			epsilon( elements[ 13 ] ) + ',' +
			epsilon( elements[ 14 ] ) + ',' +
			epsilon( elements[ 15 ] ) +
		')';

		if ( isIE ) {

			return 'translate(-50%,-50%)' +
				'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)' +
				cameraCSSMatrix +
				matrix3d;

		}

		return 'translate(-50%,-50%)' + matrix3d;

	}

	function renderObject( object, scene, camera, cameraCSSMatrix ) {

		if ( object instanceof THREE.CSS3DObject ) {

			object.onBeforeRender( _this, scene, camera );

			var style;

			if ( object instanceof THREE.CSS3DSprite ) {

				// http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/

				matrix.copy( camera.matrixWorldInverse );
				matrix.transpose();
				matrix.copyPosition( object.matrixWorld );
				matrix.scale( object.scale );

				matrix.elements[ 3 ] = 0;
				matrix.elements[ 7 ] = 0;
				matrix.elements[ 11 ] = 0;
				matrix.elements[ 15 ] = 1;

				style = getObjectCSSMatrix( matrix, cameraCSSMatrix );

			} else {

				style = getObjectCSSMatrix( object.matrixWorld, cameraCSSMatrix );

			}

			var element = object.element;
			var cachedObject = cache.objects.get( object );

			if ( cachedObject === undefined || cachedObject.style !== style ) {

				element.style.WebkitTransform = style;
				element.style.transform = style;

				var objectData = { style: style };

				if ( isIE ) {

					objectData.distanceToCameraSquared = getDistanceToSquared( camera, object );

				}

				cache.objects.set( object, objectData );

			}

			if ( element.parentNode !== cameraElement ) {

				cameraElement.appendChild( element );

			}

			object.onAfterRender( _this, scene, camera );

		}

		for ( var i = 0, l = object.children.length; i < l; i ++ ) {

			renderObject( object.children[ i ], scene, camera, cameraCSSMatrix );

		}

	}

	var getDistanceToSquared = function () {

		var a = new THREE.Vector3();
		var b = new THREE.Vector3();

		return function ( object1, object2 ) {

			a.setFromMatrixPosition( object1.matrixWorld );
			b.setFromMatrixPosition( object2.matrixWorld );

			return a.distanceToSquared( b );

		};

	}();

	function filterAndFlatten( scene ) {

		var result = [];

		scene.traverse( function ( object ) {

			if ( object instanceof THREE.CSS3DObject ) result.push( object );

		} );

		return result;

	}

	function zOrder( scene ) {

		var sorted = filterAndFlatten( scene ).sort( function ( a, b ) {

			var distanceA = cache.objects.get( a ).distanceToCameraSquared;
			var distanceB = cache.objects.get( b ).distanceToCameraSquared;

			return distanceA - distanceB;

		} );

		var zMax = sorted.length;

		for ( var i = 0, l = sorted.length; i < l; i ++ ) {

			sorted[ i ].element.style.zIndex = zMax - i;

		}

	}

	this.render = function ( scene, camera ) {

		var fov = camera.projectionMatrix.elements[ 5 ] * _heightHalf;

		if ( cache.camera.fov !== fov ) {

			if ( camera.isPerspectiveCamera ) {

				domElement.style.WebkitPerspective = fov + 'px';
				domElement.style.perspective = fov + 'px';

			} else {

				domElement.style.WebkitPerspective = '';
				domElement.style.perspective = '';

			}

			cache.camera.fov = fov;

		}

		if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
		if ( camera.parent === null ) camera.updateMatrixWorld();

		if ( camera.isOrthographicCamera ) {

			var tx = - ( camera.right + camera.left ) / 2;
			var ty = ( camera.top + camera.bottom ) / 2;

		}

		var cameraCSSMatrix = camera.isOrthographicCamera ?
			'scale(' + fov + ')' + 'translate(' + epsilon( tx ) + 'px,' + epsilon( ty ) + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse ) :
			'translateZ(' + fov + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse );

		var style = cameraCSSMatrix +
			'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)';

		if ( cache.camera.style !== style && ! isIE ) {

			cameraElement.style.WebkitTransform = style;
			cameraElement.style.transform = style;

			cache.camera.style = style;

		}

		renderObject( scene, scene, camera, cameraCSSMatrix );

		if ( isIE ) {

			// IE10 and 11 does not support 'preserve-3d'.
			// Thus, z-order in 3D will not work.
			// We have to calc z-order manually and set CSS z-index for IE.
			// FYI: z-index can't handle object intersection
			zOrder( scene );

		}

	};

};
<|endoftext|>


<commit_before>import * as fflate from '../libs/fflate.module.js';

class USDZExporter {

	async parse( scene ) {

		const files = {};
		const modelFileName = 'model.usda';

		// model file should be first in USDZ archive so we init it here
		files[ modelFileName ] = null;

		let output = buildHeader();

		const materials = {};
		const textures = {};

		scene.traverseVisible( ( object ) => {

			if ( object.isMesh && object.material.isMeshStandardMaterial ) {

				const geometry = object.geometry;
				const material = object.material;

				const geometryFileName = 'geometries/Geometry_' + geometry.id + '.usd';

				if ( ! ( geometryFileName in files ) ) {

					const meshObject = buildMeshObject( geometry );
					files[ geometryFileName ] = buildUSDFileAsString( meshObject );

				}

				if ( ! ( material.uuid in materials ) ) {

					materials[ material.uuid ] = material;

				}

				output += buildXform( object, geometry, material );

			}

		} );

		output += buildMaterials( materials, textures );

		files[ modelFileName ] = fflate.strToU8( output );
		output = null;

		for ( const id in textures ) {

			const texture = textures[ id ];
			const color = id.split( '_' )[ 1 ];
			const isRGBA = texture.format === 1023;

			const canvas = imageToCanvas( texture.image, color );
			const blob = await new Promise( resolve => canvas.toBlob( resolve, isRGBA ? 'image/png' : 'image/jpeg', 1 ) );

			files[ `textures/Texture_${ id }.${ isRGBA ? 'png' : 'jpg' }` ] = new Uint8Array( await blob.arrayBuffer() );

		}

		// 64 byte alignment
		// https://github.com/101arrowz/fflate/issues/39#issuecomment-777263109

		let offset = 0;

		for ( const filename in files ) {

			const file = files[ filename ];
			const headerSize = 34 + filename.length;

			offset += headerSize;

			const offsetMod64 = offset & 63;

			if ( offsetMod64 !== 4 ) {

				const padLength = 64 - offsetMod64;
				const padding = new Uint8Array( padLength );

				files[ filename ] = [ file, { extra: { 12345: padding } } ];

			}

			offset = file.length;

		}

		return fflate.zipSync( files, { level: 0 } );

	}

}

function imageToCanvas( image, color ) {

	if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
		( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
		( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) ||
		( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {

		const scale = 1024 / Math.max( image.width, image.height );

		const canvas = document.createElement( 'canvas' );
		canvas.width = image.width * Math.min( 1, scale );
		canvas.height = image.height * Math.min( 1, scale );

		const context = canvas.getContext( '2d' );
		context.drawImage( image, 0, 0, canvas.width, canvas.height );

		if ( color !== undefined ) {

			const hex = parseInt( color, 16 );

			const r = ( hex >> 16 & 255 ) / 255;
			const g = ( hex >> 8 & 255 ) / 255;
			const b = ( hex & 255 ) / 255;

			const imagedata = context.getImageData( 0, 0, canvas.width, canvas.height );
			const data = imagedata.data;

			for ( let i = 0; i < data.length; i += 4 ) {

				data[ i + 0 ] = data[ i + 0 ] * r;
				data[ i + 1 ] = data[ i + 1 ] * g;
				data[ i + 2 ] = data[ i + 2 ] * b;

			}

			context.putImageData( imagedata, 0, 0 );

		}

		return canvas;

	}

}

//

const PRECISION = 7;

function buildHeader() {

	return `#usda 1.0
(
    customLayerData = {
        string creator = "Three.js USDZExporter"
    }
    metersPerUnit = 1
    upAxis = "Y"
)

`;

}

function buildUSDFileAsString( dataToInsert ) {

	let output = buildHeader();
	output += dataToInsert;
	return fflate.strToU8( output );

}

// Xform

function buildXform( object, geometry, material ) {

	const name = 'Object_' + object.id;
	const transform = buildMatrix( object.matrixWorld );

	if ( object.matrixWorld.determinant() < 0 ) {

		console.warn( 'THREE.USDZExporter: USDZ does not support negative scales', object );

	}

	return `def Xform "${ name }" (
    prepend references = @./geometries/Geometry_${ geometry.id }.usd@</Geometry>
)
{
    matrix4d xformOp:transform = ${ transform }
    uniform token[] xformOpOrder = ["xformOp:transform"]

    rel material:binding = </Materials/Material_${ material.id }>
}

`;

}

function buildMatrix( matrix ) {

	const array = matrix.elements;

	return `( ${ buildMatrixRow( array, 0 ) }, ${ buildMatrixRow( array, 4 ) }, ${ buildMatrixRow( array, 8 ) }, ${ buildMatrixRow( array, 12 ) } )`;

}

function buildMatrixRow( array, offset ) {

	return `(${ array[ offset + 0 ] }, ${ array[ offset + 1 ] }, ${ array[ offset + 2 ] }, ${ array[ offset + 3 ] })`;

}

// Mesh

function buildMeshObject( geometry ) {

	const mesh = buildMesh( geometry );
	return `
def "Geometry"
{
  ${mesh}
}
`;

}

function buildMesh( geometry ) {

	const name = 'Geometry';
	const attributes = geometry.attributes;
	const count = attributes.position.count;

	return `
    def Mesh "${ name }"
    {
        int[] faceVertexCounts = [${ buildMeshVertexCount( geometry ) }]
        int[] faceVertexIndices = [${ buildMeshVertexIndices( geometry ) }]
        normal3f[] normals = [${ buildVector3Array( attributes.normal, count )}] (
            interpolation = "vertex"
        )
        point3f[] points = [${ buildVector3Array( attributes.position, count )}]
        float2[] primvars:st = [${ buildVector2Array( attributes.uv, count )}] (
            interpolation = "vertex"
        )
        uniform token subdivisionScheme = "none"
    }
`;

}

function buildMeshVertexCount( geometry ) {

	const count = geometry.index !== null ? geometry.index.array.length : geometry.attributes.position.count;

	return Array( count / 3 ).fill( 3 ).join( ', ' );

}

function buildMeshVertexIndices( geometry ) {

	if ( geometry.index !== null ) {

		return geometry.index.array.join( ', ' );

	}

	const array = [];
	const length = geometry.attributes.position.count;

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

		array.push( i );

	}

	return array.join( ', ' );

}

function buildVector3Array( attribute, count ) {

	if ( attribute === undefined ) {

		console.warn( 'USDZExporter: Normals missing.' );
		return Array( count ).fill( '(0, 0, 0)' ).join( ', ' );

	}

	const array = [];
	const data = attribute.array;

	for ( let i = 0; i < data.length; i += 3 ) {

		array.push( `(${ data[ i + 0 ].toPrecision( PRECISION ) }, ${ data[ i + 1 ].toPrecision( PRECISION ) }, ${ data[ i + 2 ].toPrecision( PRECISION ) })` );

	}

	return array.join( ', ' );

}

function buildVector2Array( attribute, count ) {

	if ( attribute === undefined ) {

		console.warn( 'USDZExporter: UVs missing.' );
		return Array( count ).fill( '(0, 0)' ).join( ', ' );

	}

	const array = [];
	const data = attribute.array;

	for ( let i = 0; i < data.length; i += 2 ) {

		array.push( `(${ data[ i + 0 ].toPrecision( PRECISION ) }, ${ 1 - data[ i + 1 ].toPrecision( PRECISION ) })` );

	}

	return array.join( ', ' );

}

// Materials

function buildMaterials( materials, textures ) {

	const array = [];

	for ( const uuid in materials ) {

		const material = materials[ uuid ];

		array.push( buildMaterial( material, textures ) );

	}

	return `def "Materials"
{
${ array.join( '' ) }
}

`;

}

function buildMaterial( material, textures ) {

	// https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html

	const pad = '            ';
	const inputs = [];
	const samplers = [];

	function buildTexture( texture, mapType, color ) {

		const id = texture.id + ( color ? '_' + color.getHexString() : '' );
		const isRGBA = texture.format === 1023;

		textures[ id ] = texture;

		return `
        def Shader "Transform2d_${ mapType }" (
            sdrMetadata = {
                string role = "math"
            }
        )
        {
            uniform token info:id = "UsdTransform2d"
            float2 inputs:in.connect = </Materials/Material_${ material.id }/uvReader_st.outputs:result>
            float2 inputs:scale = ${ buildVector2( texture.repeat ) }
            float2 inputs:translation = ${ buildVector2( texture.offset ) }
            float2 outputs:result
        }

        def Shader "Texture_${ texture.id }_${ mapType }"
        {
            uniform token info:id = "UsdUVTexture"
            asset inputs:file = @textures/Texture_${ id }.${ isRGBA ? 'png' : 'jpg' }@
            float2 inputs:st.connect = </Materials/Material_${ material.id }/Transform2d_${ mapType }.outputs:result>
            token inputs:wrapS = "repeat"
            token inputs:wrapT = "repeat"
            float outputs:r
            float outputs:g
            float outputs:b
            float3 outputs:rgb
        }`;

	}

	if ( material.map !== null ) {

		inputs.push( `${ pad }color3f inputs:diffuseColor.connect = </Materials/Material_${ material.id }/Texture_${ material.map.id }_diffuse.outputs:rgb>` );

		samplers.push( buildTexture( material.map, 'diffuse', material.color ) );

	} else {

		inputs.push( `${ pad }color3f inputs:diffuseColor = ${ buildColor( material.color ) }` );

	}

	if ( material.emissiveMap !== null ) {

		inputs.push( `${ pad }color3f inputs:emissiveColor.connect = </Materials/Material_${ material.id }/Texture_${ material.emissiveMap.id }_emissive.outputs:rgb>` );

		samplers.push( buildTexture( material.emissiveMap, 'emissive' ) );

	} else if ( material.emissive.getHex() > 0 ) {

		inputs.push( `${ pad }color3f inputs:emissiveColor = ${ buildColor( material.emissive ) }` );

	}

	if ( material.normalMap !== null ) {

		inputs.push( `${ pad }normal3f inputs:normal.connect = </Materials/Material_${ material.id }/Texture_${ material.normalMap.id }_normal.outputs:rgb>` );

		samplers.push( buildTexture( material.normalMap, 'normal' ) );

	}

	if ( material.aoMap !== null ) {

		inputs.push( `${ pad }float inputs:occlusion.connect = </Materials/Material_${ material.id }/Texture_${ material.aoMap.id }_occlusion.outputs:r>` );

		samplers.push( buildTexture( material.aoMap, 'occlusion' ) );

	}

	if ( material.roughnessMap !== null ) {

		inputs.push( `${ pad }float inputs:roughness.connect = </Materials/Material_${ material.id }/Texture_${ material.roughnessMap.id }_roughness.outputs:g>` );

		samplers.push( buildTexture( material.roughnessMap, 'roughness' ) );

	} else {

		inputs.push( `${ pad }float inputs:roughness = ${ material.roughness }` );

	}

	if ( material.metalnessMap !== null ) {

		inputs.push( `${ pad }float inputs:metallic.connect = </Materials/Material_${ material.id }/Texture_${ material.metalnessMap.id }_metallic.outputs:b>` );

		samplers.push( buildTexture( material.metalnessMap, 'metallic' ) );

	} else {

		inputs.push( `${ pad }float inputs:metallic = ${ material.metalness }` );

	}

	inputs.push( `${ pad }float inputs:opacity = ${ material.opacity }` );

	if ( material.isMeshPhysicalMaterial ) {

		inputs.push( `${ pad }float inputs:clearcoat = ${ material.clearcoat }` );
		inputs.push( `${ pad }float inputs:clearcoatRoughness = ${ material.clearcoatRoughness }` );
		inputs.push( `${ pad }float inputs:ior = ${ material.ior }` );

	}

	return `
    def Material "Material_${ material.id }"
    {
        def Shader "PreviewSurface"
        {
            uniform token info:id = "UsdPreviewSurface"
${ inputs.join( '\n' ) }
            int inputs:useSpecularWorkflow = 0
            token outputs:surface
        }

        token outputs:surface.connect = </Materials/Material_${ material.id }/PreviewSurface.outputs:surface>
        token inputs:frame:stPrimvarName = "st"

        def Shader "uvReader_st"
        {
            uniform token info:id = "UsdPrimvarReader_float2"
            token inputs:varname.connect = </Materials/Material_${ material.id }.inputs:frame:stPrimvarName>
            float2 inputs:fallback = (0.0, 0.0)
            float2 outputs:result
        }

${ samplers.join( '\n' ) }

    }
`;

}

function buildColor( color ) {

	return `(${ color.r }, ${ color.g }, ${ color.b })`;

}

function buildVector2( vector ) {

	return `(${ vector.x }, ${ vector.y })`;

}

export { USDZExporter };
<commit_msg>USDZExporter: Only include metalnessMap/roughnessMap when metalness/roughness is 1 (#22201)<commit_after>import * as fflate from '../libs/fflate.module.js';

class USDZExporter {

	async parse( scene ) {

		const files = {};
		const modelFileName = 'model.usda';

		// model file should be first in USDZ archive so we init it here
		files[ modelFileName ] = null;

		let output = buildHeader();

		const materials = {};
		const textures = {};

		scene.traverseVisible( ( object ) => {

			if ( object.isMesh && object.material.isMeshStandardMaterial ) {

				const geometry = object.geometry;
				const material = object.material;

				const geometryFileName = 'geometries/Geometry_' + geometry.id + '.usd';

				if ( ! ( geometryFileName in files ) ) {

					const meshObject = buildMeshObject( geometry );
					files[ geometryFileName ] = buildUSDFileAsString( meshObject );

				}

				if ( ! ( material.uuid in materials ) ) {

					materials[ material.uuid ] = material;

				}

				output += buildXform( object, geometry, material );

			}

		} );

		output += buildMaterials( materials, textures );

		files[ modelFileName ] = fflate.strToU8( output );
		output = null;

		for ( const id in textures ) {

			const texture = textures[ id ];
			const color = id.split( '_' )[ 1 ];
			const isRGBA = texture.format === 1023;

			const canvas = imageToCanvas( texture.image, color );
			const blob = await new Promise( resolve => canvas.toBlob( resolve, isRGBA ? 'image/png' : 'image/jpeg', 1 ) );

			files[ `textures/Texture_${ id }.${ isRGBA ? 'png' : 'jpg' }` ] = new Uint8Array( await blob.arrayBuffer() );

		}

		// 64 byte alignment
		// https://github.com/101arrowz/fflate/issues/39#issuecomment-777263109

		let offset = 0;

		for ( const filename in files ) {

			const file = files[ filename ];
			const headerSize = 34 + filename.length;

			offset += headerSize;

			const offsetMod64 = offset & 63;

			if ( offsetMod64 !== 4 ) {

				const padLength = 64 - offsetMod64;
				const padding = new Uint8Array( padLength );

				files[ filename ] = [ file, { extra: { 12345: padding } } ];

			}

			offset = file.length;

		}

		return fflate.zipSync( files, { level: 0 } );

	}

}

function imageToCanvas( image, color ) {

	if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
		( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
		( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) ||
		( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {

		const scale = 1024 / Math.max( image.width, image.height );

		const canvas = document.createElement( 'canvas' );
		canvas.width = image.width * Math.min( 1, scale );
		canvas.height = image.height * Math.min( 1, scale );

		const context = canvas.getContext( '2d' );
		context.drawImage( image, 0, 0, canvas.width, canvas.height );

		if ( color !== undefined ) {

			const hex = parseInt( color, 16 );

			const r = ( hex >> 16 & 255 ) / 255;
			const g = ( hex >> 8 & 255 ) / 255;
			const b = ( hex & 255 ) / 255;

			const imagedata = context.getImageData( 0, 0, canvas.width, canvas.height );
			const data = imagedata.data;

			for ( let i = 0; i < data.length; i += 4 ) {

				data[ i + 0 ] = data[ i + 0 ] * r;
				data[ i + 1 ] = data[ i + 1 ] * g;
				data[ i + 2 ] = data[ i + 2 ] * b;

			}

			context.putImageData( imagedata, 0, 0 );

		}

		return canvas;

	}

}

//

const PRECISION = 7;

function buildHeader() {

	return `#usda 1.0
(
    customLayerData = {
        string creator = "Three.js USDZExporter"
    }
    metersPerUnit = 1
    upAxis = "Y"
)

`;

}

function buildUSDFileAsString( dataToInsert ) {

	let output = buildHeader();
	output += dataToInsert;
	return fflate.strToU8( output );

}

// Xform

function buildXform( object, geometry, material ) {

	const name = 'Object_' + object.id;
	const transform = buildMatrix( object.matrixWorld );

	if ( object.matrixWorld.determinant() < 0 ) {

		console.warn( 'THREE.USDZExporter: USDZ does not support negative scales', object );

	}

	return `def Xform "${ name }" (
    prepend references = @./geometries/Geometry_${ geometry.id }.usd@</Geometry>
)
{
    matrix4d xformOp:transform = ${ transform }
    uniform token[] xformOpOrder = ["xformOp:transform"]

    rel material:binding = </Materials/Material_${ material.id }>
}

`;

}

function buildMatrix( matrix ) {

	const array = matrix.elements;

	return `( ${ buildMatrixRow( array, 0 ) }, ${ buildMatrixRow( array, 4 ) }, ${ buildMatrixRow( array, 8 ) }, ${ buildMatrixRow( array, 12 ) } )`;

}

function buildMatrixRow( array, offset ) {

	return `(${ array[ offset + 0 ] }, ${ array[ offset + 1 ] }, ${ array[ offset + 2 ] }, ${ array[ offset + 3 ] })`;

}

// Mesh

function buildMeshObject( geometry ) {

	const mesh = buildMesh( geometry );
	return `
def "Geometry"
{
  ${mesh}
}
`;

}

function buildMesh( geometry ) {

	const name = 'Geometry';
	const attributes = geometry.attributes;
	const count = attributes.position.count;

	return `
    def Mesh "${ name }"
    {
        int[] faceVertexCounts = [${ buildMeshVertexCount( geometry ) }]
        int[] faceVertexIndices = [${ buildMeshVertexIndices( geometry ) }]
        normal3f[] normals = [${ buildVector3Array( attributes.normal, count )}] (
            interpolation = "vertex"
        )
        point3f[] points = [${ buildVector3Array( attributes.position, count )}]
        float2[] primvars:st = [${ buildVector2Array( attributes.uv, count )}] (
            interpolation = "vertex"
        )
        uniform token subdivisionScheme = "none"
    }
`;

}

function buildMeshVertexCount( geometry ) {

	const count = geometry.index !== null ? geometry.index.array.length : geometry.attributes.position.count;

	return Array( count / 3 ).fill( 3 ).join( ', ' );

}

function buildMeshVertexIndices( geometry ) {

	if ( geometry.index !== null ) {

		return geometry.index.array.join( ', ' );

	}

	const array = [];
	const length = geometry.attributes.position.count;

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

		array.push( i );

	}

	return array.join( ', ' );

}

function buildVector3Array( attribute, count ) {

	if ( attribute === undefined ) {

		console.warn( 'USDZExporter: Normals missing.' );
		return Array( count ).fill( '(0, 0, 0)' ).join( ', ' );

	}

	const array = [];
	const data = attribute.array;

	for ( let i = 0; i < data.length; i += 3 ) {

		array.push( `(${ data[ i + 0 ].toPrecision( PRECISION ) }, ${ data[ i + 1 ].toPrecision( PRECISION ) }, ${ data[ i + 2 ].toPrecision( PRECISION ) })` );

	}

	return array.join( ', ' );

}

function buildVector2Array( attribute, count ) {

	if ( attribute === undefined ) {

		console.warn( 'USDZExporter: UVs missing.' );
		return Array( count ).fill( '(0, 0)' ).join( ', ' );

	}

	const array = [];
	const data = attribute.array;

	for ( let i = 0; i < data.length; i += 2 ) {

		array.push( `(${ data[ i + 0 ].toPrecision( PRECISION ) }, ${ 1 - data[ i + 1 ].toPrecision( PRECISION ) })` );

	}

	return array.join( ', ' );

}

// Materials

function buildMaterials( materials, textures ) {

	const array = [];

	for ( const uuid in materials ) {

		const material = materials[ uuid ];

		array.push( buildMaterial( material, textures ) );

	}

	return `def "Materials"
{
${ array.join( '' ) }
}

`;

}

function buildMaterial( material, textures ) {

	// https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html

	const pad = '            ';
	const inputs = [];
	const samplers = [];

	function buildTexture( texture, mapType, color ) {

		const id = texture.id + ( color ? '_' + color.getHexString() : '' );
		const isRGBA = texture.format === 1023;

		textures[ id ] = texture;

		return `
        def Shader "Transform2d_${ mapType }" (
            sdrMetadata = {
                string role = "math"
            }
        )
        {
            uniform token info:id = "UsdTransform2d"
            float2 inputs:in.connect = </Materials/Material_${ material.id }/uvReader_st.outputs:result>
            float2 inputs:scale = ${ buildVector2( texture.repeat ) }
            float2 inputs:translation = ${ buildVector2( texture.offset ) }
            float2 outputs:result
        }

        def Shader "Texture_${ texture.id }_${ mapType }"
        {
            uniform token info:id = "UsdUVTexture"
            asset inputs:file = @textures/Texture_${ id }.${ isRGBA ? 'png' : 'jpg' }@
            float2 inputs:st.connect = </Materials/Material_${ material.id }/Transform2d_${ mapType }.outputs:result>
            token inputs:wrapS = "repeat"
            token inputs:wrapT = "repeat"
            float outputs:r
            float outputs:g
            float outputs:b
            float3 outputs:rgb
        }`;

	}

	if ( material.map !== null ) {

		inputs.push( `${ pad }color3f inputs:diffuseColor.connect = </Materials/Material_${ material.id }/Texture_${ material.map.id }_diffuse.outputs:rgb>` );

		samplers.push( buildTexture( material.map, 'diffuse', material.color ) );

	} else {

		inputs.push( `${ pad }color3f inputs:diffuseColor = ${ buildColor( material.color ) }` );

	}

	if ( material.emissiveMap !== null ) {

		inputs.push( `${ pad }color3f inputs:emissiveColor.connect = </Materials/Material_${ material.id }/Texture_${ material.emissiveMap.id }_emissive.outputs:rgb>` );

		samplers.push( buildTexture( material.emissiveMap, 'emissive' ) );

	} else if ( material.emissive.getHex() > 0 ) {

		inputs.push( `${ pad }color3f inputs:emissiveColor = ${ buildColor( material.emissive ) }` );

	}

	if ( material.normalMap !== null ) {

		inputs.push( `${ pad }normal3f inputs:normal.connect = </Materials/Material_${ material.id }/Texture_${ material.normalMap.id }_normal.outputs:rgb>` );

		samplers.push( buildTexture( material.normalMap, 'normal' ) );

	}

	if ( material.aoMap !== null ) {

		inputs.push( `${ pad }float inputs:occlusion.connect = </Materials/Material_${ material.id }/Texture_${ material.aoMap.id }_occlusion.outputs:r>` );

		samplers.push( buildTexture( material.aoMap, 'occlusion' ) );

	}

	if ( material.roughnessMap !== null && material.roughness === 1 ) {

		inputs.push( `${ pad }float inputs:roughness.connect = </Materials/Material_${ material.id }/Texture_${ material.roughnessMap.id }_roughness.outputs:g>` );

		samplers.push( buildTexture( material.roughnessMap, 'roughness' ) );

	} else {

		inputs.push( `${ pad }float inputs:roughness = ${ material.roughness }` );

	}

	if ( material.metalnessMap !== null && material.metalness === 1 ) {

		inputs.push( `${ pad }float inputs:metallic.connect = </Materials/Material_${ material.id }/Texture_${ material.metalnessMap.id }_metallic.outputs:b>` );

		samplers.push( buildTexture( material.metalnessMap, 'metallic' ) );

	} else {

		inputs.push( `${ pad }float inputs:metallic = ${ material.metalness }` );

	}

	inputs.push( `${ pad }float inputs:opacity = ${ material.opacity }` );

	if ( material.isMeshPhysicalMaterial ) {

		inputs.push( `${ pad }float inputs:clearcoat = ${ material.clearcoat }` );
		inputs.push( `${ pad }float inputs:clearcoatRoughness = ${ material.clearcoatRoughness }` );
		inputs.push( `${ pad }float inputs:ior = ${ material.ior }` );

	}

	return `
    def Material "Material_${ material.id }"
    {
        def Shader "PreviewSurface"
        {
            uniform token info:id = "UsdPreviewSurface"
${ inputs.join( '\n' ) }
            int inputs:useSpecularWorkflow = 0
            token outputs:surface
        }

        token outputs:surface.connect = </Materials/Material_${ material.id }/PreviewSurface.outputs:surface>
        token inputs:frame:stPrimvarName = "st"

        def Shader "uvReader_st"
        {
            uniform token info:id = "UsdPrimvarReader_float2"
            token inputs:varname.connect = </Materials/Material_${ material.id }.inputs:frame:stPrimvarName>
            float2 inputs:fallback = (0.0, 0.0)
            float2 outputs:result
        }

${ samplers.join( '\n' ) }

    }
`;

}

function buildColor( color ) {

	return `(${ color.r }, ${ color.g }, ${ color.b })`;

}

function buildVector2( vector ) {

	return `(${ vector.x }, ${ vector.y })`;

}

export { USDZExporter };
<|endoftext|>


<commit_before>// =============================================================================
// Boost.js | Colours
// (c) Mathigon
// =============================================================================

import { clamp, tabulate } from '@mathigon/core';

const shortHexRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
const longHexRegex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;

// -----------------------------------------------------------------------------
// Static Colours (gradients from http://www.sron.nl/~pault/colourschemes.pdf)

const rainbow = ['#31b304', '#1f7aff', '#b30469','#ff941f'];

const temperature = ['#3D52A1', '#3A89C9', '#77B7E5', '#B4DDF7', '#E6F5FE',
  '#FFFAD2', '#FFE3AA', '#F9BD7E', '#ED875E', '#D24D3E', '#AE1C3E'];

const solar = ['#FFFFE5', '#FFF7BC', '#FEE391', '#FEC44F', '#FB9A29',
  '#EC7014', '#CC4C02', '#993404', '#662506'];

// -----------------------------------------------------------------------------
// Helper Functions

function pad2(str) {
  return str.length === 1 ? '0' + str : str;
}

// Gets the colour of a multi-step gradient at a given percentage p
function getColourAt(gradient, p) {
  p = clamp(p, 0, 0.9999);  // FIXME
  const r = Math.floor(p * (gradient.length - 1));
  const q = p * (gradient.length - 1) - r;
  return Colour.mix(gradient[r + 1], gradient[r], q);
}

/**
 * Colour generation and conversion class.
 */
export class Colour {

  // -------------------------------------------------------------------------
  // Gradients

  /**
   * Generates a rainbow gradient with a given number of steps.
   * @param {number} steps
   * @returns {Colour[]}
   */
  static rainbow(steps) {
    return tabulate(x => getColourAt(rainbow, x/(steps-1)), steps);
  }

  /**
   * Generates a temperature gradient with a given number of steps.
   * @param {number} steps
   * @returns {Colour[]}
   */
  static temperature(steps) {
    const scale = clamp(0.1 * steps, 0, 1);
    return tabulate(function(x) {
      return getColourAt(temperature, (1-scale)/2 + scale*x/(steps-1) ); }, steps);
  }

  /**
   * Generates a solar gradient with a given number of steps.
   * @param {number} steps
   * @returns {Colour[]}
   */
  static solar(steps) {
    return tabulate(x => getColourAt(solar, x/(steps-1)), steps);
  }

  // -------------------------------------------------------------------------
  // Constructor Functions

  /**
   * @param {number} r
   * @param {number} g
   * @param {number} b
   * @param {number=} a
   */
  constructor(r, g, b, a = 1) {
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
  }

  /**
   * Creates a Colour instance from a hex string.
   * @param {string} hex
   * @returns {Colour}
   */
  static fromHex(hex) {
    hex = hex.replace(shortHexRegex, function(m, r, g, b) {
      return r + r + g + g + b + b;
    });

    const rgbParts = longHexRegex.exec(hex);
    if (!rgbParts) return new Colour(0,0,0);

    return new Colour(
      parseInt(rgbParts[1], 16),
      parseInt(rgbParts[2], 16),
      parseInt(rgbParts[3], 16)
    );
  }

  /**
   * Creates a Colour instance from an rgb or rgba CSS string.
   * @param {string} rgb
   * @returns {Colour}
   */
  static fromRgb(rgb) {
    const c = rgb.replace(/rgba?\(/,'').replace(')','').split(',');

    return new Colour(
      +c[0],
      +c[1],
      +c[2],
      (c.length > 3) ? +c[3] : 1
    );
  }

  // -------------------------------------------------------------------------
  // Properties

  /**
   * Converts this colour to a hex string.
   * @returns {string}
   */
  get hex() {
    const c = [this.r, this.g, this.b].map(x => pad2(Math.round(x).toString(16)));
    return '#' + c.join('');
  }

  /**
   * Converts this colour to an rgba string.
   * @returns {string}
   */
  get rgb() {
    const c = [this.r, this.g, this.b].map(x => Math.round(x)).join(',');
    return 'rgba(' + c + ',' + this.a + ')';
  }

  /**
   * Converts this colour to an hsl string.
   * @returns {string}
   */
  get hsl() {
    const r = this.r / 255;
    const g = this.g / 255;
    const b = this.b / 255;

    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);

    let h, s;
    const l = (max + min) / 2;

    if (max === min) {
      h = s = 0; // achromatic
    } else {
      const d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      switch(max){
        case r: h = (g - b) / d + (g < b ? 6 : 0); break;
        case g: h = (b - r) / d + 2; break;
        case b: h = (r - g) / d + 4; break;
      }
      h /= 6;
    }

    return 'hsl(' + [h, s, l].join(',') + ')';
  }

  toString() {
    return this.rgb;
  }

  /**
   * Creates a copy of this colour.
   * @returns {Colour}
   */
  copy() {
    return new Colour(this.r, this.g, this.b, this.a);
  }

  // -------------------------------------------------------------------------
  // Operations

  /**
   * Linearly interpolates two colours or hex strings.
   * @param {Colour|string} c1
   * @param {Colour|string} c2
   * @param {number} p
   * @returns {Colour}
   */
  static mix(c1, c2, p = 0.5) {
    p = clamp(p, 0, 1);

    if (!(c1 instanceof Colour)) c1 = Colour.fromHex(c1);
    if (!(c2 instanceof Colour)) c2 = Colour.fromHex(c2);

    return new Colour(
        p * c1.r + (1 - p) * c2.r,
        p * c1.g + (1 - p) * c2.g,
        p * c1.b + (1 - p) * c2.b,
        p * c1.a + (1 - p) * c2.a
    );
  }
}
<commit_msg>Update rainbow colours<commit_after>// =============================================================================
// Boost.js | Colours
// (c) Mathigon
// =============================================================================

import { clamp, tabulate } from '@mathigon/core';

const shortHexRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
const longHexRegex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;

// -----------------------------------------------------------------------------
// Static Colours (gradients from http://www.sron.nl/~pault/colourschemes.pdf)

const rainbow = ['#22ab24', '#0f82f2', '#cd0e66','#fd8c00'];

const temperature = ['#3D52A1', '#3A89C9', '#77B7E5', '#B4DDF7', '#E6F5FE',
  '#FFFAD2', '#FFE3AA', '#F9BD7E', '#ED875E', '#D24D3E', '#AE1C3E'];

const solar = ['#FFFFE5', '#FFF7BC', '#FEE391', '#FEC44F', '#FB9A29',
  '#EC7014', '#CC4C02', '#993404', '#662506'];

// -----------------------------------------------------------------------------
// Helper Functions

function pad2(str) {
  return str.length === 1 ? '0' + str : str;
}

// Gets the colour of a multi-step gradient at a given percentage p
function getColourAt(gradient, p) {
  p = clamp(p, 0, 0.9999);  // FIXME
  const r = Math.floor(p * (gradient.length - 1));
  const q = p * (gradient.length - 1) - r;
  return Colour.mix(gradient[r + 1], gradient[r], q);
}

/**
 * Colour generation and conversion class.
 */
export class Colour {

  // -------------------------------------------------------------------------
  // Gradients

  /**
   * Generates a rainbow gradient with a given number of steps.
   * @param {number} steps
   * @returns {Colour[]}
   */
  static rainbow(steps) {
    return tabulate(x => getColourAt(rainbow, x/(steps-1)), steps);
  }

  /**
   * Generates a temperature gradient with a given number of steps.
   * @param {number} steps
   * @returns {Colour[]}
   */
  static temperature(steps) {
    const scale = clamp(0.1 * steps, 0, 1);
    return tabulate(function(x) {
      return getColourAt(temperature, (1-scale)/2 + scale*x/(steps-1) ); }, steps);
  }

  /**
   * Generates a solar gradient with a given number of steps.
   * @param {number} steps
   * @returns {Colour[]}
   */
  static solar(steps) {
    return tabulate(x => getColourAt(solar, x/(steps-1)), steps);
  }

  // -------------------------------------------------------------------------
  // Constructor Functions

  /**
   * @param {number} r
   * @param {number} g
   * @param {number} b
   * @param {number=} a
   */
  constructor(r, g, b, a = 1) {
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
  }

  /**
   * Creates a Colour instance from a hex string.
   * @param {string} hex
   * @returns {Colour}
   */
  static fromHex(hex) {
    hex = hex.replace(shortHexRegex, function(m, r, g, b) {
      return r + r + g + g + b + b;
    });

    const rgbParts = longHexRegex.exec(hex);
    if (!rgbParts) return new Colour(0,0,0);

    return new Colour(
      parseInt(rgbParts[1], 16),
      parseInt(rgbParts[2], 16),
      parseInt(rgbParts[3], 16)
    );
  }

  /**
   * Creates a Colour instance from an rgb or rgba CSS string.
   * @param {string} rgb
   * @returns {Colour}
   */
  static fromRgb(rgb) {
    const c = rgb.replace(/rgba?\(/,'').replace(')','').split(',');

    return new Colour(
      +c[0],
      +c[1],
      +c[2],
      (c.length > 3) ? +c[3] : 1
    );
  }

  // -------------------------------------------------------------------------
  // Properties

  /**
   * Converts this colour to a hex string.
   * @returns {string}
   */
  get hex() {
    const c = [this.r, this.g, this.b].map(x => pad2(Math.round(x).toString(16)));
    return '#' + c.join('');
  }

  /**
   * Converts this colour to an rgba string.
   * @returns {string}
   */
  get rgb() {
    const c = [this.r, this.g, this.b].map(x => Math.round(x)).join(',');
    return 'rgba(' + c + ',' + this.a + ')';
  }

  /**
   * Converts this colour to an hsl string.
   * @returns {string}
   */
  get hsl() {
    const r = this.r / 255;
    const g = this.g / 255;
    const b = this.b / 255;

    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);

    let h, s;
    const l = (max + min) / 2;

    if (max === min) {
      h = s = 0; // achromatic
    } else {
      const d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      switch(max){
        case r: h = (g - b) / d + (g < b ? 6 : 0); break;
        case g: h = (b - r) / d + 2; break;
        case b: h = (r - g) / d + 4; break;
      }
      h /= 6;
    }

    return 'hsl(' + [h, s, l].join(',') + ')';
  }

  toString() {
    return this.rgb;
  }

  /**
   * Creates a copy of this colour.
   * @returns {Colour}
   */
  copy() {
    return new Colour(this.r, this.g, this.b, this.a);
  }

  // -------------------------------------------------------------------------
  // Operations

  /**
   * Linearly interpolates two colours or hex strings.
   * @param {Colour|string} c1
   * @param {Colour|string} c2
   * @param {number} p
   * @returns {Colour}
   */
  static mix(c1, c2, p = 0.5) {
    p = clamp(p, 0, 1);

    if (!(c1 instanceof Colour)) c1 = Colour.fromHex(c1);
    if (!(c2 instanceof Colour)) c2 = Colour.fromHex(c2);

    return new Colour(
        p * c1.r + (1 - p) * c2.r,
        p * c1.g + (1 - p) * c2.g,
        p * c1.b + (1 - p) * c2.b,
        p * c1.a + (1 - p) * c2.a
    );
  }
}
<|endoftext|>


<commit_before>const isPromise = require('is-promise')
const memory = require('./storages/memory')
const defaultStorage = require('./storages/file-sync')

const init = (
  db,
  key,
  source,
  {
    storage = defaultStorage,
    format = {}
  } = {}) => {
  db.source = source

  // Set storage
  // In-memory only if no source is provided
  db.storage = {
    ...memory,
    ...(db.source && storage)
  }

  db.read = (s = source) => {
    const r = db.storage.read(s, format.deserialize)

    return isPromise(r)
      ? r.then(db.plant)
      : db.plant(r)
  }

  db.write = function (dest = source, argValue) {
    const value = arguments.length === 2
      ? argValue
      : db.getState()

    const w = db.storage.write(dest, db.getState(), format.serialize)
    return isPromise(w)
      ? w.then(() => value)
      : value
  }

  db.plant = (state) => {
    db[key] = state
    return db
  }

  db.getState = () => db[key]

  db.setState = (state) => {
    db.plant(state)
    return db.write()
  }

  return db.read()
}

module.exports = {
  init
}
<commit_msg>Refactor<commit_after>const isPromise = require('is-promise')
const memory = require('./storages/memory')
const defaultStorage = require('./storages/file-sync')

const init = (
  db,
  key,
  source,
  {
    storage = defaultStorage,
    format = {}
  } = {}) => {
  db.source = source

  // Set storage
  // In-memory only if no source is provided
  db.storage = {
    ...memory,
    ...(db.source && storage)
  }

  db.read = (s = source) => {
    const r = db.storage.read(s, format.deserialize)

    return isPromise(r)
      ? r.then(db.plant)
      : db.plant(r)
  }

  db.write = (dest = source, ...args) => {
    const value = args.length
      ? args[0]
      : db.getState()

    const w = db.storage.write(dest, db.getState(), format.serialize)
    return isPromise(w)
      ? w.then(() => value)
      : value
  }

  db.plant = (state) => {
    db[key] = state
    return db
  }

  db.getState = () => db[key]

  db.setState = (state) => {
    db.plant(state)
    return db.write()
  }

  return db.read()
}

module.exports = {
  init
}
<|endoftext|>


<commit_before>define(['underscore', 'require', 'app', 'backbone.marionette'],
  function (_, require, App, Marionette) {
    'use strict';

    var navigationItem = App.request('addNavigationItem', {
      name: 'Modules',
      icon: 'search',
      url: '#modules'
    });

    return Marionette.Controller.extend({
      showModules: function () {
        require(['../views/ModulesView'],
          function (ModulesView) {
            navigationItem.select();
            App.mainRegion.show(new ModulesView());
          });
      },
      showModule: function (id) {
        require(['../views/ModuleView', 'nusmods', 'common/models/ModuleModel'],
          function (ModuleView, NUSMods, ModuleModel) {
            navigationItem.select();
            NUSMods.getMod(id, _.bind(function (mod) {
              mod.id = id;
              var moduleModel = new ModuleModel(mod);
              App.mainRegion.show(new ModuleView({model: moduleModel}));
            }, this));
          });
      }
    });
  });
<commit_msg>Convert module code to upper case for routing<commit_after>define(['underscore', 'require', 'app', 'backbone.marionette'],
  function (_, require, App, Marionette) {
    'use strict';

    var navigationItem = App.request('addNavigationItem', {
      name: 'Modules',
      icon: 'search',
      url: '#modules'
    });

    return Marionette.Controller.extend({
      showModules: function () {
        require(['../views/ModulesView'],
          function (ModulesView) {
            navigationItem.select();
            App.mainRegion.show(new ModulesView());
          });
      },
      showModule: function (id) {
        require(['../views/ModuleView', 'nusmods', 'common/models/ModuleModel'],
          function (ModuleView, NUSMods, ModuleModel) {
            navigationItem.select();
            var modCode = id.toUpperCase();
            NUSMods.getMod(modCode, _.bind(function (mod) {
              mod.id = modCode;
              var moduleModel = new ModuleModel(mod);
              App.mainRegion.show(new ModuleView({model: moduleModel}));
            }, this));
          });
      }
    });
  });
<|endoftext|>


<commit_before>
(function() {

'use strict';

MetadataCtrl.$inject = ['$scope', 'desks', 'metadata', '$filter'];
function MetadataCtrl($scope, desks, metadata, $filter) {
	desks.initialize()
	.then(function() {
		$scope.deskLookup = desks.deskLookup;
		$scope.userLookup = desks.userLookup;
	});

	metadata.initialize().then(function() {
		$scope.metadata = metadata.values;
	});
}

MetadataListEditingDirective.$inject = [];
function MetadataListEditingDirective() {
	return {
		scope: {
			item: '=',
			disabled: '=',
			field: '@',
			list: '='
		},
		templateUrl: 'scripts/superdesk-authoring/metadata/views/metadata-terms.html',
		link: function(scope) {

			scope.terms = [];
			scope.selectedTerm = '';

			scope.searchTerms = function(term) {
				console.log(scope.list);
		        if (!term) {
		            scope.terms = [];
		        } else {
		            scope.terms = _.filter(scope.list, function(t) {
		                return ((t.name.toLowerCase().indexOf(term.toLowerCase()) !== -1) &&
		                    !_.find(scope.item[scope.field], {qcode: t.qcode}));
		            });
		        }

		        return scope.terms;
		    };

		    scope.selectTerm = function(term) {
		        if (term) {

		        	//instead of simple push, extend the item[field] in order to trigger dirty $watch
					var t = _.clone(scope.item[scope.field]) || [];
					t.push(term);

					//build object
					var o = {};
					o[scope.field] = t;
					_.extend(scope.item, o);

					scope.selectedTerm = '';
		        }
		    };

		    scope.removeTerm = function(term) {
		    	var temp = _.without(scope.item[scope.field], term);

		    	//build object
		    	var o = {};
				o[scope.field] = temp;

		    	_.extend(scope.item, o);
		    };
		}
	};
}

MetadataService.$inject = ['api', '$q', 'metadataMock'];
function MetadataService(api, $q, metadataMock) {

	var service = {
		values: {},
		loaded: null,
		fetchMetadataValues: function() {
			var self = this;

			_.each(metadataMock, function(val) {
				self.values[val._id] = val._items;
			});

			return $q.when();
		},
		fetchSubjectcodes: function(code) {
			var self = this;
			return api.get('/subjectcodes').then(function(result) {
				self.values.subjectcodes = result._items;
			});
		},
		initialize: function() {
			if (!this.loaded) {
				this.loaded = this.fetchMetadataValues()
					.then(angular.bind(this, this.fetchSubjectcodes));
			}
			return this.loaded;
		}
	};

	return service;
}

//just for mocking purpose - will be replcaed by API call
var metadataMock = [
	{
		_id: 'categories',
		_items: [
			{
				name: 'Domestic, non-Washington, general news item',
				qcode: 'A'
			},
			{
				name: 'Special Events. National tabular election items',
				qcode: 'B'
			},
			{
				name: 'International news, including United Nations and undated roundups keyed to foreign events',
				qcode: 'I'
			}
		]
	},
	{
		_id: 'newsvalues',
		_items: [
			{
				name: '1',
				qcode: 1
			},
			{
				name: '2',
				qcode: 2
			},
			{
				name: '3',
				qcode: 3
			}
		]
	},
	{
		_id: 'priority',
		_items: [
			{
				name: 'B',
				qcode: 'B'
			},
			{
				name: 'D',
				qcode: 'D'
			},
			{
				name: 'F',
				qcode: 'F'
			}
		]
	},
	{
		_id: 'genre',
		_items: [
			{
				qcode: 'exclusive',
				name: 'Exclusive'
			},
			{
				qcode: 'profile',
				name: 'Profile'
			},
			{
				qcode: 'wrapup',
				name: 'Wrapup'
			}
		]
	}
];
angular.module('superdesk.authoring.metadata', ['superdesk.authoring.widgets'])
	.config(['authoringWidgetsProvider', function(authoringWidgetsProvider) {
		authoringWidgetsProvider
			.widget('metadata', {
				icon: 'info',
				label: gettext('Info'),
				template: 'scripts/superdesk-authoring/metadata/views/metadata-widget.html',
				order: 1
			});
	}])

	.controller('MetadataWidgetCtrl', MetadataCtrl)
	.service('metadata', MetadataService)
	.directive('sdMetaTerms', MetadataListEditingDirective)
	.value('metadataMock', metadataMock);
})();
<commit_msg>fix(metadata): fix spacing<commit_after>
(function() {

'use strict';

MetadataCtrl.$inject = ['$scope', 'desks', 'metadata', '$filter'];
function MetadataCtrl($scope, desks, metadata, $filter) {
	desks.initialize()
	.then(function() {
		$scope.deskLookup = desks.deskLookup;
		$scope.userLookup = desks.userLookup;
	});

	metadata.initialize().then(function() {
		$scope.metadata = metadata.values;
	});
}

MetadataListEditingDirective.$inject = [];
function MetadataListEditingDirective() {
	return {
		scope: {
			item: '=',
			disabled: '=',
			field: '@',
			list: '='
		},
		templateUrl: 'scripts/superdesk-authoring/metadata/views/metadata-terms.html',
		link: function(scope) {

			scope.terms = [];
			scope.selectedTerm = '';

			scope.searchTerms = function(term) {
				if (!term) {
					scope.terms = [];
				} else {
					scope.terms = _.filter(scope.list, function(t) {
					return ((t.name.toLowerCase().indexOf(term.toLowerCase()) !== -1) &&
					!_.find(scope.item[scope.field], {qcode: t.qcode}));
					});
				}

				return scope.terms;
			};

			scope.selectTerm = function(term) {
				if (term) {

					//instead of simple push, extend the item[field] in order to trigger dirty $watch
					var t = _.clone(scope.item[scope.field]) || [];
					t.push(term);

					//build object
					var o = {};
					o[scope.field] = t;
					_.extend(scope.item, o);

					scope.selectedTerm = '';
				}
			};

			scope.removeTerm = function(term) {
				var temp = _.without(scope.item[scope.field], term);

				//build object
				var o = {};
				o[scope.field] = temp;

				_.extend(scope.item, o);
			};
		}
	};
}

MetadataService.$inject = ['api', '$q', 'metadataMock'];
function MetadataService(api, $q, metadataMock) {

	var service = {
		values: {},
		loaded: null,
		fetchMetadataValues: function() {
			var self = this;

			_.each(metadataMock, function(val) {
				self.values[val._id] = val._items;
			});

			return $q.when();
		},
		fetchSubjectcodes: function(code) {
			var self = this;
			return api.get('/subjectcodes').then(function(result) {
				self.values.subjectcodes = result._items;
			});
		},
		initialize: function() {
			if (!this.loaded) {
				this.loaded = this.fetchMetadataValues()
					.then(angular.bind(this, this.fetchSubjectcodes));
			}
			return this.loaded;
		}
	};

	return service;
}

//just for mocking purpose - will be replcaed by API call
var metadataMock = [
	{
		_id: 'categories',
		_items: [
			{
				name: 'Domestic, non-Washington, general news item',
				qcode: 'A'
			},
			{
				name: 'Special Events. National tabular election items',
				qcode: 'B'
			},
			{
				name: 'International news, including United Nations and undated roundups keyed to foreign events',
				qcode: 'I'
			}
		]
	},
	{
		_id: 'newsvalues',
		_items: [
			{
				name: '1',
				qcode: 1
			},
			{
				name: '2',
				qcode: 2
			},
			{
				name: '3',
				qcode: 3
			}
		]
	},
	{
		_id: 'priority',
		_items: [
			{
				name: 'B',
				qcode: 'B'
			},
			{
				name: 'D',
				qcode: 'D'
			},
			{
				name: 'F',
				qcode: 'F'
			}
		]
	},
	{
		_id: 'genre',
		_items: [
			{
				qcode: 'exclusive',
				name: 'Exclusive'
			},
			{
				qcode: 'profile',
				name: 'Profile'
			},
			{
				qcode: 'wrapup',
				name: 'Wrapup'
			}
		]
	}
];
angular.module('superdesk.authoring.metadata', ['superdesk.authoring.widgets'])
	.config(['authoringWidgetsProvider', function(authoringWidgetsProvider) {
		authoringWidgetsProvider
			.widget('metadata', {
				icon: 'info',
				label: gettext('Info'),
				template: 'scripts/superdesk-authoring/metadata/views/metadata-widget.html',
				order: 1
			});
	}])

	.controller('MetadataWidgetCtrl', MetadataCtrl)
	.service('metadata', MetadataService)
	.directive('sdMetaTerms', MetadataListEditingDirective)
	.value('metadataMock', metadataMock);
})();
<|endoftext|>


<commit_before>angular
    .module('eArkPlatform.common.directives.basket')
    .controller('BasketController', BasketController)
    .directive('orderBasket', basketDirective);

/**
 * Main Controller for the Basket module
 * @param $scope
 * @constructor
 */

function BasketController($scope, $state, basketService, sessionService, $mdDialog){

    var bdc = this;
    var order;

    bdc.basket = basketService.basket;
    bdc.fileInfoDiag = fileInfoDiag;

    bdc.orderForm = {
        order: {
            plannedDate: new Date(),
        }
    };

    $scope.removeItem = function(item) {
        basketService.removeFromBasket(item).then(function(response){
            if (response) { // Updating current search list
                basketService.currentSearch.documents.findIndex(function (bItem, bIndex) {
                    if (item.path === bItem.path) {
                        basketService.currentSearch.documents[bIndex].baskOp = 'delete';
                    }
                });
            }
        });
    };

    $scope.compileOrder = function(orderData) {
        var userInfo = sessionService.getUserInfo();
        var packagedOrder = groupByPackage(basketService.basket);
        orderData.origin = "WEB";
        orderData.orderDate = new Date().toISOString();
        if (orderData.plannedDate) {
            orderData.plannedDate = orderData.plannedDate.toISOString();
        }
        orderData.user = {
            userName: userInfo.user.userName,
            firstname: userInfo.user.firstname,
            lastname: userInfo.user.lastname,
            email: userInfo.user.email
        };
        orderData.items = packagedOrder;
        return orderData;
    };

    function groupByPackage(basket) {
        var tmp = [];
        basket.forEach(function (item) {
            if (tmp.length < 1)
                tmp.push({
                    packageId: item.packageId,
                    items: [cleanItem(item)]
                });
            else {
                var pIndex = tmp.findIndex(function (pack) {
                    return pack.packageId == item.packageId;
                });
                if (pIndex != -1) {
                    tmp[pIndex].items.push(cleanItem(item));
                }
                else {
                    tmp.push({
                        packageId: item.packageId,
                        items: [cleanItem(item)]
                    });
                }
            }
        });
        return tmp;
    }

    /**
     * That's the content and returns only the necessary data that we need for each ordered item
     * @param item
     */
    function cleanItem(item){
        var cleanedItem = {};

        cleanedItem.title = item.filename;
        cleanedItem.packageId = item.packageId;
        cleanedItem.confidential =  item.confidential ? item.confidential : false ;
        cleanedItem.path = item.path;
        cleanedItem.contentType = item.content_type ? item.content_type :"";
        cleanedItem.size = item.stream_size;
        return cleanedItem;
    }
    $scope.submitMethod = function(ev, formData){
        // Preprocssing order before POSTing
        var formDataCopy = angular.copy(formData);
        var finalOrder = { order: $scope.compileOrder(formDataCopy) };

        basketService.submitOrder(finalOrder).then(function(response) {
            if(response){
                //clean everything if successful
                basketService.currentSearch = {};
                basketService.basket =  bdc.basket = [];
                $state.go('orderBrowse');
            }
        });
    };

    function fileInfoDiag(ev, doc) {
        $mdDialog.show({
          controller: fileInfoDialogController,
          templateUrl: 'app/src/order/view/fileInfoDiag.html',
          parent: angular.element(document.body),
          targetEvent: ev,
          locals: { document: doc },
          clickOutsideToClose: true,
          fullscreen: true
        });
    }

    function fileInfoDialogController($scope, $mdDialog, document) {
        var fidc = this;

        $scope.doc = document;

        $scope.hide = function() {
          $mdDialog.hide();
        };

        $scope.cancel = function() {
          $mdDialog.cancel();
        };
    }
}

/*
 * Basket directive
 */
function basketDirective() {
    return {
        restrict:'E',
        templateUrl : 'app/src/common/directives/basket/view/basket.html',
        scope: {}
    };
}
<commit_msg>changed packageId to package<commit_after>angular
    .module('eArkPlatform.common.directives.basket')
    .controller('BasketController', BasketController)
    .directive('orderBasket', basketDirective);

/**
 * Main Controller for the Basket module
 * @param $scope
 * @constructor
 */

function BasketController($scope, $state, basketService, sessionService, $mdDialog){

    var bdc = this;
    var order;

    bdc.basket = basketService.basket;
    bdc.fileInfoDiag = fileInfoDiag;

    bdc.orderForm = {
        order: {
            plannedDate: new Date(),
        }
    };

    $scope.removeItem = function(item) {
        basketService.removeFromBasket(item).then(function(response){
            if (response) { // Updating current search list
                basketService.currentSearch.documents.findIndex(function (bItem, bIndex) {
                    if (item.path === bItem.path) {
                        basketService.currentSearch.documents[bIndex].baskOp = 'delete';
                    }
                });
            }
        });
    };

    $scope.compileOrder = function(orderData) {
        var userInfo = sessionService.getUserInfo();
        var packagedOrder = groupByPackage(basketService.basket);
        orderData.origin = "WEB";
        orderData.orderDate = new Date().toISOString();
        if (orderData.plannedDate) {
            orderData.plannedDate = orderData.plannedDate.toISOString();
        }
        orderData.user = {
            userName: userInfo.user.userName,
            firstname: userInfo.user.firstname,
            lastname: userInfo.user.lastname,
            email: userInfo.user.email
        };
        orderData.items = packagedOrder;
        return orderData;
    };

    function groupByPackage(basket) {
        var tmp = [];
        basket.forEach(function (item) {
            if (tmp.length < 1)
                tmp.push({
                    packageId: item.package,
                    items: [cleanItem(item)]
                });
            else {
                var pIndex = tmp.findIndex(function (pack) {
                    return pack.packageId == item.package;
                });
                if (pIndex != -1) {
                    tmp[pIndex].items.push(cleanItem(item));
                }
                else {
                    tmp.push({
                        packageId: item.package,
                        items: [cleanItem(item)]
                    });
                }
            }
        });
        return tmp;
    }

    /**
     * That's the content and returns only the necessary data that we need for each ordered item
     * @param item
     */
    function cleanItem(item){
        var cleanedItem = {};

        cleanedItem.title = item.filename;
        cleanedItem.packageId = item.package;
        cleanedItem.confidential =  item.confidential ? item.confidential : false ;
        cleanedItem.path = item.path;
        cleanedItem.contentType = item.content_type ? item.content_type :"";
        cleanedItem.size = item.stream_size;
        return cleanedItem;
    }
    $scope.submitMethod = function(ev, formData){
        // Preprocssing order before POSTing
        var formDataCopy = angular.copy(formData);
        var finalOrder = { order: $scope.compileOrder(formDataCopy) };

        basketService.submitOrder(finalOrder).then(function(response) {
            if(response){
                //clean everything if successful
                basketService.currentSearch = {};
                basketService.basket =  bdc.basket = [];
                $state.go('orderBrowse');
            }
        });
    };

    function fileInfoDiag(ev, doc) {
        $mdDialog.show({
          controller: fileInfoDialogController,
          templateUrl: 'app/src/order/view/fileInfoDiag.html',
          parent: angular.element(document.body),
          targetEvent: ev,
          locals: { document: doc },
          clickOutsideToClose: true,
          fullscreen: true
        });
    }

    function fileInfoDialogController($scope, $mdDialog, document) {
        var fidc = this;

        $scope.doc = document;

        $scope.hide = function() {
          $mdDialog.hide();
        };

        $scope.cancel = function() {
          $mdDialog.cancel();
        };
    }
}

/*
 * Basket directive
 */
function basketDirective() {
    return {
        restrict:'E',
        templateUrl : 'app/src/common/directives/basket/view/basket.html',
        scope: {}
    };
}
<|endoftext|>


<commit_before>// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
// License: GNU General Public License v3. See license.txt

wn.pages['stock-level'].onload = function(wrapper) {
	wn.ui.make_app_page({
		parent: wrapper,
		title: 'Stock Level',
		single_column: true
	});

	new erpnext.StockLevel(wrapper);

	wrapper.appframe.add_home_breadcrumb()
	wrapper.appframe.add_module_icon("Stock")
	wrapper.appframe.add_breadcrumb("icon-bar-chart");
}

wn.require("app/js/stock_grid_report.js");

erpnext.StockLevel = erpnext.StockGridReport.extend({
	init: function(wrapper) {
		var me = this;

		this._super({
			title: "Stock Level",
			page: wrapper,
			parent: $(wrapper).find('.layout-main'),
			appframe: wrapper.appframe,
			doctypes: ["Item", "Warehouse", "Stock Ledger Entry", "Production Order",
				"Material Request Item", "Purchase Order Item", "Sales Order Item", "Brand"],
		});

		this.wrapper.bind("make", function() {
			wn.utils.set_footnote(me, me.wrapper.get(0),
				"
 \

 \
						Projected Qty = Actual Qty + Planned Qty + Requested Qty \
						+ Ordered Qty - Reserved Qty
 \

 \

Actual Qty: Quantity available in the warehouse.
 \

Planned Qty: Quantity, for which, Production Order has been raised, \
							but is pending to be manufactured.
 \

Requested Qty: Quantity requested for purchase, but not ordered.
 \

Ordered Qty: Quantity ordered for purchase, but not received.
 \

Reserved Qty: Quantity ordered for sale, but not delivered.
 \

 \

");
		});
	},

	setup_columns: function() {
		this.columns = [
			{id: "item_code", name: "Item Code", field: "item_code", width: 160,
				link_formatter: {
					filter_input: "item_code",
					open_btn: true,
					doctype: '"Item"',
				}},
			{id: "item_name", name: "Item Name", field: "item_name", width: 100,
				formatter: this.text_formatter},
			{id: "description", name: "Description", field: "description", width: 200,
				formatter: this.text_formatter},
			{id: "brand", name: "Brand", field: "brand", width: 100,
				link_formatter: {filter_input: "brand"}},
			{id: "warehouse", name: "Warehouse", field: "warehouse", width: 100,
				link_formatter: {filter_input: "warehouse"}},
			{id: "uom", name: "UOM", field: "uom", width: 60},
			{id: "actual_qty", name: "Actual Qty",
				field: "actual_qty", width: 80, formatter: this.currency_formatter},
			{id: "planned_qty", name: "Planned Qty",
				field: "planned_qty", width: 80, formatter: this.currency_formatter},
			{id: "requested_qty", name: "Requested Qty",
				field: "requested_qty", width: 80, formatter: this.currency_formatter},
			{id: "ordered_qty", name: "Ordered Qty",
				field: "ordered_qty", width: 80, formatter: this.currency_formatter},
			{id: "reserved_qty", name: "Reserved Qty",
				field: "reserved_qty", width: 80, formatter: this.currency_formatter},
			{id: "projected_qty", name: "Projected Qty",
				field: "projected_qty", width: 80, formatter: this.currency_formatter},
			{id: "re_order_level", name: "Re-Order Level",
				field: "re_order_level", width: 80, formatter: this.currency_formatter},
			{id: "re_order_qty", name: "Re-Order Qty",
				field: "re_order_qty", width: 80, formatter: this.currency_formatter},
		];
	},

	filters: [
		{fieldtype:"Link", label: "Item Code", link:"Item", default_value: "Select Item...",
			filter: function(val, item, opts) {
				return item.item_code == val || !val;
			}},

		{fieldtype:"Select", label: "Warehouse", link:"Warehouse",
			default_value: "Select Warehouse...", filter: function(val, item, opts) {
				return item.warehouse == val || val == opts.default_value;
			}},

		{fieldtype:"Select", label: "Brand", link:"Brand",
			default_value: "Select Brand...", filter: function(val, item, opts) {
				return val == opts.default_value || item.brand == val;
			}},
		{fieldtype:"Button", label: "Refresh", icon:"icon-refresh icon-white", cssClass:"btn-info"},
		{fieldtype:"Button", label: "Reset Filters"}
	],

	setup_filters: function() {
		var me = this;
		this._super();

		this.wrapper.bind("apply_filters_from_route", function() { me.toggle_enable_brand(); });
		this.filter_inputs.item_code.change(function() { me.toggle_enable_brand(); });

		this.trigger_refresh_on_change(["item_code", "warehouse", "brand"]);
	},

	toggle_enable_brand: function() {
		if(!this.filter_inputs.item_code.val()) {
			this.filter_inputs.brand.prop("disabled", false);
		} else {
			this.filter_inputs.brand
				.val(this.filter_inputs.brand.get(0).opts.default_value)
				.prop("disabled", true);
		}
	},

	init_filter_values: function() {
		this._super();
		this.filter_inputs.warehouse.get(0).selectedIndex = 0;
	},

	prepare_data: function() {
		var me = this;

		if(!this._data) {
			this._data = [];
			this.item_warehouse_map = {};
			this.item_by_name = this.make_name_map(wn.report_dump.data["Item"]);
			this.calculate_quantities();
		}

		this.data = [].concat(this._data);
		this.data = $.map(this.data, function(d) {
			return me.apply_filters(d) ? d : null;
		});

		this.calculate_total();
	},

	calculate_quantities: function() {
		var me = this;
		$.each([
			["Stock Ledger Entry", "actual_qty"],
			["Production Order", "planned_qty"],
			["Material Request Item", "requested_qty"],
			["Purchase Order Item", "ordered_qty"],
			["Sales Order Item", "reserved_qty"]],
			function(i, v) {
				$.each(wn.report_dump.data[v[0]], function(i, item) {
					var row = me.get_row(item.item_code, item.warehouse);
					row[v[1]] += flt(item.qty);
				});
			}
		);

		// sort by item, warehouse
		this._data = $.map(Object.keys(this.item_warehouse_map).sort(), function(key) {
			return me.item_warehouse_map[key];
		});

		// calculate projected qty
		$.each(this._data, function(i, row) {
			row.projected_qty = row.actual_qty + row.planned_qty + row.requested_qty
				+ row.ordered_qty - row.reserved_qty;
		});

		// filter out rows with zero values
		this._data = $.map(this._data, function(d) {
			return me.apply_zero_filter(null, d, null, me) ? d : null;
		});
	},

	get_row: function(item_code, warehouse) {
		var key = item_code + ":" + warehouse;
		if(!this.item_warehouse_map[key]) {
			var item = this.item_by_name[item_code];
			var row = {
				item_code: item_code,
				warehouse: warehouse,
				description: item.description,
				brand: item.brand,
				item_name: item.item_name || item.name,
				uom: item.stock_uom,
				id: key,
			}
			this.reset_item_values(row);

			row["re_order_level"] = item.re_order_level
			row["re_order_qty"] = item.re_order_qty

			this.item_warehouse_map[key] = row;
		}
		return this.item_warehouse_map[key];
	},

	calculate_total: function() {
		var me = this;
		// show total if a specific item is selected and warehouse is not filtered
		if(this.is_default("warehouse") && !this.is_default("item_code")) {
			var total = {
				id: "_total",
				item_code: "Total",
				_style: "font-weight: bold",
				_show: true
			};
			this.reset_item_values(total);

			$.each(this.data, function(i, row) {
				$.each(me.columns, function(i, col) {
					if (col.formatter==me.currency_formatter) {
						total[col.id] += row[col.id];
					}
				});
			});

			this.data = this.data.concat([total]);
		}
	}
})
<commit_msg>Fix translate stock_level.js<commit_after>// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
// License: GNU General Public License v3. See license.txt

wn.pages['stock-level'].onload = function(wrapper) {
	wn.ui.make_app_page({
		parent: wrapper,
		title: wn._('Stock Level'),
		single_column: true
	});

	new erpnext.StockLevel(wrapper);

	wrapper.appframe.add_home_breadcrumb()
	wrapper.appframe.add_module_icon("Stock")
	wrapper.appframe.add_breadcrumb("icon-bar-chart");
}

wn.require("app/js/stock_grid_report.js");

erpnext.StockLevel = erpnext.StockGridReport.extend({
	init: function(wrapper) {
		var me = this;

		this._super({
			title: wn._("Stock Level"),
			page: wrapper,
			parent: $(wrapper).find('.layout-main'),
			appframe: wrapper.appframe,
			doctypes: ["Item", "Warehouse", "Stock Ledger Entry", "Production Order",
				"Material Request Item", "Purchase Order Item", "Sales Order Item", "Brand"],
		});

		this.wrapper.bind("make", function() {
			wn.utils.set_footnote(me, me.wrapper.get(0),
				"
 \

 \
						Projected Qty = Actual Qty + Planned Qty + Requested Qty \
						+ Ordered Qty - Reserved Qty
 \

 \

"+wn._("Actual Qty: Quantity available in the warehouse.") +"
"+
						"
"+wn._("Planned Qty: Quantity, for which, Production Order has been raised,")+
							wn._("but is pending to be manufactured.")+ "
 " +
						"
"+wn._("Requested Qty: Quantity requested for purchase, but not ordered.") + "
" +
						"
" + wn._("Ordered Qty: Quantity ordered for purchase, but not received.")+ "
" +
						"
" + wn._("Reserved Qty: Quantity ordered for sale, but not delivered.") +  "
" +
					"
 \

");
		});
	},

	setup_columns: function() {
		this.columns = [
			{id: "item_code", name: wn._("Item Code"), field: "item_code", width: 160,
				link_formatter: {
					filter_input: "item_code",
					open_btn: true,
					doctype: '"Item"',
				}},
			{id: "item_name", name: wn._("Item Name"), field: "item_name", width: 100,
				formatter: this.text_formatter},
			{id: "description", name: wn._("Description"), field: "description", width: 200,
				formatter: this.text_formatter},
			{id: "brand", name: wn._("Brand"), field: "brand", width: 100,
				link_formatter: {filter_input: "brand"}},
			{id: "warehouse", name: wn._("Warehouse"), field: "warehouse", width: 100,
				link_formatter: {filter_input: "warehouse"}},
			{id: "uom", name: wn._("UOM"), field: "uom", width: 60},
			{id: "actual_qty", name: wn._("Actual Qty"),
				field: "actual_qty", width: 80, formatter: this.currency_formatter},
			{id: "planned_qty", name: wn._("Planned Qty"),
				field: "planned_qty", width: 80, formatter: this.currency_formatter},
			{id: "requested_qty", name: wn._("Requested Qty"),
				field: "requested_qty", width: 80, formatter: this.currency_formatter},
			{id: "ordered_qty", name: wn._("Ordered Qty"),
				field: "ordered_qty", width: 80, formatter: this.currency_formatter},
			{id: "reserved_qty", name: wn._("Reserved Qty"),
				field: "reserved_qty", width: 80, formatter: this.currency_formatter},
			{id: "projected_qty", name: wn._("Projected Qty"),
				field: "projected_qty", width: 80, formatter: this.currency_formatter},
			{id: "re_order_level", name: wn._("Re-Order Level"),
				field: "re_order_level", width: 80, formatter: this.currency_formatter},
			{id: "re_order_qty", name: wn._("Re-Order Qty"),
				field: "re_order_qty", width: 80, formatter: this.currency_formatter},
		];
	},

	filters: [
		{fieldtype:"Link", label: wn._("Item Code"), link:"Item", default_value: "Select Item...",
			filter: function(val, item, opts) {
				return item.item_code == val || !val;
			}},

		{fieldtype:"Select", label: wn._("Warehouse"), link:"Warehouse",
			default_value: "Select Warehouse...", filter: function(val, item, opts) {
				return item.warehouse == val || val == opts.default_value;
			}},

		{fieldtype:"Select", label: wn._("Brand"), link:"Brand",
			default_value: "Select Brand...", filter: function(val, item, opts) {
				return val == opts.default_value || item.brand == val;
			}},
		{fieldtype:"Button", label: wn._("Refresh"), icon:"icon-refresh icon-white", cssClass:"btn-info"},
		{fieldtype:"Button", label: wn._("Reset Filters")}
	],

	setup_filters: function() {
		var me = this;
		this._super();

		this.wrapper.bind("apply_filters_from_route", function() { me.toggle_enable_brand(); });
		this.filter_inputs.item_code.change(function() { me.toggle_enable_brand(); });

		this.trigger_refresh_on_change(["item_code", "warehouse", "brand"]);
	},

	toggle_enable_brand: function() {
		if(!this.filter_inputs.item_code.val()) {
			this.filter_inputs.brand.prop("disabled", false);
		} else {
			this.filter_inputs.brand
				.val(this.filter_inputs.brand.get(0).opts.default_value)
				.prop("disabled", true);
		}
	},

	init_filter_values: function() {
		this._super();
		this.filter_inputs.warehouse.get(0).selectedIndex = 0;
	},

	prepare_data: function() {
		var me = this;

		if(!this._data) {
			this._data = [];
			this.item_warehouse_map = {};
			this.item_by_name = this.make_name_map(wn.report_dump.data["Item"]);
			this.calculate_quantities();
		}

		this.data = [].concat(this._data);
		this.data = $.map(this.data, function(d) {
			return me.apply_filters(d) ? d : null;
		});

		this.calculate_total();
	},

	calculate_quantities: function() {
		var me = this;
		$.each([
			["Stock Ledger Entry", "actual_qty"],
			["Production Order", "planned_qty"],
			["Material Request Item", "requested_qty"],
			["Purchase Order Item", "ordered_qty"],
			["Sales Order Item", "reserved_qty"]],
			function(i, v) {
				$.each(wn.report_dump.data[v[0]], function(i, item) {
					var row = me.get_row(item.item_code, item.warehouse);
					row[v[1]] += flt(item.qty);
				});
			}
		);

		// sort by item, warehouse
		this._data = $.map(Object.keys(this.item_warehouse_map).sort(), function(key) {
			return me.item_warehouse_map[key];
		});

		// calculate projected qty
		$.each(this._data, function(i, row) {
			row.projected_qty = row.actual_qty + row.planned_qty + row.requested_qty
				+ row.ordered_qty - row.reserved_qty;
		});

		// filter out rows with zero values
		this._data = $.map(this._data, function(d) {
			return me.apply_zero_filter(null, d, null, me) ? d : null;
		});
	},

	get_row: function(item_code, warehouse) {
		var key = item_code + ":" + warehouse;
		if(!this.item_warehouse_map[key]) {
			var item = this.item_by_name[item_code];
			var row = {
				item_code: item_code,
				warehouse: warehouse,
				description: item.description,
				brand: item.brand,
				item_name: item.item_name || item.name,
				uom: item.stock_uom,
				id: key,
			}
			this.reset_item_values(row);

			row["re_order_level"] = item.re_order_level
			row["re_order_qty"] = item.re_order_qty

			this.item_warehouse_map[key] = row;
		}
		return this.item_warehouse_map[key];
	},

	calculate_total: function() {
		var me = this;
		// show total if a specific item is selected and warehouse is not filtered
		if(this.is_default("warehouse") && !this.is_default("item_code")) {
			var total = {
				id: "_total",
				item_code: "Total",
				_style: "font-weight: bold",
				_show: true
			};
			this.reset_item_values(total);

			$.each(this.data, function(i, row) {
				$.each(me.columns, function(i, col) {
					if (col.formatter==me.currency_formatter) {
						total[col.id] += row[col.id];
					}
				});
			});

			this.data = this.data.concat([total]);
		}
	}
})
<|endoftext|>