<commit_before>define(
    [
        './widget-extensions',
        './router',
        './events',
        './htmlCanvas',
        'jquery'
    ],

    function (ext, router, events, htmlCanvas, jQuery) {

        /**
         * Base for all widgets. A widget can keep state in variables, contain logic and
         * render itself using `renderOn()`.
         *
         * @example
         *
         *      var titleWidget = function(spec) {
         *          var that = widget(spec);
         *
         *          var title = spec.title || 'Hello World';
         *
         *          that.renderContentOn = function(html) {
         *              html.h1(title)
         *          }
         *
         *          return that;
         *      };
         *
         *      var helloWorldWidget = titleWidget({title: 'Hello Widget!'});
         *
         *      $(document).ready(function() {
         *          helloWorldWidget.appendTo('BODY');
         *      });
         *
         * Widgets can also be rendered on a HTML canvas (since widget implements `appendToBrush()`). Eg.
         *
         *      html.div(helloWorldWidget)
         *
         * It is therefor easy to compose widgets from other widgets.
         *
         *
         * @param {Object} spec
         * @param {String} [spec.id] Unique id for widget. Also used for root element when attached/rendered to DOM.
         *                           If not provided an ID will automatically be generated and assigned.
         * @param {Object} [my]
         *
         * @returns {widget}
         */
        var widget = function (spec, my) {
            my = my || {};
            spec = spec || {};

            /** @typedef {{}} widget */
            var that = {};

            var id = spec.id || idGenerator.newId();

            /** When within an update transaction, do not update the widget */
            my.inUpdateTransaction = false;

            /**
             * Keep track of the rendered subwidgets
             */
            var children = [];

            /** Events for widget */
            my.events = events.eventCategory();

            /**
             * Event triggered each time the widget is attached (or
             * reattached due to an update of rendering) to the DOM.
             */
            that.didAttach = my.events.createEvent();

            //
            // Public
            //

            /**
             * Returns a unique id for the widget
             *
             * @returns {String}
             */
            that.getId = function () {
                return id;
            };

            that.id = that.getId; //TODO: deprecated

            /**
             * Performance tasks need for freeing/releasing/cleaning-up resources used by widget.
             *
             * Should always be executed before a widget is disposed. Especially
             * if the widget register events to avoid memory leaks.
             *
             * If overridden in a subclass, make sure to execute `my.disposeWidget()`.
             *
             */
            that.dispose = function() {
                my.disposeWidget();
            };

            /**
             * Renders the widget on a JQuery / DOM
             *
             * @example
             *    widget.appendTo('BODY');
             *
             * @param aJQuery
             */
            that.appendTo = function (aJQuery) {
                renderBasicOn(htmlCanvas(aJQuery));
                that.triggerDidAttach();
            };

            /**
             * Does the same as appendTo except it first clear the
             * elements matched by aJQuery
             *
             * @param aJQuery
             */
            that.replace = function (aJQuery) {
                var canvas = htmlCanvas(aJQuery);
                canvas.root.asJQuery().empty();
                renderBasicOn(canvas);
                that.triggerDidAttach();
            };

            /**
             * Answers a jQuery that match the root DOM element. By default
             * by selecting an element that have the same ID as the widget.
             *
             * See 'renderOn'
             *
             * @returns {*}
             */
            that.asJQuery = function () {
                return jQuery('#' + that.getId());
            };

            /**
             * Answers true if if widget have rendered content in DOM
             *
             * @returns {boolean}
             */
            that.isRendered = function () {
                return that.asJQuery().length > 0;
            };

            /**
             * Implementation for `appendToBrush()` to allow a widget to be
             * appended to a brush. See 'htmlCanvas'.
             *
             * Basically it allows us to do:
             *      html.div(widget);
             *
             * @param aTagBrush
             */
            that.appendToBrush = function (aTagBrush) {
                renderBasicOn(htmlCanvas(aTagBrush.asJQuery()));
            };

            /**
             * Trigger the `didAttach` event on the receiver and all
             * rendered subwidgets.
             */
            that.triggerDidAttach = function() {
                that.didAttach.trigger();
                children.forEach(function (widget) {
                    widget.triggerDidAttach();
                });
            };

            // Expose events
            that.on = my.events.on;
            that.onceOn = my.events.onceOn;
            that.off = my.events.off;
            that.trigger = my.events.trigger;

            //
            // Protected
            //

            /**
             * Exposes the internal ID generator. Generates new unique IDs to be used
             * for sub-widgets, etc.
             *
             * @returns {String}
             */
            my.nextId = function () {
                return idGenerator.newId();
            };

            /**
             * Widget specific dispose.
             */
            my.disposeWidget = function() {
              my.events.dispose();
            };

            my.trigger = my.events.trigger;

            // Route / Controller extensions

            my.router = router.getRouter();

            my.linkTo = my.router.linkTo;
            my.linkToPath = my.router.linkToPath;
            my.linkToUrl = my.router.linkToUrl;

            my.redirectTo = my.router.redirectTo;
            my.redirectToPath = my.router.redirectToPath;
            my.redirectToUrl = my.router.redirectToUrl;

            my.getParameters = my.router.getParameters;
            my.getParameter = my.router.getParameter;
            my.setParameters = my.router.setParameters;

            //
            // Render
            //

            /**
             * Private rendering function.  This is the function
             * internally called each time the widget is rendered, in
             * `appendTo`, `replace` and `update`.
             *
             */
            function renderBasicOn(html) {
                my.withChildrenRegistration(function() {
                    that.renderOn(html);
                });
            }

            /**
             * Main entry point for rendering. For convenience 'renderOn' will  wrap the content
             * rendered by 'renderContentOn' in a root element (renderRootOn) that will be matched
             * by asJQuery.
             *
             * Usually concrete widgets override 'renderContentOn' to render it content. Widgets
             * can override 'renderOn' but must then make sure that it can be matched by 'asJQuery'.
             *
             * One way to do that is to make sure to have only one root element and setting the ID of
             * that element to the ID of the widget.
             *
             * @example
             *
             *      that.renderOn = function (html) {
             *          html.ul({id: that.getId()}
             *              html.li('BMW'),
             *              html.li('Toyota')
             *          );
             *      };
             *
             *
             * @param html
             */
            that.renderOn = function (html) {
                // Renders widget by wrapping `renderContentOn()` in a root element.
                my.renderRootOn(html).render(that.renderContentOn);
            };

            my.withChildrenRegistration = function(fn) {
                var parent = currentWidget.get();
                if(parent) {
                    parent.registerChild(that);
                }
                withCurrentWidget(function () {
                    children = [];
                    fn();
                }, that);
            };

            that.registerChild = function(widget) {
                children.push(widget);
            };

            /**
             * Renders a wrapper element (by default a 'widgetjs-widget' tag) and
             * set the element ID to the ID of the widget so that it can be found by
             * 'asJQuery' eg. when we re-render using 'update'.
             *
             * @param html
             * @returns {htmlBrush}
             */
            my.renderRootOn = function (html) {
                return html.tag('widgetjs-widget').id(id);
            };

            /**
             * Overridden in concrete widgets to render widget to canvas/DOM.
             *
             * @example
             *
             *      that.renderContentOn = function (html) {
             *          html.ul(
             *              html.li('BMW'),
             *              html.li('Toyota')
             *          );
             *      };
             *
             * @param {htmlCanvas} html
             */
            that.renderContentOn = function (html) {};

            /**
             * Re-renders the widget and replace it in the DOM
             *
             * Content is first re-rendered on a document fragment. Update then replace the element matched
             * by 'asJQuery' with the new content.
             *
             */
            that.update = function () {
                if (my.inUpdateTransaction || !that.isRendered()) {
                    return;
                }

                // Re-render
                var html = htmlCanvas();
                renderBasicOn(html);

                // Replace our self
                that.asJQuery().replaceWith(html.root.element);
                that.triggerDidAttach();
            };

            that.withinTransaction = function(fn, onDone) {
                if(my.inUpdateTransaction) {
                    fn();
                } else {
                    try {
                        my.inUpdateTransaction = true;
                        fn();
                    }
                    finally {
                        my.inUpdateTransaction = false;
                        if(onDone) {
							onDone();
						}
                    }
                }
            };

            /**
             * Evaluate `fn`, ensuring that an update will be
             * performed after evaluating the function. Nested calls
             * to `withUpdate` or `update` will result in updating the
             * widget only once.
             */
            that.withUpdate = function(fn) {
				that.withinTransaction(fn, that.update);
            };

			that.withNoUpdate = function(fn) {
				that.withinTransaction(fn);
			};

            // Third party protected extensions** added to `my`.
            // See widget-extensions.js
            for (var extProperty in ext) {
                if (ext.hasOwnProperty(extProperty)) {
                    my[extProperty] = ext[extProperty];
                }
            }

            return that;
        };

        /**
         * Creates unique ids used by widgets to identify their root div.
         */
        var idGenerator = (function () {
            var that = {};
            var id = 0;

            that.newId = function () {
                id += 1;
                return id.toString();
            };

            return that;
        })();

        /**
         * Helpers for keeping track of the currently rendered widget.
         */
        var currentWidget = (function () {
            var current;
            return {
                get: function () {
                    return current;
                },
                set: function (widget) {
                    current = widget;
                }
            };
        })();

        /**
         * Set `widget` as the current widget while evaluating `fn`.
         */
        function withCurrentWidget(fn, widget) {
            var current = currentWidget.get();
            try {
                currentWidget.set(widget);
                fn();
            } finally {
                currentWidget.set(current);
            }
        }

        return widget;
    }
);
<commit_msg>* src/widget.js: tabify and use unix line endings + utf-8.<commit_after>define(
	[
		'./widget-extensions',
		'./router',
		'./events',
		'./htmlCanvas',
		'jquery'
	],

	function (ext, router, events, htmlCanvas, jQuery) {

		/**
		 * Base for all widgets. A widget can keep state in variables, contain logic and
		 * render itself using `renderOn()`.
		 *
		 * @example
		 *
		 *		var titleWidget = function(spec) {
		 *			var that = widget(spec);
		 *
		 *			var title = spec.title || 'Hello World';
		 *
		 *			that.renderContentOn = function(html) {
		 *				html.h1(title)
		 *			}
		 *
		 *			return that;
		 *		};
		 *
		 *		var helloWorldWidget = titleWidget({title: 'Hello Widget!'});
		 *
		 *		$(document).ready(function() {
		 *			helloWorldWidget.appendTo('BODY');
		 *		});
		 *
		 * Widgets can also be rendered on a HTML canvas (since widget implements `appendToBrush()`). Eg.
		 *
		 *		html.div(helloWorldWidget)
		 *
		 * It is therefor easy to compose widgets from other widgets.
		 *
		 *
		 * @param {Object} spec
		 * @param {String} [spec.id] Unique id for widget. Also used for root element when attached/rendered to DOM.
		 *							 If not provided an ID will automatically be generated and assigned.
		 * @param {Object} [my]
		 *
		 * @returns {widget}
		 */
		var widget = function (spec, my) {
			my = my || {};
			spec = spec || {};

			/** @typedef {{}} widget */
			var that = {};

			var id = spec.id || idGenerator.newId();

			/** When within an update transaction, do not update the widget */
			my.inUpdateTransaction = false;

			/**
			 * Keep track of the rendered subwidgets
			 */
			var children = [];

			/** Events for widget */
			my.events = events.eventCategory();

			/**
			 * Event triggered each time the widget is attached (or
			 * reattached due to an update of rendering) to the DOM.
			 */
			that.didAttach = my.events.createEvent();

			//
			// Public
			//

			/**
			 * Returns a unique id for the widget
			 *
			 * @returns {String}
			 */
			that.getId = function () {
				return id;
			};

			that.id = that.getId; //TODO: deprecated

			/**
			 * Performance tasks need for freeing/releasing/cleaning-up resources used by widget.
			 *
			 * Should always be executed before a widget is disposed. Especially
			 * if the widget register events to avoid memory leaks.
			 *
			 * If overridden in a subclass, make sure to execute `my.disposeWidget()`.
			 *
			 */
			that.dispose = function() {
				my.disposeWidget();
			};

			/**
			 * Renders the widget on a JQuery / DOM
			 *
			 * @example
			 *	  widget.appendTo('BODY');
			 *
			 * @param aJQuery
			 */
			that.appendTo = function (aJQuery) {
				renderBasicOn(htmlCanvas(aJQuery));
				that.triggerDidAttach();
			};

			/**
			 * Does the same as appendTo except it first clear the
			 * elements matched by aJQuery
			 *
			 * @param aJQuery
			 */
			that.replace = function (aJQuery) {
				var canvas = htmlCanvas(aJQuery);
				canvas.root.asJQuery().empty();
				renderBasicOn(canvas);
				that.triggerDidAttach();
			};

			/**
			 * Answers a jQuery that match the root DOM element. By default
			 * by selecting an element that have the same ID as the widget.
			 *
			 * See 'renderOn'
			 *
			 * @returns {*}
			 */
			that.asJQuery = function () {
				return jQuery('#' + that.getId());
			};

			/**
			 * Answers true if if widget have rendered content in DOM
			 *
			 * @returns {boolean}
			 */
			that.isRendered = function () {
				return that.asJQuery().length > 0;
			};

			/**
			 * Implementation for `appendToBrush()` to allow a widget to be
			 * appended to a brush. See 'htmlCanvas'.
			 *
			 * Basically it allows us to do:
			 *		html.div(widget);
			 *
			 * @param aTagBrush
			 */
			that.appendToBrush = function (aTagBrush) {
				renderBasicOn(htmlCanvas(aTagBrush.asJQuery()));
			};

			/**
			 * Trigger the `didAttach` event on the receiver and all
			 * rendered subwidgets.
			 */
			that.triggerDidAttach = function() {
				that.didAttach.trigger();
				children.forEach(function (widget) {
					widget.triggerDidAttach();
				});
			};

			// Expose events
			that.on = my.events.on;
			that.onceOn = my.events.onceOn;
			that.off = my.events.off;
			that.trigger = my.events.trigger;

			//
			// Protected
			//

			/**
			 * Exposes the internal ID generator. Generates new unique IDs to be used
			 * for sub-widgets, etc.
			 *
			 * @returns {String}
			 */
			my.nextId = function () {
				return idGenerator.newId();
			};

			/**
			 * Widget specific dispose.
			 */
			my.disposeWidget = function() {
				my.events.dispose();
			};

			my.trigger = my.events.trigger;

			// Route / Controller extensions

			my.router = router.getRouter();

			my.linkTo = my.router.linkTo;
			my.linkToPath = my.router.linkToPath;
			my.linkToUrl = my.router.linkToUrl;

			my.redirectTo = my.router.redirectTo;
			my.redirectToPath = my.router.redirectToPath;
			my.redirectToUrl = my.router.redirectToUrl;

			my.getParameters = my.router.getParameters;
			my.getParameter = my.router.getParameter;
			my.setParameters = my.router.setParameters;

			//
			// Render
			//

			/**
			 * Private rendering function.	This is the function
			 * internally called each time the widget is rendered, in
			 * `appendTo`, `replace` and `update`.
			 *
			 */
			function renderBasicOn(html) {
				my.withChildrenRegistration(function() {
					that.renderOn(html);
				});
			}

			/**
			 * Main entry point for rendering. For convenience 'renderOn' will	wrap the content
			 * rendered by 'renderContentOn' in a root element (renderRootOn) that will be matched
			 * by asJQuery.
			 *
			 * Usually concrete widgets override 'renderContentOn' to render it content. Widgets
			 * can override 'renderOn' but must then make sure that it can be matched by 'asJQuery'.
			 *
			 * One way to do that is to make sure to have only one root element and setting the ID of
			 * that element to the ID of the widget.
			 *
			 * @example
			 *
			 *		that.renderOn = function (html) {
			 *			html.ul({id: that.getId()}
			 *				html.li('BMW'),
			 *				html.li('Toyota')
			 *			);
			 *		};
			 *
			 *
			 * @param html
			 */
			that.renderOn = function (html) {
				// Renders widget by wrapping `renderContentOn()` in a root element.
				my.renderRootOn(html).render(that.renderContentOn);
			};

			my.withChildrenRegistration = function(fn) {
				var parent = currentWidget.get();
				if(parent) {
					parent.registerChild(that);
				}
				withCurrentWidget(function () {
					children = [];
					fn();
				}, that);
			};

			that.registerChild = function(widget) {
				children.push(widget);
			};

			/**
			 * Renders a wrapper element (by default a 'widgetjs-widget' tag) and
			 * set the element ID to the ID of the widget so that it can be found by
			 * 'asJQuery' eg. when we re-render using 'update'.
			 *
			 * @param html
			 * @returns {htmlBrush}
			 */
			my.renderRootOn = function (html) {
				return html.tag('widgetjs-widget').id(id);
			};

			/**
			 * Overridden in concrete widgets to render widget to canvas/DOM.
			 *
			 * @example
			 *
			 *		that.renderContentOn = function (html) {
			 *			html.ul(
			 *				html.li('BMW'),
			 *				html.li('Toyota')
			 *			);
			 *		};
			 *
			 * @param {htmlCanvas} html
			 */
			that.renderContentOn = function (html) {};

			/**
			 * Re-renders the widget and replace it in the DOM
			 *
			 * Content is first re-rendered on a document fragment. Update then replace the element matched
			 * by 'asJQuery' with the new content.
			 *
			 */
			that.update = function () {
				if (my.inUpdateTransaction || !that.isRendered()) {
					return;
				}

				// Re-render
				var html = htmlCanvas();
				renderBasicOn(html);

				// Replace our self
				that.asJQuery().replaceWith(html.root.element);
				that.triggerDidAttach();
			};

			that.withinTransaction = function(fn, onDone) {
				if(my.inUpdateTransaction) {
					fn();
				} else {
					try {
						my.inUpdateTransaction = true;
						fn();
					}
					finally {
						my.inUpdateTransaction = false;
						if(onDone) {
							onDone();
						}
					}
				}
			};

			/**
			 * Evaluate `fn`, ensuring that an update will be
			 * performed after evaluating the function. Nested calls
			 * to `withUpdate` or `update` will result in updating the
			 * widget only once.
			 */
			that.withUpdate = function(fn) {
				that.withinTransaction(fn, that.update);
			};

			that.withNoUpdate = function(fn) {
				that.withinTransaction(fn);
			};

			// Third party protected extensions** added to `my`.
			// See widget-extensions.js
			for (var extProperty in ext) {
				if (ext.hasOwnProperty(extProperty)) {
					my[extProperty] = ext[extProperty];
				}
			}

			return that;
		};

		/**
		 * Creates unique ids used by widgets to identify their root div.
		 */
		var idGenerator = (function () {
			var that = {};
			var id = 0;

			that.newId = function () {
				id += 1;
				return id.toString();
			};

			return that;
		})();

		/**
		 * Helpers for keeping track of the currently rendered widget.
		 */
		var currentWidget = (function () {
			var current;
			return {
				get: function () {
					return current;
				},
				set: function (widget) {
					current = widget;
				}
			};
		})();

		/**
		 * Set `widget` as the current widget while evaluating `fn`.
		 */
		function withCurrentWidget(fn, widget) {
			var current = currentWidget.get();
			try {
				currentWidget.set(widget);
				fn();
			} finally {
				currentWidget.set(current);
			}
		}

		return widget;
	}
);
<|endoftext|>


