<commit_before>
"use strict";

var EventEmitter = require('events').EventEmitter;
var WritableStream = require('stream').Writable;
var util = require( './util' );

/**
 * @summary Input stream for sending audio to Mumble.
 *
 * @description
 * The stream implements the `WritableStream` interface.
 *
 * The input data format can be specified with the constructor options. The
 * final audio will be converted to mono 16-bit PCM at 48 kHz.
 *
 * Currently the packets are sent to murmur in 10ms packets. The sample rate
 * should be such that it can divide the audio to packets of that size.
 *
 * @constructor
 * @this {MumbleInputStream}
 * @param {MumbleConnection} connection Mumble connection to write to
 * @param {Number} options.sampleRate - Input sample rate. Defaults to 48000.
 * @param {Number} options.channels - Input channels. Defaults to 1.
 * @param {Number} options.gain - Volume multiplier. Defaults to 1.
 */
var MumbleInputStream = function( connection, options ) {
    var self = this;
    options = options || {};

    this.connection = connection;
    this.channels = options.channels || 1;
    this.whisperId = options.whisperId;
    this.sampleRate = options.sampleRate || 48000;
    this.gain = options.gain || 1;

    if( options && options.decodeStrings === false ) {
        throw new TypeError( "MumbleInputStream does not support decoded string writes" );
    }

    this.processInterval = setInterval(
        this._processBuffer.bind( this ),
        this.connection.FRAME_LENGTH );

    this.processObserver = new EventEmitter();
    this.frameQueue = [];

    this.lastFrame = this._createFrameBuffer();

    this.lastFrameWritten = 0;
    this.queuedForPlay = 0;
    this.lastWrite = null;
    this.sent = 0;

    // Call the Writable constructor
    WritableStream.call( this, options );
};
MumbleInputStream.prototype = Object.create( WritableStream.prototype );

MumbleInputStream.prototype.LOCAL_BUFFER_SIZE = 10;
MumbleInputStream.prototype.PLAY_BUFFER_SIZE = 5;

/**
 * @summary Closes the stream
 */
MumbleInputStream.prototype.close = function () {
    clearInterval( this.processInterval );
};

/**
 * @summary Change the volume multiplier
 */
MumbleInputStream.prototype.setGain = function(gain) {
    if (gain >= 0) {
        this.gain = gain;
    }
};

/**
 * Create new frame buffer
 *
 * @private
 */
MumbleInputStream.prototype._createFrameBuffer = function() {
    return new Buffer(
        this.sampleRate /
        1000 * this.connection.FRAME_LENGTH *
        2 * ( this.channels) );
};

/**
 * Processes the frameQueue by sending frames to the connection
 *
 * @private
 */
MumbleInputStream.prototype._processBuffer = function() {

    // If there's been a gap in the audio, reset the sequence id.
    if( !this.lastWrite ||
        this.lastWrite + 20*this.connection.FRAME_LENGTH < Date.now() ) {
        this.voiceSequence = this.connection.voiceSequence;
        this.lastWrite = Date.now();
        return;
    }

    while( this.lastWrite + this.connection.FRAME_LENGTH < Date.now() ) {
        if( this.frameQueue.length > 0 ) {

            // Deque the first sample from the queue.
            var frame = this.frameQueue.shift();

            // Process the sample to fit the Mumble expectations.
            if( this.gain !== 1 )
                frame = util.applyGain( frame, this.gain );
            if( this.channels > 1 )
                frame = util.downmixChannels( frame, this.channels );
            if( this.sampleRate !== 48000 )
                frame = util.resample( frame, this.sampleRate, 48000 );

            // Now the sample should be 48kHz 16-bit mono audio.
            // We can send it to Mumble.
            this.voiceSequence +=
                this.connection.sendVoiceFrame( frame, this.whisperId, this.voiceSequence );
            this.sent += frame.length / 2;

            // Raise 'written' event to notify the _write implementation that
            // it might be able to write more chunks to the internal buffer.
            this.processObserver.emit( 'written' );
        }

        this.lastWrite += this.connection.FRAME_LENGTH;
    }

    return;
};

/**
 * Writes incoming chunks to the voice channel
 *
 * This method is called by the WritableStream when data is being written.
 *
 * @private
 *
 * @param {Buffer} chunk The chunk to be written
 * @param {String} encoding Not used
 * @param {Function} callback Callback to signal writing the chunk is done.
 */
MumbleInputStream.prototype._write = function( chunk, encoding, callback ) {

    while( true ) {

        // If we are at the buffer cap, wait until the buffer is emptied
        // before writing the rest.
        if( this.frameQueue.length >= this.LOCAL_BUFFER_SIZE ) {
            var self = this;
            this.processObserver.once( 'written', function () {
                self._write( chunk, encoding, callback );
            });
            return;
        }

        // Write the chunk to the current buffer.
        var writtenBefore = this.lastFrameWritten;
        chunk.copy( this.lastFrame, this.lastFrameWritten, 0 );
        var written = writtenBefore + chunk.length;

        // Check if we've written the last frame full.
        if( written >= this.lastFrame.length ) {

            // Frame is full.
            // Fix the 'written' value and queue the frame.
            written = this.lastFrame.length;
            this.frameQueue.push( this.lastFrame );
            this.lastFrame = this._createFrameBuffer();
            this.lastFrameWritten = 0;

        } else {

            // Frame not full. Advance the lastFrameWritten.
            this.lastFrameWritten = written;

        }

        // Check if the chunk was written in full or if some remains.
        if( chunk.length > (written - writtenBefore) ) {

            // Chunk was larger than remaining space in the last frame.
            chunk = chunk.slice( written - writtenBefore );

        } else {

            // Chunk was written completely.
            return callback();

        }

    }
};

module.exports = MumbleInputStream;
<commit_msg>Added setGain param documentation<commit_after>
"use strict";

var EventEmitter = require('events').EventEmitter;
var WritableStream = require('stream').Writable;
var util = require( './util' );

/**
 * @summary Input stream for sending audio to Mumble.
 *
 * @description
 * The stream implements the `WritableStream` interface.
 *
 * The input data format can be specified with the constructor options. The
 * final audio will be converted to mono 16-bit PCM at 48 kHz.
 *
 * Currently the packets are sent to murmur in 10ms packets. The sample rate
 * should be such that it can divide the audio to packets of that size.
 *
 * @constructor
 * @this {MumbleInputStream}
 * @param {MumbleConnection} connection Mumble connection to write to
 * @param {Number} options.sampleRate - Input sample rate. Defaults to 48000.
 * @param {Number} options.channels - Input channels. Defaults to 1.
 * @param {Number} options.gain - Volume multiplier. Defaults to 1.
 */
var MumbleInputStream = function( connection, options ) {
    var self = this;
    options = options || {};

    this.connection = connection;
    this.channels = options.channels || 1;
    this.whisperId = options.whisperId;
    this.sampleRate = options.sampleRate || 48000;
    this.gain = options.gain || 1;

    if( options && options.decodeStrings === false ) {
        throw new TypeError( "MumbleInputStream does not support decoded string writes" );
    }

    this.processInterval = setInterval(
        this._processBuffer.bind( this ),
        this.connection.FRAME_LENGTH );

    this.processObserver = new EventEmitter();
    this.frameQueue = [];

    this.lastFrame = this._createFrameBuffer();

    this.lastFrameWritten = 0;
    this.queuedForPlay = 0;
    this.lastWrite = null;
    this.sent = 0;

    // Call the Writable constructor
    WritableStream.call( this, options );
};
MumbleInputStream.prototype = Object.create( WritableStream.prototype );

MumbleInputStream.prototype.LOCAL_BUFFER_SIZE = 10;
MumbleInputStream.prototype.PLAY_BUFFER_SIZE = 5;

/**
 * @summary Closes the stream
 */
MumbleInputStream.prototype.close = function () {
    clearInterval( this.processInterval );
};

/**
 * @summary Change the volume multiplier
 *
 * @param {Number} gain - New gain value.
 */
MumbleInputStream.prototype.setGain = function( gain ) {
    if( gain <= 0 )
        throw new Error( 'Gain must be non-negative.' );

    this.gain = gain;
};

/**
 * Create new frame buffer
 *
 * @private
 */
MumbleInputStream.prototype._createFrameBuffer = function() {
    return new Buffer(
        this.sampleRate /
        1000 * this.connection.FRAME_LENGTH *
        2 * ( this.channels) );
};

/**
 * Processes the frameQueue by sending frames to the connection
 *
 * @private
 */
MumbleInputStream.prototype._processBuffer = function() {

    // If there's been a gap in the audio, reset the sequence id.
    if( !this.lastWrite ||
        this.lastWrite + 20*this.connection.FRAME_LENGTH < Date.now() ) {
        this.voiceSequence = this.connection.voiceSequence;
        this.lastWrite = Date.now();
        return;
    }

    while( this.lastWrite + this.connection.FRAME_LENGTH < Date.now() ) {
        if( this.frameQueue.length > 0 ) {

            // Deque the first sample from the queue.
            var frame = this.frameQueue.shift();

            // Process the sample to fit the Mumble expectations.
            if( this.gain !== 1 )
                frame = util.applyGain( frame, this.gain );
            if( this.channels > 1 )
                frame = util.downmixChannels( frame, this.channels );
            if( this.sampleRate !== 48000 )
                frame = util.resample( frame, this.sampleRate, 48000 );

            // Now the sample should be 48kHz 16-bit mono audio.
            // We can send it to Mumble.
            this.voiceSequence +=
                this.connection.sendVoiceFrame( frame, this.whisperId, this.voiceSequence );
            this.sent += frame.length / 2;

            // Raise 'written' event to notify the _write implementation that
            // it might be able to write more chunks to the internal buffer.
            this.processObserver.emit( 'written' );
        }

        this.lastWrite += this.connection.FRAME_LENGTH;
    }

    return;
};

/**
 * Writes incoming chunks to the voice channel
 *
 * This method is called by the WritableStream when data is being written.
 *
 * @private
 *
 * @param {Buffer} chunk The chunk to be written
 * @param {String} encoding Not used
 * @param {Function} callback Callback to signal writing the chunk is done.
 */
MumbleInputStream.prototype._write = function( chunk, encoding, callback ) {

    while( true ) {

        // If we are at the buffer cap, wait until the buffer is emptied
        // before writing the rest.
        if( this.frameQueue.length >= this.LOCAL_BUFFER_SIZE ) {
            var self = this;
            this.processObserver.once( 'written', function () {
                self._write( chunk, encoding, callback );
            });
            return;
        }

        // Write the chunk to the current buffer.
        var writtenBefore = this.lastFrameWritten;
        chunk.copy( this.lastFrame, this.lastFrameWritten, 0 );
        var written = writtenBefore + chunk.length;

        // Check if we've written the last frame full.
        if( written >= this.lastFrame.length ) {

            // Frame is full.
            // Fix the 'written' value and queue the frame.
            written = this.lastFrame.length;
            this.frameQueue.push( this.lastFrame );
            this.lastFrame = this._createFrameBuffer();
            this.lastFrameWritten = 0;

        } else {

            // Frame not full. Advance the lastFrameWritten.
            this.lastFrameWritten = written;

        }

        // Check if the chunk was written in full or if some remains.
        if( chunk.length > (written - writtenBefore) ) {

            // Chunk was larger than remaining space in the last frame.
            chunk = chunk.slice( written - writtenBefore );

        } else {

            // Chunk was written completely.
            return callback();

        }

    }
};

module.exports = MumbleInputStream;
<|endoftext|>


