Source: functionsFolder.js

/**
 * @module functionsFolder
 * @description Adds the [Functions]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function} folder into [dat.gui]{@link https://github.com/anhr/dat.gui}.
 * 
 * @author [Andrej Hristoliubov]{@link https://anhr.github.io/AboutMe/}
 *
 * @copyright 2011 Data Arts Team, Google Creative Lab
 *
 * @license under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 */

//import { dat } from './dat/dat.module.js';
import Options from './Options.js'
import three from './three.js'

class functionsFolder {

	/**
	 * Adds the [Functions]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function} folder into [dat.gui]{@link https://github.com/anhr/dat.gui}.
	 * @param {GUI} fParent parent folder for functions folder.
	 * @param {Object} scales [AxesHelper]{@link https://raw.githack.com/anhr/AxesHelper/master/jsdoc/module-AxesHelper.html} options.scales for details.
	 * @param {Function} onFinishChange callback function is called every time, when user have entered new value of the function and the function controller is lost of the focus.
	 * <pre>
	 * parameter value is new value of the function.
	 * </pre>
	 * @param {Options} options <a href="../../jsdoc/Options/Options.html" target="_blank">Options</a> instance. The following options are available.
	 * See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
	 * @param {Function} [options.getLanguageCode=language code of your browser] returns the "primary language" subtag of the version of the browser.
	 * @param {object} [vector] Vector with initial text of the function
	 * @param {string} [vector.x] text of the x axis function
	 * @param {string} [vector.y] text of the y axis function
	 * @param {string} [vector.z] text of the z axis function
	*/
	constructor( fParent, onFinishChange, options, vector ) {

//		var options = settings.options || new Options();
		if ( !options.boOptions ) {

//			options = new Options( options );
			console.error( 'functionsFolder: call options = new Options( options ) first' );
			return;

		}
/*
		gui = gui || options.dat.gui;
		if ( !gui || options.dat.cameraGui === false )
			return;
*/
		const dat = three.dat,//options.dat.dat,
			THREE = three.THREE,
			scales = options.scales;
		const _this = this;
		var boError = false,//true - обнаружена ошибка ввода. Нужно вывести сообщение об ошибке и вернуть фокус на поле управления
			boAlert = false;//предотвращает бесконечный вывод сообщения об ошибке

		//Localization
		/*
			const getLanguageCode = options.getLanguageCode || function () { return 'en'; };
			const _languageCode = getLanguageCode();
		*/
		const lang = {

			functions: 'Functions',

			defaultButton: 'Default',
			defaultTitle: 'Restore default functions.',

		};

		const _languageCode = options.getLanguageCode === undefined ? 'en'//Default language is English
			: options.getLanguageCode();

		switch ( _languageCode ) {

			case 'ru'://Russian language

				lang.functions = 'Функции';

				lang.defaultButton = 'Восстановить';
				lang.defaultTitle = 'Восстановить функции.';

				break;
			default://Custom language
				if ( ( options.lang === undefined ) || ( options.lang.languageCode != _languageCode ) )
					break;

				Object.keys( options.lang ).forEach( function ( key ) {

					if ( lang[key] === undefined )
						return;
					lang[key] = options.lang[key];

				} );

		}

		if ( vector ) {

			vector.x = getFuncText( vector.x );
			vector.y = getFuncText( vector.y );
			vector.z = getFuncText( vector.z );

		} else vector = { x: '', y: '', z: '', }

		const fFunctions = fParent.addFolder( lang.functions ),
/*
			vector = {

				x: options.vector ? getFuncText( options.vector.x ) : '',
				y: options.vector ? getFuncText( options.vector.y ) : '',
				z: options.vector ? getFuncText( options.vector.z ) : '',
				w: options.vector ? getFuncText( options.vector.w ) : '',

			},
*/
			//onFinishChange вызывается даже если vector не изменился. Поэтому такой onFinishChange пропускается
			vectorCur = {

				x: vector.x,
				y: vector.y,
				z: vector.z,
				w: vector.w,

			},
			cFunctions = {};
		function createControl( axisName ) {

			if ( vector[axisName] === undefined )
				return;
			cFunctions[axisName] = fFunctions.add( vector, axisName ).onFinishChange( function ( value ) {

				__onFinishChange( value, axisName, vectorCur );

			} );
			dat.controllerNameAndTitle( cFunctions[axisName], getAxisName( axisName ) );

		}
		function getAxisName( axisName ) { return scales[axisName] && scales[axisName].name ? scales[axisName].name : axisName; }
		createControl( 'x' );
		createControl( 'y' );
		createControl( 'z' );
		createControl( 'w' );

		//Default scale button
		const buttonDefault = fFunctions.add( {

			defaultF: function ( value ) {

			},

		}, 'defaultF' );
		dat.controllerNameAndTitle( buttonDefault, lang.defaultButton, lang.defaultTitle );

		function getFuncText( func ) {

			if ( func === undefined )
				return;
			if ( typeof func === 'object' ) {

				if ( func instanceof THREE.Color ) return func.getStyle();
				if ( Array.isArray( func ) ) return JSON.stringify( func )
				func = func.func ? func.func : func;

			}
			const typeofFunc = typeof func;
			switch ( typeofFunc ) {

				case 'number':
					func = func.toString();//если это не делать будет создан NumberControllerBox, ктороый не позволяет вводить float
				case 'string':
					return func;
				case 'function':
					return func.toString().split( /return (.*)/ )[1];
				default: console.error( 'functionsFolder.getFuncText(...): typeof func = ' + typeofFunc );
					return;
			}

		}
		function __onFinishChange( value, axisName, vectorCur ) {

			if ( ( vectorCur[axisName] === value ) && !boError )
				return;
			try {

				boError = false;
				vectorCur[axisName] = value;
				var func;
				const typeofValue = typeof value;
				switch ( typeofValue ) {

					case 'string':

						var float = parseFloat( value );
						if ( float.toString() !== value ) {

							//						const color = value.replace(/\s/g, "").toLowerCase().split( /rgb\((\d+),(\d+),(\d+)\)/ );
							const color = value.replace( /\s/g, "" ).split( /rgb\((\d+),(\d+),(\d+)\)/ );
							if ( color.length === 5 ) func = new THREE.Color( value );
							else {

								var array;
								try {

									array = JSON.parse( value );

								} catch ( e ) { }
								if ( Array.isArray( array ) ) func = array;
								else {

									func = new Function( 't', 'a', 'b', 'return ' + value );

								}

							}

						} else func = float;
						break;

					case 'number':

						func = value;
						break;

					default:
						console.error( 'onFinishChange( ' + value + ' ): invalid type = ' + typeofValue );
						return;

				}

				//Новое значение введено правильно
				onFinishChange( func, axisName );
				boAlert = false;

			} catch ( e ) {

				if ( !boAlert ) {

					alert( 'Axis: ' + getAxisName( axisName ) + '. Function: "' + value + '". ' + e );
					boAlert = true;

				}
				_this.setFocus( axisName );

			}

		}

		/**
		 * set the function text
		 * @param {object} _vector vector of the axis functions.
		 * @param {object} _vector.x x axis function.
		 * @param {object} _vector.y y axis function.
		 * @param {object} _vector.z z axis function.
		 */
		this.setFunction = function ( _vector ) {

			_vector = _vector || options.vector;
			if ( !_vector )
				return;

			const vector = {

				x: _vector ? getFuncText( _vector.x ) : '',
				y: _vector ? getFuncText( _vector.y ) : '',
				z: _vector ? getFuncText( _vector.z ) : '',
				w: _vector ? getFuncText( _vector.w ) : '',

			},
				//onFinishChange вызывается даже если vector не изменился. Поэтому такой onFinishChange пропускается
				vectorCur = {

					x: vector.x,
					y: vector.y,
					z: vector.z,
					w: vector.w,

				};
			if ( !_vector.vectorDefault )
				_vector.vectorDefault = {

					x: vector.x,
					y: vector.y,
					z: vector.z,
					w: vector.w,

				};
			function setVectorAxis( axisName ) {

				if ( _vector[axisName] === undefined )
					return;
				cFunctions[axisName].__onFinishChange = function ( value ) {

					__onFinishChange( value, axisName, vectorCur );

				}
				vector[axisName] = getFuncText( _vector[axisName] );
				cFunctions[axisName].setValue( vector[axisName] );
				vectorCur[axisName] = vector[axisName];

			}
			setVectorAxis( 'x' );
			setVectorAxis( 'y' );
			var dislay = false;
			if ( _vector.z ) {

				setVectorAxis( 'z' );
				dislay = true;

			}
			buttonDefault.object.defaultF = function ( value ) {

				function setValue( axisName ) {

					if ( !cFunctions[axisName] )
						return;
					cFunctions[axisName].setValue( _vector.vectorDefault[axisName] );
					cFunctions[axisName].__onFinishChange( _vector.vectorDefault[axisName] );

				}
				setValue( 'x' );
				setValue( 'y' );
				setValue( 'z' );
				setValue( 'w' );

			}
			function dislayEl( controller, displayController ) {

				if ( controller === undefined )
					return;
				if ( typeof displayController === "boolean" )
					displayController = displayController ? 'block' : 'none';
				var el = controller.domElement;
				while ( el.tagName.toUpperCase() !== "LI" ) el = el.parentElement;
				el.style.display = displayController;

			}
			dislayEl( cFunctions.z, dislay );
			setVectorAxis( 'w' );

		}
		this.setFunction();

		/**
		 * Display functions folder
		 * @param {string|boolean} display 'block' or true - functions folder is visible.
		 * <p>'none' or false - functions folder is hide.</p>
		 */
		this.displayFolder = function ( display ) {

			fFunctions.domElement.style.display = typeof display === "boolean" ?
				display ? 'block' : 'none' :
				display;

		}
		/**
		* set focus to controller
		* @param {string} axisName Name of the axis of the controller
		*/
		this.setFocus = function ( axisName ) {

			cFunctions[axisName].domElement.childNodes[0].focus();
			boError = true;

		}

	}

}

export default functionsFolder;