<commit_before>/*
 * ! ${copyright}
 */

sap.ui.define([
	'sap/ui/mdc/valuehelp/base/Container',
	'sap/ui/mdc/util/loadModules',
	"sap/ui/dom/units/Rem",
	"sap/ui/thirdparty/jquery"
], function(
	Container,
	loadModules,
	Rem,
	jQuery
) {
	"use strict";

	var MPopover, MLibrary, Toolbar, ToolbarSpacer;

	/**
	 * Constructor for a new Popover container.
	 *
	 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
	 * @param {object} [mSettings] Initial settings for the new control
	 * @class Container for the {@link sap.ui.mdc.ValueHelp ValueHelp} element showing a popover.
	 * @extends sap.ui.mdc.valuehelp.base.Container
	 * @version ${version}
	 * @constructor
	 * @abstract
	 * @private
	 * @ui5-restricted sap.ui.mdc
	 * @since 1.95.0
	 * @experimental As of version 1.95
	 * @alias sap.ui.mdc.valuehelp.Popover
	 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
	 */
	var Popover = Container.extend("sap.ui.mdc.valuehelp.Popover", /** @lends sap.ui.mdc.valuehelp.Popover.prototype */
	{
		metadata: {
			library: "sap.ui.mdc",
			interfaces: [
				"sap.ui.mdc.valuehelp.ITypeaheadContainer",
				"sap.ui.mdc.valuehelp.IDialogContainer"
			],
			properties: {
			},
			defaultAggregation: "content"
		}
	});

	Popover.prototype.invalidate = function(oOrigin) {

		if (oOrigin) {
			var oPopover = this.getAggregation("_container");
			var oContent = this._oCurrentContent;
			if (oPopover && oContent && oOrigin === oContent && !this._bIsBeingDestroyed) {
				// Content invalidated -> invalidate Popover to rerender content
				oPopover.invalidate(oOrigin);
			} else { // standard logic
				Container.prototype.invalidate.apply(this, arguments);
			}
		}

	};

	Popover.prototype._getUIAreaForContent = function() {
		// if List or Table needs to be rerendered because of a change the corresponding UIArea is the static-UIArea used in the Popover.
		var oPopover = this.getAggregation("_container");
		if (oPopover) {
			return oPopover.getUIArea();
		}

		return Container.prototype._getUIAreaForContent.apply(this, arguments);

	};

	Popover.prototype._getContent = function () {
		var oContent = this.getContent();
		return oContent && oContent[0];
	};

	Popover.prototype._getContainer = function () {
		var oPopover = this.getAggregation("_container");

		if (!oPopover) {
			return loadModules([
				"sap/m/Popover",
				"sap/m/library",
				"sap/m/Toolbar",
				"sap/m/ToolbarSpacer"
			]).then(function (aLoaded) {
				MPopover = aLoaded[0];
				MLibrary = aLoaded[1];
				Toolbar = aLoaded[2];
				ToolbarSpacer = aLoaded[3];

				oPopover = new MPopover(this.getId() + "-pop", {
					contentHeight: "auto",
					placement: MLibrary.PlacementType.VerticalPreferredBottom,
					showHeader: false,
					showArrow: false,
					title: this.getTitle(),
					titleAlignment: MLibrary.TitleAlignment.Center,
					afterOpen: this._handleOpened.bind(this),
					afterClose: this._handleClosed.bind(this)
				}).addStyleClass("sapMdcValueHelpPopover").addStyleClass("sapMComboBoxBasePicker").addStyleClass("sapMComboBoxBasePicker-CTX"); // to have a ComboBox popup

				oPopover.isPopupAdaptationAllowed = function () {
					return false;
				};

				oPopover.addStyleClass(this._isSingleSelect() ? "sapMdcValueHelpSingleSelect" : "sapMdcValueHelpMultiSelect");

				oPopover.addDelegate({onsapshow: this._handleRequestSwitchToDialog.bind(this)});

				oPopover._getAllContent = function() {
					var oParent = this.getParent();
					var aContent = [];
					if (oParent) {
						if (this._oCurrentContent) {
							aContent.push(this._oCurrentContent);
						}
					}
					return aContent;
				}.bind(this);

				var oContent = this._getContent();
				var oContainerConfig = this._getContainerConfig(oContent);

				if (oContainerConfig) {
					oPopover.setShowArrow(!!oContainerConfig.showArrow);
					oPopover.setShowHeader(!!oContainerConfig.showHeader);
					oPopover.setResizable(!!oContainerConfig.resizable);

					if (oContainerConfig.getContentWidth) {
						oPopover.setContentWidth(oContainerConfig.getContentWidth());
					}
				}

				this.setAggregation("_container", oPopover, true);
				return oPopover;
			}.bind(this));
		}

		return oPopover;
	};

	var _setContainerHeight = function () {
		var oContainer = this.getAggregation("_container");
		var oContent = this._getContent();
		var oContainerConfig = oContent && this._getContainerConfig(oContent);
		if (oContainer && oContainerConfig && oContainerConfig.getContentHeight) {
			var iHeight = oContainerConfig.getContentHeight();
			var iContainerHeight = oContainer.$().find(".sapMPopoverCont").height();
			var bContainerHeightBelowLimit = iContainerHeight < Rem.toPx("30rem");
			oContainer.setContentHeight(!iHeight || bContainerHeightBelowLimit || iContainerHeight >= iHeight ? "auto" : "30rem");
			oContainer.invalidate(); // TODO: Better way? Popover does not always update correctly
		}
	};

	Popover.prototype.providesScrolling = function () {
		return true;
	};

	Popover.prototype._observeChanges = function (oChanges) {
		if (oChanges.name === "content") {
			var oContent = oChanges.child;
			if (oChanges.mutation === "remove") {
				oContent.detachEvent("contentUpdated", _setContainerHeight, this);
				oContent.detachNavigated(this._handleNavigated, this);
			} else {
				oContent.attachEvent("contentUpdated", _setContainerHeight, this); // TODO: put event in content interface ot check existance?
			}
		}
		Container.prototype._observeChanges.apply(this, arguments);
	};

	Popover.prototype._placeContent = function (oPopover) {

		var oContent = this._getContent();
		var oContentPromise = oContent && oContent.getContent();
		var oBeforeShowPromise = oContent && oContent.onBeforeShow();
		var oContainerConfig = this._getContainerConfig(oContent);
		var oFooterContentPromise = oContainerConfig && oContainerConfig.getFooter && oContainerConfig.getFooter();

		return Promise.all([oContentPromise, oFooterContentPromise, oBeforeShowPromise]).then(function (aContents) {
			this._oCurrentContent = aContents[0];
			var oFooterContent = aContents[1];

			if (oFooterContent && oPopover.getFooter() != oFooterContent && oFooterContent.isA && oFooterContent.isA("sap.m.Toolbar")) {
				oPopover.setFooter(oFooterContent);
				return oPopover;
			}

			if (oFooterContent) {
				if (!oPopover.getFooter()) {
					var aToolbarContent = [new ToolbarSpacer(this.getId() + "-Spacer")].concat(oFooterContent);
					var oToolbar = new Toolbar(this.getId() + "-TB", {
						content: aToolbarContent
					}).setModel(this._oManagedObjectModel, "$help"); // TODO: ManagedObjectModel never created!!!
					oPopover.setFooter(oToolbar);
				} else {
					// TODO: update Toolbar?
				}
			} else if (oPopover.getFooter()) {
				oPopover.setFooter();
			}
			return oPopover;
		}.bind(this));
	};

	Popover.prototype._open = function (oPopover) {
		if (oPopover.isOpen()) {
			return;
		}

		Container.prototype._open.apply(this, arguments);

		var oControl = this._getControl();
		var oTarget = oControl && oControl.getFocusElementForValueHelp ? oControl.getFocusElementForValueHelp(this.isTypeahead()) : oControl;

		if (oTarget && oTarget.getDomRef()) {
			oPopover.setContentMinWidth(jQuery(oTarget.getDomRef()).outerWidth() + "px");
			if (!this.isFocusInHelp()) {
				oPopover.setInitialFocus(oTarget);
			}
			oPopover.openBy(oTarget);
		}
	};

	Popover.prototype._close = function () {

		Container.prototype._close.apply(this, arguments);
		var oPopover = this.getAggregation("_container");
		if (oPopover) {
			oPopover.close();
		}

	};

	Popover.prototype._handleOpened = function () {
		_setContainerHeight.call(this);

		Container.prototype._handleOpened.apply(this, arguments);

		var oContent = this._getContent();

		if (oContent) {
			oContent.onContainerOpen();
			oContent.onShow();
		}

	};

	Popover.prototype._handleConfirmed = function (oEvent) {
		this.fireConfirm({close: oEvent.getParameter("close") || this._isSingleSelect()});
	};

	Popover.prototype._handleClosed = function (oEvent) {

		var oContent = this._getContent();

		if (oContent) {
			oContent.onHide();
			oContent.onContainerClose();
		}

		var oPopover = this.getAggregation("_container");
		if (oPopover) {
			oPopover._oPreviousFocus = null; // TODO - find real solution
		}

		Container.prototype._handleClosed.apply(this, arguments);
	};

	Popover.prototype.removeFocus = function() {
		var oContent = this._getContent();
		if (oContent) {
			oContent.removeFocus();
		}
	};

	Popover.prototype._navigate = function (iStep) {
		var oContent = this._getContent();
		if (oContent) {
			return oContent.navigate(iStep);
		}
	};

	Popover.prototype.getItemForValue = function(oConfig) {
		var oContent = this._getContent();
		if (oContent) {
			return oContent.getItemForValue(oConfig);
		}
	};

	Popover.prototype.isValidationSupported = function(oConfig) {
		var oContent = this._getContent();
		if (oContent) {
			return oContent.isValidationSupported();
		}
	};

	Popover.prototype.getUseAsValueHelp = function() {

		var oContent = this._getContent();
		return oContent && oContent.getUseAsValueHelp && oContent.getUseAsValueHelp();

	};

	Popover.prototype.getValueHelpIcon = function() {

		// ask content for icon
		var oContent = this._getContent();
		return oContent && oContent.getValueHelpIcon();

	};

	Popover.prototype.getAriaAttributes = function(iMaxConditions) {

		var oContent = this._getContent();
		var oContentAttributes = oContent && oContent.getAriaAttributes(iMaxConditions);

		if (oContentAttributes) {
			return {
				contentId: oContentAttributes.contentId,
				ariaHasPopup: oContentAttributes.ariaHasPopup,
				role: this.isDialog() ? "combobox" : null, // Popover is a ComboBox, but only if used as valuehelp, only typeahead has no role (see sap.m.Input)
				roleDescription: oContentAttributes.roleDescription // for multiselect-mTable it needs to be set
			};
		}

		return Container.prototype.getAriaAttributes.apply(this, arguments);

	};

	Popover.prototype.shouldOpenOnClick = function() {

		var oContent = this._getContent();
		return !!oContent && oContent.shouldOpenOnClick();

	};

	Popover.prototype.shouldOpenOnNavigate = function() {

		var oContent = this._getContent();
		return !!oContent && oContent.shouldOpenOnNavigate();

	};

	Popover.prototype.isFocusInHelp = function() {

		var oContent = this._getContent();
		return !!oContent && oContent.isFocusInHelp();

	};

	Popover.prototype.isMultiSelect = function() {

		var oContent = this._getContent();
		return !!oContent && oContent.isMultiSelect();

	};

	Popover.prototype.isTypeaheadSupported = function () {

		var oContent = this._getContent();
		return oContent && oContent.isSearchSupported();

	};

	Popover.prototype.exit = function () {
		if (this._oCurrentContent) {
			if (!this._oCurrentContent.bIsDestroyed) {
				this._oCurrentContent.destroy();
			}
			this._oCurrentContent = null;
		}
	};

	return Popover;

});
<commit_msg>[INTERNAL] ValueHelp: don't leave Fieldgroup if focus item<commit_after>/*
 * ! ${copyright}
 */