<commit_before>(function () {

	'use strict';

	window.Cosmoz = window.Cosmoz || {};

	var
		viewInfoInstances = [],
		sharedViewInfo = {
			desktop: false,
			effects: 10,
			height: 0,
			landscape: false,
			mobile: false,
			portrait: true,
			tablet: false,
			width: 0
		};

	/**
	 * Behavior to inherit the viewInfo property with device and viewport info
	 @polymerBehavior
	 */
	Cosmoz.ViewInfoBehavior = {
		properties: {
			/**
			 * viewInfo object
			 * {
			 * 	desktop: Boolean,
			 * 	effects: Number (1-10),
			 * 	height: Number,
			 * 	landscape: Boolean,
			 * 	mobile: Boolean,
			 * 	portrait: Boolean,
			 * 	tablet: Boolean,
			 * 	width: Number
			 * }
			 */
			viewInfo: {
				type: Object,
				notify: true
			}
		},

		attached() {
			viewInfoInstances.push(this);
			// Needed to make template views trigger on load and not only on resize
			this.viewInfo = sharedViewInfo;
		},

		detached() {
			const i = viewInfoInstances.indexOf(this);
			if (i >= 0) {
				viewInfoInstances.splice(i, 1);
			}
		}
	};

	Polymer({
		behaviors: [
			Polymer.IronResizableBehavior
		],
		is: 'cosmoz-viewinfo',
		properties: {
			/**
			 * Level of effects to use (0-10)

			 * NOT_IN_USE
			 */
			effects: {
				type: Number,
				value: 10,
				observer: '_effectsChanged'
			},
			/**
			 * Width breakpoint for mobile

			 * https://www.google.com/design/spec/layout/adaptive-ui.html#adaptive-ui-breakpoints
			 */
			mobileBreakpoint: {
				type: Number,
				value: 600
			},
			/**
			 * Width breakpoint for tablet

			 * https://www.google.com/design/spec/layout/adaptive-ui.html#adaptive-ui-breakpoints
			 */
			tabletBreakpoint: {
				type: Number,
				value: 960
			},
			/**
			 * Minimum delay between each viewinfo-resize event (ms)
			 */
			throttleTimeout: {
				type: Number,
				value: 250
			},
			/**
			 * Whether we're currently throttling resize-events
			 */
			_throttling: {
				type: Boolean,
				value: false
			}
		},
		listeners: {
			'iron-resize': '_onResize'
		},
		_effectsChanged(newValue) {
			this._notifyInstances({ effects: newValue });
		},
		/**
		* Loop over registered ViewInfoBehavior components and notify of changes

 	 * TODO: Don't reset the viewInfo property, but rather notify specific properties
		 *
		 * @param  {Object} delta object with changes
		 * @returns {void}
		 */
		_notifyInstances(delta) {
			viewInfoInstances.forEach(instance => {
				if (!instance) {
					return;
				}
				Object.keys(delta).forEach(key => {
					instance.notifyPath('viewInfo.' + key, delta[key]);
				});
			});
		},
		/**
		 * Called on `iron-resize`, throttles `viewinfo-resize` events
		 * @returns {void}
		 */
		_onResize() {
			const update = this._updateViewSize(),
				throttleFunction = function () {
					viewInfoInstances.forEach(element => {
					// Only fire on visible elements, offsetParent should be null for hidden
					// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
						if (element.offsetParent !== null) {
							element.fire('viewinfo-resize', {
								bigger: update
							});
						}
					});
					this._throttling = false;
				}.bind(this);

			if (update === undefined) {
				return;
			}

			if (!this._throttling) {
				window.setTimeout(throttleFunction, this.throttleTimeout);
				this._throttling = true;
			}
		},
		/**
		 * Recalculate viewInfo and updated sharedViewInfo accordingly
		 *
		 * @returns {Bolean}  returns true if sharedViewInfo.width is lower the next width
		 */
		_updateViewSize() {
			const
				prevWidth = sharedViewInfo.width,
				next = {
					height: this.clientHeight || this.offsetHeight || this.scrollHeight,
					width: this.clientWidth || this.offsetWidth || this.scrollWidth
				};

			next.portrait = next.height > next.width;
			next.landscape = !next.portrait;

			if (next.width <= this.mobileBreakpoint) {
				next.mobile = true;
				next.tablet = false;
			} else if (next.width <= this.tabletBreakpoint) {
				next.mobile = false;
				next.tablet = true;
			} else {
				next.mobile = false;
				next.tablet = false;
			}

			next.desktop = !next.mobile && !next.tablet;

			const delta = this._getDelta(sharedViewInfo, next);

			Object.keys(delta).forEach(key => {
				sharedViewInfo[key] = delta[key];
			});

			this._notifyInstances(delta);

			return prevWidth < next.width;
		},

		/**
		 ** Calculate the diff between two objects
		 *
		 * @param  {Object} prev first object
		 * @param  {Object} next second object
		 * @returns {Object}      delta
		 */
		_getDelta(prev, next) {
			let delta = {};
			Object.keys(next).forEach(key => {
				const nextVal = next[key];
				if (prev[key] === undefined || prev[key] !== nextVal) {
					delta[key] = nextVal;
				}
			});
			return delta;
		}
	});
}());
<commit_msg>debounce throttleResize <commit_after>(function () {

	'use strict';

	window.Cosmoz = window.Cosmoz || {};

	var
		viewInfoInstances = [],
		sharedViewInfo = {
			desktop: false,
			effects: 10,
			height: 0,
			landscape: false,
			mobile: false,
			portrait: true,
			tablet: false,
			width: 0
		};

	/**
	 * Behavior to inherit the viewInfo property with device and viewport info
	 @polymerBehavior
	 */
	Cosmoz.ViewInfoBehavior = {
		properties: {
			/**
			 * viewInfo object
			 * {
			 * 	desktop: Boolean,
			 * 	effects: Number (1-10),
			 * 	height: Number,
			 * 	landscape: Boolean,
			 * 	mobile: Boolean,
			 * 	portrait: Boolean,
			 * 	tablet: Boolean,
			 * 	width: Number
			 * }
			 */
			viewInfo: {
				type: Object,
				notify: true
			}
		},

		attached() {
			viewInfoInstances.push(this);
			// Needed to make template views trigger on load and not only on resize
			this.viewInfo = sharedViewInfo;
		},

		detached() {
			const i = viewInfoInstances.indexOf(this);
			if (i >= 0) {
				viewInfoInstances.splice(i, 1);
			}
		}
	};

	Polymer({
		behaviors: [
			Polymer.IronResizableBehavior
		],
		is: 'cosmoz-viewinfo',
		properties: {
			/**
			 * Level of effects to use (0-10)

			 * NOT_IN_USE
			 */
			effects: {
				type: Number,
				value: 10,
				observer: '_effectsChanged'
			},
			/**
			 * Width breakpoint for mobile

			 * https://www.google.com/design/spec/layout/adaptive-ui.html#adaptive-ui-breakpoints
			 */
			mobileBreakpoint: {
				type: Number,
				value: 600
			},
			/**
			 * Width breakpoint for tablet

			 * https://www.google.com/design/spec/layout/adaptive-ui.html#adaptive-ui-breakpoints
			 */
			tabletBreakpoint: {
				type: Number,
				value: 960
			},
			/**
			 * Minimum delay between each viewinfo-resize event (ms)
			 */
			throttleTimeout: {
				type: Number,
				value: 250
			}
		},
		listeners: {
			'iron-resize': '_onResize'
		},
		_effectsChanged(newValue) {
			this._notifyInstances({ effects: newValue });
		},
		/**
		* Loop over registered ViewInfoBehavior components and notify of changes

 	 * TODO: Don't reset the viewInfo property, but rather notify specific properties
		 *
		 * @param  {Object} delta object with changes
		 * @returns {void}
		 */
		_notifyInstances(delta) {
			viewInfoInstances.forEach(instance => {
				if (!instance) {
					return;
				}
				Object.keys(delta).forEach(key => {
					instance.notifyPath('viewInfo.' + key, delta[key]);
				});
			});
		},

		/**
		 * Called on `iron-resize`, throttles `viewinfo-resize` events
		 * @returns {void}
		 */
		_onResize() {
			if (!Array.isArray(viewInfoInstances) || viewInfoInstances.length === 0) {
				return;
			}

			this.debounce('_throttleResize', () => {
				const update = this._updateViewSize();

				if (update === undefined) {
					return;
				}
				viewInfoInstances.filter((el) => {
				// Only fire on visible elements, offsetParent should be null for hidden
				// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
					return el.offsetParent !== null;
				}).forEach(element => {
					element.fire('viewinfo-resize', {
						bigger: update
					});
				});
			}, this.throttleTimeout);
		},

		/**
		 * Recalculate viewInfo and updated sharedViewInfo accordingly
		 *
		 * @returns {Bolean}  returns true if sharedViewInfo.width is lower the next width
		 */
		_updateViewSize() {
			const
				prevWidth = sharedViewInfo.width,
				next = {
					height: this.clientHeight || this.offsetHeight || this.scrollHeight,
					width: this.clientWidth || this.offsetWidth || this.scrollWidth
				};

			next.portrait = next.height > next.width;
			next.landscape = !next.portrait;

			if (next.width <= this.mobileBreakpoint) {
				next.mobile = true;
				next.tablet = false;
			} else if (next.width <= this.tabletBreakpoint) {
				next.mobile = false;
				next.tablet = true;
			} else {
				next.mobile = false;
				next.tablet = false;
			}

			next.desktop = !next.mobile && !next.tablet;

			const delta = this._getDelta(sharedViewInfo, next);

			Object.keys(delta).forEach(key => {
				sharedViewInfo[key] = delta[key];
			});

			this._notifyInstances(delta);

			return prevWidth < next.width;
		},

		/**
		 ** Calculate the diff between two objects
		 *
		 * @param  {Object} prev first object
		 * @param  {Object} next second object
		 * @returns {Object}      delta
		 */
		_getDelta(prev, next) {
			let delta = {};
			Object.keys(next).forEach(key => {
				const nextVal = next[key];
				if (prev[key] === undefined || prev[key] !== nextVal) {
					delta[key] = nextVal;
				}
			});
			return delta;
		}
	});
}());
<|endoftext|>