<commit_before>
import { HudObject } from './HudObject';
import { Cfg } from '../core/lib';
import { MutableGlyphString } from '../core/GlyphString';
import { Materials } from '../materials/Materials';

import {
	Vector3, Math as _Math, Face3,
	Geometry, RingGeometry, CylinderBufferGeometry,
	MeshBasicMaterial, MeshPhongMaterial, MeshLambertMaterial,
	FrontSide, VertexColors,
	Mesh, Group
} from '../Three';

function Compass () {

	const stdWidth  = HudObject.stdWidth;
	const stdMargin = HudObject.stdMargin;

	Group.call( this );

	this.name = 'CV.Compass';

	const cg1 = new CylinderBufferGeometry( stdWidth * 0.90, stdWidth, 3, 32, 1, true );
	cg1.rotateX( Math.PI / 2 );

	const c1 = new Mesh( cg1, new MeshPhongMaterial( { color: Cfg.themeValue( 'hud.bezel' ), specular: 0x888888 } ) );

	const cg2 = new RingGeometry( stdWidth * 0.9, stdWidth, 4, 1, -Math.PI / 32 + Math.PI / 2, Math.PI / 16 );
	cg2.translate( 0, 0, 5 );

	const c2 = new Mesh( cg2, new MeshBasicMaterial( { color: Cfg.themeValue( 'hud.compass.top1' ) } ) );

	const r1 = _makeRose( stdWidth * 0.9, 0.141, Cfg.themeColor( 'hud.compass.bottom1' ), Cfg.themeColor( 'hud.compass.bottom2' ) );
	const r2 = _makeRose( stdWidth * 0.9, 0.141, Cfg.themeColor( 'hud.compass.top1' ), Cfg.themeColor( 'hud.compass.top2' ) );

	r1.rotateZ( Math.PI / 4 );
	r1.merge( r2 );

	const rMesh = new Mesh( r1, new MeshLambertMaterial( { vertexColors: VertexColors, side: FrontSide, flatShading: true } ) );

	const rotaryGroup = new Group();

	rotaryGroup.addStatic( c1 );
	rotaryGroup.addStatic( c2 );
	rotaryGroup.addStatic( rMesh );

	this.add( rotaryGroup );
	this.rotaryGroup = rotaryGroup;

	const offset = stdWidth + stdMargin;

	this.translateX( -offset );
	this.translateY(  offset );

	this.lastRotation = 0;

	const material = Materials.getGlyphMaterial( HudObject.atlasSpec, 0 );
	const label = new MutableGlyphString( '000\u00B0', material );

	label.translateX( - label.getWidth() / 2 );
	label.translateY( stdWidth + 5 );

	this.addStatic( label );

	this.label = label;

	return this;

	// make 'petal' for compass rose
	function _makePetal ( radius, scale, color1, color2 ) {

		const innerR = radius * scale;
		const g = new Geometry();

		g.vertices.push( new Vector3( 0, radius, 0 ) );
		g.vertices.push( new Vector3( innerR ,innerR, 0 ) );
		g.vertices.push( new Vector3( 0, 0, 14 * scale ) );
		g.vertices.push( new Vector3( -innerR, innerR, 0 ) );

		var f1 = new Face3( 0, 2, 1, new Vector3( 0, 0, 1 ), color1, 0 );
		var f2 = new Face3( 0, 3, 2, new Vector3( 0, 0, 1 ), color2, 0 );

		g.faces.push( f1 );
		g.faces.push( f2 );

		return g;

	}

	function _makeRose ( radius, scale, color1, color2 ) {

		const p1 = _makePetal( radius, scale, color1, color2 );
		const p2 = p1.clone();
		const p3 = p1.clone();
		const p4 = p1.clone();

		p2.rotateZ( Math.PI / 2 );
		p3.rotateZ( Math.PI );
		p4.rotateZ( Math.PI / 2 * 3 );

		p1.merge( p2 );
		p1.merge( p3 );
		p1.merge( p4 );

		p1.computeFaceNormals();

		return p1;

	}

}

