<commit_before>/*
 * @class Tooltip
 * @inherits DivOverlay
 * @aka L.Tooltip
 * Used to display small texts on top of map layers.
 *
 * @example
 *
 * ```js
 * marker.bindTooltip("my tooltip text").openTooltip();
 * ```
 * Note about tooltip offset. Leaflet takes two options in consideration
 * for computing tooltip offseting:
 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
 *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
 *   move it to the bottom. Negatives will move to the left and top.
 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
 *   should adapt this value if you use a custom icon.
 */

// @namespace Tooltip
L.Tooltip = L.DivOverlay.extend({

	// @section
	// @aka Tooltip options
	options: {
		// @option pane: String = 'tooltipPane'
		// `Map pane` where the tooltip will be added.
		pane: 'tooltipPane',

		// @option offset: Point = Point(0, 0)
		// Optional offset of the tooltip position.
		offset: [0, 0],

		// @option direction: String = 'auto'
		// Direction where to open the tooltip. Possible values are: `right`, `left`,
		// `top`, `bottom`, `center`, `auto`.
		// `auto` will dynamicaly switch between `right` and `left` according to the tooltip
		// position on the map.
		direction: 'auto',

		// @option permanent: Boolean = false
		// Whether to open the tooltip permanently or only on mouseover.
		permanent: false,

		// @option sticky: Boolean = false
		// If true, the tooltip will follow the mouse instead of being fixed at the feature center.
		sticky: false,

		// @option interactive: Boolean = false
		// If true, the tooltip will listen to the feature events.
		interactive: false,

		// @option opacity: Number = 0.9
		// Tooltip container opacity.
		opacity: 0.9
	},

	onAdd: function (map) {
		L.DivOverlay.prototype.onAdd.call(this, map);
		this.setOpacity(this.options.opacity);

		// @namespace Map
		// @section Tooltip events
		// @event tooltipopen: TooltipEvent
		// Fired when a tooltip is opened in the map.
		map.fire('tooltipopen', {tooltip: this});

		if (this._source) {
			// @namespace Layer
			// @section Tooltip events
			// @event tooltipopen: TooltipEvent
			// Fired when a tooltip bound to this layer is opened.
			this._source.fire('tooltipopen', {tooltip: this}, true);
		}
	},

	onRemove: function (map) {
		L.DivOverlay.prototype.onRemove.call(this, map);

		// @namespace Map
		// @section Tooltip events
		// @event tooltipclose: TooltipEvent
		// Fired when a tooltip in the map is closed.
		map.fire('tooltipclose', {tooltip: this});

		if (this._source) {
			// @namespace Layer
			// @section Tooltip events
			// @event tooltipclose: TooltipEvent
			// Fired when a tooltip bound to this layer is closed.
			this._source.fire('tooltipclose', {tooltip: this}, true);
		}
	},

	getEvents: function () {
		var events = L.DivOverlay.prototype.getEvents.call(this);

		if (L.Browser.touch && !this.options.permanent) {
			events.preclick = this._close;
		}

		return events;
	},

	_close: function () {
		if (this._map) {
			this._map.closeTooltip(this);
		}
	},

	_initLayout: function () {
		var prefix = 'leaflet-tooltip',
		    className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');

		this._contentNode = this._container = L.DomUtil.create('div', className);
	},

	_updateLayout: function () {},

	_adjustPan: function () {},

	_setPosition: function (pos) {
		var map = this._map,
		    container = this._container,
		    centerPoint = map.latLngToContainerPoint(map.getCenter()),
		    tooltipPoint = map.layerPointToContainerPoint(pos),
		    direction = this.options.direction,
		    tooltipWidth = container.offsetWidth,
		    tooltipHeight = container.offsetHeight,
		    offset = L.point(this.options.offset),
		    anchor = this._getAnchor();

		if (direction === 'top') {
			pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y));
		} else if (direction === 'bottom') {
			pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y));
		} else if (direction === 'center') {
			pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y));
		} else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
			direction = 'right';
			pos = pos.add([offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y]);
		} else {
			direction = 'left';
			pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y));
		}

		L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
		L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
		L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
		L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
		L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
		L.DomUtil.setPosition(container, pos);
	},

	_updatePosition: function () {
		var pos = this._map.latLngToLayerPoint(this._latlng);
		this._setPosition(pos);
	},

	setOpacity: function (opacity) {
		this.options.opacity = opacity;

		if (this._container) {
			L.DomUtil.setOpacity(this._container, opacity);
		}
	},

	_animateZoom: function (e) {
		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
		this._setPosition(pos);
	},

	_getAnchor: function () {
		// Where should we anchor the tooltip on the source layer?
		return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
	}

});

// @namespace Tooltip
// @factory L.tooltip(options?: Tooltip options, source?: Layer)
// Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
L.tooltip = function (options, source) {
	return new L.Tooltip(options, source);
};

// @namespace Map
// @section Methods for Layers and Controls
L.Map.include({

	// @method openTooltip(tooltip: Tooltip): this
	// Opens the specified tooltip.
	// @alternative
	// @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
	// Creates a tooltip with the specified content and options and open it.
	openTooltip: function (tooltip, latlng, options) {
		if (!(tooltip instanceof L.Tooltip)) {
			tooltip = new L.Tooltip(options).setContent(tooltip);
		}

		if (latlng) {
			tooltip.setLatLng(latlng);
		}

		if (this.hasLayer(tooltip)) {
			return this;
		}

		return this.addLayer(tooltip);
	},

	// @method closeTooltip(tooltip?: Tooltip): this
	// Closes the tooltip given as parameter.
	closeTooltip: function (tooltip) {
		if (tooltip) {
			this.removeLayer(tooltip);
		}
		return this;
	}

});

/*
 * @namespace Layer
 * @section Tooltip methods example
 *
 * All layers share a set of methods convenient for binding tooltips to it.
 *
 * ```js
 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
 * layer.openTooltip();
 * layer.closeTooltip();
 * ```
 */

// @section Tooltip methods
L.Layer.include({

	// @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
	// Binds a tooltip to the layer with the passed `content` and sets up the
	// neccessary event listeners. If a `Function` is passed it will receive
	// the layer as the first argument and should return a `String` or `HTMLElement`.
	bindTooltip: function (content, options) {

		if (content instanceof L.Tooltip) {
			L.setOptions(content, options);
			this._tooltip = content;
			content._source = this;
		} else {
			if (!this._tooltip || options) {
				this._tooltip = L.tooltip(options, this);
			}
			this._tooltip.setContent(content);

		}

		this._initTooltipInteractions();

		if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
			this.openTooltip();
		}

		return this;
	},

	// @method unbindTooltip(): this
	// Removes the tooltip previously bound with `bindTooltip`.
	unbindTooltip: function () {
		if (this._tooltip) {
			this._initTooltipInteractions(true);
			this.closeTooltip();
			this._tooltip = null;
		}
		return this;
	},

	_initTooltipInteractions: function (remove) {
		if (!remove && this._tooltipHandlersAdded) { return; }
		var onOff = remove ? 'off' : 'on',
		    events = {
			remove: this.closeTooltip,
			move: this._moveTooltip
		    };
		if (!this._tooltip.options.permanent) {
			events.mouseover = this._openTooltip;
			events.mouseout = this.closeTooltip;
			if (this._tooltip.options.sticky) {
				events.mousemove = this._moveTooltip;
			}
			if (L.Browser.touch) {
				events.click = this._openTooltip;
			}
		} else {
			events.add = this._openTooltip;
		}
		this[onOff](events);
		this._tooltipHandlersAdded = !remove;
	},

	// @method openTooltip(latlng?: LatLng): this
	// Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
	openTooltip: function (layer, latlng) {
		if (!(layer instanceof L.Layer)) {
			latlng = layer;
			layer = this;
		}

		if (layer instanceof L.FeatureGroup) {
			for (var id in this._layers) {
				layer = this._layers[id];
				break;
			}
		}

		if (!latlng) {
			latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
		}

		if (this._tooltip && this._map) {

			// set tooltip source to this layer
			this._tooltip._source = layer;

			// update the tooltip (content, layout, ect...)
			this._tooltip.update();

			// open the tooltip on the map
			this._map.openTooltip(this._tooltip, latlng);

			// Tooltip container may not be defined if not permanent and never
			// opened.
			if (this._tooltip.options.interactive && this._tooltip._container) {
				L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
				this.addInteractiveTarget(this._tooltip._container);
			}
		}

		return this;
	},

	// @method closeTooltip(): this
	// Closes the tooltip bound to this layer if it is open.
	closeTooltip: function () {
		if (this._tooltip) {
			this._tooltip._close();
			if (this._tooltip.options.interactive && this._tooltip._container) {
				L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
				this.removeInteractiveTarget(this._tooltip._container);
			}
		}
		return this;
	},

	// @method toggleTooltip(): this
	// Opens or closes the tooltip bound to this layer depending on its current state.
	toggleTooltip: function (target) {
		if (this._tooltip) {
			if (this._tooltip._map) {
				this.closeTooltip();
			} else {
				this.openTooltip(target);
			}
		}
		return this;
	},

	// @method isTooltipOpen(): boolean
	// Returns `true` if the tooltip bound to this layer is currently open.
	isTooltipOpen: function () {
		return this._tooltip.isOpen();
	},

	// @method setTooltipContent(content: String|HTMLElement|Tooltip): this
	// Sets the content of the tooltip bound to this layer.
	setTooltipContent: function (content) {
		if (this._tooltip) {
			this._tooltip.setContent(content);
		}
		return this;
	},

	// @method getTooltip(): Tooltip
	// Returns the tooltip bound to this layer.
	getTooltip: function () {
		return this._tooltip;
	},

	_openTooltip: function (e) {
		var layer = e.layer || e.target;

		if (!this._tooltip || !this._map) {
			return;
		}
		this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
	},

	_moveTooltip: function (e) {
		var latlng = e.latlng, containerPoint, layerPoint;
		if (this._tooltip.options.sticky && e.originalEvent) {
			containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
			layerPoint = this._map.containerPointToLayerPoint(containerPoint);
			latlng = this._map.layerPointToLatLng(layerPoint);
		}
		this._tooltip.setLatLng(latlng);
	}
});
<commit_msg>round tooltip position (#5089)<commit_after>/*
 * @class Tooltip
 * @inherits DivOverlay
 * @aka L.Tooltip
 * Used to display small texts on top of map layers.
 *
 * @example
 *
 * ```js
 * marker.bindTooltip("my tooltip text").openTooltip();
 * ```
 * Note about tooltip offset. Leaflet takes two options in consideration
 * for computing tooltip offseting:
 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
 *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
 *   move it to the bottom. Negatives will move to the left and top.
 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
 *   should adapt this value if you use a custom icon.
 */

// @namespace Tooltip
L.Tooltip = L.DivOverlay.extend({

	// @section
	// @aka Tooltip options
	options: {
		// @option pane: String = 'tooltipPane'
		// `Map pane` where the tooltip will be added.
		pane: 'tooltipPane',

		// @option offset: Point = Point(0, 0)
		// Optional offset of the tooltip position.
		offset: [0, 0],

		// @option direction: String = 'auto'
		// Direction where to open the tooltip. Possible values are: `right`, `left`,
		// `top`, `bottom`, `center`, `auto`.
		// `auto` will dynamicaly switch between `right` and `left` according to the tooltip
		// position on the map.
		direction: 'auto',

		// @option permanent: Boolean = false
		// Whether to open the tooltip permanently or only on mouseover.
		permanent: false,

		// @option sticky: Boolean = false
		// If true, the tooltip will follow the mouse instead of being fixed at the feature center.
		sticky: false,

		// @option interactive: Boolean = false
		// If true, the tooltip will listen to the feature events.
		interactive: false,

		// @option opacity: Number = 0.9
		// Tooltip container opacity.
		opacity: 0.9
	},

	onAdd: function (map) {
		L.DivOverlay.prototype.onAdd.call(this, map);
		this.setOpacity(this.options.opacity);

		// @namespace Map
		// @section Tooltip events
		// @event tooltipopen: TooltipEvent
		// Fired when a tooltip is opened in the map.
		map.fire('tooltipopen', {tooltip: this});

		if (this._source) {
			// @namespace Layer
			// @section Tooltip events
			// @event tooltipopen: TooltipEvent
			// Fired when a tooltip bound to this layer is opened.
			this._source.fire('tooltipopen', {tooltip: this}, true);
		}
	},

	onRemove: function (map) {
		L.DivOverlay.prototype.onRemove.call(this, map);

		// @namespace Map
		// @section Tooltip events
		// @event tooltipclose: TooltipEvent
		// Fired when a tooltip in the map is closed.
		map.fire('tooltipclose', {tooltip: this});

		if (this._source) {
			// @namespace Layer
			// @section Tooltip events
			// @event tooltipclose: TooltipEvent
			// Fired when a tooltip bound to this layer is closed.
			this._source.fire('tooltipclose', {tooltip: this}, true);
		}
	},

	getEvents: function () {
		var events = L.DivOverlay.prototype.getEvents.call(this);

		if (L.Browser.touch && !this.options.permanent) {
			events.preclick = this._close;
		}

		return events;
	},

	_close: function () {
		if (this._map) {
			this._map.closeTooltip(this);
		}
	},

	_initLayout: function () {
		var prefix = 'leaflet-tooltip',
		    className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');

		this._contentNode = this._container = L.DomUtil.create('div', className);
	},

	_updateLayout: function () {},

	_adjustPan: function () {},

	_setPosition: function (pos) {
		var map = this._map,
		    container = this._container,
		    centerPoint = map.latLngToContainerPoint(map.getCenter()),
		    tooltipPoint = map.layerPointToContainerPoint(pos),
		    direction = this.options.direction,
		    tooltipWidth = container.offsetWidth,
		    tooltipHeight = container.offsetHeight,
		    offset = L.point(this.options.offset),
		    anchor = this._getAnchor();

		if (direction === 'top') {
			pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
		} else if (direction === 'bottom') {
			pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y, true));
		} else if (direction === 'center') {
			pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
		} else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
			direction = 'right';
			pos = pos.add(L.point(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
		} else {
			direction = 'left';
			pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
		}

		L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
		L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
		L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
		L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
		L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
		L.DomUtil.setPosition(container, pos);
	},

	_updatePosition: function () {
		var pos = this._map.latLngToLayerPoint(this._latlng);
		this._setPosition(pos);
	},

	setOpacity: function (opacity) {
		this.options.opacity = opacity;

		if (this._container) {
			L.DomUtil.setOpacity(this._container, opacity);
		}
	},

	_animateZoom: function (e) {
		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
		this._setPosition(pos);
	},

	_getAnchor: function () {
		// Where should we anchor the tooltip on the source layer?
		return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
	}

});