<commit_before>(function(funcName, baseObj) {
    "use strict";
    // The public function name defaults to window.docReady
    // but you can modify the last line of this function to pass in a different object or method name
    // if you want to put them in a different namespace and those will be used instead of
    // window.docReady(...)
    funcName = funcName || "docReady";
    baseObj = baseObj || window;
    var readyList = [];
    var readyFired = false;
    var readyEventHandlersInstalled = false;

    // call this when the document is ready
    // this function protects itself against being called more than once
    function ready() {
        if (!readyFired) {
            // this must be set to true before we start calling callbacks
            readyFired = true;
            for (var i = 0; i < readyList.length; i++) {
                // if a callback here happens to add new ready handlers,
                // the docReady() function will see that it already fired
                // and will schedule the callback to run right after
                // this event loop finishes so all handlers will still execute
                // in order and no new ones will be added to the readyList
                // while we are processing the list
                readyList[i].fn.call(window, readyList[i].ctx);
            }
            // allow any closures held by these functions to free
            readyList = [];
        }
    }

    function readyStateChange() {
        if ( document.readyState === "complete" ) {
            ready();
        }
    }

    // This is the one public interface
    // docReady(fn, context);
    // the context argument is optional - if present, it will be passed
    // as an argument to the callback
    baseObj[funcName] = function(callback, context) {
        // if ready has already fired, then just schedule the callback
        // to fire asynchronously, but right away
        if (readyFired) {
            setTimeout(function() {callback(context);}, 1);
            return;
        } else {
            // add the function and context to the list
            readyList.push({fn: callback, ctx: context});
        }
        // if document already ready to go, schedule the ready function to run
        // IE only safe when readyState is "complete", others safe when readyState is "interactive"
        if (document.readyState === "complete" || (!document.attachEvent && document.readyState === "interactive")) {
            setTimeout(ready, 1);
        } else if (!readyEventHandlersInstalled) {
            // otherwise if we don't have event handlers installed, install them
            if (document.addEventListener) {
                // first choice is DOMContentLoaded event
                document.addEventListener("DOMContentLoaded", ready, false);
                // backup is window load event
                window.addEventListener("load", ready, false);
            } else {
                // must be IE
                document.attachEvent("onreadystatechange", readyStateChange);
                window.attachEvent("onload", ready);
            }
            readyEventHandlersInstalled = true;
        }
    }
})("docReady", window);
// modify this previous line to pass in your own method name
// and object for the method to be attached to
<commit_msg>Add type checking for callback<commit_after>(function(funcName, baseObj) {
    "use strict";
    // The public function name defaults to window.docReady
    // but you can modify the last line of this function to pass in a different object or method name
    // if you want to put them in a different namespace and those will be used instead of
    // window.docReady(...)
    funcName = funcName || "docReady";
    baseObj = baseObj || window;
    var readyList = [];
    var readyFired = false;
    var readyEventHandlersInstalled = false;

    // call this when the document is ready
    // this function protects itself against being called more than once
    function ready() {
        if (!readyFired) {
            // this must be set to true before we start calling callbacks
            readyFired = true;
            for (var i = 0; i < readyList.length; i++) {
                // if a callback here happens to add new ready handlers,
                // the docReady() function will see that it already fired
                // and will schedule the callback to run right after
                // this event loop finishes so all handlers will still execute
                // in order and no new ones will be added to the readyList
                // while we are processing the list
                readyList[i].fn.call(window, readyList[i].ctx);
            }
            // allow any closures held by these functions to free
            readyList = [];
        }
    }

    function readyStateChange() {
        if ( document.readyState === "complete" ) {
            ready();
        }
    }

    // This is the one public interface
    // docReady(fn, context);
    // the context argument is optional - if present, it will be passed
    // as an argument to the callback
    baseObj[funcName] = function(callback, context) {
        if (typeof callback !== "function") {
            throw new TypeError("callback for docReady(fn) must be a function");
        }
        // if ready has already fired, then just schedule the callback
        // to fire asynchronously, but right away
        if (readyFired) {
            setTimeout(function() {callback(context);}, 1);
            return;
        } else {
            // add the function and context to the list
            readyList.push({fn: callback, ctx: context});
        }
        // if document already ready to go, schedule the ready function to run
        // IE only safe when readyState is "complete", others safe when readyState is "interactive"
        if (document.readyState === "complete" || (!document.attachEvent && document.readyState === "interactive")) {
            setTimeout(ready, 1);
        } else if (!readyEventHandlersInstalled) {
            // otherwise if we don't have event handlers installed, install them
            if (document.addEventListener) {
                // first choice is DOMContentLoaded event
                document.addEventListener("DOMContentLoaded", ready, false);
                // backup is window load event
                window.addEventListener("load", ready, false);
            } else {
                // must be IE
                document.attachEvent("onreadystatechange", readyStateChange);
                window.attachEvent("onload", ready);
            }
            readyEventHandlersInstalled = true;
        }
    }
})("docReady", window);
// modify this previous line to pass in your own method name
// and object for the method to be attached to
<|endoftext|>


<commit_before>import debug from 'debug';
import reqwest from 'reqwest';
import { defaults } from 'lodash';
import { Map as makeMap, List } from 'immutable';

import {
  blurSearch, clearControlError, closeWebsocket, openWebsocket, receiveError,
  receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError,
  receiveControlNodeRemoved, receiveControlPipe, receiveControlPipeStatus,
  receiveControlSuccess, receiveTopologies, receiveNotFound,
  receiveNodesForTopology, receiveNodes,
} from '../actions/app-actions';

import { getCurrentTopologyUrl } from '../utils/topology-utils';
import { layersTopologyIdsSelector } from '../selectors/resource-view/layout';
import { activeTopologyOptionsSelector } from '../selectors/topology';
import { isPausedSelector } from '../selectors/time-travel';

import { API_REFRESH_INTERVAL, TOPOLOGY_REFRESH_INTERVAL } from '../constants/timer';

const log = debug('scope:web-api-utils');

const reconnectTimerInterval = 5000;
const updateFrequency = '5s';
const FIRST_RENDER_TOO_LONG_THRESHOLD = 100; // ms
const csrfToken = (() => {
  // Check for token at window level or parent level (for iframe);
  /* eslint-disable no-underscore-dangle */
  const token = typeof window !== 'undefined'
    ? window.__WEAVEWORKS_CSRF_TOKEN || parent.__WEAVEWORKS_CSRF_TOKEN
    : null;
  /* eslint-enable no-underscore-dangle */
  if (!token || token === '$__CSRF_TOKEN_PLACEHOLDER__') {
    // Authfe did not replace the token in the static html.
    return null;
  }

  return token;
})();

let socket;
let reconnectTimer = 0;
let topologyTimer = 0;
let apiDetailsTimer = 0;
let controlErrorTimer = 0;
let currentUrl = null;
let createWebsocketAt = null;
let firstMessageOnWebsocketAt = null;
let continuePolling = true;

export function getSerializedTimeTravelTimestamp(state) {
  // The timestamp parameter will be used only if it's in the past.
  if (!isPausedSelector(state)) return null;

  return state.get('pausedAt').toISOString();
}

export function buildUrlQuery(params = makeMap(), state) {
  // Attach the time travel timestamp to every request to the backend.
  params = params.set('timestamp', getSerializedTimeTravelTimestamp(state));

  // Ignore the entries with values `null` or `undefined`.
  return params.map((value, param) => {
    if (value === undefined || value === null) return null;
    if (List.isList(value)) {
      value = value.join(',');
    }
    return `${param}=${value}`;
  }).filter(s => s).join('&');
}

export function basePath(urlPath) {
  //
  // "/scope/terminal.html" -> "/scope"
  // "/scope/" -> "/scope"
  // "/scope" -> "/scope"
  // "/" -> ""
  //
  const parts = urlPath.split('/');
  // if the last item has a "." in it, e.g. foo.html...
  if (parts[parts.length - 1].indexOf('.') !== -1) {
    return parts.slice(0, -1).join('/');
  }
  return parts.join('/').replace(/\/$/, '');
}

export function basePathSlash(urlPath) {
  //
  // "/scope/terminal.html" -> "/scope/"
  // "/scope/" -> "/scope/"
  // "/scope" -> "/scope/"
  // "/" -> "/"
  //
  return `${basePath(urlPath)}/`;
}

export function getApiPath(pathname = window.location.pathname) {
  if (process.env.SCOPE_API_PREFIX) {
    return basePath(`${process.env.SCOPE_API_PREFIX}${pathname}`);
  }

  return basePath(pathname);
}

export function getWebsocketUrl(host = window.location.host, pathname = window.location.pathname) {
  const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
  return `${wsProto}://${host}${process.env.SCOPE_API_PREFIX || ''}${basePath(pathname)}`;
}

function buildWebsocketUrl(topologyUrl, topologyOptions = makeMap(), state) {
  topologyOptions = topologyOptions.set('t', updateFrequency);
  const optionsQuery = buildUrlQuery(topologyOptions, state);
  return `${getWebsocketUrl()}${topologyUrl}/ws?${optionsQuery}`;
}

function createWebsocket(websocketUrl, getState, dispatch) {
  if (socket) {
    socket.onclose = null;
    socket.onerror = null;
    socket.close();
    // onclose() is not called, but that's fine since we're opening a new one
    // right away
  }

  // profiling
  createWebsocketAt = new Date();
  firstMessageOnWebsocketAt = null;

  socket = new WebSocket(websocketUrl);

  socket.onopen = () => {
    log(`Opening websocket to ${websocketUrl}`);
    dispatch(openWebsocket());
  };

  socket.onclose = () => {
    clearTimeout(reconnectTimer);
    log(`Closing websocket to ${websocketUrl}`, socket.readyState);
    socket = null;
    dispatch(closeWebsocket());

    if (continuePolling && !isPausedSelector(getState())) {
      reconnectTimer = setTimeout(() => {
        createWebsocket(websocketUrl, getState, dispatch);
      }, reconnectTimerInterval);
    }
  };

  socket.onerror = () => {
    log(`Error in websocket to ${websocketUrl}`);
    dispatch(receiveError(websocketUrl));
  };

  socket.onmessage = (event) => {
    const msg = JSON.parse(event.data);
    dispatch(receiveNodesDelta(msg));

    // profiling (receiveNodesDelta triggers synchronous render)
    if (!firstMessageOnWebsocketAt) {
      firstMessageOnWebsocketAt = new Date();
      const timeToFirstMessage = firstMessageOnWebsocketAt - createWebsocketAt;
      if (timeToFirstMessage > FIRST_RENDER_TOO_LONG_THRESHOLD) {
        log('Time (ms) to first nodes render after websocket was created',
          firstMessageOnWebsocketAt - createWebsocketAt);
      }
    }
  };
}

/**
  * XHR wrapper. Applies a CSRF token (if it exists) and content-type to all requests.
  * Any opts that get passed in will override the defaults.
  */
function doRequest(opts) {
  const config = defaults(opts, {
    contentType: 'application/json',
    type: 'json'
  });
  if (csrfToken) {
    config.headers = Object.assign({}, config.headers, { 'X-CSRF-Token': csrfToken });
  }

  return reqwest(config);
}

/**
 * Does a one-time fetch of all the nodes for a custom list of topologies.
 */
function getNodesForTopologies(state, dispatch, topologyIds, topologyOptions = makeMap()) {
  // fetch sequentially
  state.get('topologyUrlsById')
    .filter((_, topologyId) => topologyIds.contains(topologyId))
    .reduce((sequence, topologyUrl, topologyId) => sequence.then(() => {
      const optionsQuery = buildUrlQuery(topologyOptions.get(topologyId), state);
      return doRequest({ url: `${getApiPath()}${topologyUrl}?${optionsQuery}` });
    })
    .then(json => dispatch(receiveNodesForTopology(json.nodes, topologyId))),
    Promise.resolve());
}

function getNodesOnce(getState, dispatch) {
  const state = getState();
  const topologyUrl = getCurrentTopologyUrl(state);
  const topologyOptions = activeTopologyOptionsSelector(state);
  const optionsQuery = buildUrlQuery(topologyOptions, state);
  const url = `${getApiPath()}${topologyUrl}?${optionsQuery}`;
  doRequest({
    url,
    success: (res) => {
      dispatch(receiveNodes(res.nodes));
    },
    error: (req) => {
      log(`Error in nodes request: ${req.responseText}`);
      dispatch(receiveError(url));
    }
  });
}

/**
 * Gets nodes for all topologies (for search).
 */
export function getAllNodes(state, dispatch) {
  const topologyOptions = state.get('topologyOptions');
  const topologyIds = state.get('topologyUrlsById').keySeq();
  getNodesForTopologies(state, dispatch, topologyIds, topologyOptions);
}

/**
 * One-time update of all the nodes of topologies that appear in the current resource view.
 * TODO: Replace the one-time snapshot with periodic polling.
 */
export function getResourceViewNodesSnapshot(state, dispatch) {
  const topologyIds = layersTopologyIdsSelector(state);
  getNodesForTopologies(state, dispatch, topologyIds);
}

// NOTE: getState is called every time to make sure the up-to-date state is used.
export function getTopologies(getState, dispatch, initialPoll = false) {
  // Used to resume polling when navigating between pages in Weave Cloud.
  continuePolling = initialPoll === true ? true : continuePolling;
  clearTimeout(topologyTimer);
  const optionsQuery = buildUrlQuery(activeTopologyOptionsSelector(getState()), getState());
  const url = `${getApiPath()}/api/topology?${optionsQuery}`;
  doRequest({
    url,
    success: (res) => {
      if (continuePolling && !isPausedSelector(getState())) {
        dispatch(receiveTopologies(res));
        topologyTimer = setTimeout(() => {
          getTopologies(getState, dispatch);
        }, TOPOLOGY_REFRESH_INTERVAL);
      }
    },
    error: (req) => {
      log(`Error in topology request: ${req.responseText}`);
      dispatch(receiveError(url));
      // Only retry in stand-alone mode
      if (continuePolling && !isPausedSelector(getState())) {
        topologyTimer = setTimeout(() => {
          getTopologies(getState, dispatch);
        }, TOPOLOGY_REFRESH_INTERVAL);
      }
    }
  });
}