Compass.prototype = Object.create( Group.prototype );

Compass.prototype.set = function () {

	const direction     = new Vector3();
	const yAxis         = new Vector3( 0, 1, 0 );
	const negativeZAxis = new Vector3( 0, 0, -1 );

	return function set ( vCamera ) {

		vCamera.getWorldDirection( direction );

		if ( direction.x === 0 && direction.y === 0 ) {

			// FIXME get camera rotation....
			return;

		}

		// we are only interested in angle to horizontal plane.
		direction.z = 0;

		var a = direction.angleTo( yAxis );

		if ( direction.x >= 0 ) a = 2 * Math.PI - a;

		if ( a === this.lastRotation ) return;

		var degrees = 360 - Math.round( _Math.radToDeg( a ) );

		const res = degrees.toString().padStart( 3, '0' ) + '\u00B0'; // unicode degree symbol

		this.label.replaceString( res );

		this.rotaryGroup.rotateOnAxis( negativeZAxis, a - this.lastRotation );

		this.lastRotation = a;

	};

} ();

export { Compass };

// EOF<commit_msg>remove  FIXME<commit_after>
import { HudObject } from './HudObject';
import { Cfg } from '../core/lib';
import { MutableGlyphString } from '../core/GlyphString';
import { Materials } from '../materials/Materials';

import {
	Vector3, Math as _Math, Face3,
	Geometry, RingGeometry, CylinderBufferGeometry,
	MeshBasicMaterial, MeshPhongMaterial, MeshLambertMaterial,
	FrontSide, VertexColors,
	Mesh, Group
} from '../Three';

function Compass () {

	const stdWidth  = HudObject.stdWidth;
	const stdMargin = HudObject.stdMargin;

	Group.call( this );

	this.name = 'CV.Compass';

	const cg1 = new CylinderBufferGeometry( stdWidth * 0.90, stdWidth, 3, 32, 1, true );
	cg1.rotateX( Math.PI / 2 );

	const c1 = new Mesh( cg1, new MeshPhongMaterial( { color: Cfg.themeValue( 'hud.bezel' ), specular: 0x888888 } ) );

	const cg2 = new RingGeometry( stdWidth * 0.9, stdWidth, 4, 1, -Math.PI / 32 + Math.PI / 2, Math.PI / 16 );
	cg2.translate( 0, 0, 5 );

	const c2 = new Mesh( cg2, new MeshBasicMaterial( { color: Cfg.themeValue( 'hud.compass.top1' ) } ) );

	const r1 = _makeRose( stdWidth * 0.9, 0.141, Cfg.themeColor( 'hud.compass.bottom1' ), Cfg.themeColor( 'hud.compass.bottom2' ) );
	const r2 = _makeRose( stdWidth * 0.9, 0.141, Cfg.themeColor( 'hud.compass.top1' ), Cfg.themeColor( 'hud.compass.top2' ) );

	r1.rotateZ( Math.PI / 4 );
	r1.merge( r2 );

	const rMesh = new Mesh( r1, new MeshLambertMaterial( { vertexColors: VertexColors, side: FrontSide, flatShading: true } ) );

	const rotaryGroup = new Group();

	rotaryGroup.addStatic( c1 );
	rotaryGroup.addStatic( c2 );
	rotaryGroup.addStatic( rMesh );

	this.add( rotaryGroup );
	this.rotaryGroup = rotaryGroup;

	const offset = stdWidth + stdMargin;

	this.translateX( -offset );
	this.translateY(  offset );

	this.lastRotation = 0;

	const material = Materials.getGlyphMaterial( HudObject.atlasSpec, 0 );
	const label = new MutableGlyphString( '000\u00B0', material );

	label.translateX( - label.getWidth() / 2 );
	label.translateY( stdWidth + 5 );

	this.addStatic( label );

	this.label = label;

	return this;

	// make 'petal' for compass rose
	function _makePetal ( radius, scale, color1, color2 ) {

		const innerR = radius * scale;
		const g = new Geometry();

		g.vertices.push( new Vector3( 0, radius, 0 ) );
		g.vertices.push( new Vector3( innerR ,innerR, 0 ) );
		g.vertices.push( new Vector3( 0, 0, 14 * scale ) );
		g.vertices.push( new Vector3( -innerR, innerR, 0 ) );

		var f1 = new Face3( 0, 2, 1, new Vector3( 0, 0, 1 ), color1, 0 );
		var f2 = new Face3( 0, 3, 2, new Vector3( 0, 0, 1 ), color2, 0 );

		g.faces.push( f1 );
		g.faces.push( f2 );

		return g;

	}

	function _makeRose ( radius, scale, color1, color2 ) {

		const p1 = _makePetal( radius, scale, color1, color2 );
		const p2 = p1.clone();
		const p3 = p1.clone();
		const p4 = p1.clone();

		p2.rotateZ( Math.PI / 2 );
		p3.rotateZ( Math.PI );
		p4.rotateZ( Math.PI / 2 * 3 );

		p1.merge( p2 );
		p1.merge( p3 );
		p1.merge( p4 );

		p1.computeFaceNormals();

		return p1;

	}

}

Compass.prototype = Object.create( Group.prototype );

Compass.prototype.set = function () {

	const direction     = new Vector3();
	const yAxis         = new Vector3( 0, 1, 0 );
	const negativeZAxis = new Vector3( 0, 0, -1 );

	return function set ( vCamera ) {

		vCamera.getWorldDirection( direction );

		// floating point limitations result in this being seldom called
		if ( direction.x === 0 && direction.y === 0 ) return;

		// we are only interested in angle to horizontal plane.
		direction.z = 0;

		var a = direction.angleTo( yAxis );

		if ( direction.x >= 0 ) a = 2 * Math.PI - a;

		if ( a === this.lastRotation ) return;

		var degrees = 360 - Math.round( _Math.radToDeg( a ) );

		const res = degrees.toString().padStart( 3, '0' ) + '\u00B0'; // unicode degree symbol

		this.label.replaceString( res );

		this.rotaryGroup.rotateOnAxis( negativeZAxis, a - this.lastRotation );

		this.lastRotation = a;

	};

} ();