// @namespace Tooltip
// @factory L.tooltip(options?: Tooltip options, source?: Layer)
// Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
L.tooltip = function (options, source) {
	return new L.Tooltip(options, source);
};

// @namespace Map
// @section Methods for Layers and Controls
L.Map.include({

	// @method openTooltip(tooltip: Tooltip): this
	// Opens the specified tooltip.
	// @alternative
	// @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
	// Creates a tooltip with the specified content and options and open it.
	openTooltip: function (tooltip, latlng, options) {
		if (!(tooltip instanceof L.Tooltip)) {
			tooltip = new L.Tooltip(options).setContent(tooltip);
		}

		if (latlng) {
			tooltip.setLatLng(latlng);
		}

		if (this.hasLayer(tooltip)) {
			return this;
		}

		return this.addLayer(tooltip);
	},

	// @method closeTooltip(tooltip?: Tooltip): this
	// Closes the tooltip given as parameter.
	closeTooltip: function (tooltip) {
		if (tooltip) {
			this.removeLayer(tooltip);
		}
		return this;
	}

});

/*
 * @namespace Layer
 * @section Tooltip methods example
 *
 * All layers share a set of methods convenient for binding tooltips to it.
 *
 * ```js
 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
 * layer.openTooltip();
 * layer.closeTooltip();
 * ```
 */

// @section Tooltip methods
L.Layer.include({

	// @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
	// Binds a tooltip to the layer with the passed `content` and sets up the
	// neccessary event listeners. If a `Function` is passed it will receive
	// the layer as the first argument and should return a `String` or `HTMLElement`.
	bindTooltip: function (content, options) {

		if (content instanceof L.Tooltip) {
			L.setOptions(content, options);
			this._tooltip = content;
			content._source = this;
		} else {
			if (!this._tooltip || options) {
				this._tooltip = L.tooltip(options, this);
			}
			this._tooltip.setContent(content);

		}

		this._initTooltipInteractions();

		if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
			this.openTooltip();
		}

		return this;
	},

	// @method unbindTooltip(): this
	// Removes the tooltip previously bound with `bindTooltip`.
	unbindTooltip: function () {
		if (this._tooltip) {
			this._initTooltipInteractions(true);
			this.closeTooltip();
			this._tooltip = null;
		}
		return this;
	},

	_initTooltipInteractions: function (remove) {
		if (!remove && this._tooltipHandlersAdded) { return; }
		var onOff = remove ? 'off' : 'on',
		    events = {
			remove: this.closeTooltip,
			move: this._moveTooltip
		    };
		if (!this._tooltip.options.permanent) {
			events.mouseover = this._openTooltip;
			events.mouseout = this.closeTooltip;
			if (this._tooltip.options.sticky) {
				events.mousemove = this._moveTooltip;
			}
			if (L.Browser.touch) {
				events.click = this._openTooltip;
			}
		} else {
			events.add = this._openTooltip;
		}
		this[onOff](events);
		this._tooltipHandlersAdded = !remove;
	},

	// @method openTooltip(latlng?: LatLng): this
	// Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
	openTooltip: function (layer, latlng) {
		if (!(layer instanceof L.Layer)) {
			latlng = layer;
			layer = this;
		}

		if (layer instanceof L.FeatureGroup) {
			for (var id in this._layers) {
				layer = this._layers[id];
				break;
			}
		}

		if (!latlng) {
			latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
		}

		if (this._tooltip && this._map) {

			// set tooltip source to this layer
			this._tooltip._source = layer;

			// update the tooltip (content, layout, ect...)
			this._tooltip.update();

			// open the tooltip on the map
			this._map.openTooltip(this._tooltip, latlng);

			// Tooltip container may not be defined if not permanent and never
			// opened.
			if (this._tooltip.options.interactive && this._tooltip._container) {
				L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
				this.addInteractiveTarget(this._tooltip._container);
			}
		}

		return this;
	},

	// @method closeTooltip(): this
	// Closes the tooltip bound to this layer if it is open.
	closeTooltip: function () {
		if (this._tooltip) {
			this._tooltip._close();
			if (this._tooltip.options.interactive && this._tooltip._container) {
				L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
				this.removeInteractiveTarget(this._tooltip._container);
			}
		}
		return this;
	},

	// @method toggleTooltip(): this
	// Opens or closes the tooltip bound to this layer depending on its current state.
	toggleTooltip: function (target) {
		if (this._tooltip) {
			if (this._tooltip._map) {
				this.closeTooltip();
			} else {
				this.openTooltip(target);
			}
		}
		return this;
	},

	// @method isTooltipOpen(): boolean
	// Returns `true` if the tooltip bound to this layer is currently open.
	isTooltipOpen: function () {
		return this._tooltip.isOpen();
	},

	// @method setTooltipContent(content: String|HTMLElement|Tooltip): this
	// Sets the content of the tooltip bound to this layer.
	setTooltipContent: function (content) {
		if (this._tooltip) {
			this._tooltip.setContent(content);
		}
		return this;
	},

	// @method getTooltip(): Tooltip
	// Returns the tooltip bound to this layer.
	getTooltip: function () {
		return this._tooltip;
	},

	_openTooltip: function (e) {
		var layer = e.layer || e.target;

		if (!this._tooltip || !this._map) {
			return;
		}
		this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
	},

	_moveTooltip: function (e) {
		var latlng = e.latlng, containerPoint, layerPoint;
		if (this._tooltip.options.sticky && e.originalEvent) {
			containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
			layerPoint = this._map.containerPointToLayerPoint(containerPoint);
			latlng = this._map.layerPointToLatLng(layerPoint);
		}
		this._tooltip.setLatLng(latlng);
	}
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Class constructor for Layout MDL component.
 * Implements MDL component design pattern defined at:
 * https://github.com/jasonmayes/mdl-component-design-pattern
 * @param {HTMLElement} element The element that will be upgraded.
 */
function MaterialLayout(element) {
  'use strict';

  this.element_ = element;

  // Initialize instance.
  this.init();
}

/**
 * Store constants in one place so they can be updated easily.
 * @enum {string | number}
 * @private
 */
MaterialLayout.prototype.Constant_ = {
  MAX_WIDTH: '(max-width: 850px)',
  TAB_SCROLL_PIXELS: 100,

  MENU_ICON: 'menu',
  CHEVRON_LEFT: 'chevron_left',
  CHEVRON_RIGHT: 'chevron_right'
};

/**
 * Modes.
 * @enum {number}
 * @private
 */
MaterialLayout.prototype.Mode_ = {
  STANDARD: 0,
  SEAMED: 1,
  WATERFALL: 2,
  SCROLL: 3
};

/**
 * Store strings for class names defined by this component that are used in
 * JavaScript. This allows us to simply change it in one place should we
 * decide to modify at a later date.
 * @enum {string}
 * @private
 */
MaterialLayout.prototype.CssClasses_ = {
  CONTAINER: 'mdl-layout__container',
  HEADER: 'mdl-layout__header',
  DRAWER: 'mdl-layout__drawer',
  CONTENT: 'mdl-layout__content',
  DRAWER_BTN: 'mdl-layout__drawer-button',

  ICON: 'material-icons',

  JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
  RIPPLE_CONTAINER: 'mdl-layout__tab-ripple-container',
  RIPPLE: 'mdl-ripple',
  RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',

  HEADER_SEAMED: 'mdl-layout__header--seamed',
  HEADER_WATERFALL: 'mdl-layout__header--waterfall',
  HEADER_SCROLL: 'mdl-layout__header--scroll',

  FIXED_HEADER: 'mdl-layout--fixed-header',
  OBFUSCATOR: 'mdl-layout__obfuscator',

  TAB_BAR: 'mdl-layout__tab-bar',
  TAB_CONTAINER: 'mdl-layout__tab-bar-container',
  TAB: 'mdl-layout__tab',
  TAB_BAR_BUTTON: 'mdl-layout__tab-bar-button',
  TAB_BAR_LEFT_BUTTON: 'mdl-layout__tab-bar-left-button',
  TAB_BAR_RIGHT_BUTTON: 'mdl-layout__tab-bar-right-button',
  PANEL: 'mdl-layout__tab-panel',

  HAS_DRAWER: 'has-drawer',
  HAS_TABS: 'has-tabs',
  HAS_SCROLLING_HEADER: 'has-scrolling-header',
  CASTING_SHADOW: 'is-casting-shadow',
  IS_COMPACT: 'is-compact',
  IS_SMALL_SCREEN: 'is-small-screen',
  IS_DRAWER_OPEN: 'is-visible',
  IS_ACTIVE: 'is-active',
  IS_UPGRADED: 'is-upgraded',
  IS_ANIMATING: 'is-animating'
};

/**
 * Handles scrolling on the content.
 * @private
 */
MaterialLayout.prototype.contentScrollHandler_ = function() {
  'use strict';

  if (this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)) {
    return;
  }

  if (this.content_.scrollTop > 0 &&
      !this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
    this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
    this.header_.classList.add(this.CssClasses_.IS_COMPACT);
    this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
  } else if (this.content_.scrollTop <= 0 &&
      this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
    this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
    this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
    this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
  }
};