function updateWebsocketChannel(getState, dispatch, forceRequest) {
  const topologyUrl = getCurrentTopologyUrl(getState());
  const topologyOptions = activeTopologyOptionsSelector(getState());
  const websocketUrl = buildWebsocketUrl(topologyUrl, topologyOptions, getState());
  // Only recreate websocket if url changed or if forced (weave cloud instance reload);
  const isNewUrl = websocketUrl !== currentUrl;
  // `topologyUrl` can be undefined initially, so only create a socket if it is truthy
  // and no socket exists, or if we get a new url.
  if (topologyUrl && (!socket || isNewUrl || forceRequest)) {
    createWebsocket(websocketUrl, getState, dispatch);
    currentUrl = websocketUrl;
  }
}

export function getNodeDetails(getState, dispatch) {
  const state = getState();
  const nodeMap = state.get('nodeDetails');
  const topologyUrlsById = state.get('topologyUrlsById');
  const currentTopologyId = state.get('currentTopologyId');
  const requestTimestamp = state.get('pausedAt');

  // get details for all opened nodes
  const obj = nodeMap.last();
  if (obj && topologyUrlsById.has(obj.topologyId)) {
    const topologyUrl = topologyUrlsById.get(obj.topologyId);
    let urlComponents = [getApiPath(), topologyUrl, '/', encodeURIComponent(obj.id)];

    // Only forward filters for nodes in the current topology.
    const topologyOptions = currentTopologyId === obj.topologyId
      ? activeTopologyOptionsSelector(state) : makeMap();

    const query = buildUrlQuery(topologyOptions, state);
    if (query) {
      urlComponents = urlComponents.concat(['?', query]);
    }
    const url = urlComponents.join('');

    doRequest({
      url,
      success: (res) => {
        // make sure node is still selected
        if (nodeMap.has(res.node.id)) {
          dispatch(receiveNodeDetails(res.node, requestTimestamp));
        }
      },
      error: (err) => {
        log(`Error in node details request: ${err.responseText}`);
        // dont treat missing node as error
        if (err.status === 404) {
          dispatch(receiveNotFound(obj.id, requestTimestamp));
        } else {
          dispatch(receiveError(topologyUrl));
        }
      }
    });
  } else if (obj) {
    log('No details or url found for ', obj);
  }
}

export function getNodes(getState, dispatch, forceRequest = false) {
  if (isPausedSelector(getState())) {
    getNodesOnce(getState, dispatch);
  } else {
    updateWebsocketChannel(getState, dispatch, forceRequest);
  }
  getNodeDetails(getState, dispatch);
}

export function getApiDetails(dispatch) {
  clearTimeout(apiDetailsTimer);
  const url = `${getApiPath()}/api`;
  doRequest({
    url,
    success: (res) => {
      dispatch(receiveApiDetails(res));
      if (continuePolling) {
        apiDetailsTimer = setTimeout(() => {
          getApiDetails(dispatch);
        }, API_REFRESH_INTERVAL);
      }
    },
    error: (req) => {
      log(`Error in api details request: ${req.responseText}`);
      receiveError(url);
      if (continuePolling) {
        apiDetailsTimer = setTimeout(() => {
          getApiDetails(dispatch);
        }, API_REFRESH_INTERVAL / 2);
      }
    }
  });
}

export function doControlRequest(nodeId, control, dispatch) {
  clearTimeout(controlErrorTimer);
  const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/`
    + `${encodeURIComponent(control.nodeId)}/${control.id}`;
  doRequest({
    method: 'POST',
    url,
    success: (res) => {
      dispatch(receiveControlSuccess(nodeId));
      if (res) {
        if (res.pipe) {
          dispatch(blurSearch());
          const resizeTtyControl = res.resize_tty_control &&
            {id: res.resize_tty_control, probeId: control.probeId, nodeId: control.nodeId};
          dispatch(receiveControlPipe(
            res.pipe,
            nodeId,
            res.raw_tty,
            resizeTtyControl,
            control
          ));
        }
        if (res.removedNode) {
          dispatch(receiveControlNodeRemoved(nodeId));
        }
      }
    },
    error: (err) => {
      dispatch(receiveControlError(nodeId, err.response));
      controlErrorTimer = setTimeout(() => {
        dispatch(clearControlError(nodeId));
      }, 10000);
    }
  });
}

export function doResizeTty(pipeId, control, cols, rows) {
  const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/`
    + `${encodeURIComponent(control.nodeId)}/${control.id}`;

  return doRequest({
    method: 'POST',
    url,
    data: JSON.stringify({pipeID: pipeId, width: cols.toString(), height: rows.toString()}),
  })
    .fail((err) => {
      log(`Error resizing pipe: ${err}`);
    });
}

export function deletePipe(pipeId, dispatch) {
  const url = `${getApiPath()}/api/pipe/${encodeURIComponent(pipeId)}`;
  doRequest({
    method: 'DELETE',
    url,
    success: () => {
      log('Closed the pipe!');
    },
    error: (err) => {
      log(`Error closing pipe:${err}`);
      dispatch(receiveError(url));
    }
  });
}

export function getPipeStatus(pipeId, dispatch) {
  const url = `${getApiPath()}/api/pipe/${encodeURIComponent(pipeId)}/check`;
  doRequest({
    method: 'GET',
    url,
    complete: (res) => {
      const status = {
        204: 'PIPE_ALIVE',
        404: 'PIPE_DELETED'
      }[res.status];

      if (!status) {
        log('Unexpected pipe status:', res.status);
        return;
      }

      dispatch(receiveControlPipeStatus(pipeId, status));
    }
  });
}

export function stopPolling() {
  clearTimeout(apiDetailsTimer);
  clearTimeout(topologyTimer);
  continuePolling = false;
}

export function teardownWebsockets() {
  clearTimeout(reconnectTimer);
  if (socket) {
    socket.onerror = null;
    socket.onclose = null;
    socket.onmessage = null;
    socket.onopen = null;
    socket.close();
    socket = null;
    currentUrl = null;
  }
}
<commit_msg>Split between topology API polling & one time calls.<commit_after>import debug from 'debug';
import reqwest from 'reqwest';
import { defaults } from 'lodash';
import { Map as makeMap, List } from 'immutable';

import {
  blurSearch, clearControlError, closeWebsocket, openWebsocket, receiveError,
  receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError,
  receiveControlNodeRemoved, receiveControlPipe, receiveControlPipeStatus,
  receiveControlSuccess, receiveTopologies, receiveNotFound,
  receiveNodesForTopology, receiveNodes,
} from '../actions/app-actions';

import { getCurrentTopologyUrl } from '../utils/topology-utils';
import { layersTopologyIdsSelector } from '../selectors/resource-view/layout';
import { activeTopologyOptionsSelector } from '../selectors/topology';
import { isPausedSelector } from '../selectors/time-travel';

import { API_REFRESH_INTERVAL, TOPOLOGY_REFRESH_INTERVAL } from '../constants/timer';

const log = debug('scope:web-api-utils');

const reconnectTimerInterval = 5000;
const updateFrequency = '5s';
const FIRST_RENDER_TOO_LONG_THRESHOLD = 100; // ms
const csrfToken = (() => {
  // Check for token at window level or parent level (for iframe);
  /* eslint-disable no-underscore-dangle */
  const token = typeof window !== 'undefined'
    ? window.__WEAVEWORKS_CSRF_TOKEN || parent.__WEAVEWORKS_CSRF_TOKEN
    : null;
  /* eslint-enable no-underscore-dangle */
  if (!token || token === '$__CSRF_TOKEN_PLACEHOLDER__') {
    // Authfe did not replace the token in the static html.
    return null;
  }

  return token;
})();

let socket;
let reconnectTimer = 0;
let topologyTimer = 0;
let apiDetailsTimer = 0;
let controlErrorTimer = 0;
let currentUrl = null;
let createWebsocketAt = null;
let firstMessageOnWebsocketAt = null;
let continuePolling = true;

export function getSerializedTimeTravelTimestamp(state) {
  // The timestamp parameter will be used only if it's in the past.
  if (!isPausedSelector(state)) return null;

  return state.get('pausedAt').toISOString();
}

export function buildUrlQuery(params = makeMap(), state) {
  // Attach the time travel timestamp to every request to the backend.
  params = params.set('timestamp', getSerializedTimeTravelTimestamp(state));

  // Ignore the entries with values `null` or `undefined`.
  return params.map((value, param) => {
    if (value === undefined || value === null) return null;
    if (List.isList(value)) {
      value = value.join(',');
    }
    return `${param}=${value}`;
  }).filter(s => s).join('&');
}

export function basePath(urlPath) {
  //
  // "/scope/terminal.html" -> "/scope"
  // "/scope/" -> "/scope"
  // "/scope" -> "/scope"
  // "/" -> ""
  //
  const parts = urlPath.split('/');
  // if the last item has a "." in it, e.g. foo.html...
  if (parts[parts.length - 1].indexOf('.') !== -1) {
    return parts.slice(0, -1).join('/');
  }
  return parts.join('/').replace(/\/$/, '');
}

export function basePathSlash(urlPath) {
  //
  // "/scope/terminal.html" -> "/scope/"
  // "/scope/" -> "/scope/"
  // "/scope" -> "/scope/"
  // "/" -> "/"
  //
  return `${basePath(urlPath)}/`;
}

export function getApiPath(pathname = window.location.pathname) {
  if (process.env.SCOPE_API_PREFIX) {
    return basePath(`${process.env.SCOPE_API_PREFIX}${pathname}`);
  }

  return basePath(pathname);
}

function topologiesUrl(state) {
  const activeTopologyOptions = activeTopologyOptionsSelector(state);
  const optionsQuery = buildUrlQuery(activeTopologyOptions, state);
  return `${getApiPath()}/api/topology?${optionsQuery}`;
}

export function getWebsocketUrl(host = window.location.host, pathname = window.location.pathname) {
  const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
  return `${wsProto}://${host}${process.env.SCOPE_API_PREFIX || ''}${basePath(pathname)}`;
}

function buildWebsocketUrl(topologyUrl, topologyOptions = makeMap(), state) {
  topologyOptions = topologyOptions.set('t', updateFrequency);
  const optionsQuery = buildUrlQuery(topologyOptions, state);
  return `${getWebsocketUrl()}${topologyUrl}/ws?${optionsQuery}`;
}

function createWebsocket(websocketUrl, getState, dispatch) {
  if (socket) {
    socket.onclose = null;
    socket.onerror = null;
    socket.close();
    // onclose() is not called, but that's fine since we're opening a new one
    // right away
  }

  // profiling
  createWebsocketAt = new Date();
  firstMessageOnWebsocketAt = null;

  socket = new WebSocket(websocketUrl);

  socket.onopen = () => {
    log(`Opening websocket to ${websocketUrl}`);
    dispatch(openWebsocket());
  };

  socket.onclose = () => {
    clearTimeout(reconnectTimer);
    log(`Closing websocket to ${websocketUrl}`, socket.readyState);
    socket = null;
    dispatch(closeWebsocket());

    if (continuePolling && !isPausedSelector(getState())) {
      reconnectTimer = setTimeout(() => {
        createWebsocket(websocketUrl, getState, dispatch);
      }, reconnectTimerInterval);
    }
  };

  socket.onerror = () => {
    log(`Error in websocket to ${websocketUrl}`);
    dispatch(receiveError(websocketUrl));
  };

  socket.onmessage = (event) => {
    const msg = JSON.parse(event.data);
    dispatch(receiveNodesDelta(msg));

    // profiling (receiveNodesDelta triggers synchronous render)
    if (!firstMessageOnWebsocketAt) {
      firstMessageOnWebsocketAt = new Date();
      const timeToFirstMessage = firstMessageOnWebsocketAt - createWebsocketAt;
      if (timeToFirstMessage > FIRST_RENDER_TOO_LONG_THRESHOLD) {
        log('Time (ms) to first nodes render after websocket was created',
          firstMessageOnWebsocketAt - createWebsocketAt);
      }
    }
  };
}