sap.ui.define([
	'sap/ui/mdc/valuehelp/base/Container',
	'sap/ui/mdc/util/loadModules',
	"sap/ui/dom/units/Rem",
	"sap/ui/thirdparty/jquery"
], function(
	Container,
	loadModules,
	Rem,
	jQuery
) {
	"use strict";

	var MPopover, MLibrary, Toolbar, ToolbarSpacer;

	/**
	 * Constructor for a new Popover container.
	 *
	 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
	 * @param {object} [mSettings] Initial settings for the new control
	 * @class Container for the {@link sap.ui.mdc.ValueHelp ValueHelp} element showing a popover.
	 * @extends sap.ui.mdc.valuehelp.base.Container
	 * @version ${version}
	 * @constructor
	 * @abstract
	 * @private
	 * @ui5-restricted sap.ui.mdc
	 * @since 1.95.0
	 * @experimental As of version 1.95
	 * @alias sap.ui.mdc.valuehelp.Popover
	 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
	 */
	var Popover = Container.extend("sap.ui.mdc.valuehelp.Popover", /** @lends sap.ui.mdc.valuehelp.Popover.prototype */
	{
		metadata: {
			library: "sap.ui.mdc",
			interfaces: [
				"sap.ui.mdc.valuehelp.ITypeaheadContainer",
				"sap.ui.mdc.valuehelp.IDialogContainer"
			],
			properties: {
			},
			defaultAggregation: "content"
		}
	});

	Popover.prototype.invalidate = function(oOrigin) {

		if (oOrigin) {
			var oPopover = this.getAggregation("_container");
			var oContent = this._oCurrentContent;
			if (oPopover && oContent && oOrigin === oContent && !this._bIsBeingDestroyed) {
				// Content invalidated -> invalidate Popover to rerender content
				oPopover.invalidate(oOrigin);
			} else { // standard logic
				Container.prototype.invalidate.apply(this, arguments);
			}
		}

	};

	Popover.prototype._getUIAreaForContent = function() {
		// if List or Table needs to be rerendered because of a change the corresponding UIArea is the static-UIArea used in the Popover.
		var oPopover = this.getAggregation("_container");
		if (oPopover) {
			return oPopover.getUIArea();
		}

		return Container.prototype._getUIAreaForContent.apply(this, arguments);

	};

	Popover.prototype._getContent = function () {
		var oContent = this.getContent();
		return oContent && oContent[0];
	};

	Popover.prototype._getContainer = function () {
		var oPopover = this.getAggregation("_container");

		if (!oPopover) {
			return loadModules([
				"sap/m/Popover",
				"sap/m/library",
				"sap/m/Toolbar",
				"sap/m/ToolbarSpacer"
			]).then(function (aLoaded) {
				MPopover = aLoaded[0];
				MLibrary = aLoaded[1];
				Toolbar = aLoaded[2];
				ToolbarSpacer = aLoaded[3];

				oPopover = new MPopover(this.getId() + "-pop", {
					contentHeight: "auto",
					placement: MLibrary.PlacementType.VerticalPreferredBottom,
					showHeader: false,
					showArrow: false,
					title: this.getTitle(),
					titleAlignment: MLibrary.TitleAlignment.Center,
					afterOpen: this._handleOpened.bind(this),
					afterClose: this._handleClosed.bind(this)
				}).addStyleClass("sapMdcValueHelpPopover").addStyleClass("sapMComboBoxBasePicker").addStyleClass("sapMComboBoxBasePicker-CTX"); // to have a ComboBox popup

				oPopover.isPopupAdaptationAllowed = function () {
					return false;
				};

				oPopover.addStyleClass(this._isSingleSelect() ? "sapMdcValueHelpSingleSelect" : "sapMdcValueHelpMultiSelect");

				oPopover.addDelegate({onsapshow: this._handleRequestSwitchToDialog.bind(this)});

				oPopover._getAllContent = function() {
					var oParent = this.getParent();
					var aContent = [];
					if (oParent) {
						if (this._oCurrentContent) {
							aContent.push(this._oCurrentContent);
						}
					}
					return aContent;
				}.bind(this);

				var oContent = this._getContent();
				var oContainerConfig = this._getContainerConfig(oContent);

				if (oContainerConfig) {
					oPopover.setShowArrow(!!oContainerConfig.showArrow);
					oPopover.setShowHeader(!!oContainerConfig.showHeader);
					oPopover.setResizable(!!oContainerConfig.resizable);

					if (oContainerConfig.getContentWidth) {
						oPopover.setContentWidth(oContainerConfig.getContentWidth());
					}
				}

				this.setAggregation("_container", oPopover, true);
				return oPopover;
			}.bind(this));
		}

		return oPopover;
	};

	var _setContainerHeight = function () {
		var oContainer = this.getAggregation("_container");
		var oContent = this._getContent();
		var oContainerConfig = oContent && this._getContainerConfig(oContent);
		if (oContainer && oContainerConfig && oContainerConfig.getContentHeight) {
			var iHeight = oContainerConfig.getContentHeight();
			var iContainerHeight = oContainer.$().find(".sapMPopoverCont").height();
			var bContainerHeightBelowLimit = iContainerHeight < Rem.toPx("30rem");
			oContainer.setContentHeight(!iHeight || bContainerHeightBelowLimit || iContainerHeight >= iHeight ? "auto" : "30rem");
			oContainer.invalidate(); // TODO: Better way? Popover does not always update correctly
		}
	};

	Popover.prototype.providesScrolling = function () {
		return true;
	};

	Popover.prototype._observeChanges = function (oChanges) {
		if (oChanges.name === "content") {
			var oContent = oChanges.child;
			if (oChanges.mutation === "remove") {
				oContent.detachEvent("contentUpdated", _setContainerHeight, this);
				oContent.detachNavigated(this._handleNavigated, this);
			} else {
				oContent.attachEvent("contentUpdated", _setContainerHeight, this); // TODO: put event in content interface ot check existance?
			}
		}
		Container.prototype._observeChanges.apply(this, arguments);
	};

	Popover.prototype._placeContent = function (oPopover) {

		var oContent = this._getContent();
		var oContentPromise = oContent && oContent.getContent();
		var oBeforeShowPromise = oContent && oContent.onBeforeShow();
		var oContainerConfig = this._getContainerConfig(oContent);
		var oFooterContentPromise = oContainerConfig && oContainerConfig.getFooter && oContainerConfig.getFooter();

		return Promise.all([oContentPromise, oFooterContentPromise, oBeforeShowPromise]).then(function (aContents) {
			this._oCurrentContent = aContents[0];
			var oFooterContent = aContents[1];

			// to prevent a Fieldgroup leave if opening control has fieldgroups, asign the fieldgroups to content
			var oControl = this._getControl();
			this._oCurrentContent.setFieldGroupIds(oControl.getFieldGroupIds());

			if (oFooterContent && oPopover.getFooter() != oFooterContent && oFooterContent.isA && oFooterContent.isA("sap.m.Toolbar")) {
				oPopover.setFooter(oFooterContent);
				return oPopover;
			}

			if (oFooterContent) {
				if (!oPopover.getFooter()) {
					var aToolbarContent = [new ToolbarSpacer(this.getId() + "-Spacer")].concat(oFooterContent);
					var oToolbar = new Toolbar(this.getId() + "-TB", {
						content: aToolbarContent
					}).setModel(this._oManagedObjectModel, "$help"); // TODO: ManagedObjectModel never created!!!
					oPopover.setFooter(oToolbar);
				} else {
					// TODO: update Toolbar?
				}
			} else if (oPopover.getFooter()) {
				oPopover.setFooter();
			}
			return oPopover;
		}.bind(this));
	};

	Popover.prototype._open = function (oPopover) {
		if (oPopover.isOpen()) {
			return;
		}

		Container.prototype._open.apply(this, arguments);

		var oControl = this._getControl();
		var oTarget = oControl && oControl.getFocusElementForValueHelp ? oControl.getFocusElementForValueHelp(this.isTypeahead()) : oControl;

		if (oTarget && oTarget.getDomRef()) {
			oPopover.setContentMinWidth(jQuery(oTarget.getDomRef()).outerWidth() + "px");
			if (!this.isFocusInHelp()) {
				oPopover.setInitialFocus(oTarget);
			}
			oPopover.openBy(oTarget);
		}
	};

	Popover.prototype._close = function () {

		Container.prototype._close.apply(this, arguments);
		var oPopover = this.getAggregation("_container");
		if (oPopover) {
			oPopover.close();
		}

	};

	Popover.prototype._handleOpened = function () {
		_setContainerHeight.call(this);

		Container.prototype._handleOpened.apply(this, arguments);

		var oContent = this._getContent();

		if (oContent) {
			oContent.onContainerOpen();
			oContent.onShow();
		}

	};

	Popover.prototype._handleConfirmed = function (oEvent) {
		this.fireConfirm({close: oEvent.getParameter("close") || this._isSingleSelect()});
	};

	Popover.prototype._handleClosed = function (oEvent) {

		var oContent = this._getContent();

		if (oContent) {
			oContent.onHide();
			oContent.onContainerClose();
		}

		var oPopover = this.getAggregation("_container");
		if (oPopover) {
			oPopover._oPreviousFocus = null; // TODO - find real solution
		}

		Container.prototype._handleClosed.apply(this, arguments);
	};

	Popover.prototype.removeFocus = function() {
		var oContent = this._getContent();
		if (oContent) {
			oContent.removeFocus();
		}
	};

	Popover.prototype._navigate = function (iStep) {
		var oContent = this._getContent();
		if (oContent) {
			return oContent.navigate(iStep);
		}
	};

	Popover.prototype.getItemForValue = function(oConfig) {
		var oContent = this._getContent();
		if (oContent) {
			return oContent.getItemForValue(oConfig);
		}
	};

	Popover.prototype.isValidationSupported = function(oConfig) {
		var oContent = this._getContent();
		if (oContent) {
			return oContent.isValidationSupported();
		}
	};

	Popover.prototype.getUseAsValueHelp = function() {

		var oContent = this._getContent();
		return oContent && oContent.getUseAsValueHelp && oContent.getUseAsValueHelp();

	};

	Popover.prototype.getValueHelpIcon = function() {

		// ask content for icon
		var oContent = this._getContent();
		return oContent && oContent.getValueHelpIcon();

	};

	Popover.prototype.getAriaAttributes = function(iMaxConditions) {

		var oContent = this._getContent();
		var oContentAttributes = oContent && oContent.getAriaAttributes(iMaxConditions);

		if (oContentAttributes) {
			return {
				contentId: oContentAttributes.contentId,
				ariaHasPopup: oContentAttributes.ariaHasPopup,
				role: this.isDialog() ? "combobox" : null, // Popover is a ComboBox, but only if used as valuehelp, only typeahead has no role (see sap.m.Input)
				roleDescription: oContentAttributes.roleDescription // for multiselect-mTable it needs to be set
			};
		}

		return Container.prototype.getAriaAttributes.apply(this, arguments);

	};

	Popover.prototype.shouldOpenOnClick = function() {

		var oContent = this._getContent();
		return !!oContent && oContent.shouldOpenOnClick();

	};

	Popover.prototype.shouldOpenOnNavigate = function() {

		var oContent = this._getContent();
		return !!oContent && oContent.shouldOpenOnNavigate();

	};

	Popover.prototype.isFocusInHelp = function() {

		var oContent = this._getContent();
		return !!oContent && oContent.isFocusInHelp();

	};

	Popover.prototype.isMultiSelect = function() {

		var oContent = this._getContent();
		return !!oContent && oContent.isMultiSelect();

	};

	Popover.prototype.isTypeaheadSupported = function () {

		var oContent = this._getContent();
		return oContent && oContent.isSearchSupported();

	};

	Popover.prototype.exit = function () {
		if (this._oCurrentContent) {
			if (!this._oCurrentContent.bIsDestroyed) {
				this._oCurrentContent.destroy();
			}
			this._oCurrentContent = null;
		}
	};

	return Popover;

});
<|endoftext|>