export { Compass };

// EOF<|endoftext|>


<commit_before>(function() {

  function stateSetter(widget, state) {
    return function() {
      widget.view.setState(state);
    };
  }

  RemoteStorage.Widget = function(remoteStorage) {
    this.rs = remoteStorage;
    this.view = new View;

    this.rs.on('ready', stateSetter(this, 'connected'));
    this.rs.on('disconnected', stateSetter(this, 'initial'));
    //this.rs.on('connecting', stateSetter(this, 'connecting'))
    this.rs.on('authing', stateSetter(this, 'authing'))
    this.rs.on('sync-busy', stateSetter(this, 'busy') );
    this.rs.on('sync-done', stateSetter(this, 'connected'))
    this.view.on( 'connect', function(a){
      this.rs.connect(a);
    }.bind(this) )
    this.view.on( 'disconnect', function(){
      this.rs.disconnect();
    }.bind(this) )
    this.view.on( 'sync', function(){
      this.rs.sync()
    }.bind(this) )

  };

  RemoteStorage.Widget.prototype = {
    display: function() {
      this.view.display.apply(this.view, arguments);
      return this;
    }
  };

  RemoteStorage.prototype.displayWidget = function() {
    if(typeof(this.widget) === 'undefined')
      (this.widget = new RemoteStorage.Widget(this)).display();
    else
      this.widget.display();
  };

  RemoteStorage.Widget._rs_init = function(remoteStorage){
    window.addEventListener('load', function() {
      remoteStorage.displayWidget();
    });
  }

    function cEl(t){
    return document.createElement(t);
  }
  function gCl(parent, className){
    return parent.getElementsByClassName(className)[0];
  }
  function gTl(parent, className){
    return parent.getElementsByTagName(className)[0];
  }

  function toggle_bubble(widget){
    var el = gCl(widget,'bubble');
    if(el.className.search('hidden') < 0){
      addClass(el, 'hidden');
    }else{
      removeClass(el, 'hidden');
    }
  }
  function show(el, display){
    if(typeof(display) === 'undefined'){
      display = 'block';
    }
    el.style.display = display
    return el;
  }
  function hide(el){
    show(el,'none');
    return el;
  }

  // MAYBE do those two in a el.className.split(' ') way more safe
  function removeClass(el, className){
    el.className = el.className.split(' ').filter( function(val){
      return val != className
    }).join(' ');
  }
  function addClass(el, className){
    el.className += ' '+className;
  }

  function View(){
    if(typeof(document) === 'undefined') {
      throw "Widget not supported";
    }
    RemoteStorage.eventHandling(this,
                                'connect',
                                'disconnect',
                                'sync',
                                'reconnect',
                                'display')

    this.display = function() {

        if( ! (typeof(this.widget) === 'undefined') )
          return this.widget;

        var element = cEl('div')
        var style = cEl('style');
        style.innerHTML = RemoteStorage.Assets.widgetCss;

        element.id="remotestorage-widget"

        element.innerHTML = RemoteStorage.Assets.widget;

        element.appendChild(style)
        document.body.appendChild(element);

      var el;
        //sync button
      el = gCl(element, 'sync')
      gTl(el, 'img').src = RemoteStorage.Assets.syncIcon
      el.addEventListener('click', this.events.sync.bind(this))

        //disconnect button
      el = gCl(element, 'disconnect')
      gTl(el, 'img').src = RemoteStorage.Assets.disconnectIcon
      el.addEventListener('click', this.events.disconnect.bind(this))

        //connect button
      var cb = gCl(element,'connect')
      gTl(cb, 'img').src=RemoteStorage.Assets.connectIcon;
      cb.addEventListener('click', this.events.connect.bind(this));

        // input
      el = gTl(element, 'form').userAddress;
      el.addEventListener('keyup', function(event){

        if(event.target.value) cb.removeAttribute('disabled');
        else cb.setAttribute('disabled','disabled');

      })

        //the cube
      el = gCl(element, 'cube');
      el.src = RemoteStorage.Assets.remoteStorageIcon
      el.addEventListener('click',
                          function(){
                            toggle_bubble(this.div)
                          }.bind(this)
                         )

      this.div = element;

      this.states.initial.call(this);
      this._emit('display');
      return this.div;
    }

    this.setState = function(state){
      var s;
	    if(typeof(this.states[state]) === 'undefined'){
  	    throw "Bad State assigned to view"
      }
      this.states[state].call(this);

    }

    this.setUserAdress = function(addr){
      widget.userAdress = addr;
    }
  };

  View.prototype = {
     // States:
     //  initial      - not connected
     //  authing      - in auth flow
     //  connected    - connected to remote storage, not syncing at the moment
     //  busy         - connected, syncing at the moment
     //  offline      - connected, but no network connectivity
     //  error        - connected, but sync error happened
     //  unauthorized - connected, but request returned 401
    currentState : 'initial',
    states :  {
      initial : function(){
        this.div.className = "remotestorage-state-initial"
        gCl(this.div,'status-text').innerHTML = "Connect remotestorage"
      },
      authing : function(){
        this.div.className = "remotestorage-state-authing"
        gCl(this.div,'status-text').innerHTML = "Connecting"
        addClass(gCl(this.div, 'cube'), 'remotestorage-loading'); //TODO needs to be undone when is that neccesary
      },
      connected : function(){
        this.div.className = "remotestorage-state-connected"
        gCl(this.div,'userAddress').innerHTML = 'user@host'; //TODO where to find the user name
        var cube = gCl(this.div,'cube');
        cube.src = RemoteStorage.Assets.remoteStorageIcon;
        removeClass(cube,'remotestorage-loading');
      },
      busy : function(){
        this.div.className = "remotestorage-state-busy";
        addClass(gCl(this.div, 'cube'), 'remotestorage-loading'); //TODO needs to be undone when is that neccesary
      },
      offline : function(){
        this.div.className = "remotestorage-state-offline";
        gCl(this.div,'cube').src = RemoteStorage.Assets.remoteStorageIconOffline;
      },
      error : function(){
        this.div.className = "remotestorage-state-error";
        gCl(this.div,'cube').src = RemoteStorage.Assets.remoteStorageIconError
      },
      unauthorized : function(){
        this.div.className = "remotestorage-state-unauthorized";
      }
    },
    events : {
      connect : function(event) {
        event.preventDefault();
        this._emit('connect', gTl(this.div, 'form').userAddress.value);
      },
      sync : function() {
        event.preventDefault();
        this._emit('sync');
      },
      disconnect : function() {
        event.preventDefault();
        this._emit('disconnect');
      },
      recconnect : function(){},
      display : function(){
      }
    }
  }

  // // the view.
  // var view = defaultView;
  // // options passed to displayWidget
  // var widgetOptions = {};
  // // passed to display() to avoid circular deps
  // var remoteStorage;

  // var reconnectInterval = 1000;

  // var offlineTimer = null;

  // var viewState;

  // function calcReconnectInterval() {
  //   var i = reconnectInterval;
  //   if(reconnectInterval < 10000) {
  //     reconnectInterval *= 2;
  //   }
  //   return i;
  // }

  // var stateActions = {
  //   offline: function() {
  //     if(! offlineTimer) {
  //       offlineTimer = setTimeout(function() {
  //         offlineTimer = null;
  //         sync.fullSync().
  //           then(function() {
  //             schedule.enable();
  //           });
  //       }, calcReconnectInterval());
  //     }
  //   },
  //   connected: function() {
  //     if(offlineTimer) {
  //       clearTimeout(offlineTimer);
  //       offlineTimer = null;
  //     }
  //   }
  // };

  // function setState(state) {
  //   if(state === viewState) {
  //     return;
  //   }
  //   viewState = state;
  //   var action = stateActions[state];
  //   if(action) {
  //     action.apply(null, arguments);
  //   }
  //   view.setState.apply(view, arguments);
  //   events.emit('state', state);
  // }

  // function requestToken(authEndpoint) {
  //   logger.info('requestToken', authEndpoint);
  //   var redirectUri = (widgetOptions.redirectUri || view.getLocation());
  //   var state;
  //   var md = redirectUri.match(/^(.+)#(.*)$/);
  //   if(md) {
  //     redirectUri = md[1];
  //     state = md[2];
  //   }
  //   var clientId = util.hostNameFromUri(redirectUri);
  //   authEndpoint += authEndpoint.indexOf('?') > 0 ? '&' : '?';
  //   var params = [
  //     ['redirect_uri', redirectUri],
  //     ['client_id', clientId],
  //     ['scope', remoteStorage.access.scopeParameter],
  //     ['response_type', 'token']
  //   ];
  //   if(typeof(state) === 'string' && state.length > 0) {
  //     params.push(['state', state]);
  //   }
  //   authEndpoint += params.map(function(kv) {
  //     return kv[0] + '=' + encodeURIComponent(kv[1]);
  //   }).join('&');
  //   console.log('redirecting to', authEndpoint);
  //   return view.redirectTo(authEndpoint);
  // }

  // function connectStorage(userAddress) {
  //   settings.set('userAddress', userAddress);
  //   setState('authing');
  //   return webfinger.getStorageInfo(userAddress).
  //     then(remoteStorage.setStorageInfo, function(error) {
  //       if(error === 'timeout') {
  //         adjustTimeout();
  //       }
  //       setState((typeof(error) === 'string') ? 'typing' : 'error', error);
  //     }).
  //     then(function(storageInfo) {
  //       if(storageInfo) {
  //         requestToken(storageInfo.properties['auth-endpoint']);
  //         schedule.enable();
  //       }
  //     }, util.curry(setState, 'error'));
  // }

  // function reconnectStorage() {
  //   connectStorage(settings.get('userAddress'));
  // }

  // function disconnectStorage() {
  //   schedule.disable();
  //   remoteStorage.flushLocal();
  //   events.emit('state', 'disconnected');
  //   events.emit('disconnect');
  // }

  // // destructively parse query string from URI fragment
  // function parseParams() {
  //   var md = view.getLocation().match(/^(.*?)#(.*)$/);
  //   var result = {};
  //   if(md) {
  //     var hash = md[2];
  //     hash.split('&').forEach(function(param) {
  //       var kv = param.split('=');
  //       if(kv[1]) {
  //         result[kv[0]] = decodeURIComponent(kv[1]);
  //       }
  //     });
  //     if(Object.keys(result).length > 0) {
  //       view.setLocation(md[1] + '#');
  //     }
  //   }
  //   return result;
  // }

  // function processParams() {
  //   var params = parseParams();

  //   // Query parameter: access_token
  //   if(params.access_token) {
  //     wireClient.setBearerToken(params.access_token);
  //   }
  //   // Query parameter: remotestorage
  //   if(params.remotestorage) {
  //     view.setUserAddress(params.remotestorage);
  //     setTimeout(function() {
  //       if(wireClient.getState() !== 'connected') {
  //         connectStorage(params.remotestorage);
  //       }
  //     }, 0);
  //   } else {
  //     var userAddress = settings.get('userAddress');
  //     if(userAddress) {
  //       view.setUserAddress(userAddress);
  //     }
  //   }
  //   // Query parameter: state
  //   if(params.state) {
  //     view.setLocation(view.getLocation().split('#')[0] + '#' + params.state);
  //   }
  // }

  // function handleSyncError(error) {
  //   if(error.message === 'unauthorized') {
  //     setState('unauthorized');
  //   } else if(error.message === 'network error') {
  //     setState('offline', error);
  //   } else {
  //     setState('error', error);
  //   }
  // }

  // function adjustTimeout() {
  //   var t = getputdelete.getTimeout();
  //   if(t < maxTimeout) {
  //     t *= timeoutAdjustmentFactor;
  //     webfinger.setTimeout(t);
  //     getputdelete.setTimeout(t);
  //   }
  // }

  // function handleSyncTimeout() {
  //   adjustTimeout();
  //   schedule.disable();
  //   setState('offline');
  // }

  // function initialSync() {
  //   if(settings.get('initialSyncDone')) {
  //     schedule.enable();
  //     store.fireInitialEvents().then(util.curry(events.emit, 'ready'));
  //   } else {
  //     setState('busy', true);
  //     sync.fullSync().then(function() {
  //       schedule.enable();
  //       settings.set('initialSyncDone', true);
  //       setState('connected');
  //       events.emit('ready');
  //     }, handleSyncError);
  //   }
  // }

  // function display(_remoteStorage, element, options) {
  //   remoteStorage = _remoteStorage;
  //   widgetOptions = options;
  //   if(! options) {
  //     options = {};
  //   }

  //   options.getLastSyncAt = function() {
  //     return (sync.lastSyncAt || new Date()).getTime();
  //   };

  //   schedule.watch('/', 30000);

  //   view.display(element, options);

  //   view.on('sync', sync.forceSync);
  //   view.on('connect', connectStorage);
  //   view.on('disconnect', disconnectStorage);
  //   view.on('reconnect', reconnectStorage);

  //   wireClient.on('busy', util.curry(setState, 'busy'));
  //   wireClient.on('unbusy', util.curry(setState, 'connected'));
  //   wireClient.on('connected', function() {
  //     setState('connected');
  //     initialSync();
  //   });
  //   wireClient.on('disconnected', util.curry(setState, 'initial'));

  //   BaseClient.on('error', util.curry(setState, 'error'));
  //   sync.on('error', handleSyncError);
  //   sync.on('timeout', handleSyncTimeout);

  //   processParams();

  //   wireClient.calcState();
  // }

  // RemoteStorage.widget = util.extend({
  //   display : display,

  //   clearSettings: settings.clear,

  //   setView: function(_view) {
  //     view = _view;
  //   }
  // }, events);

})();
<commit_msg>missing params in widget event handlers<commit_after>(function() {

  function stateSetter(widget, state) {
    return function() {
      widget.view.setState(state);
    };
  }

  RemoteStorage.Widget = function(remoteStorage) {
    this.rs = remoteStorage;
    this.view = new View;

    this.rs.on('ready', stateSetter(this, 'connected'));
    this.rs.on('disconnected', stateSetter(this, 'initial'));
    //this.rs.on('connecting', stateSetter(this, 'connecting'))
    this.rs.on('authing', stateSetter(this, 'authing'))
    this.rs.on('sync-busy', stateSetter(this, 'busy') );
    this.rs.on('sync-done', stateSetter(this, 'connected'))
    this.view.on( 'connect', function(a){
      this.rs.connect(a);
    }.bind(this) )
    this.view.on( 'disconnect', function(){
      this.rs.disconnect();
    }.bind(this) )
    this.view.on( 'sync', function(){
      this.rs.sync()
    }.bind(this) )

  };

  RemoteStorage.Widget.prototype = {
    display: function() {
      this.view.display.apply(this.view, arguments);
      return this;
    }
  };

  RemoteStorage.prototype.displayWidget = function() {
    if(typeof(this.widget) === 'undefined')
      (this.widget = new RemoteStorage.Widget(this)).display();
    else
      this.widget.display();
  };

  RemoteStorage.Widget._rs_init = function(remoteStorage){
    window.addEventListener('load', function() {
      remoteStorage.displayWidget();
    });
  }

    function cEl(t){
    return document.createElement(t);
  }
  function gCl(parent, className){
    return parent.getElementsByClassName(className)[0];
  }
  function gTl(parent, className){
    return parent.getElementsByTagName(className)[0];
  }

  function toggle_bubble(widget){
    var el = gCl(widget,'bubble');
    if(el.className.search('hidden') < 0){
      addClass(el, 'hidden');
    }else{
      removeClass(el, 'hidden');
    }
  }
  function show(el, display){
    if(typeof(display) === 'undefined'){
      display = 'block';
    }
    el.style.display = display
    return el;
  }
  function hide(el){
    show(el,'none');
    return el;
  }

  // MAYBE do those two in a el.className.split(' ') way more safe
  function removeClass(el, className){
    el.className = el.className.split(' ').filter( function(val){
      return val != className
    }).join(' ');
  }
  function addClass(el, className){
    el.className += ' '+className;
  }

  function View(){
    if(typeof(document) === 'undefined') {
      throw "Widget not supported";
    }
    RemoteStorage.eventHandling(this,
                                'connect',
                                'disconnect',
                                'sync',
                                'reconnect',
                                'display')

    this.display = function() {

        if( ! (typeof(this.widget) === 'undefined') )
          return this.widget;

        var element = cEl('div')
        var style = cEl('style');
        style.innerHTML = RemoteStorage.Assets.widgetCss;

        element.id="remotestorage-widget"

        element.innerHTML = RemoteStorage.Assets.widget;

        element.appendChild(style)
        document.body.appendChild(element);

      var el;
        //sync button
      el = gCl(element, 'sync')
      gTl(el, 'img').src = RemoteStorage.Assets.syncIcon
      el.addEventListener('click', this.events.sync.bind(this))

        //disconnect button
      el = gCl(element, 'disconnect')
      gTl(el, 'img').src = RemoteStorage.Assets.disconnectIcon
      el.addEventListener('click', this.events.disconnect.bind(this))

        //connect button
      var cb = gCl(element,'connect')
      gTl(cb, 'img').src=RemoteStorage.Assets.connectIcon;
      cb.addEventListener('click', this.events.connect.bind(this));

        // input
      el = gTl(element, 'form').userAddress;
      el.addEventListener('keyup', function(event){

        if(event.target.value) cb.removeAttribute('disabled');
        else cb.setAttribute('disabled','disabled');

      })

        //the cube
      el = gCl(element, 'cube');
      el.src = RemoteStorage.Assets.remoteStorageIcon
      el.addEventListener('click',
                          function(){
                            toggle_bubble(this.div)
                          }.bind(this)
                         )

      this.div = element;

      this.states.initial.call(this);
      this._emit('display');
      return this.div;
    }

    this.setState = function(state){
      var s;
	    if(typeof(this.states[state]) === 'undefined'){
  	    throw "Bad State assigned to view"
      }
      this.states[state].call(this);

    }

    this.setUserAddress = function(addr){
      widget.userAddress = addr;
    }
  };

  View.prototype = {
     // States:
     //  initial      - not connected
     //  authing      - in auth flow
     //  connected    - connected to remote storage, not syncing at the moment
     //  busy         - connected, syncing at the moment
     //  offline      - connected, but no network connectivity
     //  error        - connected, but sync error happened
     //  unauthorized - connected, but request returned 401
    currentState : 'initial',
    states :  {
      initial : function(){
        this.div.className = "remotestorage-state-initial"
        gCl(this.div,'status-text').innerHTML = "Connect remotestorage"
      },
      authing : function(){
        this.div.className = "remotestorage-state-authing"
        gCl(this.div,'status-text').innerHTML = "Connecting"
        addClass(gCl(this.div, 'cube'), 'remotestorage-loading'); //TODO needs to be undone when is that neccesary
      },
      connected : function(){
        this.div.className = "remotestorage-state-connected"
        gCl(this.div,'userAddress').innerHTML = 'user@host'; //TODO where to find the user name
        var cube = gCl(this.div,'cube');
        cube.src = RemoteStorage.Assets.remoteStorageIcon;
        removeClass(cube,'remotestorage-loading');
      },
      busy : function(){
        this.div.className = "remotestorage-state-busy";
        addClass(gCl(this.div, 'cube'), 'remotestorage-loading'); //TODO needs to be undone when is that neccesary
      },
      offline : function(){
        this.div.className = "remotestorage-state-offline";
        gCl(this.div,'cube').src = RemoteStorage.Assets.remoteStorageIconOffline;
      },
      error : function(){
        this.div.className = "remotestorage-state-error";
        gCl(this.div,'cube').src = RemoteStorage.Assets.remoteStorageIconError
      },
      unauthorized : function(){
        this.div.className = "remotestorage-state-unauthorized";
      }
    },
    events : {
      connect : function(event) {
        event.preventDefault();
        this._emit('connect', gTl(this.div, 'form').userAddress.value);
      },
      sync : function(event) {
        event.preventDefault();
        this._emit('sync');
      },
      disconnect : function(event) {
        event.preventDefault();
        this._emit('disconnect');
      },
      recconnect : function(){},
      display : function(){
      }
    }
  }

  // // the view.
  // var view = defaultView;
  // // options passed to displayWidget
  // var widgetOptions = {};
  // // passed to display() to avoid circular deps
  // var remoteStorage;

  // var reconnectInterval = 1000;

  // var offlineTimer = null;

  // var viewState;

  // function calcReconnectInterval() {
  //   var i = reconnectInterval;
  //   if(reconnectInterval < 10000) {
  //     reconnectInterval *= 2;
  //   }
  //   return i;
  // }

  // var stateActions = {
  //   offline: function() {
  //     if(! offlineTimer) {
  //       offlineTimer = setTimeout(function() {
  //         offlineTimer = null;
  //         sync.fullSync().
  //           then(function() {
  //             schedule.enable();
  //           });
  //       }, calcReconnectInterval());
  //     }
  //   },
  //   connected: function() {
  //     if(offlineTimer) {
  //       clearTimeout(offlineTimer);
  //       offlineTimer = null;
  //     }
  //   }
  // };

  // function setState(state) {
  //   if(state === viewState) {
  //     return;
  //   }
  //   viewState = state;
  //   var action = stateActions[state];
  //   if(action) {
  //     action.apply(null, arguments);
  //   }
  //   view.setState.apply(view, arguments);
  //   events.emit('state', state);
  // }

  // function requestToken(authEndpoint) {
  //   logger.info('requestToken', authEndpoint);
  //   var redirectUri = (widgetOptions.redirectUri || view.getLocation());
  //   var state;
  //   var md = redirectUri.match(/^(.+)#(.*)$/);
  //   if(md) {
  //     redirectUri = md[1];
  //     state = md[2];
  //   }
  //   var clientId = util.hostNameFromUri(redirectUri);
  //   authEndpoint += authEndpoint.indexOf('?') > 0 ? '&' : '?';
  //   var params = [
  //     ['redirect_uri', redirectUri],
  //     ['client_id', clientId],
  //     ['scope', remoteStorage.access.scopeParameter],
  //     ['response_type', 'token']
  //   ];
  //   if(typeof(state) === 'string' && state.length > 0) {
  //     params.push(['state', state]);
  //   }
  //   authEndpoint += params.map(function(kv) {
  //     return kv[0] + '=' + encodeURIComponent(kv[1]);
  //   }).join('&');
  //   console.log('redirecting to', authEndpoint);
  //   return view.redirectTo(authEndpoint);
  // }

  // function connectStorage(userAddress) {
  //   settings.set('userAddress', userAddress);
  //   setState('authing');
  //   return webfinger.getStorageInfo(userAddress).
  //     then(remoteStorage.setStorageInfo, function(error) {
  //       if(error === 'timeout') {
  //         adjustTimeout();
  //       }
  //       setState((typeof(error) === 'string') ? 'typing' : 'error', error);
  //     }).
  //     then(function(storageInfo) {
  //       if(storageInfo) {
  //         requestToken(storageInfo.properties['auth-endpoint']);
  //         schedule.enable();
  //       }
  //     }, util.curry(setState, 'error'));
  // }

  // function reconnectStorage() {
  //   connectStorage(settings.get('userAddress'));
  // }

  // function disconnectStorage() {
  //   schedule.disable();
  //   remoteStorage.flushLocal();
  //   events.emit('state', 'disconnected');
  //   events.emit('disconnect');
  // }

  // // destructively parse query string from URI fragment
  // function parseParams() {
  //   var md = view.getLocation().match(/^(.*?)#(.*)$/);
  //   var result = {};
  //   if(md) {
  //     var hash = md[2];
  //     hash.split('&').forEach(function(param) {
  //       var kv = param.split('=');
  //       if(kv[1]) {
  //         result[kv[0]] = decodeURIComponent(kv[1]);
  //       }
  //     });
  //     if(Object.keys(result).length > 0) {
  //       view.setLocation(md[1] + '#');
  //     }
  //   }
  //   return result;
  // }

  // function processParams() {
  //   var params = parseParams();

  //   // Query parameter: access_token
  //   if(params.access_token) {
  //     wireClient.setBearerToken(params.access_token);
  //   }
  //   // Query parameter: remotestorage
  //   if(params.remotestorage) {
  //     view.setUserAddress(params.remotestorage);
  //     setTimeout(function() {
  //       if(wireClient.getState() !== 'connected') {
  //         connectStorage(params.remotestorage);
  //       }
  //     }, 0);
  //   } else {
  //     var userAddress = settings.get('userAddress');
  //     if(userAddress) {
  //       view.setUserAddress(userAddress);
  //     }
  //   }
  //   // Query parameter: state
  //   if(params.state) {
  //     view.setLocation(view.getLocation().split('#')[0] + '#' + params.state);
  //   }
  // }

  // function handleSyncError(error) {
  //   if(error.message === 'unauthorized') {
  //     setState('unauthorized');
  //   } else if(error.message === 'network error') {
  //     setState('offline', error);
  //   } else {
  //     setState('error', error);
  //   }
  // }

  // function adjustTimeout() {
  //   var t = getputdelete.getTimeout();
  //   if(t < maxTimeout) {
  //     t *= timeoutAdjustmentFactor;
  //     webfinger.setTimeout(t);
  //     getputdelete.setTimeout(t);
  //   }
  // }

  // function handleSyncTimeout() {
  //   adjustTimeout();
  //   schedule.disable();
  //   setState('offline');
  // }

  // function initialSync() {
  //   if(settings.get('initialSyncDone')) {
  //     schedule.enable();
  //     store.fireInitialEvents().then(util.curry(events.emit, 'ready'));
  //   } else {
  //     setState('busy', true);
  //     sync.fullSync().then(function() {
  //       schedule.enable();
  //       settings.set('initialSyncDone', true);
  //       setState('connected');
  //       events.emit('ready');
  //     }, handleSyncError);
  //   }
  // }

  // function display(_remoteStorage, element, options) {
  //   remoteStorage = _remoteStorage;
  //   widgetOptions = options;
  //   if(! options) {
  //     options = {};
  //   }

  //   options.getLastSyncAt = function() {
  //     return (sync.lastSyncAt || new Date()).getTime();
  //   };

  //   schedule.watch('/', 30000);

  //   view.display(element, options);

  //   view.on('sync', sync.forceSync);
  //   view.on('connect', connectStorage);
  //   view.on('disconnect', disconnectStorage);
  //   view.on('reconnect', reconnectStorage);

  //   wireClient.on('busy', util.curry(setState, 'busy'));
  //   wireClient.on('unbusy', util.curry(setState, 'connected'));
  //   wireClient.on('connected', function() {
  //     setState('connected');
  //     initialSync();
  //   });
  //   wireClient.on('disconnected', util.curry(setState, 'initial'));

  //   BaseClient.on('error', util.curry(setState, 'error'));
  //   sync.on('error', handleSyncError);
  //   sync.on('timeout', handleSyncTimeout);

  //   processParams();

  //   wireClient.calcState();
  // }

  // RemoteStorage.widget = util.extend({
  //   display : display,

  //   clearSettings: settings.clear,

  //   setView: function(_view) {
  //     view = _view;
  //   }
  // }, events);

})();
<|endoftext|>