/**
 * Handles changes in screen size.
 * @private
 */
MaterialLayout.prototype.screenSizeHandler_ = function() {
  'use strict';

  if (this.screenSizeMediaQuery_.matches) {
    this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN);
  } else {
    this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN);
    // Collapse drawer (if any) when moving to a large screen size.
    if (this.drawer_) {
      this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
    }
  }
};

/**
 * Handles toggling of the drawer.
 * @param {Element} drawer The drawer container element.
 * @private
 */
MaterialLayout.prototype.drawerToggleHandler_ = function() {
  'use strict';

  this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
};

/**
 * Handles (un)setting the `is-animating` class
 */
MaterialLayout.prototype.headerTransitionEndHandler = function() {
  'use strict';

  this.header_.classList.remove(this.CssClasses_.IS_ANIMATING);
};

/**
 * Handles expanding the header on click
 */
MaterialLayout.prototype.headerClickHandler = function() {
  'use strict';

  if (this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
    this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
    this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
  }
};

/**
 * Reset tab state, dropping active classes
 * @private
 */
MaterialLayout.prototype.resetTabState_ = function(tabBar) {
  'use strict';

  for (var k = 0; k < tabBar.length; k++) {
    tabBar[k].classList.remove(this.CssClasses_.IS_ACTIVE);
  }
};

/**
 * Reset panel state, droping active classes
 * @private
 */
MaterialLayout.prototype.resetPanelState_ = function(panels) {
  'use strict';

  for (var j = 0; j < panels.length; j++) {
    panels[j].classList.remove(this.CssClasses_.IS_ACTIVE);
  }
};

/**
 * Initialize element.
 */
MaterialLayout.prototype.init = function() {
  'use strict';

  if (this.element_) {
    var container = document.createElement('div');
    container.classList.add(this.CssClasses_.CONTAINER);
    this.element_.parentElement.insertBefore(container, this.element_);
    this.element_.parentElement.removeChild(this.element_);
    container.appendChild(this.element_);

    var directChildren = this.element_.childNodes;
    for (var c = 0; c < directChildren.length; c++) {
      var child = directChildren[c];
      if (child.classList &&
          child.classList.contains(this.CssClasses_.HEADER)) {
        this.header_ = child;
      }

      if (child.classList &&
          child.classList.contains(this.CssClasses_.DRAWER)) {
        this.drawer_ = child;
      }

      if (child.classList &&
          child.classList.contains(this.CssClasses_.CONTENT)) {
        this.content_ = child;
      }
    }

    if (this.header_) {
      this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR);
    }

    var mode = this.Mode_.STANDARD;

    // Keep an eye on screen size, and add/remove auxiliary class for styling
    // of small screens.
    this.screenSizeMediaQuery_ = window.matchMedia(this.Constant_.MAX_WIDTH);
    this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this));
    this.screenSizeHandler_();

    if (this.header_) {
      if (this.header_.classList.contains(this.CssClasses_.HEADER_SEAMED)) {
        mode = this.Mode_.SEAMED;
      } else if (this.header_.classList.contains(
          this.CssClasses_.HEADER_WATERFALL)) {
        mode = this.Mode_.WATERFALL;
        this.header_.addEventListener('transitionend',
          this.headerTransitionEndHandler.bind(this));
        this.header_.addEventListener('click',
          this.headerClickHandler.bind(this));
      } else if (this.header_.classList.contains(
          this.CssClasses_.HEADER_SCROLL)) {
        mode = this.Mode_.SCROLL;
        container.classList.add(this.CssClasses_.HAS_SCROLLING_HEADER);
      }

      if (mode === this.Mode_.STANDARD) {
        this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
        if (this.tabBar_) {
          this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW);
        }
      } else if (mode === this.Mode_.SEAMED || mode === this.Mode_.SCROLL) {
        this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
        if (this.tabBar_) {
          this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW);
        }
      } else if (mode === this.Mode_.WATERFALL) {
        // Add and remove shadows depending on scroll position.
        // Also add/remove auxiliary class for styling of the compact version of
        // the header.
        this.content_.addEventListener('scroll',
            this.contentScrollHandler_.bind(this));
        this.contentScrollHandler_();
      }
    }

    // Add drawer toggling button to our layout, if we have an openable drawer.
    if (this.drawer_) {
      var drawerButton = document.createElement('div');
      drawerButton.classList.add(this.CssClasses_.DRAWER_BTN);
      var drawerButtonIcon = document.createElement('i');
      drawerButtonIcon.classList.add(this.CssClasses_.ICON);
      drawerButtonIcon.textContent = this.Constant_.MENU_ICON;
      drawerButton.appendChild(drawerButtonIcon);
      drawerButton.addEventListener('click',
          this.drawerToggleHandler_.bind(this));

      // Add a class if the layout has a drawer, for altering the left padding.
      // Adds the HAS_DRAWER to the elements since this.header_ may or may
      // not be present.
      this.element_.classList.add(this.CssClasses_.HAS_DRAWER);

      // If we have a fixed header, add the button to the header rather than
      // the layout.
      if (this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)) {
        this.header_.insertBefore(drawerButton, this.header_.firstChild);
      } else {
        this.element_.insertBefore(drawerButton, this.content_);
      }

      var obfuscator = document.createElement('div');
      obfuscator.classList.add(this.CssClasses_.OBFUSCATOR);
      this.element_.appendChild(obfuscator);
      obfuscator.addEventListener('click',
          this.drawerToggleHandler_.bind(this));
    }

    // Initialize tabs, if any.
    if (this.header_ && this.tabBar_) {
      this.element_.classList.add(this.CssClasses_.HAS_TABS);

      var tabContainer = document.createElement('div');
      tabContainer.classList.add(this.CssClasses_.TAB_CONTAINER);
      this.header_.insertBefore(tabContainer, this.tabBar_);
      this.header_.removeChild(this.tabBar_);

      var leftButton = document.createElement('div');
      leftButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
      leftButton.classList.add(this.CssClasses_.TAB_BAR_LEFT_BUTTON);
      var leftButtonIcon = document.createElement('i');
      leftButtonIcon.classList.add(this.CssClasses_.ICON);
      leftButtonIcon.textContent = this.Constant_.CHEVRON_LEFT;
      leftButton.appendChild(leftButtonIcon);
      leftButton.addEventListener('click', function() {
        this.tabBar_.scrollLeft -= this.Constant_.TAB_SCROLL_PIXELS;
      }.bind(this));

      var rightButton = document.createElement('div');
      rightButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
      rightButton.classList.add(this.CssClasses_.TAB_BAR_RIGHT_BUTTON);
      var rightButtonIcon = document.createElement('i');
      rightButtonIcon.classList.add(this.CssClasses_.ICON);
      rightButtonIcon.textContent = this.Constant_.CHEVRON_RIGHT;
      rightButton.appendChild(rightButtonIcon);
      rightButton.addEventListener('click', function() {
        this.tabBar_.scrollLeft += this.Constant_.TAB_SCROLL_PIXELS;
      }.bind(this));

      tabContainer.appendChild(leftButton);
      tabContainer.appendChild(this.tabBar_);
      tabContainer.appendChild(rightButton);

      // Add and remove buttons depending on scroll position.
      var tabScrollHandler = function() {
        if (this.tabBar_.scrollLeft > 0) {
          leftButton.classList.add(this.CssClasses_.IS_ACTIVE);
        } else {
          leftButton.classList.remove(this.CssClasses_.IS_ACTIVE);
        }

        if (this.tabBar_.scrollLeft <
            this.tabBar_.scrollWidth - this.tabBar_.offsetWidth) {
          rightButton.classList.add(this.CssClasses_.IS_ACTIVE);
        } else {
          rightButton.classList.remove(this.CssClasses_.IS_ACTIVE);
        }
      }.bind(this);

      this.tabBar_.addEventListener('scroll', tabScrollHandler);
      tabScrollHandler();

      if (this.tabBar_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) {
        this.tabBar_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
      }

      // Select element tabs, document panels
      var tabs = this.tabBar_.querySelectorAll('.' + this.CssClasses_.TAB);
      var panels = this.content_.querySelectorAll('.' + this.CssClasses_.PANEL);

      // Create new tabs for each tab element
      for (var i = 0; i < tabs.length; i++) {
        new MaterialLayoutTab(tabs[i], tabs, panels, this);
      }
    }

    this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
  }
};

function MaterialLayoutTab(tab, tabs, panels, layout) {
  'use strict';

  if (tab) {
    if (layout.tabBar_.classList.contains(
        layout.CssClasses_.JS_RIPPLE_EFFECT)) {
      var rippleContainer = document.createElement('span');
      rippleContainer.classList.add(layout.CssClasses_.RIPPLE_CONTAINER);
      rippleContainer.classList.add(layout.CssClasses_.JS_RIPPLE_EFFECT);
      var ripple = document.createElement('span');
      ripple.classList.add(layout.CssClasses_.RIPPLE);
      rippleContainer.appendChild(ripple);
      tab.appendChild(rippleContainer);
    }

    tab.addEventListener('click', function(e) {
      e.preventDefault();
      var href = tab.href.split('#')[1];
      var panel = layout.content_.querySelector('#' + href);
      layout.resetTabState_(tabs);
      layout.resetPanelState_(panels);
      tab.classList.add(layout.CssClasses_.IS_ACTIVE);
      panel.classList.add(layout.CssClasses_.IS_ACTIVE);
    });

  }
}

// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
  constructor: MaterialLayout,
  classAsString: 'MaterialLayout',
  cssClass: 'mdl-js-layout'
});
<commit_msg>Issue if Drawer has mdl-layout--small-screen-only or large-screen-only. Then the hamburger Icon needs to be hidden on small/large screen.  Fixed this by adding class to slider-Icon(hamburger).<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Class constructor for Layout MDL component.
 * Implements MDL component design pattern defined at:
 * https://github.com/jasonmayes/mdl-component-design-pattern
 * @param {HTMLElement} element The element that will be upgraded.
 */
function MaterialLayout(element) {
  'use strict';

  this.element_ = element;

  // Initialize instance.
  this.init();
}

/**
 * Store constants in one place so they can be updated easily.
 * @enum {string | number}
 * @private
 */