<commit_before>YUI.add('aui-scheduler-tests', function(Y) {

    var suite = new Y.Test.Suite('aui-scheduler');
    var DateMath = Y.DataType.DateMath;

    var today = new Date();
    var JANUARY_1 = DateMath.getJan1(today.getFullYear());
    var JULY_1 = DateMath.getDate(today.getFullYear(), 6, 1);
    var NO_DST_OFFSET = (JANUARY_1.getTimezoneOffset() === JULY_1.getTimezoneOffset());
    var WEEK_LENGTH = DateMath.WEEK_LENGTH;

    suite.add(new Y.Test.Case({
        name: 'Automated Tests',

        _should: {
            ignore: {
                'should display event in month view in the week DST begins': NO_DST_OFFSET,
                'should display event in month view in the last day of first week under DST': NO_DST_OFFSET,
                'should not display "Show n more" link with only two events': NO_DST_OFFSET,
                'should display last day of event spanning to DST from one week before': NO_DST_OFFSET
            }
        },

        setUp: function() {
            this._agendaView = new Y.SchedulerAgendaView(),
            this._dayView = new Y.SchedulerDayView(),
            this._monthView = new Y.SchedulerMonthView(),
            this._weekView = new Y.SchedulerWeekView();
        },

        tearDown: function() {
            if (this._scheduler) {
                this._scheduler.destroy();
                delete this._scheduler;
            }
        },

        _createScheduler: function(config) {
            this._scheduler = new Y.Scheduler(Y.merge({
                boundingBox: '#myScheduler',
                date: new Date(2013, 11, 4),
                eventRecorder: new Y.SchedulerEventRecorder(),
                items: [
                    {
                        color: '#8D8',
                        content: 'Colorful',
                        endDate: new Date(2013, 11, 6, 6),
                        startDate: new Date(2013, 11, 6, 2)
                    }
                ],
                render: true,
                views: [
                    this._weekView,
                    this._dayView,
                    this._monthView,
                    this._agendaView
                ]
            }, config));
        },

        _getLocalTimeZoneDSTFirstDay: function() {
            var curDate = JULY_1,
                dstOffset = Math.min(JANUARY_1.getTimezoneOffset(), JULY_1.getTimezoneOffset()),
                prevDate = DateMath.subtract(curDate, DateMath.DAY, 1),
                step;

            if (NO_DST_OFFSET) {
                return null;
            }

            dstOffset = Math.min(JANUARY_1.getTimezoneOffset(), JULY_1.getTimezoneOffset());
            curDate = JULY_1;
            prevDate = DateMath.subtract(curDate, DateMath.DAY, 1);

            if (curDate.getTimezoneOffset() !== dstOffset) {
                // If current date is not under DST, go forward to find when
                // DST will start.
                step = 1;
            }
            else {
                // If current date is under DST, go back to find when DST
                // started.
                step = -1;
            }

            while (prevDate.getTimezoneOffset() <= curDate.getTimezoneOffset()) {
                prevDate = DateMath.add(prevDate, DateMath.DAY, step);
                curDate = DateMath.add(curDate, DateMath.DAY, step);
            }

            // The returned date should satisfy some criteria. Here we check
            // whether they are true. Since time zone rules are complex and
            // prone to change, this ensures the test will fail if it cannot
            // proceed.
            Y.Assert.isFalse(
                DateMath.isDayOverlap(prevDate, DateMath.subtract(curDate, DateMath.DAY, 1)),
                'The previous date should be one day before the current one'
            );

            Y.Assert.areEqual(
                curDate.getTimezoneOffset(),
                dstOffset,
                'The current date should have DST offset'
            );

            Y.Assert.isTrue(
                curDate.getTimezoneOffset() < prevDate.getTimezoneOffset(),
                'The previous date should have a smaller offset'
            );

            return curDate;
        },

        'should be able to switch views': function() {
            this._createScheduler();

            Y.Assert.areSame(
                this._weekView,
                this._scheduler.get('activeView'),
                'The initial view should be week view'
            );

            Y.one('button.scheduler-base-view-day').simulate('click');
            Y.Assert.areSame(
                this._dayView,
                this._scheduler.get('activeView'),
                'The day view should have become active'
            );

            Y.one('button.scheduler-base-view-month').simulate('click');
            Y.Assert.areSame(
                this._monthView,
                this._scheduler.get('activeView'),
                'The month view should have become active'
            );

            Y.one('button.scheduler-base-view-agenda').simulate('click');
            Y.Assert.areSame(
                this._agendaView,
                this._scheduler.get('activeView'),
                'The agenda view should have become active'
            );
        },

        'event color is encoded in RGB': function() {
            this._createScheduler();

            var events = this._scheduler.getEventsByDay(new Date(2013, 11, 6));
            Y.Assert.areEqual(1, events.length);

            var node = events[0].get('node').item(0);

            Y.Assert.isNotNull(node);
            Y.Assert.isTrue(Y.Lang.String.startsWith(node.getStyle('color'), 'rgb('));
            Y.Assert.isTrue(Y.Lang.String.startsWith(node.getStyle('backgroundColor'), 'rgb('));
        },

        'events in the scheduler view should respond to the click event': function() {
            var recorder;

            this._createScheduler();
            this._scheduler.set('disabled', true);

            recorder = this._scheduler.get('eventRecorder');

            Y.one('button.scheduler-base-view-month').simulate('click');
            Y.one('.scheduler-event').simulate('click');

            Y.Assert.isTrue(
                recorder.popover.get('visible'),
                'Popover should be visible when event is clicked in month view'
            );

            Y.one('button.scheduler-base-view-agenda').simulate('click');
            Y.one('.scheduler-view-agenda-event').simulate('click');

            Y.Assert.isTrue(
                recorder.popover.get('visible'),
                'Popover should be visible when event is clicked in agenda view'
            );
        },

        'views select dropdown should be set to active view': function() {
            this._createScheduler({
                activeView: this._monthView
            });

            var options = this._scheduler.viewsSelectNode.get('childNodes');
            var selectedIndex = this._scheduler.viewsSelectNode.get('selectedIndex');

            Y.Assert.areSame(
                this._scheduler.get('activeView').get('name'),
                options.item(selectedIndex).getAttribute('data-view-name'),
                'The views select dropdown should be set to month view'
            );
        },

        'should display the first week of an event in month view': function() {
            // Second Wednesday
            var startDate = new Date(2013, 11, 11, 12, 1);

            this._createScheduler({
                activeView: this._monthView,
                items: [
                    {
                        color: '#8D8',
                        content: 'Many days',
                        endDate: DateMath.add(startDate, DateMath.MONTH, 3),
                        startDate: startDate
                    }
                ]
            });

            var rows = Y.all('.scheduler-view-table-row');

            Y.Assert.areEqual(
                0, rows.item(0).all('.scheduler-event').size(),
                'There should be no event in the first row'
            );

            Y.Assert.areEqual(
                1, rows.item(1).all('.scheduler-event').size(),
                'There should be one event in the second row'
            );

            Y.Assert.areEqual(
                1, rows.item(2).all('.scheduler-event').size(),
                'There should be one event in the third row'
            );
        },

        'should display event in month view in the week DST begins': function() {
            var dstDate = this._getLocalTimeZoneDSTFirstDay(),
                endDate,
                firstDayOfWeek = DateMath.getFirstDayOfWeek(dstDate),
                rows,
                startDate;

            if (dstDate === null) {
                Y.Assert.pass('The current machine time zone has no DSTs');

                return;
            }

            endDate = DateMath.add(dstDate, DateMath.MONTH, 1);
            startDate = DateMath.subtract(dstDate, DateMath.MONTH, 1);

            this._createScheduler({
                activeView: this._monthView,
                date: dstDate,
                firstDayOfWeek: firstDayOfWeek.getDay(),
                items: [
                    {
                        color: '#8D8',
                        content: 'Many days',
                        endDate: endDate,
                        startDate: startDate
                    }
                ]
            });

            rows = Y.all('.scheduler-view-table-row');

            rows.each(function(row, index) {
                Y.Assert.areEqual(
                    1, row.all('.scheduler-event').size(),
                    'There should be an event at row #'.concat(index)
                );
            });
        },

        'should display event in month view in the last day of first week under DST': function() {
            var dstDate = this._getLocalTimeZoneDSTFirstDay(),
                endDate,
                events,
                firstDayOfWeek = DateMath.getFirstDayOfWeek(dstDate),
                startDate;

            if (dstDate === null) {
                Y.Assert.pass('The current machine time zone has no DSTs');

                return;
            }

            endDate = DateMath.add(dstDate, DateMath.MONTH, 2);
            startDate = DateMath.subtract(dstDate, DateMath.MONTH, 2);

            this._createScheduler({
                activeView: this._monthView,
                date: dstDate,
                firstDayOfWeek: (firstDayOfWeek.getDay() - 1) % 7,
                items: [
                    {
                        color: '#8D8',
                        content: 'Event 1',
                        endDate: endDate,
                        startDate: startDate
                    }
                ]
            });

            events = Y.all('.scheduler-event');

            events.each(function(event, index) {
                var column = event.ancestor('.scheduler-view-table-data-col');

                Y.Assert.areEqual(
                    7, column.getAttribute('colspan'),
                    'Event column #'.concat(index).concat(' should fill row.')
                );
            });
        },

        'should not display "Show n more" link with only two events': function() {
            var dstDate = this._getLocalTimeZoneDSTFirstDay(),
                endDate,
                firstDayOfWeek = DateMath.getFirstDayOfWeek(dstDate),
                startDate;

            if (dstDate === null) {
                Y.Assert.pass('The current machine time zone has no DSTs');

                return;
            }

            endDate = DateMath.add(dstDate, DateMath.MONTH, 2);
            startDate = DateMath.subtract(dstDate, DateMath.MONTH, 2);

            this._createScheduler({
                activeView: this._monthView,
                date: dstDate,
                firstDayOfWeek: (firstDayOfWeek.getDay() - 1) % 7,
                items: [
                    {
                        color: '#8D8',
                        content: 'Event 1',
                        endDate: endDate,
                        startDate: startDate
                    },
                    {
                        color: '#474',
                        content: 'Event 2',
                        endDate: endDate,
                        startDate: startDate
                    }
                ]
            });

            Y.Assert.areEqual(
                0, Y.all('.scheduler-view-table-more').size(),
                '"Show n more" link should not be displayed.'
            );
        },

        'should display last day of event spanning to DST from one week before': function() {
            var column,
                dstDate = this._getLocalTimeZoneDSTFirstDay(),
                endDate,
                events,
                startDate;

            endDate = DateMath.subtract(dstDate, DateMath.DAY, 1);
            startDate = DateMath.subtract(dstDate, DateMath.DAY, WEEK_LENGTH);

            this._createScheduler({
                activeView: this._monthView,
                date: dstDate,
                firstDayOfWeek: startDate.getDay(),
                items: [
                    {
                        color: '#8D8',
                        content: 'Event 1',
                        endDate: endDate,
                        startDate: startDate
                    }
                ]
            });

            events = Y.all('.scheduler-event');

            Y.Assert.areEqual(
                1, events.size(),
                'Event should span through only one week'
            );

            var column = events.item(0).ancestor('.scheduler-view-table-data-col');

            Y.Assert.areEqual(
                7, column.getAttribute('colspan'),
                'Event should fill entire week.'
            );
        }
    }));

    Y.Test.Runner.add(suite);

}, '', {
    requires: ['node-event-simulate', 'test', 'aui-scheduler', 'aui-datatype']
});
<commit_msg>AUI-1865 Source formatting: removing unnecessary check<commit_after>YUI.add('aui-scheduler-tests', function(Y) {

    var suite = new Y.Test.Suite('aui-scheduler');
    var DateMath = Y.DataType.DateMath;

    var today = new Date();
    var JANUARY_1 = DateMath.getJan1(today.getFullYear());
    var JULY_1 = DateMath.getDate(today.getFullYear(), 6, 1);
    var NO_DST_OFFSET = (JANUARY_1.getTimezoneOffset() === JULY_1.getTimezoneOffset());
    var WEEK_LENGTH = DateMath.WEEK_LENGTH;

    suite.add(new Y.Test.Case({
        name: 'Automated Tests',

        _should: {
            ignore: {
                'should display event in month view in the week DST begins': NO_DST_OFFSET,
                'should display event in month view in the last day of first week under DST': NO_DST_OFFSET,
                'should not display "Show n more" link with only two events': NO_DST_OFFSET,
                'should display last day of event spanning to DST from one week before': NO_DST_OFFSET
            }
        },

        setUp: function() {
            this._agendaView = new Y.SchedulerAgendaView(),
            this._dayView = new Y.SchedulerDayView(),
            this._monthView = new Y.SchedulerMonthView(),
            this._weekView = new Y.SchedulerWeekView();
        },

        tearDown: function() {
            if (this._scheduler) {
                this._scheduler.destroy();
                delete this._scheduler;
            }
        },

        _createScheduler: function(config) {
            this._scheduler = new Y.Scheduler(Y.merge({
                boundingBox: '#myScheduler',
                date: new Date(2013, 11, 4),
                eventRecorder: new Y.SchedulerEventRecorder(),
                items: [
                    {
                        color: '#8D8',
                        content: 'Colorful',
                        endDate: new Date(2013, 11, 6, 6),
                        startDate: new Date(2013, 11, 6, 2)
                    }
                ],
                render: true,
                views: [
                    this._weekView,
                    this._dayView,
                    this._monthView,
                    this._agendaView
                ]
            }, config));
        },

        _getLocalTimeZoneDSTFirstDay: function() {
            var curDate = JULY_1,
                dstOffset = Math.min(JANUARY_1.getTimezoneOffset(), JULY_1.getTimezoneOffset()),
                prevDate = DateMath.subtract(curDate, DateMath.DAY, 1),
                step;

            if (NO_DST_OFFSET) {
                return null;
            }

            dstOffset = Math.min(JANUARY_1.getTimezoneOffset(), JULY_1.getTimezoneOffset());
            curDate = JULY_1;
            prevDate = DateMath.subtract(curDate, DateMath.DAY, 1);

            if (curDate.getTimezoneOffset() !== dstOffset) {
                // If current date is not under DST, go forward to find when
                // DST will start.
                step = 1;
            }
            else {
                // If current date is under DST, go back to find when DST
                // started.
                step = -1;
            }

            while (prevDate.getTimezoneOffset() <= curDate.getTimezoneOffset()) {
                prevDate = DateMath.add(prevDate, DateMath.DAY, step);
                curDate = DateMath.add(curDate, DateMath.DAY, step);
            }

            // The returned date should satisfy some criteria. Here we check
            // whether they are true. Since time zone rules are complex and
            // prone to change, this ensures the test will fail if it cannot
            // proceed.
            Y.Assert.isFalse(
                DateMath.isDayOverlap(prevDate, DateMath.subtract(curDate, DateMath.DAY, 1)),
                'The previous date should be one day before the current one'
            );

            Y.Assert.areEqual(
                curDate.getTimezoneOffset(),
                dstOffset,
                'The current date should have DST offset'
            );

            Y.Assert.isTrue(
                curDate.getTimezoneOffset() < prevDate.getTimezoneOffset(),
                'The previous date should have a smaller offset'
            );

            return curDate;
        },

        'should be able to switch views': function() {
            this._createScheduler();

            Y.Assert.areSame(
                this._weekView,
                this._scheduler.get('activeView'),
                'The initial view should be week view'
            );

            Y.one('button.scheduler-base-view-day').simulate('click');
            Y.Assert.areSame(
                this._dayView,
                this._scheduler.get('activeView'),
                'The day view should have become active'
            );

            Y.one('button.scheduler-base-view-month').simulate('click');
            Y.Assert.areSame(
                this._monthView,
                this._scheduler.get('activeView'),
                'The month view should have become active'
            );

            Y.one('button.scheduler-base-view-agenda').simulate('click');
            Y.Assert.areSame(
                this._agendaView,
                this._scheduler.get('activeView'),
                'The agenda view should have become active'
            );
        },

        'event color is encoded in RGB': function() {
            this._createScheduler();

            var events = this._scheduler.getEventsByDay(new Date(2013, 11, 6));
            Y.Assert.areEqual(1, events.length);

            var node = events[0].get('node').item(0);

            Y.Assert.isNotNull(node);
            Y.Assert.isTrue(Y.Lang.String.startsWith(node.getStyle('color'), 'rgb('));
            Y.Assert.isTrue(Y.Lang.String.startsWith(node.getStyle('backgroundColor'), 'rgb('));
        },

        'events in the scheduler view should respond to the click event': function() {
            var recorder;

            this._createScheduler();
            this._scheduler.set('disabled', true);

            recorder = this._scheduler.get('eventRecorder');

            Y.one('button.scheduler-base-view-month').simulate('click');
            Y.one('.scheduler-event').simulate('click');

            Y.Assert.isTrue(
                recorder.popover.get('visible'),
                'Popover should be visible when event is clicked in month view'
            );

            Y.one('button.scheduler-base-view-agenda').simulate('click');
            Y.one('.scheduler-view-agenda-event').simulate('click');

            Y.Assert.isTrue(
                recorder.popover.get('visible'),
                'Popover should be visible when event is clicked in agenda view'
            );
        },

        'views select dropdown should be set to active view': function() {
            this._createScheduler({
                activeView: this._monthView
            });

            var options = this._scheduler.viewsSelectNode.get('childNodes');
            var selectedIndex = this._scheduler.viewsSelectNode.get('selectedIndex');

            Y.Assert.areSame(
                this._scheduler.get('activeView').get('name'),
                options.item(selectedIndex).getAttribute('data-view-name'),
                'The views select dropdown should be set to month view'
            );
        },

        'should display the first week of an event in month view': function() {
            // Second Wednesday
            var startDate = new Date(2013, 11, 11, 12, 1);

            this._createScheduler({
                activeView: this._monthView,
                items: [
                    {
                        color: '#8D8',
                        content: 'Many days',
                        endDate: DateMath.add(startDate, DateMath.MONTH, 3),
                        startDate: startDate
                    }
                ]
            });

            var rows = Y.all('.scheduler-view-table-row');

            Y.Assert.areEqual(
                0, rows.item(0).all('.scheduler-event').size(),
                'There should be no event in the first row'
            );

            Y.Assert.areEqual(
                1, rows.item(1).all('.scheduler-event').size(),
                'There should be one event in the second row'
            );

            Y.Assert.areEqual(
                1, rows.item(2).all('.scheduler-event').size(),
                'There should be one event in the third row'
            );
        },

        'should display event in month view in the week DST begins': function() {
            var dstDate = this._getLocalTimeZoneDSTFirstDay(),
                endDate,
                firstDayOfWeek = DateMath.getFirstDayOfWeek(dstDate),
                rows,
                startDate;

            endDate = DateMath.add(dstDate, DateMath.MONTH, 1);
            startDate = DateMath.subtract(dstDate, DateMath.MONTH, 1);

            this._createScheduler({
                activeView: this._monthView,
                date: dstDate,
                firstDayOfWeek: firstDayOfWeek.getDay(),
                items: [
                    {
                        color: '#8D8',
                        content: 'Many days',
                        endDate: endDate,
                        startDate: startDate
                    }
                ]
            });

            rows = Y.all('.scheduler-view-table-row');

            rows.each(function(row, index) {
                Y.Assert.areEqual(
                    1, row.all('.scheduler-event').size(),
                    'There should be an event at row #'.concat(index)
                );
            });
        },

        'should display event in month view in the last day of first week under DST': function() {
            var dstDate = this._getLocalTimeZoneDSTFirstDay(),
                endDate,
                events,
                firstDayOfWeek = DateMath.getFirstDayOfWeek(dstDate),
                startDate;

            endDate = DateMath.add(dstDate, DateMath.MONTH, 2);
            startDate = DateMath.subtract(dstDate, DateMath.MONTH, 2);

            this._createScheduler({
                activeView: this._monthView,
                date: dstDate,
                firstDayOfWeek: (firstDayOfWeek.getDay() - 1) % 7,
                items: [
                    {
                        color: '#8D8',
                        content: 'Event 1',
                        endDate: endDate,
                        startDate: startDate
                    }
                ]
            });

            events = Y.all('.scheduler-event');

            events.each(function(event, index) {
                var column = event.ancestor('.scheduler-view-table-data-col');

                Y.Assert.areEqual(
                    7, column.getAttribute('colspan'),
                    'Event column #'.concat(index).concat(' should fill row.')
                );
            });
        },

        'should not display "Show n more" link with only two events': function() {
            var dstDate = this._getLocalTimeZoneDSTFirstDay(),
                endDate,
                firstDayOfWeek = DateMath.getFirstDayOfWeek(dstDate),
                startDate;

            endDate = DateMath.add(dstDate, DateMath.MONTH, 2);
            startDate = DateMath.subtract(dstDate, DateMath.MONTH, 2);

            this._createScheduler({
                activeView: this._monthView,
                date: dstDate,
                firstDayOfWeek: (firstDayOfWeek.getDay() - 1) % 7,
                items: [
                    {
                        color: '#8D8',
                        content: 'Event 1',
                        endDate: endDate,
                        startDate: startDate
                    },
                    {
                        color: '#474',
                        content: 'Event 2',
                        endDate: endDate,
                        startDate: startDate
                    }
                ]
            });

            Y.Assert.areEqual(
                0, Y.all('.scheduler-view-table-more').size(),
                '"Show n more" link should not be displayed.'
            );
        },

        'should display last day of event spanning to DST from one week before': function() {
            var column,
                dstDate = this._getLocalTimeZoneDSTFirstDay(),
                endDate,
                events,
                startDate;

            endDate = DateMath.subtract(dstDate, DateMath.DAY, 1);
            startDate = DateMath.subtract(dstDate, DateMath.DAY, WEEK_LENGTH);

            this._createScheduler({
                activeView: this._monthView,
                date: dstDate,
                firstDayOfWeek: startDate.getDay(),
                items: [
                    {
                        color: '#8D8',
                        content: 'Event 1',
                        endDate: endDate,
                        startDate: startDate
                    }
                ]
            });

            events = Y.all('.scheduler-event');

            Y.Assert.areEqual(
                1, events.size(),
                'Event should span through only one week'
            );

            var column = events.item(0).ancestor('.scheduler-view-table-data-col');

            Y.Assert.areEqual(
                7, column.getAttribute('colspan'),
                'Event should fill entire week.'
            );
        }
    }));

    Y.Test.Runner.add(suite);

}, '', {
    requires: ['node-event-simulate', 'test', 'aui-scheduler', 'aui-datatype']
});
<|endoftext|>