<commit_before>'use strict';

angular.module('jkuri.touchspin', [])
  .provider('ngTouchSpin', ngTouchSpinProvider)
  .directive('ngTouchSpin', ngTouchSpinDirective);

ngTouchSpinDirective.$inject = ['$timeout', '$interval', '$document', 'ngTouchSpin'];
function ngTouchSpinDirective($timeout, $interval, $document, ngTouchSpin) {

	var key_codes = {
		left  : 37,
		right : 39
	};

	var setScopeValues = function (scope, attrs) {
		scope.min = parseFloat(Number(attrs.min)) || 0;
		scope.max = parseFloat(Number(attrs.max)) || 100;
		scope.step = attrs.step || 1;
		scope.prefix = attrs.prefix || undefined;
		scope.postfix = attrs.postfix || undefined;
		scope.decimals = attrs.decimals || 0;
		scope.stepInterval = attrs.stepInterval || 100;
		scope.stepIntervalDelay = attrs.stepIntervalDelay || 500;
		scope.initval = attrs.initval || '';
		scope.val = attrs.value || scope.initval;
	};

	return {
		restrict: 'EA',
		require: '?ngModel',
		scope: true,
		replace: true,
		link: function (scope, element, attrs, ngModel) {
			setScopeValues(scope, attrs);

			var $body = $document.find('body');
			var timeout, timer, helper = true, oldval = scope.val, clickStart;

			ngModel.$setViewValue(scope.val);
			scope.focused = false;

			scope.decrement = function () {
				oldval = scope.val;
				var value = parseFloat(parseFloat(Number(scope.val)) - parseFloat(scope.step)).toFixed(scope.decimals);

				if (value < scope.min) {
					value = parseFloat(scope.min).toFixed(scope.decimals);
					scope.val = value;
					ngModel.$setViewValue(value);
					return;
				}

				scope.val = value;
				ngModel.$setViewValue(value);
			};

			scope.increment = function () {
				oldval = scope.val;
				var value = parseFloat(parseFloat(Number(scope.val)) + parseFloat(scope.step)).toFixed(scope.decimals);

				if (value > scope.max) return;

				scope.val = value;
				ngModel.$setViewValue(value);
			};

			scope.startSpinUp = function () {
				scope.checkValue();
				scope.increment();

				clickStart = Date.now();
				scope.stopSpin();

				$timeout(function() {
					timer = $interval(function() {
						scope.increment();
					}, scope.stepInterval);
				}, scope.stepIntervalDelay);
			};

			scope.startSpinDown = function () {
				scope.checkValue();
				scope.decrement();

				clickStart = Date.now();

				var timeout = $timeout(function() {
					timer = $interval(function() {
						scope.decrement();
					}, scope.stepInterval);
				}, scope.stepIntervalDelay);
			};

			scope.stopSpin = function () {
				if (Date.now() - clickStart > scope.stepIntervalDelay) {
					$timeout.cancel(timeout);
					$interval.cancel(timer);
				} else {
					$timeout(function() {
						$timeout.cancel(timeout);
						$interval.cancel(timer);
					}, scope.stepIntervalDelay);
				}
			};

			scope.checkValue = function () {
				var val;

				if (scope.val !== '' && !scope.val.match(/^-?(?:\d+|\d*\.\d+)$/i)) {
					val = oldval !== '' ? parseFloat(oldval).toFixed(scope.decimals) : parseFloat(scope.min).toFixed(scope.decimals);
					scope.val = val;
					ngModel.$setViewValue(val);
				}

				scope.focused = false;
			};

      scope.updateValue = function  () {
        var newValue = scope.val;

        if (newValue !== '') {
          if (newValue.match(/^-?(?:\d+|\d*\.\d+)$/i)) {
            newValue = parseFloat(newValue);
            if (newValue < parseFloat(scope.min)) newValue = parseFloat(scope.min);
            if (newValue > parseFloat(scope.max)) newValue = parseFloat(scope.max);
            newValue = newValue.toFixed(scope.decimals);
          } else {
            newValue = oldval;
          }
          scope.val = newValue;
          ngModel.$setViewValue(newValue);
        }

        console.log({newValue: newValue, val: scope.val, modelValue: ngModel.$modelValue});
      };

      /**
       * I have no idea what is causing it but the modelValue gets overwritten to an empty string.
       * Change it back into the current value if it does!
       */
      function modelChanged() {
        console.log({modelValue: ngModel.$modelValue, val: scope.val});
        if (ngModel.$modelValue == '' && scope.val) ngModel.$setViewValue(scope.val);
      }
      ngModel.$viewChangeListeners.push(modelChanged);

			scope.focus = function () {
				scope.focused = true;
			};

			ngModel.$render = function () {
				scope.val = ngModel.$viewValue;
			};

      if (ngTouchSpin.arrowControlsEnabled()) {
        $body.bind('keydown', function(event) {
          if (!scope.focused) {
            return;
          }

          event.preventDefault();

          var which = event.which;

          if (which === key_codes.right) {
            scope.increment();
          } else if (which === key_codes.left) {
            scope.decrement();
          }

          scope.$apply();
        });
      }

		},
		template:
		'
' +
		'  ' +
		'    <button type="button" class="btn btn-default" ng-mousedown="startSpinDown()" ng-mouseup="stopSpin()"></button>' +
		'  ' +
		'  ' +
		'  <input type="text" ng-model="val" class="form-control" ng-change="updateValue()" ng-focus="focus()">' +
		'  ' +
		'  ' +
		'    <button type="button" class="btn btn-default" ng-mousedown="startSpinUp()" ng-mouseup="stopSpin()"></button>' +
		'  ' +
		'
'
	};
}