/**
  * XHR wrapper. Applies a CSRF token (if it exists) and content-type to all requests.
  * Any opts that get passed in will override the defaults.
  */
function doRequest(opts) {
  const config = defaults(opts, {
    contentType: 'application/json',
    type: 'json'
  });
  if (csrfToken) {
    config.headers = Object.assign({}, config.headers, { 'X-CSRF-Token': csrfToken });
  }

  return reqwest(config);
}

/**
 * Does a one-time fetch of all the nodes for a custom list of topologies.
 */
function getNodesForTopologies(state, dispatch, topologyIds, topologyOptions = makeMap()) {
  // fetch sequentially
  state.get('topologyUrlsById')
    .filter((_, topologyId) => topologyIds.contains(topologyId))
    .reduce((sequence, topologyUrl, topologyId) => sequence.then(() => {
      const optionsQuery = buildUrlQuery(topologyOptions.get(topologyId), state);
      return doRequest({ url: `${getApiPath()}${topologyUrl}?${optionsQuery}` });
    })
    .then(json => dispatch(receiveNodesForTopology(json.nodes, topologyId))),
    Promise.resolve());
}

function getNodesOnce(getState, dispatch) {
  const state = getState();
  const topologyUrl = getCurrentTopologyUrl(state);
  const topologyOptions = activeTopologyOptionsSelector(state);
  const optionsQuery = buildUrlQuery(topologyOptions, state);
  const url = `${getApiPath()}${topologyUrl}?${optionsQuery}`;
  doRequest({
    url,
    success: (res) => {
      dispatch(receiveNodes(res.nodes));
    },
    error: (req) => {
      log(`Error in nodes request: ${req.responseText}`);
      dispatch(receiveError(url));
    }
  });
}

/**
 * Gets nodes for all topologies (for search).
 */
export function getAllNodes(state, dispatch) {
  const topologyOptions = state.get('topologyOptions');
  const topologyIds = state.get('topologyUrlsById').keySeq();
  getNodesForTopologies(state, dispatch, topologyIds, topologyOptions);
}

/**
 * One-time update of all the nodes of topologies that appear in the current resource view.
 * TODO: Replace the one-time snapshot with periodic polling.
 */
export function getResourceViewNodesSnapshot(state, dispatch) {
  const topologyIds = layersTopologyIdsSelector(state);
  getNodesForTopologies(state, dispatch, topologyIds);
}

function pollTopologies(getState, dispatch, initialPoll = false) {
  // Used to resume polling when navigating between pages in Weave Cloud.
  continuePolling = initialPoll === true ? true : continuePolling;
  clearTimeout(topologyTimer);
  // NOTE: getState is called every time to make sure the up-to-date state is used.
  const url = topologiesUrl(getState());
  doRequest({
    url,
    success: (res) => {
      if (continuePolling && !isPausedSelector(getState())) {
        dispatch(receiveTopologies(res));
        topologyTimer = setTimeout(() => {
          pollTopologies(getState, dispatch);
        }, TOPOLOGY_REFRESH_INTERVAL);
      }
    },
    error: (req) => {
      log(`Error in topology request: ${req.responseText}`);
      dispatch(receiveError(url));
      // Only retry in stand-alone mode
      if (continuePolling && !isPausedSelector(getState())) {
        topologyTimer = setTimeout(() => {
          pollTopologies(getState, dispatch);
        }, TOPOLOGY_REFRESH_INTERVAL);
      }
    }
  });
}

function getTopologiesOnce(getState, dispatch) {
  const url = topologiesUrl(getState());
  doRequest({
    url,
    success: (res) => {
      dispatch(receiveTopologies(res));
    },
    error: (req) => {
      log(`Error in topology request: ${req.responseText}`);
      dispatch(receiveError(url));
    }
  });
}

function updateWebsocketChannel(getState, dispatch, forceRequest) {
  const topologyUrl = getCurrentTopologyUrl(getState());
  const topologyOptions = activeTopologyOptionsSelector(getState());
  const websocketUrl = buildWebsocketUrl(topologyUrl, topologyOptions, getState());
  // Only recreate websocket if url changed or if forced (weave cloud instance reload);
  const isNewUrl = websocketUrl !== currentUrl;
  // `topologyUrl` can be undefined initially, so only create a socket if it is truthy
  // and no socket exists, or if we get a new url.
  if (topologyUrl && (!socket || isNewUrl || forceRequest)) {
    createWebsocket(websocketUrl, getState, dispatch);
    currentUrl = websocketUrl;
  }
}

export function getNodeDetails(getState, dispatch) {
  const state = getState();
  const nodeMap = state.get('nodeDetails');
  const topologyUrlsById = state.get('topologyUrlsById');
  const currentTopologyId = state.get('currentTopologyId');
  const requestTimestamp = state.get('pausedAt');

  // get details for all opened nodes
  const obj = nodeMap.last();
  if (obj && topologyUrlsById.has(obj.topologyId)) {
    const topologyUrl = topologyUrlsById.get(obj.topologyId);
    let urlComponents = [getApiPath(), topologyUrl, '/', encodeURIComponent(obj.id)];

    // Only forward filters for nodes in the current topology.
    const topologyOptions = currentTopologyId === obj.topologyId
      ? activeTopologyOptionsSelector(state) : makeMap();

    const query = buildUrlQuery(topologyOptions, state);
    if (query) {
      urlComponents = urlComponents.concat(['?', query]);
    }
    const url = urlComponents.join('');

    doRequest({
      url,
      success: (res) => {
        // make sure node is still selected
        if (nodeMap.has(res.node.id)) {
          dispatch(receiveNodeDetails(res.node, requestTimestamp));
        }
      },
      error: (err) => {
        log(`Error in node details request: ${err.responseText}`);
        // dont treat missing node as error
        if (err.status === 404) {
          dispatch(receiveNotFound(obj.id, requestTimestamp));
        } else {
          dispatch(receiveError(topologyUrl));
        }
      }
    });
  } else if (obj) {
    log('No details or url found for ', obj);
  }
}

export function getTopologies(getState, dispatch, forceRequest) {
  if (isPausedSelector(getState())) {
    getTopologiesOnce(getState, dispatch);
  } else {
    pollTopologies(getState, dispatch, forceRequest);
  }
}

export function getNodes(getState, dispatch, forceRequest = false) {
  if (isPausedSelector(getState())) {
    getNodesOnce(getState, dispatch);
  } else {
    updateWebsocketChannel(getState, dispatch, forceRequest);
  }
  getNodeDetails(getState, dispatch);
}

export function getApiDetails(dispatch) {
  clearTimeout(apiDetailsTimer);
  const url = `${getApiPath()}/api`;
  doRequest({
    url,
    success: (res) => {
      dispatch(receiveApiDetails(res));
      if (continuePolling) {
        apiDetailsTimer = setTimeout(() => {
          getApiDetails(dispatch);
        }, API_REFRESH_INTERVAL);
      }
    },
    error: (req) => {
      log(`Error in api details request: ${req.responseText}`);
      receiveError(url);
      if (continuePolling) {
        apiDetailsTimer = setTimeout(() => {
          getApiDetails(dispatch);
        }, API_REFRESH_INTERVAL / 2);
      }
    }
  });
}

export function doControlRequest(nodeId, control, dispatch) {
  clearTimeout(controlErrorTimer);
  const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/`
    + `${encodeURIComponent(control.nodeId)}/${control.id}`;
  doRequest({
    method: 'POST',
    url,
    success: (res) => {
      dispatch(receiveControlSuccess(nodeId));
      if (res) {
        if (res.pipe) {
          dispatch(blurSearch());
          const resizeTtyControl = res.resize_tty_control &&
            {id: res.resize_tty_control, probeId: control.probeId, nodeId: control.nodeId};
          dispatch(receiveControlPipe(
            res.pipe,
            nodeId,
            res.raw_tty,
            resizeTtyControl,
            control
          ));
        }
        if (res.removedNode) {
          dispatch(receiveControlNodeRemoved(nodeId));
        }
      }
    },
    error: (err) => {
      dispatch(receiveControlError(nodeId, err.response));
      controlErrorTimer = setTimeout(() => {
        dispatch(clearControlError(nodeId));
      }, 10000);
    }
  });
}

export function doResizeTty(pipeId, control, cols, rows) {
  const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/`
    + `${encodeURIComponent(control.nodeId)}/${control.id}`;

  return doRequest({
    method: 'POST',
    url,
    data: JSON.stringify({pipeID: pipeId, width: cols.toString(), height: rows.toString()}),
  })
    .fail((err) => {
      log(`Error resizing pipe: ${err}`);
    });
}

export function deletePipe(pipeId, dispatch) {
  const url = `${getApiPath()}/api/pipe/${encodeURIComponent(pipeId)}`;
  doRequest({
    method: 'DELETE',
    url,
    success: () => {
      log('Closed the pipe!');
    },
    error: (err) => {
      log(`Error closing pipe:${err}`);
      dispatch(receiveError(url));
    }
  });
}

export function getPipeStatus(pipeId, dispatch) {
  const url = `${getApiPath()}/api/pipe/${encodeURIComponent(pipeId)}/check`;
  doRequest({
    method: 'GET',
    url,
    complete: (res) => {
      const status = {
        204: 'PIPE_ALIVE',
        404: 'PIPE_DELETED'
      }[res.status];

      if (!status) {
        log('Unexpected pipe status:', res.status);
        return;
      }

      dispatch(receiveControlPipeStatus(pipeId, status));
    }
  });
}

export function stopPolling() {
  clearTimeout(apiDetailsTimer);
  clearTimeout(topologyTimer);
  continuePolling = false;
}

export function teardownWebsockets() {
  clearTimeout(reconnectTimer);
  if (socket) {
    socket.onerror = null;
    socket.onclose = null;
    socket.onmessage = null;
    socket.onopen = null;
    socket.close();
    socket = null;
    currentUrl = null;
  }
}
<|endoftext|>


<commit_before>module.exports = Snackbar;

/**
 * Creates a datepicker component.
 */
function Datepicker() {}

Datepicker.prototype.view = __dirname + '/views';
Datepicker.prototype.style = __dirname + '/styles';
Datepicker.prototype.name = 'datepicker';
Datepicker.prototype.components = [];

require('./operations');
require('./actions');
<commit_msg>Add init functions<commit_after>var moment = require('moment');

module.exports = Datepicker;

/**
 * Creates a datepicker component.
 */
function Datepicker() {}

Datepicker.prototype.view = __dirname + '/views';
Datepicker.prototype.style = __dirname + '/styles';
Datepicker.prototype.name = 'datepicker';
Datepicker.prototype.components = [];

require('./operations');
require('./actions');
require('./viewhelpers');

Datepicker.prototype.init = function(model) {
  var initialDate = this.getAttribute('initialDate');
  var today = moment.utc();
  var selectedDate = moment.utc(initialDate);
  var currentDate = selectedDate.clone();
  var month = this._getMonth(selectedDate);

  model.setEach({
    isMonthViewOne: true,
    monthTwoPositionClass: 'days---dates--right',
    monthOne: month,
    selectedDate: selectedDate,
    currentDate: currentDate,
    today: today
  });

  this._initYears();
};

Datepicker.prototype._initYears = function() {
  var options = this.getAttribute('options');
  var currentYear = this.model.get('currentDate').year();
  var firstYear = (options && options.minDate) ? moment(options.minDate).year() : currentYear - 100;
  var lastYear = (options && options.maxDate) ? moment(options.maxDate).year() : currentYear + 100;
  var years = [];

  while (firstYear <= lastYear) {
    years.push(firstYear);
    firstYear++;
  }

  this.model.set('years', years);
};
<|endoftext|>