<commit_before>import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Tooltip } from 'react-tippy';
import { isParent } from 'utils/dom';
import cx from 'classnames';
import JSZip from 'jszip';
import snakeCase from 'lodash/snakeCase';
import JSZipUtils from 'jszip-utils';
import { saveAs } from 'file-saver';
import { track } from 'app/analytics';
import moment from 'moment';

import Button from 'components/ui/button';
import Icon from 'components/ui/icon';

import settingsIcon from 'assets/icons/settings.svg';
import shareIcon from 'assets/icons/share.svg';
import infoIcon from 'assets/icons/info.svg';
import mapIcon from 'assets/icons/map-button.svg';
import downloadIcon from 'assets/icons/download.svg';

import WidgetSettings from './components/widget-settings';

import './styles.scss';

class WidgetHeader extends PureComponent {
  state = {
    tooltipOpen: false
  };

  renderDownloadButton = () => {
    const { downloadLink } = this.props;

    return (
      <Fragment>
        <Button
          className={cx('download-button')}
          theme="theme-button-small square"
          tooltip={{ text: 'Download data for this view' }}
          extLink={downloadLink}
        >
          <Icon icon={downloadIcon} className="download-icon" />
        </Button>

      </Fragment>
    );
  };

  renderMapButton = () => {
    const {
      widget,
      active,
      config,
      locationName,
      isDeviceTouch,
      setActiveWidget
    } = this.props;
    const isSmall = !config.large;

    return (
      <Button
        className={cx('map-button', { '-active': active })}
        theme={cx(
          'theme-button-small',
          { small: isSmall },
          { square: isDeviceTouch || isSmall }
        )}
        tooltip={{ text: active ? 'Currently displayed' : 'Show on map' }}
        onClick={() => {
          setActiveWidget(widget);
          track('viewWidgetOnMap', {
            label: `${widget} in ${locationName || ''}`
          });
        }}
      >
        {isSmall || isDeviceTouch ? (
          <Icon icon={mapIcon} className="map-icon" />
        ) : (
          'SHOW ON MAP'
        )}
      </Button>
    );
  };