MaterialLayout.prototype.Constant_ = {
  MAX_WIDTH: '(max-width: 850px)',
  TAB_SCROLL_PIXELS: 100,

  MENU_ICON: 'menu',
  CHEVRON_LEFT: 'chevron_left',
  CHEVRON_RIGHT: 'chevron_right'
};

/**
 * Modes.
 * @enum {number}
 * @private
 */
MaterialLayout.prototype.Mode_ = {
  STANDARD: 0,
  SEAMED: 1,
  WATERFALL: 2,
  SCROLL: 3
};

/**
 * Store strings for class names defined by this component that are used in
 * JavaScript. This allows us to simply change it in one place should we
 * decide to modify at a later date.
 * @enum {string}
 * @private
 */
MaterialLayout.prototype.CssClasses_ = {
  CONTAINER: 'mdl-layout__container',
  HEADER: 'mdl-layout__header',
  DRAWER: 'mdl-layout__drawer',
  CONTENT: 'mdl-layout__content',
  DRAWER_BTN: 'mdl-layout__drawer-button',

  ICON: 'material-icons',

  JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
  RIPPLE_CONTAINER: 'mdl-layout__tab-ripple-container',
  RIPPLE: 'mdl-ripple',
  RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',

  HEADER_SEAMED: 'mdl-layout__header--seamed',
  HEADER_WATERFALL: 'mdl-layout__header--waterfall',
  HEADER_SCROLL: 'mdl-layout__header--scroll',

  FIXED_HEADER: 'mdl-layout--fixed-header',
  OBFUSCATOR: 'mdl-layout__obfuscator',

  TAB_BAR: 'mdl-layout__tab-bar',
  TAB_CONTAINER: 'mdl-layout__tab-bar-container',
  TAB: 'mdl-layout__tab',
  TAB_BAR_BUTTON: 'mdl-layout__tab-bar-button',
  TAB_BAR_LEFT_BUTTON: 'mdl-layout__tab-bar-left-button',
  TAB_BAR_RIGHT_BUTTON: 'mdl-layout__tab-bar-right-button',
  PANEL: 'mdl-layout__tab-panel',

  HAS_DRAWER: 'has-drawer',
  HAS_TABS: 'has-tabs',
  HAS_SCROLLING_HEADER: 'has-scrolling-header',
  CASTING_SHADOW: 'is-casting-shadow',
  IS_COMPACT: 'is-compact',
  IS_SMALL_SCREEN: 'is-small-screen',
  IS_DRAWER_OPEN: 'is-visible',
  IS_ACTIVE: 'is-active',
  IS_UPGRADED: 'is-upgraded',
  IS_ANIMATING: 'is-animating',

  ON_LARGE_SCREEN : 'mdl-layout--large-screen-only',
  ON_SMALL_SCREEN  : 'mdl-layout--small-screen-only'

};

/**
 * Handles scrolling on the content.
 * @private
 */
MaterialLayout.prototype.contentScrollHandler_ = function() {
  'use strict';

  if (this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)) {
    return;
  }

  if (this.content_.scrollTop > 0 &&
      !this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
    this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
    this.header_.classList.add(this.CssClasses_.IS_COMPACT);
    this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
  } else if (this.content_.scrollTop <= 0 &&
      this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
    this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
    this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
    this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
  }
};

/**
 * Handles changes in screen size.
 * @private
 */
MaterialLayout.prototype.screenSizeHandler_ = function() {
  'use strict';

  if (this.screenSizeMediaQuery_.matches) {
    this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN);
  } else {
    this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN);
    // Collapse drawer (if any) when moving to a large screen size.
    if (this.drawer_) {
      this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
    }
  }
};

/**
 * Handles toggling of the drawer.
 * @param {Element} drawer The drawer container element.
 * @private
 */
MaterialLayout.prototype.drawerToggleHandler_ = function() {
  'use strict';

  this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
};

/**
 * Handles (un)setting the `is-animating` class
 */
MaterialLayout.prototype.headerTransitionEndHandler = function() {
  'use strict';

  this.header_.classList.remove(this.CssClasses_.IS_ANIMATING);
};

/**
 * Handles expanding the header on click
 */
MaterialLayout.prototype.headerClickHandler = function() {
  'use strict';

  if (this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
    this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
    this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
  }
};

/**
 * Reset tab state, dropping active classes
 * @private
 */
MaterialLayout.prototype.resetTabState_ = function(tabBar) {
  'use strict';

  for (var k = 0; k < tabBar.length; k++) {
    tabBar[k].classList.remove(this.CssClasses_.IS_ACTIVE);
  }
};

/**
 * Reset panel state, droping active classes
 * @private
 */
MaterialLayout.prototype.resetPanelState_ = function(panels) {
  'use strict';

  for (var j = 0; j < panels.length; j++) {
    panels[j].classList.remove(this.CssClasses_.IS_ACTIVE);
  }
};

/**
 * Initialize element.
 */
MaterialLayout.prototype.init = function() {
  'use strict';

  if (this.element_) {
    var container = document.createElement('div');
    container.classList.add(this.CssClasses_.CONTAINER);
    this.element_.parentElement.insertBefore(container, this.element_);
    this.element_.parentElement.removeChild(this.element_);
    container.appendChild(this.element_);

    var directChildren = this.element_.childNodes;
    for (var c = 0; c < directChildren.length; c++) {
      var child = directChildren[c];
      if (child.classList &&
          child.classList.contains(this.CssClasses_.HEADER)) {
        this.header_ = child;
      }

      if (child.classList &&
          child.classList.contains(this.CssClasses_.DRAWER)) {
        this.drawer_ = child;
      }

      if (child.classList &&
          child.classList.contains(this.CssClasses_.CONTENT)) {
        this.content_ = child;
      }
    }

    if (this.header_) {
      this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR);
    }

    var mode = this.Mode_.STANDARD;

    // Keep an eye on screen size, and add/remove auxiliary class for styling
    // of small screens.
    this.screenSizeMediaQuery_ = window.matchMedia(this.Constant_.MAX_WIDTH);
    this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this));
    this.screenSizeHandler_();

    if (this.header_) {
      if (this.header_.classList.contains(this.CssClasses_.HEADER_SEAMED)) {
        mode = this.Mode_.SEAMED;
      } else if (this.header_.classList.contains(
          this.CssClasses_.HEADER_WATERFALL)) {
        mode = this.Mode_.WATERFALL;
        this.header_.addEventListener('transitionend',
          this.headerTransitionEndHandler.bind(this));
        this.header_.addEventListener('click',
          this.headerClickHandler.bind(this));
      } else if (this.header_.classList.contains(
          this.CssClasses_.HEADER_SCROLL)) {
        mode = this.Mode_.SCROLL;
        container.classList.add(this.CssClasses_.HAS_SCROLLING_HEADER);
      }

      if (mode === this.Mode_.STANDARD) {
        this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
        if (this.tabBar_) {
          this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW);
        }
      } else if (mode === this.Mode_.SEAMED || mode === this.Mode_.SCROLL) {
        this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
        if (this.tabBar_) {
          this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW);
        }
      } else if (mode === this.Mode_.WATERFALL) {
        // Add and remove shadows depending on scroll position.
        // Also add/remove auxiliary class for styling of the compact version of
        // the header.
        this.content_.addEventListener('scroll',
            this.contentScrollHandler_.bind(this));
        this.contentScrollHandler_();
      }
    }

    // Add drawer toggling button to our layout, if we have an openable drawer.
    if (this.drawer_) {
      var drawerButton = document.createElement('div');
      drawerButton.classList.add(this.CssClasses_.DRAWER_BTN);

      if (this.drawer_.classList.contains(this.CssClasses_.ON_LARGE_SCREEN)) {
        //If drawer has ON_LARGE_SCREEN class then add it to the drawer toggle button as well.
        drawerButton.classList.add(this.CssClasses_.ON_LARGE_SCREEN);
      } else if (this.drawer_.classList.contains(this.CssClasses_.ON_SMALL_SCREEN)) {
        //If drawer has ON_SMALL_SCREEN class then add it to the drawer toggle button as well.
        drawerButton.classList.add(this.CssClasses_.ON_SMALL_SCREEN);
      }
      var drawerButtonIcon = document.createElement('i');
      drawerButtonIcon.classList.add(this.CssClasses_.ICON);
      drawerButtonIcon.textContent = this.Constant_.MENU_ICON;
      drawerButton.appendChild(drawerButtonIcon);
      drawerButton.addEventListener('click',
          this.drawerToggleHandler_.bind(this));

      // Add a class if the layout has a drawer, for altering the left padding.
      // Adds the HAS_DRAWER to the elements since this.header_ may or may
      // not be present.
      this.element_.classList.add(this.CssClasses_.HAS_DRAWER);

      // If we have a fixed header, add the button to the header rather than
      // the layout.
      if (this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)) {
        this.header_.insertBefore(drawerButton, this.header_.firstChild);
      } else {
        this.element_.insertBefore(drawerButton, this.content_);
      }

      var obfuscator = document.createElement('div');
      obfuscator.classList.add(this.CssClasses_.OBFUSCATOR);
      this.element_.appendChild(obfuscator);
      obfuscator.addEventListener('click',
          this.drawerToggleHandler_.bind(this));
    }

    // Initialize tabs, if any.
    if (this.header_ && this.tabBar_) {
      this.element_.classList.add(this.CssClasses_.HAS_TABS);

      var tabContainer = document.createElement('div');
      tabContainer.classList.add(this.CssClasses_.TAB_CONTAINER);
      this.header_.insertBefore(tabContainer, this.tabBar_);
      this.header_.removeChild(this.tabBar_);

      var leftButton = document.createElement('div');
      leftButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
      leftButton.classList.add(this.CssClasses_.TAB_BAR_LEFT_BUTTON);
      var leftButtonIcon = document.createElement('i');
      leftButtonIcon.classList.add(this.CssClasses_.ICON);
      leftButtonIcon.textContent = this.Constant_.CHEVRON_LEFT;
      leftButton.appendChild(leftButtonIcon);
      leftButton.addEventListener('click', function() {
        this.tabBar_.scrollLeft -= this.Constant_.TAB_SCROLL_PIXELS;
      }.bind(this));

      var rightButton = document.createElement('div');
      rightButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
      rightButton.classList.add(this.CssClasses_.TAB_BAR_RIGHT_BUTTON);
      var rightButtonIcon = document.createElement('i');
      rightButtonIcon.classList.add(this.CssClasses_.ICON);
      rightButtonIcon.textContent = this.Constant_.CHEVRON_RIGHT;
      rightButton.appendChild(rightButtonIcon);
      rightButton.addEventListener('click', function() {
        this.tabBar_.scrollLeft += this.Constant_.TAB_SCROLL_PIXELS;
      }.bind(this));

      tabContainer.appendChild(leftButton);
      tabContainer.appendChild(this.tabBar_);
      tabContainer.appendChild(rightButton);

      // Add and remove buttons depending on scroll position.
      var tabScrollHandler = function() {
        if (this.tabBar_.scrollLeft > 0) {
          leftButton.classList.add(this.CssClasses_.IS_ACTIVE);
        } else {
          leftButton.classList.remove(this.CssClasses_.IS_ACTIVE);
        }

        if (this.tabBar_.scrollLeft <
            this.tabBar_.scrollWidth - this.tabBar_.offsetWidth) {
          rightButton.classList.add(this.CssClasses_.IS_ACTIVE);
        } else {
          rightButton.classList.remove(this.CssClasses_.IS_ACTIVE);
        }
      }.bind(this);

      this.tabBar_.addEventListener('scroll', tabScrollHandler);
      tabScrollHandler();

      if (this.tabBar_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) {
        this.tabBar_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
      }

      // Select element tabs, document panels
      var tabs = this.tabBar_.querySelectorAll('.' + this.CssClasses_.TAB);
      var panels = this.content_.querySelectorAll('.' + this.CssClasses_.PANEL);

      // Create new tabs for each tab element
      for (var i = 0; i < tabs.length; i++) {
        new MaterialLayoutTab(tabs[i], tabs, panels, this);
      }
    }

    this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
  }
};