function ngTouchSpinProvider() {
  var arrowControlsEnabled = true;

  /**
   * @ngdoc method
   * @name ngTouchSpinProvider#arrowControlsEnabled
   * @description
   * @param {boolean=} flag enable or disable arrow controls
   * @returns {*} current value if used as getter or itself (chaining) if used as setter
   */
  this.arrowControlsEnabled = function(flag) {
    if (angular.isDefined(flag)) {
      arrowControlsEnabled = flag;
      return this;
    } else {
      return arrowControlsEnabled;
    }
  };

  this.$get = function () {
    return this;
  };
}
<commit_msg>Revert to original value when left blank<commit_after>'use strict';

angular.module('jkuri.touchspin', [])
  .provider('ngTouchSpin', ngTouchSpinProvider)
  .directive('ngTouchSpin', ngTouchSpinDirective);

ngTouchSpinDirective.$inject = ['$timeout', '$interval', '$document', 'ngTouchSpin'];
function ngTouchSpinDirective($timeout, $interval, $document, ngTouchSpin) {

	var key_codes = {
		left  : 37,
		right : 39
	};

	var setScopeValues = function (scope, attrs) {
		scope.min = parseFloat(Number(attrs.min)) || 0;
		scope.max = parseFloat(Number(attrs.max)) || 100;
		scope.step = attrs.step || 1;
		scope.prefix = attrs.prefix || undefined;
		scope.postfix = attrs.postfix || undefined;
		scope.decimals = attrs.decimals || 0;
		scope.stepInterval = attrs.stepInterval || 100;
		scope.stepIntervalDelay = attrs.stepIntervalDelay || 500;
		scope.initval = attrs.initval || '';
		scope.val = attrs.value || scope.initval;
	};

	return {
		restrict: 'EA',
		require: '?ngModel',
		scope: true,
		replace: true,
		link: function (scope, element, attrs, ngModel) {
			setScopeValues(scope, attrs);

			var $body = $document.find('body');
			var timeout, timer, helper = true, oldval = scope.val, clickStart;

			ngModel.$setViewValue(scope.val);
			scope.focused = false;

			scope.decrement = function () {
				oldval = scope.val;
				var value = parseFloat(parseFloat(Number(scope.val)) - parseFloat(scope.step)).toFixed(scope.decimals);

				if (value < scope.min) {
					value = parseFloat(scope.min).toFixed(scope.decimals);
					scope.val = value;
					ngModel.$setViewValue(value);
					return;
				}

				scope.val = value;
				ngModel.$setViewValue(value);
			};

			scope.increment = function () {
				oldval = scope.val;
				var value = parseFloat(parseFloat(Number(scope.val)) + parseFloat(scope.step)).toFixed(scope.decimals);

				if (value > scope.max) return;

				scope.val = value;
				ngModel.$setViewValue(value);
			};

			scope.startSpinUp = function () {
				scope.checkValue();
				scope.increment();

				clickStart = Date.now();
				scope.stopSpin();

				$timeout(function() {
					timer = $interval(function() {
						scope.increment();
					}, scope.stepInterval);
				}, scope.stepIntervalDelay);
			};

			scope.startSpinDown = function () {
				scope.checkValue();
				scope.decrement();

				clickStart = Date.now();

				var timeout = $timeout(function() {
					timer = $interval(function() {
						scope.decrement();
					}, scope.stepInterval);
				}, scope.stepIntervalDelay);
			};

			scope.stopSpin = function () {
				if (Date.now() - clickStart > scope.stepIntervalDelay) {
					$timeout.cancel(timeout);
					$interval.cancel(timer);
				} else {
					$timeout(function() {
						$timeout.cancel(timeout);
						$interval.cancel(timer);
					}, scope.stepIntervalDelay);
				}
			};

			scope.checkValue = function () {
				var val;

				if (scope.val !== '' && !scope.val.match(/^-?(?:\d+|\d*\.\d+)$/i)) {
					val = oldval !== '' ? parseFloat(oldval).toFixed(scope.decimals) : parseFloat(scope.min).toFixed(scope.decimals);
					scope.val = val;
					ngModel.$setViewValue(val);
				}

				scope.focused = false;
			};

      scope.updateValue = function () {
        var newValue = scope.val;

        if (newValue !== '') {
          if (newValue.match(/^-?(?:\d+|\d*\.\d+)$/i)) {
            newValue = parseFloat(newValue);
            if (newValue < scope.min) newValue = scope.min;
            if (newValue > scope.max) newValue = scope.max;
            newValue = newValue.toFixed(scope.decimals);
          } else {
            newValue = oldval;
          }
          scope.val = newValue;
          ngModel.$setViewValue(newValue);
        }
      };

      scope.handleEmptyValue = function () {
        var newValue = scope.val;

        if (newValue === '' && attrs.value) {
          newValue = attrs.value;
        }

        scope.val = newValue;
        ngModel.$setViewValue(newValue);
      };

      /**
       * I have no idea what is causing it but the modelValue gets overwritten to an empty string.
       * Change it back into the current value if it does!
       */
      function modelChanged() {
        if (ngModel.$modelValue == '' && scope.val) ngModel.$setViewValue(scope.val);
      }
      ngModel.$viewChangeListeners.push(modelChanged);

			scope.focus = function () {
				scope.focused = true;
			};

			ngModel.$render = function () {
				scope.val = ngModel.$viewValue;
			};

      if (ngTouchSpin.arrowControlsEnabled()) {
        $body.bind('keydown', function(event) {
          if (!scope.focused) {
            return;
          }

          event.preventDefault();

          var which = event.which;

          if (which === key_codes.right) {
            scope.increment();
          } else if (which === key_codes.left) {
            scope.decrement();
          }

          scope.$apply();
        });
      }

		},
		template:
		'
' +
		'  ' +
		'    <button type="button" class="btn btn-default" ng-mousedown="startSpinDown()" ng-mouseup="stopSpin()"></button>' +
		'  ' +
		'  ' +
		'  <input type="text" ng-model="val" class="form-control" ng-change="updateValue()" ng-blur="handleEmptyValue()" ng-focus="focus()">' +
		'  ' +
		'  ' +
		'    <button type="button" class="btn btn-default" ng-mousedown="startSpinUp()" ng-mouseup="stopSpin()"></button>' +
		'  ' +
		'
'
	};
}