  renderSettingsButton = () => {
    const {
      modalClosing,
      locationName,
      title,
      settings,
      config,
      loading,
      setWidgetSettings,
      widget,
      options,
      setModalMetaSettings,
      downloadLink
    } = this.props;
    const { tooltipOpen } = this.state;
    return (
      <Fragment>
        <Tooltip
          className="widget-tooltip-theme"
          theme="light"
          position="bottom-right"
          offset={-95}
          trigger="click"
          interactive
          onRequestClose={() => {
            const isTargetOnTooltip = isParent(
              this.widgetSettingsRef,
              this.widgetSettingsRef.evt
            );
            this.widgetSettingsRef.clearEvt();

            if (!modalClosing && !isTargetOnTooltip) {
              this.setState({ tooltipOpen: false });
            }
          }}
          onShow={() => this.setState({ tooltipOpen: true })}
          arrow
          useContext
          open={tooltipOpen}
          html={
            <WidgetSettings
              ref={node => {
                this.widgetSettingsRef = node;
              }}
              widget={widget}
              settings={settings}
              config={config}
              options={options}
              loading={loading}
              onSettingsChange={setWidgetSettings}
              setModalMetaSettings={setModalMetaSettings}
            />
          }
        >
          <Button
            className="theme-button-small square"
            tooltip={{ text: 'Filter and customize the data' }}
            trackingData={{
              event: 'open-settings',
              label: `${title} in ${locationName || ''}`
            }}
          >
            <Icon icon={settingsIcon} className="settings-icon" />
          </Button>
        </Tooltip>
        {!downloadLink &&
}
      </Fragment>
    );
  };

  renderMetadataButton = () => {
    const { metakey, setModalMetaSettings, simple } = this.props;
    return (
      <Button
        theme={cx('theme-button-small square', {
          'theme-button-grey-filled theme-button-xsmall': simple
        })}
        onClick={() => setModalMetaSettings(metakey)}
        tooltip={{ text: 'Learn more about the data' }}
      >
        <Icon icon={infoIcon} />
      </Button>
    );
  };

  renderShareButton = () => {
    const { shareData, setShareModal } = this.props;
    return (
      <Button
        className="theme-button-small square"
        onClick={() => setShareModal(shareData)}
        tooltip={{ text: 'Share or embed this widget' }}
      >
        <Icon icon={shareIcon} />
      </Button>
    );
  };

  generateZipFromURL = files => {
    const {
      title,
      config,
      settings,
      allLocation,
      parentLocationData,
      locationData,
      childLocationData,
      locationObject
    } = this.props;
    const { metaKey } = config;

    const metadata = {
      title,
      ...(settings && {
        ...Object.keys(settings).reduce(
          (obj, key) => ({
            ...obj,
            ...(!['activeData', 'page', 'page_size', 'ifl'].includes(key) && {
              [snakeCase(key)]: settings[key]
            })
          }),
          {}
        )
      }),
      date_downloaded: moment().format('YYYY-MM-DD'),
      metadata: `https://production-api.globalforestwatch.org/v1/gfw-metadata/${
        metaKey
      }`,
      link: 'https://www.globalforestwatch.org'.concat(
        allLocation.pathname,
        '?',
        allLocation.search
      )
    };

    const metadataFile = Object.entries(metadata)
      .map(entry => `${entry[0]},${entry[1]}`)
      .join('\n');

    let parentAdminLevel = 'global';
    let adminLevel = (locationObject && locationObject.adminLevel) || 'global';
    let childAdminLevel = 'adm2';

    if (adminLevel === 'global') {
      childAdminLevel = 'iso';
    }
    if (adminLevel === 'adm0') {
      adminLevel = 'iso';
      childAdminLevel = 'adm1';
    }
    if (adminLevel === 'adm1') {
      parentAdminLevel = 'iso';
    }
    if (adminLevel === 'adm2') {
      parentAdminLevel = 'adm1';
    }

    const parentLocationMetadataFile =
      parentLocationData &&
      [`name,${parentAdminLevel}${parentAdminLevel !== 'iso' ? '__id' : ''}`]
        .concat(
          parentLocationData.map(entry => `"${entry.label}","${entry.value}"`)
        )
        .join('\n');

    const locationMetadataFile =
      locationData &&
      adminLevel !== 'global' &&
      [`name,${adminLevel}${adminLevel !== 'iso' ? '__id' : ''}`]
        .concat(locationData.map(entry => `"${entry.label}","${entry.value}"`))
        .join('\n');

    const childLocationMetadataFile =
      childLocationData &&
      [`name,${childAdminLevel}${childAdminLevel !== 'iso' ? '__id' : ''}`]
        .concat(
          childLocationData.map(entry => `"${entry.label}","${entry.value}"`)
        )
        .join('\n');

    const urlToPromise = url =>
      new Promise((resolve, reject) => {
        JSZipUtils.getBinaryContent(url, (err, data) => {
          if (err) {
            reject(err);
          } else {
            resolve(data);
          }
        });
      });
    const filenames = [];
    const zip = new JSZip();
    files.forEach((file, index) => {
      const { name, url } = file;
      let filename;
      try {
        filename =
          name ||
          url
            .split('?')[0]
            .split('/')
            .pop();
        if (filenames.includes(filename)) {
          filename = filename.concat(`-${index}.csv`);
        } else {
          filenames.push(filename);
          filename = filename.concat('.csv');
        }
      } catch (error) {
        filename = `file ${index + 1}.csv`;
      }
      zip.file(filename, urlToPromise(url), { binary: true });
    });
    zip.file('metadata.csv', metadataFile);
    if (parentLocationMetadataFile) {
      zip.file(`${parentAdminLevel}_metadata.csv`, parentLocationMetadataFile);
    }
    if (locationMetadataFile) {
      zip.file(`${adminLevel}_metadata.csv`, locationMetadataFile);
    }
    if (childLocationMetadataFile) {
      zip.file(`${childAdminLevel}_metadata.csv`, childLocationMetadataFile);
    }
    zip.generateAsync({ type: 'blob' }).then(content => {
      saveAs(content, `${title}.zip`);
    });
  };

  renderDownloadButton = () => {
    const params = { ...this.props.location, ...this.props.settings };
    const urls = this.props.getDataURL(params);
    return (
      <Button
        className="theme-button-small square"
        onClick={() => this.generateZipFromURL(urls)}
        tooltip={{ text: 'Download data for this widget' }}
      >
        <Icon icon={downloadIcon} />
      </Button>
    );
  };

  render() {
    const {
      title,
      settings,
      options,
      embed,
      config,
      simple,
      getDataURL,
      downloadLink
    } = this.props;

    return (

{title}

          {!embed &&
            !simple &&
            (config.layers || config.datasets) &&
            !config.hideLayers &&
            this.renderMapButton()}
          {!simple &&
            settings &&
            options &&
            config.options &&
            !config.hideSettings &&
            this.renderSettingsButton()}
          {downloadLink && this.renderDownloadButton()}

            {this.renderMetadataButton()}
            {process.env.FEATURE_ENV === 'staging' &&
              !simple &&
              getDataURL &&
              this.renderDownloadButton()}
            {!simple && this.renderShareButton()}

    );
  }
}