function MaterialLayoutTab(tab, tabs, panels, layout) {
  'use strict';

  if (tab) {
    if (layout.tabBar_.classList.contains(
        layout.CssClasses_.JS_RIPPLE_EFFECT)) {
      var rippleContainer = document.createElement('span');
      rippleContainer.classList.add(layout.CssClasses_.RIPPLE_CONTAINER);
      rippleContainer.classList.add(layout.CssClasses_.JS_RIPPLE_EFFECT);
      var ripple = document.createElement('span');
      ripple.classList.add(layout.CssClasses_.RIPPLE);
      rippleContainer.appendChild(ripple);
      tab.appendChild(rippleContainer);
    }

    tab.addEventListener('click', function(e) {
      e.preventDefault();
      var href = tab.href.split('#')[1];
      var panel = layout.content_.querySelector('#' + href);
      layout.resetTabState_(tabs);
      layout.resetPanelState_(panels);
      tab.classList.add(layout.CssClasses_.IS_ACTIVE);
      panel.classList.add(layout.CssClasses_.IS_ACTIVE);
    });

  }
}

// The component registers itself. It can assume componentHandler is available
// in the global scope.
componentHandler.register({
  constructor: MaterialLayout,
  classAsString: 'MaterialLayout',
  cssClass: 'mdl-js-layout'
});
<|endoftext|>


<commit_before>import partition from 'lodash.partition';

export default function(items, props) {
  const { columns, columnWidth } = props;

  const columnHeights = [];
  for (let i = 0; i < columns; i++) { columnHeights.push(0); }

  let tries = 0;
  let positions = [];
  const positionsHash = {};

  if (items.length) {
    const toLayout = [...items];

    while (toLayout.length) {

      const toLayoutRow = [];
      const toLayoutColumnHeights = [...columnHeights];

      for (let i = 0, j = 0; i < toLayout.length && j < columns; i++) {
        const item = toLayout[i];
        if (!item.doubleSize) j++;
        toLayoutRow.push(item);
      }

      const [double, single] = partition(toLayoutRow, item => item.doubleSize);

      for (let i = 0; i < columns - 1; i++) {
        if (double.length && toLayoutColumnHeights[i] !== null &&
              toLayoutColumnHeights[i] === toLayoutColumnHeights[i + 1]) {
          const item = double.shift();
          const height = Math.round(item.itemRect.height);
          const x = i * columnWidth;
          const y = toLayoutColumnHeights[i];

          toLayoutColumnHeights[i] = null;
          if (i < toLayoutColumnHeights.length - 1) toLayoutColumnHeights[i + 1] = null;

          columnHeights[i] += height;
          columnHeights[i + 1] += height;

          toLayout.splice(toLayout.indexOf(item), 1);
          positionsHash[item.key] = [x, y];
        }
      }

      let singleColumns = toLayoutColumnHeights.filter(_ => _ !== null);

      while (singleColumns.length && single.length) {
        const column = toLayoutColumnHeights.indexOf(Math.min.apply(null, singleColumns));

        const item = single.shift();
        const height = Math.round(item.itemRect.height);
        const x = column * columnWidth;
        const y = toLayoutColumnHeights[column];

        toLayoutColumnHeights[column] = null;
        columnHeights[column] += height;

        toLayout.splice(toLayout.indexOf(item), 1);
        positionsHash[item.key] = [x, y];

        singleColumns = toLayoutColumnHeights.filter(_ => _ !== null);
      }

      if (double.length && !single.length) {
        tries++;

        if (tries > 5) {
          console.log('Oh God', double.map(item => item.key));
          break;
        }
      } else {
        tries = 0;
      }

    }

  }

  positions = items.map(item => positionsHash[item.key] || [0, 2000]);

  const gridWidth = columns * columnWidth;
  const gridHeight = Math.max.apply(null, columnHeights);

  return { positions, gridWidth, gridHeight };
}
<commit_msg>Remove root2 layout<commit_after><|endoftext|>


<commit_before>/*
 * @Author: Rodrigo Soares
 * @Date: 2018-01-03 17:48:48
 * @Last Modified by: Rodrigo Soares
 * @Last Modified time: 2019-05-21 13:35:38
 */

/**
 * Check if is artboard
 * @param  {Object}  layer The layers
 * @return {Boolean}
 */
function isArtboard(layer) {
  return layer instanceof MSArtboardGroup || layer instanceof MSSymbolMaster
}

/**
 * Check if has symbol instance
 *
 * @param {Object} layer
 * @returns {Boolean}
 */
function isSymbolInstance(layer) {
  try {
    return (
      layer instanceof MSSymbolInstance && layer.symbolMaster() !== undefined
    )
  } catch (error) {
    return false
  }
}

/**
 * Get the name of the symbol instance
 *
 * @param {Object} layer
 * @returns {String} Name of the symbol
 */
function getSymbolName(layer) {
  let name = ""
  if (isSymbolInstance(layer)) {
    name = String(layer.symbolMaster().name())
  }
  return name
}

/**
 * Check if layer styles is applied
 *
 * @param {Object} layer
 * @returns {Boolean}
 */
function hasLayerStyle(layer) {
  try {
    return layer.sharedStyle() instanceof MSSharedStyle
  } catch (error) {
    return false
  }
}

function getLayerStyle(layer) {
  let name = ""

  if (hasLayerStyle(layer)) {
    name = String(layer.sharedStyle().name())
  }

  return name
}

function layerObject(layer, idx) {
  const parentName =
    layer.parentGroup() == null ? "" : layer.parentGroup().name()

  return {
    layer,
    name: String(layer.name()),
    frame: layer.frame(),
    idx,
    width: Math.floor(layer.frame().width()),
    height: Math.floor(layer.frame().height()),
    parentName: String(parentName),
    symbolName: getSymbolName(layer),
    layerStyle: getLayerStyle(layer)
  }
}

/**
 * Parse common data
 * @param  {Object} context Sketch context
 * @return {Object}         Parsed Data
 */
export function parseData(context, onlyArtboards = false) {
  let contextData = context.selection
  if (onlyArtboards) {
    const aBoards = []
    contextData.forEach(el => {
      while (el && !isArtboard(el)) {
        el = el.parentGroup()
      }
      if (el) aBoards.push(el)
    })
    contextData = Array.from(new Set(aBoards))
  }

  const data = {
    doc: context.document,
    pageName: String(context.document.currentPage().name()),
    selectionCount: Array.isArray(contextData)
      ? contextData.length
      : contextData.count(),
    selection: []
  }

  let hasSymbol = false
  let lStyle = false
  contextData.forEach((layer, i) => {
    data.selection[i] = layerObject(layer, i)

    if (!hasSymbol) hasSymbol = isSymbolInstance(layer)
    if (!lStyle) lStyle = hasLayerStyle(layer)
  })

  data.hasSymbol = hasSymbol
  data.hasLayerStyle = lStyle

  return data
}

export function findReplaceDataParser(context) {
  const data = parseData(context)
  const layers = data.doc.currentPage().children()
  data.allLayers = []

  layers.forEach((layer, i) => {
    data.allLayers[i] = layerObject(layer, i)
  })

  return data
}
<commit_msg>Added trycatch to symbol name<commit_after>/*
 * @Author: Rodrigo Soares
 * @Date: 2018-01-03 17:48:48
 * @Last Modified by: Rodrigo Soares
 * @Last Modified time: 2019-05-22 09:18:40
 */

/**
 * Check if is artboard
 * @param  {Object}  layer The layers
 * @return {Boolean}
 */
function isArtboard(layer) {
  return layer instanceof MSArtboardGroup || layer instanceof MSSymbolMaster
}

/**
 * Check if has symbol instance
 *
 * @param {Object} layer
 * @returns {Boolean}
 */
function isSymbolInstance(layer) {
  try {
    return (
      layer instanceof MSSymbolInstance && layer.symbolMaster() !== undefined
    )
  } catch (error) {
    return false
  }
}

/**
 * Get the name of the symbol instance
 *
 * @param {Object} layer
 * @returns {String} Name of the symbol
 */
function getSymbolName(layer) {
  let name = ""
  if (isSymbolInstance(layer)) {
    name = String(layer.symbolMaster().name())
  }
  return name
}

/**
 * Check if layer styles is applied
 *
 * @param {Object} layer
 * @returns {Boolean}
 */
function hasLayerStyle(layer) {
  try {
    return layer.sharedStyle() instanceof MSSharedStyle
  } catch (error) {
    return false
  }
}

function getLayerStyle(layer) {
  let name = ""

  if (hasLayerStyle(layer)) {
    try {
      name = String(layer.sharedStyle().name())
      // eslint-disable-next-line no-empty
    } catch (error) {}
  }

  return name
}

function layerObject(layer, idx) {
  const parentName =
    layer.parentGroup() == null ? "" : layer.parentGroup().name()

  return {
    layer,
    name: String(layer.name()),
    frame: layer.frame(),
    idx,
    width: Math.floor(layer.frame().width()),
    height: Math.floor(layer.frame().height()),
    parentName: String(parentName),
    symbolName: getSymbolName(layer),
    layerStyle: getLayerStyle(layer)
  }
}

/**
 * Parse common data
 * @param  {Object} context Sketch context
 * @return {Object}         Parsed Data
 */
export function parseData(context, onlyArtboards = false) {
  let contextData = context.selection
  if (onlyArtboards) {
    const aBoards = []
    contextData.forEach(el => {
      while (el && !isArtboard(el)) {
        el = el.parentGroup()
      }
      if (el) aBoards.push(el)
    })
    contextData = Array.from(new Set(aBoards))
  }

  const data = {
    doc: context.document,
    pageName: String(context.document.currentPage().name()),
    selectionCount: Array.isArray(contextData)
      ? contextData.length
      : contextData.count(),
    selection: []
  }

  let hasSymbol = false
  let lStyle = false
  contextData.forEach((layer, i) => {
    data.selection[i] = layerObject(layer, i)

    if (!hasSymbol) hasSymbol = isSymbolInstance(layer)
    if (!lStyle) lStyle = hasLayerStyle(layer)
  })

  data.hasSymbol = hasSymbol
  data.hasLayerStyle = lStyle

  return data
}

export function findReplaceDataParser(context) {
  const data = parseData(context)
  const layers = data.doc.currentPage().children()
  data.allLayers = []

  layers.forEach((layer, i) => {
    data.allLayers[i] = layerObject(layer, i)
  })

  return data
}
<|endoftext|>


<commit_before>import https from 'https';
import { concat } from 'concat-stream';
import { findNearest } from 'find-nearest-file';
import habitat from 'habitat';