<commit_before>// Middleware generally used for actions needing parsed queryStrings
import queryString from 'query-string'
import uuid from 'node-uuid'

import {enablePresentationMode} from 'src/shared/actions/app'
import {editRawTextAsync} from 'src/data_explorer/actions/view'
import {templateVariablesSelectedByName} from 'src/dashboards/actions'

export const queryStringConfig = store => next => action => {
  next(action)
  const qs = queryString.parse(window.location.search)

  // Presentation Mode
  if (qs.present === 'true') {
    next(enablePresentationMode())
  }

  // Data Explorer Query Config
  if (qs.query) {
    const query = decodeURIComponent(qs.query)
    const state = store.getState()
    const defaultSource = state.sources.find(source => source.default)
    let id = window.location.hash // Stored on hash to prevent page reload

    if (defaultSource && !id) {
      // Find query by raw text
      for (const qid in state.dataExplorerQueryConfigs) {
        const qc = state.dataExplorerQueryConfigs[qid]
        if (qc && qc.rawText === query) {
          id = qid
        }
      }

      id = id || uuid.v4()

      qs.queryID = id
      window.location.hash = id
    }

    if (defaultSource && !state.dataExplorerQueryConfigs[id]) {
      const url = defaultSource.links.queries
      editRawTextAsync(url, id, query)(next)
    }
  }

  // Select Template Variable By Name
  const dashboardRegex = /\/sources\/(\d+?)\/dashboards\/(\d+?)/
  if (dashboardRegex.test(window.location.pathname)) {
    const dashboardID = window.location.pathname.match(dashboardRegex)[2]
    next(templateVariablesSelectedByName(+dashboardID, qs))
  }
}
<commit_msg>Remove DE QC query string param code for this PR.<commit_after>// Middleware generally used for actions needing parsed queryStrings
import queryString from 'query-string'
import uuid from 'node-uuid'

import {enablePresentationMode} from 'src/shared/actions/app'
import {editRawTextAsync} from 'src/data_explorer/actions/view'
import {templateVariablesSelectedByName} from 'src/dashboards/actions'

export const queryStringConfig = store => next => action => {
  next(action)
  const qs = queryString.parse(window.location.search)

  // Presentation Mode
  if (qs.present === 'true') {
    next(enablePresentationMode())
  }

  // Select Template Variable By Name
  const dashboardRegex = /\/sources\/(\d+?)\/dashboards\/(\d+?)/
  if (dashboardRegex.test(window.location.pathname)) {
    const dashboardID = window.location.pathname.match(dashboardRegex)[2]
    next(templateVariablesSelectedByName(+dashboardID, qs))
  }
}
<|endoftext|>


<commit_before>'use strict';

var Bloodhound = require('corejs-typeahead/dist/bloodhound.js');
var _  = require('lodash');
var ko = require('knockout');
var deflate = require('pako/lib/deflate');

var savedConfig = null;

var GLYPH_SIZE_MIN = 12;
var GLYPH_SIZE_MAX = 30;

////////////////////////////////////////////////////////////////////////////////

var knownKeywords = _(require('../../../../lib/embedded_fonts/client_config'))
  // get list of keywords of all glyphs in the font,
  // flatten array, make sure all elementes are Strings,
  // and return an array with unique elements only
  .map(function (font) {
    return _.map(font.glyphs, function (glyph) {
      return glyph.search;
    });
  })
  .flatten()
  .map(String)
  .uniq()
  .sort()
  .valueOf();

////////////////////////////////////////////////////////////////////////////////

function ToolbarModel() {
  //
  // Essential properties
  //

  this.fontSize = ko.observable(N.app.fontSize());
  this.fontSizeMin = GLYPH_SIZE_MIN;
  this.fontSizeMax = GLYPH_SIZE_MAX;

  this.fontSize.subscribe(_.debounce(function (value) {
    if (value < GLYPH_SIZE_MIN) { return; }

    N.app.fontSize(value);
    N.wire.emit('session_save');
  }, 50));

  // true, after download button pressed, until font buildeing finished
  this.building = ko.observable(false);
  // true while saving session
  this.saving   = ko.observable(false);

  //
  // Proxy to global properties
  //

  this.selectedGlyphs = N.app.fontsList.selectedGlyphs;
  this.selectedCount  = N.app.fontsList.selectedCount;
  this.searchWord     = N.app.searchWord;
  this.searchMode     = N.app.searchMode;
  this.fontName       = N.app.fontName;
  this.cssPrefixText  = N.app.cssPrefixText;
  this.cssUseSuffix   = N.app.cssUseSuffix;
  this.hinting        = N.app.hinting;
  this.apiMode        = N.app.apiMode;
  this.apiUrl         = N.app.apiUrl;

  this.fontName.subscribe(function (value) {
    var cleared = String(value).toLowerCase().replace(/[^a-z0-9_\-]/g, '');
    if (cleared !== value) { N.app.fontName(cleared); }
  });
}

////////////////////////////////////////////////////////////////////////////////

N.wire.once('navigate.done', function (data) {
  var $view   = $('#toolbar'),
      toolbar = new ToolbarModel();

  // Save user session (loaded via API) back to database
  function save() {
    if (!N.app.apiSessionId) { return Promise.resolve(); }

    // Skip first  save attempt (it happens on app init)
    if (!savedConfig) {
      savedConfig = N.app.getConfig();
      return Promise.resolve();
    }

    toolbar.saving(true);
    return N.app.serverSave()
      .then(() => {
        toolbar.saving(false);
      })
      .catch(err => {
        toolbar.saving(false);
        throw err;
      });
  }

  //
  // Autosave session to server in API mode
  //
  N.wire.on('session_save',  _.debounce(function () {
    save().catch(err => N.wire.emit('error', err));
  }, 3000));

  //
  // Save on button press
  //
  N.wire.on('api.update', function api_save() {
    return save();
  });

  //
  // Export on button press (save & go to pingback url)
  //
  N.wire.on('api.export',  function api_export() {
    return save().then(() => {
      window.location = N.app.apiUrl();
    });
  });

  //
  // build font on button press
  //
  N.wire.on('build_font', function build_font() {
    // That should not happen, but check for safety
    if (!N.app.fontsList.selectedCount()) {
      return;
    }

    var config = N.app.getConfig();

    N.logger.debug('About to build font', config);

    toolbar.building(true);

    let rpc_params = {
      config: new Blob(
        [ deflate.gzip(JSON.stringify(config)) ],
        { type: 'application/octet-stream' }
      )
    };

    return N.io.rpc('fontello.font.generate', rpc_params).then(res => {
      toolbar.building(false);

      // inject download url via iframe to start download
      var id = res.id;  // generated file id
      var url = N.router.linkTo('fontello.font.download', { id });
      $('iframe#' + id).remove();
      $('')
        .attr({ id, src: url })
        .css('display', 'none')
        .appendTo(window.document.body);
    }, err => {
      toolbar.building(false);
      throw err;
    });
  });

  //
  // Initialize jquery fontSize slider
  //

  //
  // Initialize Twitter Bootstrap typeahead plugin
  //

  // constructs the suggestion engine
  var keywords = new Bloodhound({
    datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
    queryTokenizer: Bloodhound.tokenizers.whitespace,
    // `states` is an array of state names defined in "The Basics"
    local: $.map(knownKeywords, function (keyword) { return { value: keyword }; })
  });
  keywords.initialize();

  $('#search')
    .on('change input keyup typeahead:selected', function () {
      N.app.searchWord($.trim($(this).val()));
    })
    .on('keyup', function (e) {
      // Clear content on escape
      if (e.keyCode === 27) {
        $(this).val('');
        N.app.searchWord('');
      }
    })
    .typeahead({
      hint: true,
      highlight: true,
      minLength: 1
    }, {
      name: 'keywords',
      displayKey: 'value',
      // `ttAdapter` wraps the suggestion engine in an adapter that
      // is compatible with the typeahead jQuery plugin
      source: keywords.ttAdapter()
    })
    .focus();

  //
  // Initialize "get config" button
  //

  $('.toolbar__get-config').on('click', function (event) {
    var config = N.app.getConfig();
    var href = 'data:application/octet-stream,' + encodeURIComponent(JSON.stringify(config, null, '  '));
    event.target.href = href;
  });

  //
  // Apply KO bindings
  //

  ko.applyBindings(toolbar, $view.get(0));

  //
  // Setup initial search string.
  //
  $.fn.setCursorPosition = function (pos) {
    if ($(this).get(0).setSelectionRange) {
      $(this).get(0).setSelectionRange(pos, pos);
    } else if ($(this).get(0).createTextRange) {
      var range = $(this).get(0).createTextRange();
      range.collapse(true);
      range.moveEnd('character', pos);
      range.moveStart('character', pos);
      range.select();
    }
  };

  var txt;

  if (data.params && data.params.search) {
    txt = data.params.search;
    $view.find('#search')
      .val(txt)
      .setCursorPosition(txt.length);
    N.app.searchWord(txt);
  }

});
<commit_msg>Remove check for first time save (no longer relevant)<commit_after>'use strict';

var Bloodhound = require('corejs-typeahead/dist/bloodhound.js');
var _  = require('lodash');
var ko = require('knockout');
var deflate = require('pako/lib/deflate');

var GLYPH_SIZE_MIN = 12;
var GLYPH_SIZE_MAX = 30;

////////////////////////////////////////////////////////////////////////////////

var knownKeywords = _(require('../../../../lib/embedded_fonts/client_config'))
  // get list of keywords of all glyphs in the font,
  // flatten array, make sure all elementes are Strings,
  // and return an array with unique elements only
  .map(function (font) {
    return _.map(font.glyphs, function (glyph) {
      return glyph.search;
    });
  })
  .flatten()
  .map(String)
  .uniq()
  .sort()
  .valueOf();

////////////////////////////////////////////////////////////////////////////////

function ToolbarModel() {
  //
  // Essential properties
  //

  this.fontSize = ko.observable(N.app.fontSize());
  this.fontSizeMin = GLYPH_SIZE_MIN;
  this.fontSizeMax = GLYPH_SIZE_MAX;

  this.fontSize.subscribe(_.debounce(function (value) {
    if (value < GLYPH_SIZE_MIN) { return; }

    N.app.fontSize(value);
    N.wire.emit('session_save');
  }, 50));

  // true, after download button pressed, until font buildeing finished
  this.building = ko.observable(false);
  // true while saving session
  this.saving   = ko.observable(false);

  //
  // Proxy to global properties
  //

  this.selectedGlyphs = N.app.fontsList.selectedGlyphs;
  this.selectedCount  = N.app.fontsList.selectedCount;
  this.searchWord     = N.app.searchWord;
  this.searchMode     = N.app.searchMode;
  this.fontName       = N.app.fontName;
  this.cssPrefixText  = N.app.cssPrefixText;
  this.cssUseSuffix   = N.app.cssUseSuffix;
  this.hinting        = N.app.hinting;
  this.apiMode        = N.app.apiMode;
  this.apiUrl         = N.app.apiUrl;

  this.fontName.subscribe(function (value) {
    var cleared = String(value).toLowerCase().replace(/[^a-z0-9_\-]/g, '');
    if (cleared !== value) { N.app.fontName(cleared); }
  });
}

////////////////////////////////////////////////////////////////////////////////