WidgetHeader.propTypes = {
  widget: PropTypes.string,
  title: PropTypes.string.isRequired,
  settings: PropTypes.object,
  location: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
  options: PropTypes.object,
  setShareModal: PropTypes.func.isRequired,
  shareData: PropTypes.object.isRequired,
  setModalMetaSettings: PropTypes.func.isRequired,
  modalClosing: PropTypes.bool,
  active: PropTypes.bool,
  config: PropTypes.object,
  locationName: PropTypes.string,
  isDeviceTouch: PropTypes.bool,
  embed: PropTypes.bool,
  loading: PropTypes.bool,
  simple: PropTypes.bool,
  setWidgetSettings: PropTypes.func,
  setActiveWidget: PropTypes.func,
  metakey: PropTypes.string,
  getDataURL: PropTypes.func,
  downloadLink: PropTypes.string,
  allLocation: PropTypes.object,
  locationData: PropTypes.array,
  childLocationData: PropTypes.array,
  locationObject: PropTypes.object,
  parentLocationData: PropTypes.array
};

export default WidgetHeader;
<commit_msg>enable downloads on widgets<commit_after>import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Tooltip } from 'react-tippy';
import { isParent } from 'utils/dom';
import cx from 'classnames';
import JSZip from 'jszip';
import snakeCase from 'lodash/snakeCase';
import JSZipUtils from 'jszip-utils';
import { saveAs } from 'file-saver';
import { track } from 'app/analytics';
import moment from 'moment';

import Button from 'components/ui/button';
import Icon from 'components/ui/icon';

import settingsIcon from 'assets/icons/settings.svg';
import shareIcon from 'assets/icons/share.svg';
import infoIcon from 'assets/icons/info.svg';
import mapIcon from 'assets/icons/map-button.svg';
import downloadIcon from 'assets/icons/download.svg';

import WidgetSettings from './components/widget-settings';

import './styles.scss';

class WidgetHeader extends PureComponent {
  state = {
    tooltipOpen: false
  };

  renderDownloadButton = () => {
    const { downloadLink } = this.props;

    return (
      <Fragment>
        <Button
          className={cx('download-button')}
          theme="theme-button-small square"
          tooltip={{ text: 'Download data for this view' }}
          extLink={downloadLink}
        >
          <Icon icon={downloadIcon} className="download-icon" />
        </Button>

      </Fragment>
    );
  };

  renderMapButton = () => {
    const {
      widget,
      active,
      config,
      locationName,
      isDeviceTouch,
      setActiveWidget
    } = this.props;
    const isSmall = !config.large;

    return (
      <Button
        className={cx('map-button', { '-active': active })}
        theme={cx(
          'theme-button-small',
          { small: isSmall },
          { square: isDeviceTouch || isSmall }
        )}
        tooltip={{ text: active ? 'Currently displayed' : 'Show on map' }}
        onClick={() => {
          setActiveWidget(widget);
          track('viewWidgetOnMap', {
            label: `${widget} in ${locationName || ''}`
          });
        }}
      >
        {isSmall || isDeviceTouch ? (
          <Icon icon={mapIcon} className="map-icon" />
        ) : (
          'SHOW ON MAP'
        )}
      </Button>
    );
  };

  renderSettingsButton = () => {
    const {
      modalClosing,
      locationName,
      title,
      settings,
      config,
      loading,
      setWidgetSettings,
      widget,
      options,
      setModalMetaSettings,
      downloadLink
    } = this.props;
    const { tooltipOpen } = this.state;
    return (
      <Fragment>
        <Tooltip
          className="widget-tooltip-theme"
          theme="light"
          position="bottom-right"
          offset={-95}
          trigger="click"
          interactive
          onRequestClose={() => {
            const isTargetOnTooltip = isParent(
              this.widgetSettingsRef,
              this.widgetSettingsRef.evt
            );
            this.widgetSettingsRef.clearEvt();

            if (!modalClosing && !isTargetOnTooltip) {
              this.setState({ tooltipOpen: false });
            }
          }}
          onShow={() => this.setState({ tooltipOpen: true })}
          arrow
          useContext
          open={tooltipOpen}
          html={
            <WidgetSettings
              ref={node => {
                this.widgetSettingsRef = node;
              }}
              widget={widget}
              settings={settings}
              config={config}
              options={options}
              loading={loading}
              onSettingsChange={setWidgetSettings}
              setModalMetaSettings={setModalMetaSettings}
            />
          }
        >
          <Button
            className="theme-button-small square"
            tooltip={{ text: 'Filter and customize the data' }}
            trackingData={{
              event: 'open-settings',
              label: `${title} in ${locationName || ''}`
            }}
          >
            <Icon icon={settingsIcon} className="settings-icon" />
          </Button>
        </Tooltip>
        {!downloadLink &&
}
      </Fragment>
    );
  };

  renderMetadataButton = () => {
    const { metakey, setModalMetaSettings, simple } = this.props;
    return (
      <Button
        theme={cx('theme-button-small square', {
          'theme-button-grey-filled theme-button-xsmall': simple
        })}
        onClick={() => setModalMetaSettings(metakey)}
        tooltip={{ text: 'Learn more about the data' }}
      >
        <Icon icon={infoIcon} />
      </Button>
    );
  };

  renderShareButton = () => {
    const { shareData, setShareModal } = this.props;
    return (
      <Button
        className="theme-button-small square"
        onClick={() => setShareModal(shareData)}
        tooltip={{ text: 'Share or embed this widget' }}
      >
        <Icon icon={shareIcon} />
      </Button>
    );
  };

  generateZipFromURL = files => {
    const {
      title,
      config,
      settings,
      allLocation,
      parentLocationData,
      locationData,
      childLocationData,
      locationObject
    } = this.props;
    const { metaKey } = config;

    const metadata = {
      title,
      ...(settings && {
        ...Object.keys(settings).reduce(
          (obj, key) => ({
            ...obj,
            ...(!['activeData', 'page', 'page_size', 'ifl'].includes(key) && {
              [snakeCase(key)]: settings[key]
            })
          }),
          {}
        )
      }),
      date_downloaded: moment().format('YYYY-MM-DD'),
      metadata: `https://production-api.globalforestwatch.org/v1/gfw-metadata/${
        metaKey
      }`,
      link: 'https://www.globalforestwatch.org'.concat(
        allLocation.pathname,
        '?',
        allLocation.search
      )
    };

    const metadataFile = Object.entries(metadata)
      .map(entry => `${entry[0]},${entry[1]}`)
      .join('\n');

    let parentAdminLevel = 'global';
    let adminLevel = (locationObject && locationObject.adminLevel) || 'global';
    let childAdminLevel = 'adm2';

    if (adminLevel === 'global') {
      childAdminLevel = 'iso';
    }
    if (adminLevel === 'adm0') {
      adminLevel = 'iso';
      childAdminLevel = 'adm1';
    }
    if (adminLevel === 'adm1') {
      parentAdminLevel = 'iso';
    }
    if (adminLevel === 'adm2') {
      parentAdminLevel = 'adm1';
    }

    const parentLocationMetadataFile =
      parentLocationData &&
      [`name,${parentAdminLevel}${parentAdminLevel !== 'iso' ? '__id' : ''}`]
        .concat(
          parentLocationData.map(entry => `"${entry.label}","${entry.value}"`)
        )
        .join('\n');

    const locationMetadataFile =
      locationData &&
      adminLevel !== 'global' &&
      [`name,${adminLevel}${adminLevel !== 'iso' ? '__id' : ''}`]
        .concat(locationData.map(entry => `"${entry.label}","${entry.value}"`))
        .join('\n');

    const childLocationMetadataFile =
      childLocationData &&
      [`name,${childAdminLevel}${childAdminLevel !== 'iso' ? '__id' : ''}`]
        .concat(
          childLocationData.map(entry => `"${entry.label}","${entry.value}"`)
        )
        .join('\n');

    const urlToPromise = url =>
      new Promise((resolve, reject) => {
        JSZipUtils.getBinaryContent(url, (err, data) => {
          if (err) {
            reject(err);
          } else {
            resolve(data);
          }
        });
      });
    const filenames = [];
    const zip = new JSZip();
    files.forEach((file, index) => {
      const { name, url } = file;
      let filename;
      try {
        filename =
          name ||
          url
            .split('?')[0]
            .split('/')
            .pop();
        if (filenames.includes(filename)) {
          filename = filename.concat(`-${index}.csv`);
        } else {
          filenames.push(filename);
          filename = filename.concat('.csv');
        }
      } catch (error) {
        filename = `file ${index + 1}.csv`;
      }
      zip.file(filename, urlToPromise(url), { binary: true });
    });
    zip.file('metadata.csv', metadataFile);
    if (parentLocationMetadataFile) {
      zip.file(`${parentAdminLevel}_metadata.csv`, parentLocationMetadataFile);
    }
    if (locationMetadataFile) {
      zip.file(`${adminLevel}_metadata.csv`, locationMetadataFile);
    }
    if (childLocationMetadataFile) {
      zip.file(`${childAdminLevel}_metadata.csv`, childLocationMetadataFile);
    }
    zip.generateAsync({ type: 'blob' }).then(content => {
      saveAs(content, `${title}.zip`);
    });
  };

  renderDownloadButton = () => {
    const params = { ...this.props.location, ...this.props.settings };
    const urls = this.props.getDataURL(params);
    return (
      <Button
        className="theme-button-small square"
        onClick={() => this.generateZipFromURL(urls)}
        tooltip={{ text: 'Download data for this widget' }}
      >
        <Icon icon={downloadIcon} />
      </Button>
    );
  };

  render() {
    const {
      title,
      settings,
      options,
      embed,
      config,
      simple,
      getDataURL,
      downloadLink
    } = this.props;

    return (

{title}

          {!embed &&
            !simple &&
            (config.layers || config.datasets) &&
            !config.hideLayers &&
            this.renderMapButton()}
          {!simple &&
            settings &&
            options &&
            config.options &&
            !config.hideSettings &&
            this.renderSettingsButton()}
          {downloadLink && this.renderDownloadButton()}

            {this.renderMetadataButton()}
            {!simple && getDataURL && this.renderDownloadButton()}
            {!simple && this.renderShareButton()}

    );
  }
}

WidgetHeader.propTypes = {
  widget: PropTypes.string,
  title: PropTypes.string.isRequired,
  settings: PropTypes.object,
  location: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
  options: PropTypes.object,
  setShareModal: PropTypes.func.isRequired,
  shareData: PropTypes.object.isRequired,
  setModalMetaSettings: PropTypes.func.isRequired,
  modalClosing: PropTypes.bool,
  active: PropTypes.bool,
  config: PropTypes.object,
  locationName: PropTypes.string,
  isDeviceTouch: PropTypes.bool,
  embed: PropTypes.bool,
  loading: PropTypes.bool,
  simple: PropTypes.bool,
  setWidgetSettings: PropTypes.func,
  setActiveWidget: PropTypes.func,
  metakey: PropTypes.string,
  getDataURL: PropTypes.func,
  downloadLink: PropTypes.string,
  allLocation: PropTypes.object,
  locationData: PropTypes.array,
  childLocationData: PropTypes.array,
  locationObject: PropTypes.object,
  parentLocationData: PropTypes.array
};