export default ({ url, token, done = () => {} }) => {

  try {
    token = token || findToken();
  } catch (error) {
    return done(error);
  }

  https.get(
    `https://api-ssl.bitly.com/v3/shorten?access_token=${token}&longUrl=${url}&format=txt`,
    (res) => res.pipe(concat({ encoding: 'string' }, (res) => done(null, res)))
  ).on('error', done);

};

function findToken() {
  habitat.load(findNearest('.chaunceyrc'));
  const token = habitat.get('BITLY_ACCESS_TOKEN');
  if (!token) throw new Error('BITLY_ACCESS_TOKEN not found');
  return token;
}
<commit_msg>Fix imports<commit_after>import https from 'https';
import concat from 'concat-stream';
import findNearest from 'find-nearest-file';
import habitat from 'habitat';

export default ({ url, token, done = () => {} }) => {

  try {
    token = token || findToken();
  } catch (error) {
    return done(error);
  }

  https.get(
    `https://api-ssl.bitly.com/v3/shorten?access_token=${token}&longUrl=${url}&format=txt`,
    (res) => res.pipe(concat({ encoding: 'string' }, (res) => done(null, res)))
  ).on('error', done);

};

function findToken() {
  habitat.load(findNearest('.chaunceyrc'));
  const token = habitat.get('BITLY_ACCESS_TOKEN');
  if (!token) throw new Error('BITLY_ACCESS_TOKEN not found');
  return token;
}
<|endoftext|>


<commit_before>// __BEGIN_LICENSE__
// Copyright (C) 2008-2010 United States Government as represented by
// the Administrator of the National Aeronautics and Space Administration.
// All Rights Reserved.
// __END_LICENSE__

xgds_plot = {};