N.wire.once('navigate.done', function (data) {
  var $view   = $('#toolbar'),
      toolbar = new ToolbarModel();

  // Save user session (loaded via API) back to database
  function save() {
    if (!N.app.apiSessionId) { return Promise.resolve(); }

    toolbar.saving(true);
    return N.app.serverSave()
      .then(() => {
        toolbar.saving(false);
      })
      .catch(err => {
        toolbar.saving(false);
        throw err;
      });
  }

  //
  // Autosave session to server in API mode
  //
  N.wire.on('session_save',  _.debounce(function () {
    save().catch(err => N.wire.emit('error', err));
  }, 3000));

  //
  // Save on button press
  //
  N.wire.on('api.update', function api_save() {
    return save();
  });

  //
  // Export on button press (save & go to pingback url)
  //
  N.wire.on('api.export',  function api_export() {
    return save().then(() => {
      window.location = N.app.apiUrl();
    });
  });

  //
  // build font on button press
  //
  N.wire.on('build_font', function build_font() {
    // That should not happen, but check for safety
    if (!N.app.fontsList.selectedCount()) {
      return;
    }

    var config = N.app.getConfig();

    N.logger.debug('About to build font', config);

    toolbar.building(true);

    let rpc_params = {
      config: new Blob(
        [ deflate.gzip(JSON.stringify(config)) ],
        { type: 'application/octet-stream' }
      )
    };

    return N.io.rpc('fontello.font.generate', rpc_params).then(res => {
      toolbar.building(false);

      // inject download url via iframe to start download
      var id = res.id;  // generated file id
      var url = N.router.linkTo('fontello.font.download', { id });
      $('iframe#' + id).remove();
      $('')
        .attr({ id, src: url })
        .css('display', 'none')
        .appendTo(window.document.body);
    }, err => {
      toolbar.building(false);
      throw err;
    });
  });

  //
  // Initialize jquery fontSize slider
  //

  //
  // Initialize Twitter Bootstrap typeahead plugin
  //

  // constructs the suggestion engine
  var keywords = new Bloodhound({
    datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
    queryTokenizer: Bloodhound.tokenizers.whitespace,
    // `states` is an array of state names defined in "The Basics"
    local: $.map(knownKeywords, function (keyword) { return { value: keyword }; })
  });
  keywords.initialize();

  $('#search')
    .on('change input keyup typeahead:selected', function () {
      N.app.searchWord($.trim($(this).val()));
    })
    .on('keyup', function (e) {
      // Clear content on escape
      if (e.keyCode === 27) {
        $(this).val('');
        N.app.searchWord('');
      }
    })
    .typeahead({
      hint: true,
      highlight: true,
      minLength: 1
    }, {
      name: 'keywords',
      displayKey: 'value',
      // `ttAdapter` wraps the suggestion engine in an adapter that
      // is compatible with the typeahead jQuery plugin
      source: keywords.ttAdapter()
    })
    .focus();

  //
  // Initialize "get config" button
  //

  $('.toolbar__get-config').on('click', function (event) {
    var config = N.app.getConfig();
    var href = 'data:application/octet-stream,' + encodeURIComponent(JSON.stringify(config, null, '  '));
    event.target.href = href;
  });

  //
  // Apply KO bindings
  //

  ko.applyBindings(toolbar, $view.get(0));

  //
  // Setup initial search string.
  //
  $.fn.setCursorPosition = function (pos) {
    if ($(this).get(0).setSelectionRange) {
      $(this).get(0).setSelectionRange(pos, pos);
    } else if ($(this).get(0).createTextRange) {
      var range = $(this).get(0).createTextRange();
      range.collapse(true);
      range.moveEnd('character', pos);
      range.moveStart('character', pos);
      range.select();
    }
  };

  var txt;

  if (data.params && data.params.search) {
    txt = data.params.search;
    $view.find('#search')
      .val(txt)
      .setCursorPosition(txt.length);
    N.app.searchWord(txt);
  }

});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Link } from 'react-router';

import OrganizationSelect from "../../components/OrganizationSelect";
import Pagination from "../../components/Pagination";
import ApplicationStore from "../../stores/ApplicationStore";
import SessionStore from "../../stores/SessionStore";

class ApplicationRow extends Component {
  render() {
    return(

        {this.props.application.id}
        <Link to={`/organizations/${this.props.application.organizationID}/applications/${this.props.application.id}`}>{this.props.application.name}</Link>
        {this.props.application.description}

    );
  }
}

class ListApplications extends Component {
  constructor() {
    super();

    this.state = {
      pageSize: 20,
      applications: [],
      isAdmin: false,
      pageNumber: 1,
      pages: 1,
    };

    this.updatePage = this.updatePage.bind(this);
  }

  componentDidMount() {
    this.updatePage(this.props);

    SessionStore.on("change", () => {
      this.setState({
        isAdmin: SessionStore.isAdmin() || SessionStore.isOrganizationAdmin(this.props.params.organizationID),
      });
    });
  }

  componentWillReceiveProps(nextProps) {
    this.updatePage(nextProps);
  }

  updatePage(props) {
    this.setState({
      isAdmin: SessionStore.isAdmin() || SessionStore.isOrganizationAdmin(props.params.organizationID),
    });

    const page = (props.location.query.page === undefined) ? 1 : props.location.query.page;

    ApplicationStore.getAllForOrganization(props.params.organizationID, this.state.pageSize, (page-1) * this.state.pageSize, (totalCount, applications) => {
      this.setState({
        applications: applications,
        pageNumber: page,
        pages: Math.ceil(totalCount / this.state.pageSize),
      });
      window.scrollTo(0, 0);
    });
  }

  render () {
    const ApplicationRows = this.state.applications.map((application, i) => <ApplicationRow key={application.id} application={application} />);

    return(

<OrganizationSelect organizationID={this.props.params.organizationID} />

<Link to={`/organizations/${this.props.params.organizationID}`}>Dashboard</Link>

Applications

              <Link className={(this.state.isAdmin ? '' : 'hidden')} to={`/organizations/${this.props.params.organizationID}/applications/create`}><button type="button" className="btn btn-default">Create application</button></Link>
              <Link to={`/organizations/${this.props.params.organizationID}/gateways`}><button type="button" className="btn btn-default">Gateways</button></Link>
              <Link className={(this.state.isAdmin ? '' : 'hidden')} to={`/organizations/${this.props.params.organizationID}/users`}><button type="button" className="btn btn-default">Users</button></Link>

                  ID
                  Name
                  Description

                {ApplicationRows}

          <Pagination pages={this.state.pages} currentPage={this.state.pageNumber} pathname="/" />

    );
  }
}

export default ListApplications;
<commit_msg>Hide gateways button when org can’t have gws.<commit_after>import React, { Component } from 'react';
import { Link } from 'react-router';

import OrganizationSelect from "../../components/OrganizationSelect";
import Pagination from "../../components/Pagination";
import ApplicationStore from "../../stores/ApplicationStore";
import SessionStore from "../../stores/SessionStore";
import OrganizationStore from "../../stores/OrganizationStore";

class ApplicationRow extends Component {
  render() {
    return(

        {this.props.application.id}
        <Link to={`/organizations/${this.props.application.organizationID}/applications/${this.props.application.id}`}>{this.props.application.name}</Link>
        {this.props.application.description}

    );
  }
}

class ListApplications extends Component {
  constructor() {
    super();

    this.state = {
      pageSize: 20,
      applications: [],
      organization: {},
      isAdmin: false,
      pageNumber: 1,
      pages: 1,
    };

    this.updatePage = this.updatePage.bind(this);
  }

  componentDidMount() {
    this.updatePage(this.props);

    OrganizationStore.getOrganization(this.props.params.organizationID, (org) => {
      this.setState({
        organization: org,
      });
    });

    SessionStore.on("change", () => {
      this.setState({
        isAdmin: SessionStore.isAdmin() || SessionStore.isOrganizationAdmin(this.props.params.organizationID),
      });
    });
  }

  componentWillReceiveProps(nextProps) {
    this.updatePage(nextProps);
  }

  updatePage(props) {
    this.setState({
      isAdmin: SessionStore.isAdmin() || SessionStore.isOrganizationAdmin(props.params.organizationID),
    });

    const page = (props.location.query.page === undefined) ? 1 : props.location.query.page;

    ApplicationStore.getAllForOrganization(props.params.organizationID, this.state.pageSize, (page-1) * this.state.pageSize, (totalCount, applications) => {
      this.setState({
        applications: applications,
        pageNumber: page,
        pages: Math.ceil(totalCount / this.state.pageSize),
      });
      window.scrollTo(0, 0);
    });
  }

  render () {
    const ApplicationRows = this.state.applications.map((application, i) => <ApplicationRow key={application.id} application={application} />);

    return(

<OrganizationSelect organizationID={this.props.params.organizationID} />

<Link to={`/organizations/${this.props.params.organizationID}`}>Dashboard</Link>

Applications

              <Link className={(this.state.isAdmin ? '' : 'hidden')} to={`/organizations/${this.props.params.organizationID}/applications/create`}><button type="button" className="btn btn-default">Create application</button></Link>
              <Link className={(this.state.isAdmin ? '' : 'hidden')} to={`/organizations/${this.props.params.organizationID}/users`}><button type="button" className="btn btn-default">Users</button></Link>
              <Link className={(this.state.organization.canHaveGateways ? '' : 'hidden')} to={`/organizations/${this.props.params.organizationID}/gateways`}><button type="button" className="btn btn-default">Gateways</button></Link>

                  ID
                  Name
                  Description

                {ApplicationRows}

          <Pagination pages={this.state.pages} currentPage={this.state.pageNumber} pathname="/" />

    );
  }
}

export default ListApplications;
<|endoftext|>


<commit_before>/*global window, nodeca, jQuery, Handlebars, Backbone, $, _*/

"use strict";

module.exports = Backbone.View.extend({
  tagName:    "li",
  events:     {"click .glyph": "onClickGlyph"},

  initialize: function () {
    var self = this;

    this.$el.attr("id", "fm-font-" + this.model.id);

    this.model.on("change",   this.render,  this);
    this.model.on("destroy",  this.remove,  this);

    this.$el.selectable({
      stop: function () {
        self.$('.glyph.ui-selected').each(function () {
          self.onClickGlyph({currentTarget: this});
        });
      }
    });
  },

  render: function () {
    var $info;

    this.$el.html(nodeca.client.fontomas.render('font-item', {
      id:         this.model.id,
      fontname:   this.model.get("font").fullname,
      css_class:  "font-embedded-" + this.model.get("embedded_id")
    }));

    // render info html
    $info = $(nodeca.client.fontomas.render('selector:font-info', this.model.toJSON()));

    // assign modal window popup handler
    this.$('.font-info').click(function () {
      $info.appendTo(window.document.body).modal();
      // prevent default browser behavior - jump to the top
      return false;
    });

    // process each glyph
    _.each(this.model.get("glyphs"), function (item, glyph_id) {
      var glyph = nodeca.client.fontomas.render('glyph-item', {
        glyph_id: glyph_id,
        tags:     (item.search || []).join(' '),
        unicode:  nodeca.client.fontomas.util.fixedFromCharCode(item.code)
      });

      this.$(".fm-glyph-group").append(glyph);
    }, this);

    return this;
  },

  remove: function () {
    this.$el.remove();
    this.trigger("remove", this.model);
  },

  onClickGlyph: function (event) {
    var $target   = $(event.currentTarget),
        glyph_id  = parseInt($target.attr("data-glyph-id"), 10),
        data      = this.model.getGlyph(glyph_id),
        selected;

    data = _.extend(data, {
      font_id:      this.model.id,
      glyph_id:     glyph_id,
      embedded_id:  this.model.get("embedded_id")
    });

    selected = $target.hasClass("selected");
    $target.toggleClass("selected", !selected);

    this.trigger("toggleGlyph", data);
  }
});
<commit_msg>Fix selectable with single clicking<commit_after>/*global window, nodeca, jQuery, Handlebars, Backbone, $, _*/

"use strict";