export default WidgetHeader;
<|endoftext|>


<commit_before>import React                from 'react';
import Relay                from 'react-relay';
import _t                   from '../../translate';
import {
  CreateTranslationMutation,
  UpdateTranslationMutation,
  DeleteTranslationMutation,
}                           from '../gral/mutations';
import { COLORS }           from '../gral/constants';
import {
  bindAll,
  mutate,
}                           from './helpers';
import Button               from './915-button';

// ==========================================
// Relay fragments
// ==========================================
const fragments = {
  theKey: () => Relay.QL`
    fragment on Key {
      id
      text
      ${DeleteTranslationMutation.getFragment('theKey')}
    }
  `,
  translation: () => Relay.QL`
    fragment on Translation {
      id
      lang, translation
      ${UpdateTranslationMutation.getFragment('translation')}
    }
  `,
};

// ==========================================
// Component
// ==========================================
class Translation extends React.Component {
  static propTypes = {
    relay:                  React.PropTypes.object.isRequired,
    theKey:                 React.PropTypes.object.isRequired,
    lang:                   React.PropTypes.string.isRequired,
    translation:            React.PropTypes.object,
    changeSelectedKey:      React.PropTypes.func.isRequired,
    fUnused:                React.PropTypes.bool.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      fEditing: false,
      text: this.getInitialTranslation(),
    };
    bindAll(this, [
      'onChange',
      'copyKey',
      'editStart',
      'editCommit',
      'editRevert',
      'editBlur',
      'deleteTranslation',
    ]);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.translation !== this.props.translation) {
      this.setState({
        text: this.getInitialTranslation(nextProps),
      });
    }
  }

  // ==========================================
  // Render
  // ==========================================
  render() {
    return (

        {this.renderTranslation()}
        {this.renderButtons()}

    );
  }

  renderTranslation() {
    const { translation, relay, fUnused } = this.props;
    const fUpdating = translation && relay.hasOptimisticUpdate(translation);
    return (

        <input ref={c => { this._input = c; }}
          type="text"
          value={this.state.text}
          onChange={this.onChange}
          onClick={this.editStart}
          style={style.input(this.state, { fUnused, fUpdating })}
        />

    );
  }

  renderButtons() {
    if (!this.state.fEditing) return null;
    const translation = this.props.translation;
    const elDelete = translation
      ?
          {' | '}
          <Button
            onClick={this.deleteTranslation}
            fCancelMouseDown
            fText
          >
            {_t('button_Delete')}
          </Button>

      : null;
    return (

        <Button
          onClick={this.copyKey}
          fCancelMouseDown
          fText
        >
          {_t('button_Copy message')}
        </Button>
        {' | '}
        <Button
          onClick={this.editCommit}
          fCancelMouseDown
          fText
        >
          {_t('button_Save')}
        </Button>
        {' | '}
        <Button
          onClick={this.editRevert}
          fCancelMouseDown
          fText
        >
          {_t('button_Revert')}
        </Button>
        {elDelete}

    );
  }

  // ==========================================
  // Handlers
  // ==========================================
  onChange(ev) { this.setState({ text: ev.currentTarget.value }); }
  copyKey() { this.setState({ text: this.props.theKey.text }); }
  editStart() {
    this.setState({ fEditing: true });
    this.props.changeSelectedKey(this.props.theKey.id);
  }
  editCommit() {
    const description = 'Click on Save translation';
    let Mutation;
    let props;
    if (this.props.translation) {
      Mutation = UpdateTranslationMutation;
      props = {
        translation: this.props.translation,
        set: {
          translation: this.state.text,
        },
      };
    } else {
      Mutation = CreateTranslationMutation;
      props = {
        set: {
          lang: this.props.lang,
          keyId: this.props.theKey.id,
          translation: this.state.text,
        },
      };
    }
    mutate({ description, Mutation, props });
    this.setState({
      fEditing: false,
      text: this.getInitialTranslation(),
    });
    this._input.blur();
  }
  editRevert() {
    this.setState({
      fEditing: false,
      text: this.getInitialTranslation(),
    });
    this._input.blur();
  }
  editBlur() {
    if (this.state.text === this.getInitialTranslation()) {
      this.setState({ fEditing: false });
    } else {
      this.editCommit();
    }
  }
  deleteTranslation() {
    this.editRevert();
    mutate({
      description: 'Click on Delete translation',
      Mutation: DeleteTranslationMutation,
      props: {
        id: this.props.translation.id,
        theKey: this.props.theKey,
      },
    });
  }

  // ==========================================
  // Helpers
  // ==========================================
  getInitialTranslation(props = this.props) {
    return props.translation ? props.translation.translation : '';
  }
}

// ==========================================
// Styles
// ==========================================
const style = {
  input: ({ fEditing }, { fUnused, fUpdating }) => ({
    background: fEditing ? undefined : 'transparent',
    border: fEditing ? undefined : '0px solid transparent',
    color: fUpdating ? 'red' : (fUnused ? COLORS.dim : undefined),
    width: '100%',
    cursor: 'beam',
  }),
};

// ==========================================
// Public API
// ==========================================
export default Relay.createContainer(Translation, { fragments });
<commit_msg>More intuitive translation edits<commit_after>import React                from 'react';
import Relay                from 'react-relay';
import _t                   from '../../translate';
import {
  CreateTranslationMutation,
  UpdateTranslationMutation,
  DeleteTranslationMutation,
}                           from '../gral/mutations';
import { COLORS }           from '../gral/constants';
import {
  bindAll,
  mutate,
}                           from './helpers';
import Icon                 from './905-icon';
import hoverable            from './hocs/hoverable';

// ==========================================
// Relay fragments
// ==========================================
const fragments = {
  theKey: () => Relay.QL`
    fragment on Key {
      id
      text
      ${DeleteTranslationMutation.getFragment('theKey')}
    }
  `,
  translation: () => Relay.QL`
    fragment on Translation {
      id
      lang, translation
      ${UpdateTranslationMutation.getFragment('translation')}
    }
  `,
};

// ==========================================
// Component
// ==========================================
class Translation extends React.Component {
  static propTypes = {
    relay:                  React.PropTypes.object.isRequired,
    theKey:                 React.PropTypes.object.isRequired,
    lang:                   React.PropTypes.string.isRequired,
    translation:            React.PropTypes.object,
    changeSelectedKey:      React.PropTypes.func.isRequired,
    fUnused:                React.PropTypes.bool.isRequired,
    // From hoverable
    hovering:               React.PropTypes.bool,
    onHoverStart:           React.PropTypes.func.isRequired,
    onHoverStop:            React.PropTypes.func.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      fEditing: false,
      text: this.getInitialTranslation(),
    };
    bindAll(this, [
      'onChange',
      'onFocus',
      'onBlur',
      'onKeyUp',
      'onClickCopyKey',
      'onClickDelete',
    ]);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.translation !== this.props.translation) {
      this.setState({
        text: this.getInitialTranslation(nextProps),
      });
    }
  }

  componentDidMount() { this.resizeTextarea(); }
  componentDidUpdate() { this.resizeTextarea(); }

  // ==========================================
  // Render
  // ==========================================
  render() {
    return (

        {this.renderTranslation()}
        {this.renderButtons()}
        {this.renderHelp()}

    );
  }

  renderTranslation() {
    const { translation, relay, fUnused } = this.props;
    const fUpdating = translation && relay.hasOptimisticUpdate(translation);
    const fEmpty = !this.state.text.length;
    return (

 { this._refPlaceholder = c; }}
          style={style.placeholder(this.state, { fUnused, fUpdating, fEmpty })}
        >
          {fEmpty ? 'x' : this.state.text}

        <textarea ref={c => { this._refInput = c; }}
          value={this.state.text}
          onChange={this.onChange}
          onFocus={this.onFocus}
          onBlur={this.onBlur}
          onKeyUp={this.onKeyUp}
          style={style.textarea(this.state, { fUnused, fUpdating })}
        />

    );
  }

  renderButtons() {
    if (!this.state.fEditing && !this.props.hovering) return null;
    const { translation } = this.props;
    const elDelete = translation
      ? <Icon
          icon="remove"
          title={_t('tooltip_Delete translation')}
          onClick={this.onClickDelete}
          style={style.iconButton}
        />
      : null;
    return (

        <Icon
          icon="copy"
          title={_t('tooltip_Copy message')}
          onClick={this.onClickCopyKey}
          style={style.iconButton}
        />
        {elDelete}

    );
  }

  renderHelp() {
    if (!this.state.fEditing) return null;
    return (

        {_t('translationHelp_Click outside or TAB to save. ESC to undo.')}

    );
  }

  // ==========================================
  // Handlers
  // ==========================================
  onChange(ev) { this.setState({ text: ev.currentTarget.value }); }
  onFocus() {
    this.setState({ fEditing: true });
    this.props.changeSelectedKey(this.props.theKey.id);
  }
  onKeyUp(ev) {
    if (ev.which !== 27) return;
    this.revert(() => { this._refInput.blur(); })
  }
  onBlur() {
    this.setState({ fEditing: false });
    if (this.state.text === this.getInitialTranslation()) return;
    const description = 'Commit translation edit';
    let Mutation;
    let props;
    if (this.props.translation) {
      Mutation = UpdateTranslationMutation;
      props = {
        translation: this.props.translation,
        set: {
          translation: this.state.text,
        },
      };
    } else {
      Mutation = CreateTranslationMutation;
      props = {
        set: {
          lang: this.props.lang,
          keyId: this.props.theKey.id,
          translation: this.state.text,
        },
      };
    }
    mutate({ description, Mutation, props });
    this.setState({ text: this.getInitialTranslation() });
  }

  onClickCopyKey() {
    this.setState({ text: this.props.theKey.text });
    this._refInput.focus();
  }
  onClickDelete() {
    this.revert(() => {
      mutate({
        description: 'Click on Delete translation',
        Mutation: DeleteTranslationMutation,
        props: {
          id: this.props.translation.id,
          theKey: this.props.theKey,
        },
      });
    });
  }

  // ==========================================
  // Helpers
  // ==========================================
  revert(cb) {
    this.setState({
      fEditing: false,
      text: this.getInitialTranslation(),
    }, cb);
  }

  getInitialTranslation(props = this.props) {
    return props.translation ? props.translation.translation : '';
  }

  resizeTextarea() {
    // if (this._refInput.scrollHeight > 150) {
    //   console.log(this._refInput)
    //   console.log(`${this._refInput.scrollHeight}, ${this._refInput.offsetHeight}`)
    // }
    let height = this._refPlaceholder.offsetHeight;
    if (this.state.fEditing) height += 4
    this._refInput.style.height = `${height}px`;
  }
}