$.extend(xgds_plot, {
    MAX_NUM_DATA_POINTS: 500,

    BASE_PLOT_OPTS: {
        xaxis: {
            mode: 'time'
        },
        yaxis: {
            labelWidth: 20
        },
        grid: {
            hoverable: true,
            clickable: true
        },
        shadowSize: 0,
        zoom: {
            interactive: true
        },
        pan: {
            interactive: true
        }
    },

    masterMeta: null,

    plots: [],

    haveNewData: false,

    timeSkew: 0.0,

    liveMode: true,

    epochToString: function (epoch) {
        var d = new Date(epoch);
        dateString = d.toGMTString();
        return dateString.replace(/ GMT$/, '');
    },

    parseIso8601: function (string) {
        var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
            "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
            "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
        var d = string.match(new RegExp(regexp));

        var offset = 0;
        var date = new Date(d[1], 0, 1);

        if (d[3]) { date.setMonth(d[3] - 1); }
        if (d[5]) { date.setDate(d[5]); }
        if (d[7]) { date.setHours(d[7]); }
        if (d[8]) { date.setMinutes(d[8]); }
        if (d[10]) { date.setSeconds(d[10]); }
        if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
        if (d[14]) {
            offset = (Number(d[16]) * 60) + Number(d[17]);
            offset *= ((d[15] == '-') ? 1 : -1);
        }

        offset -= date.getTimezoneOffset();
        time = (Number(date) + (offset * 60 * 1000));
        return Number(time);
    },

    pushTruncate: function (arr, vals, n) {
        if (arr.length >= n-1) {
            arr.splice(0, 1);
        }
        arr.push(vals);
    },

    dotProduct: function (u, v) {
        var n = u.length;
        var ret = 0;
        for (var i=0; i < n; i++) {
            ret += u[i] * v[i];
        }
        return ret;
    },

    gaussianKernel: function (sigma, k) {
        var halfK = Math.floor(0.5 * k);
        var ret = new Array(k);
        var sum = 0;
        for (var i=0; i < k; i++) {
            var x = (i - halfK) / sigma;
            var y = Math.exp(-x*x);
            ret[i] = y;
            sum += y;
        }
        for (var i=0; i < k; i++) {
            ret[i] /= sum;
        }
        return ret;
    },

    getLastKSamples: function (arr, k) {
        var ret = [];
        var n = arr.length;
        for (var i=0; i < k; i++) {
            ret.push(arr[n-i-1][1]);
        }
        return ret;
    },

    /**********************************************************************/

    value: {

        Scalar: function (meta) {
            this.meta = $.extend(true, {}, meta);
            this.raw = [];
            if (this.meta.smoothing) {
                this.smooth = [];
                this.kernelWidth = this.meta.smoothing.sigmaPoints * 4;
                this.halfKernelWidth = Math.floor(0.5 * this.kernelWidth);
                this.kernel = xgds_plot.gaussianKernel(this.meta.smoothing.sigmaPoints,
                                                       this.kernelWidth);
            }
        },

        Ratio: function (meta) {
            this.meta = $.extend(true, {}, meta);
            this.raw = [];
            this.numerator = [];
            this.denominator = [];
            if (this.meta.smoothing) {
                this.smooth = [];
                this.kernelWidth = this.meta.smoothing.sigmaPoints * 4;
                this.halfKernelWidth = Math.floor(0.5 * this.kernelWidth);
                this.kernel = xgds_plot.gaussianKernel(this.meta.smoothing.sigmaPoints,
                                                       this.kernelWidth);
            }
        }

    },

    /**********************************************************************/

    updateRange: function (plot) {
        var xopts = plot.getAxes().xaxis.options;
        var tminSpan = $('#tmin');
        var tmaxSpan = $('#tmax');
        tminSpan.html(xgds_plot.epochToString(xopts.min));
        tmaxSpan.html(xgds_plot.epochToString(xopts.max) + ' UTC');
    },

    matchXRange: function (masterPlot) {
        var masterXopts = masterPlot.getAxes().xaxis.options;
        $.each(xgds_plot.plots, function (i, info) {
            var slavePlot = info.plot;
            if (slavePlot != null && slavePlot != masterPlot) {
                var slaveXopts = slavePlot.getAxes().xaxis.options;

                if (! ((masterXopts.min == slaveXopts.min)
                       && (masterXopts.max == slaveXopts.max))) {
                    slaveXopts.min = masterXopts.min;
                    slaveXopts.max = masterXopts.max;
                    slavePlot.setupGrid();
                    slavePlot.draw();
                }
            }
        });
    },

    setLiveMode: function (liveMode) {
        xgds_plot.liveMode = liveMode;
        var liveModeCheckBox = $('#liveModeCheckBox');
        if (liveMode) {
            liveModeCheckBox.attr('checked', 'checked');
        } else {
            liveModeCheckBox.removeAttr('checked');
        }
    },

    updatePlots: function () {
        $.each(xgds_plot.plots, function (i, info) {
            xgds_plot.updatePlot(info);
        });

        xgds_plot.haveNewData = false;
    },

    updatePlot: function (info) {
        if (!info.show) return;

        var data;
        if (info.plot == undefined || xgds_plot.haveNewData) {
            var data0 = info.timeSeries.getPlotData();
            var raw = data0[0];
            var smooth = data0[1];
            var styledRaw = $.extend(true, {}, raw, info.meta.seriesOpts);
            var styledSmooth = $.extend(true, {}, smooth,
                                        info.meta.smoothing.seriesOpts);
            data = [styledRaw, styledSmooth];
        }

        if (info.plot == undefined) {
            // make new plot
            var opts = $.extend(true,
                                {
                                    hooks: {
                                        draw: xgds_plot.updateRange
                                    }
                                },
                                xgds_plot.BASE_PLOT_OPTS,
                                info.meta.plotOpts);

            var plot = $.plot(info.plotDiv, data, opts);
            info.plot = plot;
            plot.getPlaceholder().bind('plotpan plotzoom', function (plot) {
                return function () {
                    xgds_plot.setLiveMode(false);
                    xgds_plot.matchXRange(plot);
                    // updateData(plot);
                }
            }(plot));
        } else {
            // update old plot
            if (xgds_plot.haveNewData) {
                info.plot.setData(data);
            }
        }

        if (xgds_plot.liveMode) {
            // auto-scroll to current time
            var serverNow = new Date().valueOf() + xgds_plot.timeSkew;
            var xopts = info.plot.getAxes().xaxis.options;
            xopts.min = serverNow - settings.XGDS_PLOT_LIVE_PLOT_HISTORY_LENGTH_MS;
            xopts.max = serverNow;
        }

        info.plot.setupGrid();
        info.plot.draw();
    },

    periodicUpdatePlots: function () {
        xgds_plot.updatePlots();
        setTimeout(xgds_plot.periodicUpdatePlots, 200);
    },

    setupPlotHandlers: function (info) {
        info.plotDiv
            .bind('plothover', function (event, pos, item) {
                if (item) {
                    var pt = item.datapoint;
                    var t = $.plot.formatDate(new Date(pt[0]), '%H:%M:%S');
                    var y = pt[1].toFixed(3);
                    info.infoDiv
                        .html(''
                              + t + ''
                              + '' + y + '');
                }
            });
    },

    onopen: function (zmq) {
        $('#socketStatus').html('connected');

        $.each(xgds_plot.plots, function (i, info) {
            var topic = info.meta.queryModel;
            var handler = function (info) {
                return function (zmq, topic, obj) {
                    info.timeSeries.add(obj);
                    xgds_plot.haveNewData = true;
                };
            }(info);

            zmq.subscribeDjango(topic, handler);
        });
    },

    onclose: function (zmq) {
        $('#socketStatus').html('disconnected');
    },

    handleMasterMeta: function (inMeta) {
        xgds_plot.masterMeta = inMeta;

        // create live mode control
        $('#liveModeControl').html('<input type="checkbox" checked="checked" id="liveModeCheckBox">'
                                   + '</input>'
                                   + ''
                                   + 'Live');
        $('#liveModeCheckBox').change(function () {
            var checked = $(this).attr('checked');
            console.log(checked);
            xgds_plot.liveMode = checked;
        });

        // create show/hide controls for each plot
        var plotControlsHtml = [];
        $.each(xgds_plot.masterMeta, function (i, meta) {
            var checked;
            if (meta.show) {
                checked = 'checked="checked" ';
            } else {
                checked = '';
            }
            plotControlsHtml
                .push('
'
                      + '<input type="checkbox" ' + checked + 'id="showPlot_' + i + '"></input>'
                      + ''
                      + meta.valueName + '
');
        });
        $('#plotControls').html(plotControlsHtml.join(""));

        // create a div for each plot
        var plotsHtml = [];
        $.each(xgds_plot.masterMeta, function (i, meta) {
            var style;
            if (meta.show) {
                style = '';
            } else {
                style = 'style="display: none"';
            }
            plotsHtml.push('
'
                           + '
'
                           + meta.valueName
                           + ''
                           + '
'
                           + '

'
                           + '
');
        });
        $("#plots").html(plotsHtml.join(""));

        // connect to data feed
        var zmqUrl = settings
            .XGDS_ZMQ_WEB_SOCKET_URL
            .replace('{{host}}', window.location.hostname);
        var zmq = new ZmqManager(zmqUrl,
                                 {onopen: xgds_plot.onopen,
                                  onclose: xgds_plot.onclose,
                                  autoReconnect: true});
        zmq.start();

        var timeSeriesTypeRegistry = {
            'xgds_plot.value.Ratio': xgds_plot.value.Ratio,
            'xgds_plot.value.Scalar': xgds_plot.value.Scalar
        };

        // initialize the xgds_plot.plots array
        $.each(xgds_plot.masterMeta, function (i, meta) {
            var timeSeriesType = timeSeriesTypeRegistry[meta.valueType];
            var timeSeries = new timeSeriesType(meta);
            var info = {index: i,
                        meta: meta,
                        plotDiv: $('#plot_' + i),
                        containerDiv: $('#plotContainer_' + i),
                        infoDiv: $('#plotInfo_' + i),
                        show: meta.show,
                        timeSeries: timeSeries,
                        plot: null}
            xgds_plot.plots.push(info);
            xgds_plot.setupPlotHandlers(info);
        });

        // start updating plots
        xgds_plot.periodicUpdatePlots();

    }, // function handleMasterMeta

    handleServerTime: function (serverTime) {
        xgds_plot.timeSkew = serverTime - new Date().valueOf();
        //console.log('timeSkew: ' + xgds_plot.timeSkew);
    },

    init: function () {
        $.getJSON(settings.SCRIPT_NAME + 'xgds_plot/meta.json', xgds_plot.handleMasterMeta);
        $.getJSON(settings.SCRIPT_NAME + 'xgds_plot/now/', xgds_plot.handleServerTime);
    }

}); // xgds_plot namespace

/**********************************************************************/

xgds_plot.value.Scalar.prototype.getValue = function (rec) {
    return [xgds_plot.parseIso8601(rec[this.meta.queryTimestampField]),
            rec[this.meta.valueField]];
}

xgds_plot.value.Scalar.prototype.add = function (rec) {
    var ty = this.getValue(rec);
    var t = ty[0];
    var y = ty[1];

    xgds_plot.pushTruncate(this.raw, [t, y], xgds_plot.MAX_NUM_DATA_POINTS);

    if (this.meta.smoothing && this.raw.length > this.kernelWidth) {
        var ysmooth = xgds_plot
            .dotProduct(this.kernel,
                        xgds_plot.getLastKSamples(this.raw, this.kernelWidth));

        var mid = this.raw.length - this.halfKernelWidth;
        var tmid = this.raw[mid][0];
        xgds_plot.pushTruncate(this.smooth, [tmid, ysmooth],
                               xgds_plot.MAX_NUM_DATA_POINTS - this.halfKernelWidth + 1);
    }
}

xgds_plot.value.Scalar.prototype.getPlotData = function () {
    return [{data: this.raw}, {data: this.smooth}];
}

/**********************************************************************/

xgds_plot.value.Ratio.prototype.getValue = function (rec) {
    return [xgds_plot.parseIso8601(rec[this.meta.queryTimestampField]),
            rec[this.meta.valueFields[0]],
            rec[this.meta.valueFields[1]]];
}

xgds_plot.value.Ratio.prototype.add = function (rec) {
    var tnd = this.getValue(rec);
    var t = tnd[0];
    var ynum = tnd[1];
    var ydenom = tnd[2];

    xgds_plot.pushTruncate(this.numerator, [t, ynum], xgds_plot.MAX_NUM_DATA_POINTS);
    xgds_plot.pushTruncate(this.denominator, [t, ydenom], xgds_plot.MAX_NUM_DATA_POINTS);
    xgds_plot.pushTruncate(this.raw, [t, ynum/ydenom], xgds_plot.MAX_NUM_DATA_POINTS);

    if (this.meta.smoothing && this.raw.length > this.kernelWidth) {
        var numSmooth = xgds_plot
            .dotProduct(this.kernel,
                        xgds_plot.getLastKSamples(this.numerator, this.kernelWidth));
        var denomSmooth = xgds_plot
            .dotProduct(this.kernel,
                        xgds_plot.getLastKSamples(this.denominator, this.kernelWidth));

        var mid = this.raw.length - this.halfKernelWidth;
        var tmid = this.raw[mid][0];

        xgds_plot.pushTruncate(this.smooth, [tmid, numSmooth / denomSmooth],
                               xgds_plot.MAX_NUM_DATA_POINTS - this.halfKernelWidth + 1);
    }
}

xgds_plot.value.Ratio.prototype.getPlotData = xgds_plot.value.Scalar.prototype.getPlotData;

<commit_msg>switched to more flexible (though slower) smoothing approach)<commit_after>// __BEGIN_LICENSE__
// Copyright (C) 2008-2010 United States Government as represented by
// the Administrator of the National Aeronautics and Space Administration.
// All Rights Reserved.
// __END_LICENSE__

xgds_plot = {};

$.extend(xgds_plot, {
    MAX_NUM_DATA_POINTS: 500,

    BASE_PLOT_OPTS: {
        xaxis: {
            mode: 'time'
        },
        yaxis: {
            labelWidth: 20
        },
        grid: {
            hoverable: true,
            clickable: true
        },
        shadowSize: 0,
        zoom: {
            interactive: true
        },
        pan: {
            interactive: true
        }
    },

    masterMeta: null,

    plots: [],

    haveNewData: false,

    timeSkew: 0.0,

    liveMode: true,

    epochToString: function (epoch) {
        var d = new Date(epoch);
        dateString = d.toGMTString();
        return dateString.replace(/ GMT$/, '');
    },

    parseIso8601: function (string) {
        var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
            "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
            "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
        var d = string.match(new RegExp(regexp));

        var offset = 0;
        var date = new Date(d[1], 0, 1);

        if (d[3]) { date.setMonth(d[3] - 1); }
        if (d[5]) { date.setDate(d[5]); }
        if (d[7]) { date.setHours(d[7]); }
        if (d[8]) { date.setMinutes(d[8]); }
        if (d[10]) { date.setSeconds(d[10]); }
        if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
        if (d[14]) {
            offset = (Number(d[16]) * 60) + Number(d[17]);
            offset *= ((d[15] == '-') ? 1 : -1);
        }

        offset -= date.getTimezoneOffset();
        time = (Number(date) + (offset * 60 * 1000));
        return Number(time);
    },

    pushTruncate: function (arr, vals, n) {
        if (arr.length >= n-1) {
            arr.splice(0, 1);
        }
        arr.push(vals);
    },

    dotProduct: function (u, v) {
        var n = u.length;
        var ret = 0;
        for (var i=0; i < n; i++) {
            ret += u[i] * v[i];
        }
        return ret;
    },

    gaussianKernel: function (sigma, k) {
        var halfK = Math.floor(0.5 * k);
        var ret = new Array(k);
        var sum = 0;
        for (var i=0; i < k; i++) {
            var x = (i - halfK) / sigma;
            var y = Math.exp(-x*x);
            ret[i] = y;
            sum += y;
        }
        for (var i=0; i < k; i++) {
            ret[i] /= sum;
        }
        return ret;
    },

    gaussian: function (x, sigma) {
        var xp = x / sigma;
        return Math.exp(-xp * xp);
    },

    getLastKSamples: function (arr, k) {
        var ret = [];
        var n = arr.length;
        for (var i=0; i < k; i++) {
            ret.push(arr[n-i-1][1]);
        }
        return ret;
    },

    /**********************************************************************/

    value: {

        Scalar: function (meta) {
            this.meta = $.extend(true, {}, meta);
            this.raw = [];
            if (this.meta.smoothing) {
                this.smooth = [];
                this.kernelWidth = this.meta.smoothing.sigmaSeconds * 4;
                this.halfKernelWidth = Math.floor(0.5 * this.kernelWidth);
                this.kernel = xgds_plot.gaussianKernel(this.meta.smoothing.sigmaSeconds,
                                                       this.kernelWidth);
            }
        },

        Ratio: function (meta) {
            this.meta = $.extend(true, {}, meta);
            this.raw = [];
            this.numerator = [];
            this.denominator = [];
            if (this.meta.smoothing) {
                this.smooth = [];
                this.kernelWidth = this.meta.smoothing.sigmaSeconds * 4;
                this.halfKernelWidth = Math.floor(0.5 * this.kernelWidth);
                this.kernel = xgds_plot.gaussianKernel(this.meta.smoothing.sigmaSeconds,
                                                       this.kernelWidth);
            }
        }

    },

    /**********************************************************************/

    updateRange: function (plot) {
        var xopts = plot.getAxes().xaxis.options;
        var tminSpan = $('#tmin');
        var tmaxSpan = $('#tmax');
        tminSpan.html(xgds_plot.epochToString(xopts.min));
        tmaxSpan.html(xgds_plot.epochToString(xopts.max) + ' UTC');
    },

    matchXRange: function (masterPlot) {
        var masterXopts = masterPlot.getAxes().xaxis.options;
        $.each(xgds_plot.plots, function (i, info) {
            var slavePlot = info.plot;
            if (slavePlot != null && slavePlot != masterPlot) {
                var slaveXopts = slavePlot.getAxes().xaxis.options;

                if (! ((masterXopts.min == slaveXopts.min)
                       && (masterXopts.max == slaveXopts.max))) {
                    slaveXopts.min = masterXopts.min;
                    slaveXopts.max = masterXopts.max;
                    slavePlot.setupGrid();
                    slavePlot.draw();
                }
            }
        });
    },

    setLiveMode: function (liveMode) {
        xgds_plot.liveMode = liveMode;
        var liveModeCheckBox = $('#liveModeCheckBox');
        if (liveMode) {
            liveModeCheckBox.attr('checked', 'checked');
        } else {
            liveModeCheckBox.removeAttr('checked');
        }
    },

    updatePlots: function () {
        $.each(xgds_plot.plots, function (i, info) {
            xgds_plot.updatePlot(info);
        });

        xgds_plot.haveNewData = false;
    },

    updatePlot: function (info) {
        if (!info.show) return;

        var data;
        if (info.plot == undefined || xgds_plot.haveNewData) {
            var data0 = info.timeSeries.getPlotData();
            var raw = data0[0];
            var smooth = data0[1];
            var styledRaw = $.extend(true, {}, raw, info.meta.seriesOpts);
            var styledSmooth = $.extend(true, {}, smooth,
                                        info.meta.smoothing.seriesOpts);
            data = [styledRaw, styledSmooth];
        }

        if (info.plot == undefined) {
            // make new plot
            var opts = $.extend(true,
                                {
                                    hooks: {
                                        draw: xgds_plot.updateRange
                                    }
                                },
                                xgds_plot.BASE_PLOT_OPTS,
                                info.meta.plotOpts);

            var plot = $.plot(info.plotDiv, data, opts);
            info.plot = plot;
            plot.getPlaceholder().bind('plotpan plotzoom', function (plot) {
                return function () {
                    xgds_plot.setLiveMode(false);
                    xgds_plot.matchXRange(plot);
                    // updateData(plot);
                }
            }(plot));
        } else {
            // update old plot
            if (xgds_plot.haveNewData) {
                info.plot.setData(data);
            }
        }

        if (xgds_plot.liveMode) {
            // auto-scroll to current time
            var serverNow = new Date().valueOf() + xgds_plot.timeSkew;
            var xopts = info.plot.getAxes().xaxis.options;
            xopts.min = serverNow - settings.XGDS_PLOT_LIVE_PLOT_HISTORY_LENGTH_MS;
            xopts.max = serverNow;
        }

        info.plot.setupGrid();
        info.plot.draw();
    },

    periodicUpdatePlots: function () {
        xgds_plot.updatePlots();
        setTimeout(xgds_plot.periodicUpdatePlots, 200);
    },

    setupPlotHandlers: function (info) {
        info.plotDiv
            .bind('plothover', function (event, pos, item) {
                if (item) {
                    var pt = item.datapoint;
                    var t = $.plot.formatDate(new Date(pt[0]), '%H:%M:%S');
                    var y = pt[1].toFixed(3);
                    info.infoDiv
                        .html(''
                              + t + ''
                              + '' + y + '');
                }
            });
    },

    onopen: function (zmq) {
        $('#socketStatus').html('connected');

        $.each(xgds_plot.plots, function (i, info) {
            var topic = info.meta.queryModel;
            var handler = function (info) {
                return function (zmq, topic, obj) {
                    info.timeSeries.add(obj);
                    xgds_plot.haveNewData = true;
                };
            }(info);

            zmq.subscribeDjango(topic, handler);
        });
    },

    onclose: function (zmq) {
        $('#socketStatus').html('disconnected');
    },

    handleMasterMeta: function (inMeta) {
        xgds_plot.masterMeta = inMeta;

        // create live mode control
        $('#liveModeControl').html('<input type="checkbox" checked="checked" id="liveModeCheckBox">'
                                   + '</input>'
                                   + ''
                                   + 'Live');
        $('#liveModeCheckBox').change(function () {
            var checked = $(this).attr('checked');
            console.log(checked);
            xgds_plot.liveMode = checked;
        });

        // create show/hide controls for each plot
        var plotControlsHtml = [];
        $.each(xgds_plot.masterMeta, function (i, meta) {
            var checked;
            if (meta.show) {
                checked = 'checked="checked" ';
            } else {
                checked = '';
            }
            plotControlsHtml
                .push('
'
                      + '<input type="checkbox" ' + checked + 'id="showPlot_' + i + '"></input>'
                      + ''
                      + meta.valueName + '
');
        });
        $('#plotControls').html(plotControlsHtml.join(""));

        // create a div for each plot
        var plotsHtml = [];
        $.each(xgds_plot.masterMeta, function (i, meta) {
            var style;
            if (meta.show) {
                style = '';
            } else {
                style = 'style="display: none"';
            }
            plotsHtml.push('
'
                           + '
'
                           + meta.valueName
                           + ''
                           + '
'
                           + '

'
                           + '
');
        });
        $("#plots").html(plotsHtml.join(""));

        // connect to data feed
        var zmqUrl = settings
            .XGDS_ZMQ_WEB_SOCKET_URL
            .replace('{{host}}', window.location.hostname);
        var zmq = new ZmqManager(zmqUrl,
                                 {onopen: xgds_plot.onopen,
                                  onclose: xgds_plot.onclose,
                                  autoReconnect: true});
        zmq.start();

        var timeSeriesTypeRegistry = {
            'xgds_plot.value.Ratio': xgds_plot.value.Ratio,
            'xgds_plot.value.Scalar': xgds_plot.value.Scalar
        };

        // initialize the xgds_plot.plots array
        $.each(xgds_plot.masterMeta, function (i, meta) {
            var timeSeriesType = timeSeriesTypeRegistry[meta.valueType];
            var timeSeries = new timeSeriesType(meta);
            var info = {index: i,
                        meta: meta,
                        plotDiv: $('#plot_' + i),
                        containerDiv: $('#plotContainer_' + i),
                        infoDiv: $('#plotInfo_' + i),
                        show: meta.show,
                        timeSeries: timeSeries,
                        plot: null}
            xgds_plot.plots.push(info);
            xgds_plot.setupPlotHandlers(info);
        });

        // start updating plots
        xgds_plot.periodicUpdatePlots();

    }, // function handleMasterMeta

    handleServerTime: function (serverTime) {
        xgds_plot.timeSkew = serverTime - new Date().valueOf();
        //console.log('timeSkew: ' + xgds_plot.timeSkew);
    },

    init: function () {
        $.getJSON(settings.SCRIPT_NAME + 'xgds_plot/meta.json', xgds_plot.handleMasterMeta);
        $.getJSON(settings.SCRIPT_NAME + 'xgds_plot/now/', xgds_plot.handleServerTime);
    }

}); // xgds_plot namespace

/**********************************************************************/

xgds_plot.value.Scalar.prototype.getValue = function (rec) {
    return [xgds_plot.parseIso8601(rec[this.meta.queryTimestampField]),
            rec[this.meta.valueField]];
}

xgds_plot.value.Scalar.prototype.add = function (rec) {
    var ty = this.getValue(rec);
    var t = ty[0];
    var y = ty[1];

    xgds_plot.pushTruncate(this.raw, [t, y], xgds_plot.MAX_NUM_DATA_POINTS);

    // note: we could avoid recalculating most of the smoothed values
    this.initSmoothing();
}

xgds_plot.value.Scalar.prototype.initSmoothing = function () {
    var sigmaMs = this.meta.smoothing.sigmaSeconds * 1000;
    this.kernelStart = 0;
    this.smooth = [];
    var self = this;
    $.each(self.raw, function (i, ty) {
        var t = ty[0];
        var sum = 0.0;
        var weightSum = 0.0;
        for (var j = self.kernelStart; j < self.raw.length; j++) {
            var typ = self.raw[j];
            var tp = typ[0];
            if ((t - tp) > (2 * sigmaMs)) {
                self.kernelStart++;
                continue;
            }
            var yp = typ[1];
            var weight = xgds_plot.gaussian(tp - t, sigmaMs);
            sum +=  weight * yp;
            weightSum += weight;
        }
        self.smooth.push([t, sum / weightSum]);
    });
}

xgds_plot.value.Scalar.prototype.getPlotData = function () {
    return [{data: this.raw}, {data: this.smooth}];
}

/**********************************************************************/

xgds_plot.value.Ratio.prototype.getValue = function (rec) {
    return [xgds_plot.parseIso8601(rec[this.meta.queryTimestampField]),
            rec[this.meta.valueFields[0]],
            rec[this.meta.valueFields[1]]];
}

xgds_plot.value.Ratio.prototype.add = function (rec) {
    var tnd = this.getValue(rec);
    var t = tnd[0];
    var ynum = tnd[1];
    var ydenom = tnd[2];

    xgds_plot.pushTruncate(this.numerator, [t, ynum], xgds_plot.MAX_NUM_DATA_POINTS);
    xgds_plot.pushTruncate(this.denominator, [t, ydenom], xgds_plot.MAX_NUM_DATA_POINTS);
    xgds_plot.pushTruncate(this.raw, [t, ynum/ydenom], xgds_plot.MAX_NUM_DATA_POINTS);

    this.initSmoothing();
}

xgds_plot.value.Ratio.prototype.initSmoothing = function () {
    var sigmaMs = this.meta.smoothing.sigmaSeconds * 1000;
    this.kernelStart = 0;
    this.smooth = [];
    var self = this;
    $.each(self.raw, function (i, ty) {
        var t = ty[0];
        var numSum = 0.0;
        var denomSum = 0.0;
        for (var j = self.kernelStart; j < self.raw.length; j++) {
            var tynump = self.numerator[j];
            var tp = tynump[0];
            if ((t - tp) > (2 * sigmaMs)) {
                self.kernelStart++;
                continue;
            }
            var ynump = tynump[1];
            var tydenomp = self.denominator[j];
            var ydenomp = tydenomp[1];
            var weight = xgds_plot.gaussian(tp - t, sigmaMs);
            numSum +=  weight * ynump;
            denomSum += weight * ydenomp;
        }
        self.smooth.push([t, numSum / denomSum]);
    });
}

xgds_plot.value.Ratio.prototype.getPlotData = xgds_plot.value.Scalar.prototype.getPlotData;

<|endoftext|>


<commit_before>/*
 * Copyright 2013 The Toolkitchen Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

(function() {

function reflect(element, name, meta) {
  return {
    obj: element,
    name: name,
    value: element[name],
    meta: meta && meta[name]
  };
}

function reflectProperty(element, name, meta) {
  try {
    var v = element[name];
    if (v !== null
        && v !== undefined
        && typeof v !== 'function'
        && typeof v !== 'object'
        //&& element.propertyIsEnumerable(k)
        && !reflectProperty.blacklist[name]) {
      var prop = reflect(element, name, meta);
    }
  } catch(x) {
    // squelch
  }
  return prop;
}

reflectProperty.blacklist = {isToolkitElement: 1};

function reflectProperties(element) {
  var props = [];
  if (element) {
    var found = {};
    var p = element.__proto__;
    var meta = element.meta && element.meta.properties;
    while (p && p != HTMLElement.prototype && p != HTMLInputElement.prototype) {
      var k = Object.keys(p);
      k.forEach(function(k) {
        if (found[k]) {
          return;
        }
        var prop = reflectProperty(element, k, meta);
        if (prop) {
          props.push(prop);
          found[k] = true;
        }
      });
      p = p.__proto__;
    }
    //
    var more = [];
    if (!element.firstElementChild) {
      more.push('textContent');
    }
    more.push('id');
    //
    meta && Object.keys(meta).forEach(function(n) {
      if (!found[n] && more.indexOf(n) === -1) {
        more.push(n);
      }
    });
    //
    more.forEach(function(k) {
      var v = element[k];
      if (typeof v !== 'function' && typeof v !== 'object') {
        props.push(reflect(element, k, meta));
      }
    });
  }
  return props;
}

function reflectStyles(element, meta) {
  var props = [];
  if (element) {
    var style = element.style;
    Object.keys(meta).forEach(function(name) {
      props.push(reflect(style, name, meta));
    });
  }
  return props;
}

window.Reflection = {
  properties: reflectProperties,
  styles: reflectStyles
};

})();
<commit_msg>always inspect properties included in metadata.<commit_after>/*
 * Copyright 2013 The Toolkitchen Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

(function() {

function reflect(element, name, meta) {
  return {
    obj: element,
    name: name,
    value: element[name],
    meta: meta && meta[name]
  };
}

function reflectProperty(element, name, meta) {
  try {
    var v = element[name];
    if (v !== null
        && v !== undefined
        && typeof v !== 'function'
        && typeof v !== 'object'
        //&& element.propertyIsEnumerable(k)
        && !reflectProperty.blacklist[name]) {
      var prop = reflect(element, name, meta);
    }
  } catch(x) {
    // squelch
  }
  return prop;
}

reflectProperty.blacklist = {isToolkitElement: 1};

function reflectProperties(element) {
  var props = [];
  if (element) {
    var found = {};
    var p = element.__proto__;
    var meta = element.meta && element.meta.properties;
    while (p && p != HTMLElement.prototype && p != HTMLInputElement.prototype) {
      var k = Object.keys(p);
      k.forEach(function(k) {
        if (found[k]) {
          return;
        }
        var prop = reflectProperty(element, k, meta);
        if (prop) {
          props.push(prop);
          found[k] = true;
        }
      });
      p = p.__proto__;
    }
    //
    var more = [];
    if (!element.firstElementChild) {
      more.push('textContent');
    }
    more.push('id');
    var whitelist = {};
    //
    meta && Object.keys(meta).forEach(function(n) {
      if (!found[n] && more.indexOf(n) === -1) {
        more.push(n);
        whitelist[n] = true;
      }
    });
    //
    more.forEach(function(k) {
      var v = element[k];
      if ((typeof v !== 'function' && typeof v !== 'object') || whitelist[k]) {
        props.push(reflect(element, k, meta));
      }
    });
  }
  return props;
}

function reflectStyles(element, meta) {
  var props = [];
  if (element) {
    var style = element.style;
    Object.keys(meta).forEach(function(name) {
      props.push(reflect(style, name, meta));
    });
  }
  return props;
}

window.Reflection = {
  properties: reflectProperties,
  styles: reflectStyles
};

})();
<|endoftext|>