function ngTouchSpinProvider() {
  var arrowControlsEnabled = true;

  /**
   * @ngdoc method
   * @name ngTouchSpinProvider#arrowControlsEnabled
   * @description
   * @param {boolean=} flag enable or disable arrow controls
   * @returns {*} current value if used as getter or itself (chaining) if used as setter
   */
  this.arrowControlsEnabled = function(flag) {
    if (angular.isDefined(flag)) {
      arrowControlsEnabled = flag;
      return this;
    } else {
      return arrowControlsEnabled;
    }
  };

  this.$get = function () {
    return this;
  };
}
<|endoftext|>


<commit_before>(function (FMS, Backbone, _, $) {
    _.extend( FMS, {
        PhotoView: FMS.FMSView.extend({
            template: 'photo',
            id: 'photo-page',
            prev: 'around',
            next: 'details',

            events: {
                'pagehide': 'destroy',
                'pagebeforeshow': 'beforeDisplay',
                'pageshow': 'afterDisplay',
                'vclick .ui-btn-left': 'onClickButtonPrev',
                'vclick .ui-btn-right': 'onClickButtonNext',
                'vclick #id_photo_button': 'takePhoto',
                'vclick #id_existing': 'addPhoto',
                'vclick #id_del_photo_button': 'deletePhoto'
            },

            beforeDisplay: function() {
                this.fixPageHeight();
                this.$('#id_del_photo_button').hide();
                if ( this.model.get('file') ) {
                    $('#id_photo_button').parents('.ui-btn').hide();
                    $('#id_existing').parents('.ui-btn').hide();
                    window.setTimeout( function() { $('#id_del_photo_button').show(); }, 250 );
                }
            },

            takePhoto: function(e) {
                e.preventDefault();
                $.mobile.loading('show');
                $('#photo').hide();
                var that = this;
                navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, { targetWidth: 2048, targetHeight: 2048, saveToPhotoAlbum: true, quality: 49, destinationType: Camera.DestinationType.FILE_URI, sourceType: navigator.camera.PictureSourceType.CAMERA, correctOrientation: true });
            },

            addPhoto: function(e) {
                e.preventDefault();
                $.mobile.loading('show');
                $('#photo').hide();
                var that = this;
                navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, { targetWidth: 2048, targetHeight: 2048, saveToPhotoAlbum: false, quality: 49, destinationType: Camera.DestinationType.FILE_URI, sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY, correctOrientation: true });
            },

            addPhotoSuccess: function(imgURI) {
                var move;
                // on iOS the photos go into a temp folder in the apps own filespace so we
                // can move them, and indeed have to as the tmp space is cleaned out by the OS
                // so draft reports might have their images removed. on android you access the
                // images where they are stored on the filesystem so if you move, and then delete
                // them, you are moving and deleting the only copy of them which is likely to be
                // surprising and unwelcome so we copy them instead.
                var fileName = CONFIG.NAMESPACE + '_' + this.model.cid + '_' + moment().unix() + '.jpg';
                if ( FMS.isAndroid ) {
                    move = FMS.files.copyURI( imgURI, fileName );
                } else {
                    move = FMS.files.moveURI( imgURI, fileName );
                }

                var that = this;
                move.done( function( file ) {
                    $('#nophoto_title').hide();
                    $('#photo_title').html(FMS.strings.photo_added).show();
                    $('#photo').attr('src', file.toURL()).addClass('small').removeClass('placeholder');
                    that.model.set('file', file.toURL());
                    FMS.saveCurrentDraft();

                    $('#photo-next-btn .ui-btn-text').text('Next');
                    $('#id_photo_button').parents('.ui-btn').hide();
                    $('#id_existing').parents('.ui-btn').hide();
                    $('#photo').show();
                    window.setTimeout(function() { $('#id_del_photo_button').show() }, 500);
                    window.setTimeout(function() { $.mobile.loading('hide') }, 100);
                });

                move.fail( function() { that.addPhotoFail(); } );
            },

            addPhotoFail: function() {
                $('#photo').show();
                $.mobile.loading('hide');
                if ( message != 'no image selected' &&
                    message != 'Selection cancelled.' &&
                    message != 'Camera cancelled.' ) {
                    this.displayAlert(FMS.strings.photo_failed);
                }
            },

            deletePhoto: function(e) {
                e.preventDefault();
                var that = this;
                var del = FMS.files.deleteURI( this.model.get('file') );

                del.done( function() {
                    $('#photo_title').hide();
                    $('#nophoto_title').show();
                    $('#id_del_photo_button').hide();
                    that.model.set('file', '');
                    FMS.saveCurrentDraft(true);
                    $('#photo').attr('src', 'images/placeholder-photo.png').addClass('placeholder').removeClass('small');

                    $('#photo-next-btn .ui-btn-text').text('Skip');
                    $('#id_photo_button').parents('.ui-btn').show();
                    $('#id_existing').parents('.ui-btn').show();
                });

            }
        })
    });
})(FMS, Backbone, _, $);
<commit_msg>split out photo option setting to a method<commit_after>(function (FMS, Backbone, _, $) {
    _.extend( FMS, {
        PhotoView: FMS.FMSView.extend({
            template: 'photo',
            id: 'photo-page',
            prev: 'around',
            next: 'details',

            events: {
                'pagehide': 'destroy',
                'pagebeforeshow': 'beforeDisplay',
                'pageshow': 'afterDisplay',
                'vclick .ui-btn-left': 'onClickButtonPrev',
                'vclick .ui-btn-right': 'onClickButtonNext',
                'vclick #id_photo_button': 'takePhoto',
                'vclick #id_existing': 'addPhoto',
                'vclick #id_del_photo_button': 'deletePhoto'
            },

            beforeDisplay: function() {
                this.fixPageHeight();
                this.$('#id_del_photo_button').hide();
                if ( this.model.get('file') ) {
                    $('#id_photo_button').parents('.ui-btn').hide();
                    $('#id_existing').parents('.ui-btn').hide();
                    window.setTimeout( function() { $('#id_del_photo_button').show(); }, 250 );
                }
            },

            getOptions: function(isFromAlbum) {
                var options = {
                    destinationType: Camera.DestinationType.FILE_URI,
                    sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY
                };

                if ( ! isFromAlbum ) {
                    options.saveToPhotoAlbum = true;
                    options.sourceType = navigator.camera.PictureSourceType.CAMERA;
                }

                // this helps with out of memory errors on iPhones but not on Android it seems
                if ( ! FMS.isAndroid ) {
                    options.quality = 49;
                    options.correctOrientation = true;
                }

                return options;
            },

            takePhoto: function(e) {
                e.preventDefault();
                $.mobile.loading('show');
                $('#photo').hide();
                var that = this;

                var options = this.getOptions();

                navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, options);
            },

            addPhoto: function(e) {
                e.preventDefault();
                $.mobile.loading('show');
                $('#photo').hide();
                var that = this;
                var options = this.getOptions(true);
                navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, options);
            },

            addPhotoSuccess: function(imgURI) {
                var move;
                // on iOS the photos go into a temp folder in the apps own filespace so we
                // can move them, and indeed have to as the tmp space is cleaned out by the OS
                // so draft reports might have their images removed. on android you access the
                // images where they are stored on the filesystem so if you move, and then delete
                // them, you are moving and deleting the only copy of them which is likely to be
                // surprising and unwelcome so we copy them instead.
                var fileName = CONFIG.NAMESPACE + '_' + this.model.cid + '_' + moment().unix() + '.jpg';
                if ( FMS.isAndroid ) {
                    move = FMS.files.copyURI( imgURI, fileName );
                } else {
                    move = FMS.files.moveURI( imgURI, fileName );
                }

                var that = this;
                move.done( function( file ) {
                    $('#nophoto_title').hide();
                    $('#photo_title').html(FMS.strings.photo_added).show();
                    $('#photo').attr('src', file.toURL()).addClass('small').removeClass('placeholder');
                    that.model.set('file', file.toURL());
                    FMS.saveCurrentDraft();

                    $('#photo-next-btn .ui-btn-text').text('Next');
                    $('#id_photo_button').parents('.ui-btn').hide();
                    $('#id_existing').parents('.ui-btn').hide();
                    $('#photo').show();
                    window.setTimeout(function() { $('#id_del_photo_button').show() }, 500);
                    window.setTimeout(function() { $.mobile.loading('hide') }, 100);
                });

                move.fail( function() { that.addPhotoFail(); } );
            },

            addPhotoFail: function() {
                $('#photo').show();
                $.mobile.loading('hide');
                if ( message != 'no image selected' &&
                    message != 'Selection cancelled.' &&
                    message != 'Camera cancelled.' ) {
                    this.displayAlert(FMS.strings.photo_failed);
                }
            },

            deletePhoto: function(e) {
                e.preventDefault();
                var that = this;
                var del = FMS.files.deleteURI( this.model.get('file') );

                del.done( function() {
                    $('#photo_title').hide();
                    $('#nophoto_title').show();
                    $('#id_del_photo_button').hide();
                    that.model.set('file', '');
                    FMS.saveCurrentDraft(true);
                    $('#photo').attr('src', 'images/placeholder-photo.png').addClass('placeholder').removeClass('small');

                    $('#photo-next-btn .ui-btn-text').text('Skip');
                    $('#id_photo_button').parents('.ui-btn').show();
                    $('#id_existing').parents('.ui-btn').show();
                });

            }
        })
    });
})(FMS, Backbone, _, $);
<|endoftext|>