// ==========================================
// Styles
// ==========================================
const style = {
  outer: {
    paddingRight: 40,
  },
  textareaWrapper: {
    position: 'relative',
  },
  placeholder: ({ fEditing }, { fUnused, fUpdating, fEmpty }) => ({
    opacity: (fEditing || fEmpty) ? 0 : 1,
    color: fUpdating ? 'red' : (fUnused ? COLORS.dim : undefined),
    width: '100%',
    cursor: 'beam',
  }),
  textarea: ({ fEditing }, { fUpdating }) => ({
    position: 'absolute',
    top: -1,
    left: -1,
    width: '100%',
    padding: fEditing ? '0px 0px 3px 0px' : '0px 0px 0px 0px',
    overflow: 'hidden',
    opacity: fEditing ? 1 : 0,
    background: fEditing ? undefined : 'transparent',
    border: fEditing ? undefined : '0px solid transparent',
    color: fUpdating ? 'red' : 'black',
    cursor: 'beam',
    fontFamily: 'inherit',
    fontSize: 'inherit',
    resize: 'none',
  }),
  buttons: {
    position: 'absolute',
    top: 1,
    right: 5,
    color: 'black',
  },
  iconButton: {
    marginLeft: 5,
  },
  help: {
    marginTop: 2,
    fontStyle: 'italic',
    color: COLORS.dim,
  },
};

// ==========================================
// Public API
// ==========================================
export default Relay.createContainer(hoverable(Translation), { fragments });
<|endoftext|>


<commit_before>import _ from 'lodash'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { ThemeProvider } from 'emotion-theming'

import { Environment, withEnvironment } from 'rt-components'
import { Flex } from 'rt-components'
import { ConnectionType, ServiceConnectionInfo } from 'system'
import Icon from './Icon'

import { Root, Body, Fill, ExpandToggle, ServiceList, ServiceRoot, ServiceName, NodeCount } from './styled.js'

export const SERVICES = ['blotter', 'reference', 'execution', 'pricing', 'analytics'].map(serviceType => ({
  serviceType
}))

class StatusBar extends Component {
  state = {
    expanded: true
  }

  resolveTheme = theme => {
    const { mode } = this.props
    const currentTheme = theme.statusBar[mode]

    theme = {
      ...theme,
      ...theme.statusBar,
      ...theme.statusBar[mode],
      serviceItem: {
        connecting: theme.statusBar.connected,
        disconnected: mode === 'connecting' ? theme.statusBar.connecting : theme.statusBar.disconnected
      }
    }

    return theme
  }

  toggleExpanded = () => this.setState(({ expanded }) => ({ expanded: !expanded }))

  render() {
    const {
      connectionStatus: { url, transportType },
      mode,
      serviceStatus
    } = this.props

    const { expanded } = this.state

    return (
      <ThemeProvider key={mode} theme={this.resolveTheme}>
        <Root expand={expanded}>

            <Icon name="check" />

            {mode === 'disconnected' ? (
              'Disconnected'
            ) : (
              <React.Fragment>
                {_.capitalize(mode)} to {url} ({transportType})
              </React.Fragment>
            )}

            <Fill />

            <ExpandToggle expand={expanded} />

          <ServiceList>
            {_.map(SERVICES, (service, index) => (
              <Service
                key={service.serviceType + service.isConnected}
                service={serviceStatus[service.serviceType] || service}
                index={index}
              />
            ))}
          </ServiceList>
        </Root>
      </ThemeProvider>
    )
  }
}

const Service = ({ service: { serviceType, isConnected, connectedInstanceCount }, index }) => (
  <ThemeProvider theme={theme => theme.serviceItem[isConnected ? 'connecting' : 'disconnected']}>
    <ServiceRoot index={index + 2}>
      <Icon name={isConnected == null ? 'ellipsis-h' : isConnected ? 'check' : 'times'} />

        <ServiceName>{serviceType}</ServiceName>
        <NodeCount>
          {connectedInstanceCount != null && (
            <React.Fragment>
              ({connectedInstanceCount} Node{connectedInstanceCount !== 1 ? 's' : ''})
            </React.Fragment>
          )}
        </NodeCount>

    </ServiceRoot>
  </ThemeProvider>
)

export default connect(({ compositeStatusService: serviceStatus, connectionStatus }) => {
  const services = Object.values(serviceStatus).map(s => s.isConnected)
  const mode =
    services.length < SERVICES.length || (services.some(s => s) && !services.every(s => s))
      ? 'connecting'
      : services.every(s => s)
        ? 'connected'
        : 'disconnected'

  return {
    connectionStatus,
    serviceStatus,
    mode
  }
})(StatusBar)
<commit_msg>Corrected <StatusBar/> theme keys<commit_after>import _ from 'lodash'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { ThemeProvider } from 'emotion-theming'

import { Environment, withEnvironment } from 'rt-components'
import { Flex } from 'rt-components'
import { ConnectionType, ServiceConnectionInfo } from 'system'
import Icon from './Icon'

import { Root, Body, Fill, ExpandToggle, ServiceList, ServiceRoot, ServiceName, NodeCount } from './styled.js'

export const SERVICES = ['blotter', 'reference', 'execution', 'pricing', 'analytics'].map(serviceType => ({
  serviceType
}))

class StatusBar extends Component {
  state = {
    expanded: true
  }

  resolveTheme = theme => {
    const { mode } = this.props
    const currentTheme = theme.statusBar[mode]

    theme = {
      ...theme,
      ...theme.statusBar[mode],
      serviceItem: {
        connected: theme.statusBar.connected,
        disconnected: mode === 'connecting' ? theme.statusBar.connecting : theme.statusBar.disconnected
      }
    }

    return theme
  }

  toggleExpanded = () => this.setState(({ expanded }) => ({ expanded: !expanded }))

  render() {
    const {
      connectionStatus: { url, transportType },
      mode,
      serviceStatus
    } = this.props

    const { expanded } = this.state

    return (
      <ThemeProvider key={mode} theme={this.resolveTheme}>
        <Root expand={expanded}>

            <Icon name="check" />

            {mode === 'disconnected' ? (
              'Disconnected'
            ) : (
              <React.Fragment>
                {_.capitalize(mode)} to {url} ({transportType})
              </React.Fragment>
            )}

            <Fill />

            <ExpandToggle expand={expanded} />

          <ServiceList>
            {_.map(SERVICES, (service, index) => (
              <Service
                key={service.serviceType + service.isConnected}
                service={serviceStatus[service.serviceType] || service}
                index={index}
              />
            ))}
          </ServiceList>
        </Root>
      </ThemeProvider>
    )
  }
}

const Service = ({ service: { serviceType, isConnected, connectedInstanceCount }, index }) => (
  <ThemeProvider theme={theme => theme.serviceItem[isConnected ? 'connected' : 'disconnected']}>
    <ServiceRoot index={index + 2}>
      <Icon name={isConnected == null ? 'ellipsis-h' : isConnected ? 'check' : 'times'} />

        <ServiceName>{serviceType}</ServiceName>
        <NodeCount>
          {connectedInstanceCount != null && (
            <React.Fragment>
              ({connectedInstanceCount} Node{connectedInstanceCount !== 1 ? 's' : ''})
            </React.Fragment>
          )}
        </NodeCount>

    </ServiceRoot>
  </ThemeProvider>
)

export default connect(({ compositeStatusService: serviceStatus, connectionStatus }) => {
  const services = Object.values(serviceStatus).map(s => s.isConnected)
  const mode =
    services.length < SERVICES.length || (services.some(s => s) && !services.every(s => s))
      ? 'connecting'
      : services.every(s => s)
        ? 'connected'
        : 'disconnected'

  return {
    connectionStatus,
    serviceStatus,
    mode
  }
})(StatusBar)
<|endoftext|>


<commit_before>var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');

chai.use(chaiAsPromised);
var expect = chai.expect;

var homeStepDefinitionsWrapper = function () {

    var homePage = require('../support/homepage.po');
    var mortalityPage = require('../support/mortalitypage.po')

    this.When(/^I hit app url$/, function () {
        browser.get('/');
    });

    this.Then(/^I should be automatically redirected to home page$/, function () {
        browser.getCurrentUrl().then(function(url) {
           expect(url).to.equal(browser.baseUrl);
        });

    });

    this.When(/^I click on Explore button in Health Information Gateway section$/, function () {
        homePage.quickHealthExploreBtn.click();
    });
    this.When(/^I click on Explore button in Youth Related card under Behavioral Risk$/, function () {
        homePage.mentalExplorerLink.click();
    });

    this.Then(/^I should get search page with default filter type "([^"]*)"$/, function (arg1) {
        browser.sleep(60000);
        expect(mortalityPage.getSelectedFilterType()).to.eventually.equal(arg1);
    });

    this.When(/^I click on explore button in Birth card under womens health section$/, function () {
        homePage.birthExplorerLink.click();
    });

    this.Then(/^I should get a info modal$/, function () {
        homePage.getPhaseTwoPopupHeading().then(function(heading){
            expect(heading).to.equal('Work in progress')
        });
    });

    this.When(/^I am at home page$/, function () {
        browser.get("/");
    });

    this.Then(/^gray banner on top reads "([^"]*)"$/, function (givenMessage) {
        homePage.getWorkInProgressMessage().then(function(foundMessage){
           expect(givenMessage).to.equal(foundMessage);
        });
    });

    this.When(/^I am at search page$/, function () {
        browser.get("/search");
    });

    this.Then(/^I see the name of application as "([^"]*)"$/, function (arg1) {
        browser.sleep(10000);
        homePage.getOWHAppName().then(function(appName){
            expect(appName).to.equal(arg1)
        });
    });
};
module.exports = homeStepDefinitionsWrapper;
<commit_msg>decreased sleep time<commit_after>var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');

chai.use(chaiAsPromised);
var expect = chai.expect;

var homeStepDefinitionsWrapper = function () {

    var homePage = require('../support/homepage.po');
    var mortalityPage = require('../support/mortalitypage.po')

    this.When(/^I hit app url$/, function () {
        browser.get('/');
    });

    this.Then(/^I should be automatically redirected to home page$/, function () {
        browser.getCurrentUrl().then(function(url) {
           expect(url).to.equal(browser.baseUrl);
        });

    });

    this.When(/^I click on Explore button in Health Information Gateway section$/, function () {
        homePage.quickHealthExploreBtn.click();
    });
    this.When(/^I click on Explore button in Youth Related card under Behavioral Risk$/, function () {
        homePage.mentalExplorerLink.click();
    });

    this.Then(/^I should get search page with default filter type "([^"]*)"$/, function (arg1) {
        browser.sleep(30000);
        expect(mortalityPage.getSelectedFilterType()).to.eventually.equal(arg1);
    });

    this.When(/^I click on explore button in Birth card under womens health section$/, function () {
        homePage.birthExplorerLink.click();
    });

    this.Then(/^I should get a info modal$/, function () {
        homePage.getPhaseTwoPopupHeading().then(function(heading){
            expect(heading).to.equal('Work in progress')
        });
    });

    this.When(/^I am at home page$/, function () {
        browser.get("/");
    });

    this.Then(/^gray banner on top reads "([^"]*)"$/, function (givenMessage) {
        homePage.getWorkInProgressMessage().then(function(foundMessage){
           expect(givenMessage).to.equal(foundMessage);
        });
    });

    this.When(/^I am at search page$/, function () {
        browser.get("/search");
    });

    this.Then(/^I see the name of application as "([^"]*)"$/, function (arg1) {
        browser.sleep(10000);
        homePage.getOWHAppName().then(function(appName){
            expect(appName).to.equal(arg1)
        });
    });
};
module.exports = homeStepDefinitionsWrapper;
<|endoftext|>