module.exports = Backbone.View.extend({
  tagName:    "li",
  events:     {"click .glyph": "onClickGlyph"},

  initialize: function () {
    var self = this;

    this.$el.attr("id", "fm-font-" + this.model.id);

    this.model.on("change",   this.render,  this);
    this.model.on("destroy",  this.remove,  this);

    this.$el.selectable({
      stop: function () {
        self.$('.glyph.ui-selected').each(function () {
          self.onClickGlyph({currentTarget: this});
        });
      }
    });
  },

  render: function () {
    var $info;

    this.$el.html(nodeca.client.fontomas.render('font-item', {
      id:         this.model.id,
      fontname:   this.model.get("font").fullname,
      css_class:  "font-embedded-" + this.model.get("embedded_id")
    }));

    // render info html
    $info = $(nodeca.client.fontomas.render('selector:font-info', this.model.toJSON()));

    // assign modal window popup handler
    this.$('.font-info').click(function () {
      $info.appendTo(window.document.body).modal();
      // prevent default browser behavior - jump to the top
      return false;
    });

    // process each glyph
    _.each(this.model.get("glyphs"), function (item, glyph_id) {
      var glyph = nodeca.client.fontomas.render('glyph-item', {
        glyph_id: glyph_id,
        tags:     (item.search || []).join(' '),
        unicode:  nodeca.client.fontomas.util.fixedFromCharCode(item.code)
      });

      this.$(".fm-glyph-group").append(glyph);
    }, this);

    return this;
  },

  remove: function () {
    this.$el.remove();
    this.trigger("remove", this.model);
  },

  onClickGlyph: function (event) {
    var $target   = $(event.currentTarget),
        glyph_id  = parseInt($target.attr("data-glyph-id"), 10),
        data      = this.model.getGlyph(glyph_id),
        selected;

    data = _.extend(data, {
      font_id:      this.model.id,
      glyph_id:     glyph_id,
      embedded_id:  this.model.get("embedded_id")
    });

    selected = $target.hasClass("selected");
    $target.toggleClass("selected", !selected);

    if (event.stopPropagation) {
      event.stopPropagation();
    }

    this.trigger("toggleGlyph", data);
  }
});
<|endoftext|>


<commit_before>"use strict";

import Immutable  from 'immutable';
import { Constants as AssessmentConstants }   from '../actions/assessment_progress';

const initialState = Immutable.fromJS({
  isSubmitted:           false,
  isStarted:             false,
  currentItemIndex:      0,

  // Number of 'check answer' api calls that have not yet returned
  numQuestionsChecking:  0,

  selectedAnswerId:      '',
  checkedResponses:      [],
  responses:             [],
  startedAt:             0,
  finishedAt:            0,
  assessmentResult:      null,
});

export default (state = initialState, action) => {

  switch(action.type){
    case AssessmentConstants.ASSESSMENT_NEXT_QUESTIONS:
      var currentItemIndex = state.get("currentItemIndex");
      var increment = action.pageSize;
      state = state.set("currentItemIndex", currentItemIndex + increment);
      break;

    case AssessmentConstants.ASSESSMENT_PREVIOUS_QUESTIONS:
      var currentItemIndex = state.get("currentItemIndex");
      var decrement = action.pageSize;
      state = state.set("currentItemIndex", currentItemIndex - decrement);
      break;

    case AssessmentConstants.ASSESSMENT_VIEWED:
      state = state.set("startedAt", Date.now());
      break;

    case AssessmentConstants.ANSWER_SELECTED:
      var responses = state.getIn(['responses', `${action.questionIndex}`]);
      if(responses === undefined || action.exclusive === true){
        // Create new list if no list exists or multi answer is not allowed
        responses = Immutable.List();
      }

      var answerIndex = responses.indexOf(action.answerId);
      var shouldToggle = !action.exclusive;

      // Only add answer to responses array if it doesn't exist
      if(answerIndex > -1){
        // Don't toggle Radio buttons
        if(shouldToggle){responses = responses.delete(answerIndex);}

      } else {
        responses = responses.push(action.answerId);
      }

      state = state.setIn(["responses", `${action.questionIndex}`], responses);
      break;

    case AssessmentConstants.ASSESSMENT_CHECK_ANSWER_DONE:
      if(!action.error){
        var checkedResponses = Immutable.Map();

        // TODO: Currently we are setting the same response for all userInput.
        // When we have an example of multi answer feedback we should figure out
        // how to assign feedback to each answer.
        action.userInput.forEach((id) => {
          var feedback = Immutable.Map(action.payload);
          checkedResponses = checkedResponses.set(id,feedback);
        });

        state = state.setIn(
          ['checkedResponses', `${action.questionIndex}`],
          checkedResponses
        );
      }

      // Decrement number of questions being checked
      var checked = state.get('numQuestionsChecking');
      if(checked <= 0){
        throw "ASSESSMENT_CHECK_ANSWER_DONE dispatched when no answers were being checked";
      }
      state = state.set('numQuestionsChecking', checked - 1);
      break;

    case AssessmentConstants.CHECK_QUESTIONS:
      var checking = state.get('numQuestionsChecking');
      state = state.set('numQuestionsChecking', action.numQuestions + checking);
      break;

    case AssessmentConstants.ASSESSMENT_SUBMITTED_DONE:
      state = state.set("finishedAt", Date.now());
      state = state.set("isSubmitted", true);
      break;

    default:

  }
  return state;
};
<commit_msg>cleanup<commit_after>"use strict";

import Immutable  from 'immutable';
import { Constants as AssessmentConstants }   from '../actions/assessment_progress';

const initialState = Immutable.fromJS({
  isSubmitted:           false,
  isStarted:             false,
  currentItemIndex:      0,

  // Number of 'check answer' api calls that have not yet returned
  numQuestionsChecking:  0,
  selectedAnswerId:      '',
  checkedResponses:      [],
  responses:             [],
  startedAt:             0,
  finishedAt:            0,
  assessmentResult:      null
});

export default (state = initialState, action) => {

  switch(action.type){
    case AssessmentConstants.ASSESSMENT_NEXT_QUESTIONS:
      var currentItemIndex = state.get("currentItemIndex");
      var increment = action.pageSize;
      state = state.set("currentItemIndex", currentItemIndex + increment);
      break;

    case AssessmentConstants.ASSESSMENT_PREVIOUS_QUESTIONS:
      var currentItemIndex = state.get("currentItemIndex");
      var decrement = action.pageSize;
      state = state.set("currentItemIndex", currentItemIndex - decrement);
      break;

    case AssessmentConstants.ASSESSMENT_VIEWED:
      state = state.set("startedAt", Date.now());
      break;

    case AssessmentConstants.ANSWER_SELECTED:
      var responses = state.getIn(['responses', `${action.questionIndex}`]);
      if(responses === undefined || action.exclusive === true){
        // Create new list if no list exists or multi answer is not allowed
        responses = Immutable.List();
      }

      var answerIndex = responses.indexOf(action.answerId);
      var shouldToggle = !action.exclusive;

      // Only add answer to responses array if it doesn't exist
      if(answerIndex > -1){
        // Don't toggle Radio buttons
        if(shouldToggle){responses = responses.delete(answerIndex);}

      } else {
        responses = responses.push(action.answerId);
      }

      state = state.setIn(["responses", `${action.questionIndex}`], responses);
      break;

    case AssessmentConstants.ASSESSMENT_CHECK_ANSWER_DONE:
      if(!action.error){
        var checkedResponses = Immutable.Map();

        // TODO: Currently we are setting the same response for all userInput.
        // When we have an example of multi answer feedback we should figure out
        // how to assign feedback to each answer.
        action.userInput.forEach((id) => {
          var feedback = Immutable.Map(action.payload);
          checkedResponses = checkedResponses.set(id,feedback);
        });

        state = state.setIn(
          ['checkedResponses', `${action.questionIndex}`],
          checkedResponses
        );
      }

      // Decrement number of questions being checked
      var checked = state.get('numQuestionsChecking');
      if(checked <= 0){
        throw "ASSESSMENT_CHECK_ANSWER_DONE dispatched when no answers were being checked";
      }
      state = state.set('numQuestionsChecking', checked - 1);
      break;

    case AssessmentConstants.CHECK_QUESTIONS:
      var checking = state.get('numQuestionsChecking');
      state = state.set('numQuestionsChecking', action.numQuestions + checking);
      break;

    case AssessmentConstants.ASSESSMENT_SUBMITTED_DONE:
      state = state.set("finishedAt", Date.now());
      state = state.set("isSubmitted", true);
      break;

    default:

  }
  return state;
};
<|endoftext|>


<commit_before>import React, { Component, Fragment } from 'react'
import PropTypes from 'prop-types'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faLink } from '@fortawesome/free-solid-svg-icons'
import DataTable from 'lib/components/DataTable'
import MutedZero from 'lib/components/MutedZero'
import Pending from 'lib/components/Pending'
import ReadableTime from 'lib/components/ReadableTime'

/* eslint-disable react/prop-types, camelcase */
const cols = [
  {
    title: <FontAwesomeIcon icon={faLink} />,
    classes: 'min',
    value: ({ post_url }) => (

        <FontAwesomeIcon icon={faLink} />

    )
  },
  {
    title: 'Feed',
    classes: '',
    value: ({ feed_name, feed_url }) => (
      <Link to={feed_url}>{feed_name}</Link>
    )
  },
  {
    title: 'Comments',
    classes: '',
    value: ({ comments }) => (
      <MutedZero value={comments.length} />
    )
  },
  {
    title: 'Attachments',
    classes: '',
    value: ({ attachments }) => (
      <MutedZero value={attachments.length} />
    )
  },
  {
    title: 'Published at',
    classes: '',
    value: ({ published_at }) => (
      <ReadableTime value={published_at} />
    )
  },
  {
    title: 'Created at',
    classes: '',
    value: ({ created_at }) => (
      <ReadableTime value={created_at} />
    )
  },
]
/* eslint-enable react/prop-types, camelcase */

class Main extends Component {
  componentDidMount () {
    const { load } = this.props
    load()
  }

  render () {
    const { posts, pending } = this.props

    if (pending) {
      return (
        <Pending />
      )
    }

    return (
      <Fragment>

Recent posts

        <DataTable
          cols={cols}
          records={posts}
        />
      </Fragment>
    )
  }
}

Main.propTypes = {
  posts: PropTypes.array,
  load: PropTypes.func,
  pending: PropTypes.bool
}

Main.defaultProps = {
  posts: [],
  load: undefined,
  pending: false
}

export default Main
<commit_msg>Fix Freefeed group link URL<commit_after>import React, { Component, Fragment } from 'react'
import PropTypes from 'prop-types'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faLink } from '@fortawesome/free-solid-svg-icons'
import DataTable from 'lib/components/DataTable'
import MutedZero from 'lib/components/MutedZero'
import Pending from 'lib/components/Pending'
import ReadableTime from 'lib/components/ReadableTime'

/* eslint-disable react/prop-types, camelcase */
const cols = [
  {
    title: <FontAwesomeIcon icon={faLink} />,
    classes: 'min',
    value: ({ post_url }) => (

        <FontAwesomeIcon icon={faLink} />

    )
  },
  {
    title: 'Feed',
    classes: '',
    value: ({ feed_name, feed_url }) => (
      {feed_name}
    )
  },
  {
    title: 'Comments',
    classes: '',
    value: ({ comments }) => (
      <MutedZero value={comments.length} />
    )
  },
  {
    title: 'Attachments',
    classes: '',
    value: ({ attachments }) => (
      <MutedZero value={attachments.length} />
    )
  },
  {
    title: 'Published at',
    classes: '',
    value: ({ published_at }) => (
      <ReadableTime value={published_at} />
    )
  },
  {
    title: 'Created at',
    classes: '',
    value: ({ created_at }) => (
      <ReadableTime value={created_at} />
    )
  },
]
/* eslint-enable react/prop-types, camelcase */

class Main extends Component {
  componentDidMount () {
    const { load } = this.props
    load()
  }

  render () {
    const { posts, pending } = this.props

    if (pending) {
      return (
        <Pending />
      )
    }

    return (
      <Fragment>

Recent posts

        <DataTable
          cols={cols}
          records={posts}
        />
      </Fragment>
    )
  }
}

Main.propTypes = {
  posts: PropTypes.array,
  load: PropTypes.func,
  pending: PropTypes.bool
}

Main.defaultProps = {
  posts: [],
  load: undefined,
  pending: false
}

export default Main
<|endoftext|>