<commit_before>import objectAssign from 'object-assign';

import isInteger from './utils/isInteger';
import {updateKey} from './createNavigator';
import {curry2} from './utils/curry';
import {$setKey} from './$set';
import {$navKey} from './$nav';
import {$applyKey} from './$apply';
import $none, {$noneKey, isNone, undefinedIfNone} from './$none';

let continueUpdateEach;
let update;

export const updateEach = (path, object, pathIndex, returnFn, mutationMarker) => {
  if (pathIndex >= path.length) {
    if (returnFn) {
      return returnFn(object);
    }
    return object;
  }
  const nav = path[pathIndex];
  if (!nav || typeof nav === 'string' || typeof nav === 'number' || typeof nav === 'boolean') {
    if (object && typeof object === 'object') {
      const value = object[nav];
      const newValue = updateEach(path, value, pathIndex + 1, returnFn);
      if (isNone(newValue)) {
        if (!(nav in object)) {
          return object;
        }
        if (Array.isArray(object)) {
          const newObject = object.slice(0);
          // Could have a silly edge here where nav was a property and not an index, which means it disappears.
          // If it's still here, that means it's an index.
          if (nav in object) {
            newObject.splice(nav, 1);
          }
          return newObject;
        }
        const newObject = objectAssign({}, object);
        delete newObject[nav];
        return newObject;
      }
      if (value === newValue) {
        return object;
      }
      if (mutationMarker) {
        if (mutationMarker.hasMutated) {
          object[nav] = newValue;
          return object;
        } else {
          mutationMarker.hasMutated = true;
        }
      }
      if (Array.isArray(object)) {
        const newObject = object.slice(0);
        newObject[nav] = newValue;
        return newObject;
      }
      return objectAssign({}, object, {[nav]: newValue});
    } else {
      // if (pathIndex === 0) {
      //   return object;
      // }
      object = isInteger(nav) ? [] : {};
      const value = object[nav];
      const newValue = updateEach(path, value, pathIndex + 1, returnFn);
      object[nav] = newValue;
      return object;
    }
  }
  if (typeof nav === 'function') {
    if (nav(object)) {
      return updateEach(path, object, pathIndex + 1, returnFn);
    } else {
      return object;
    }
  }
  let updateFn;
  switch (nav['@@qim/nav']) {
    case $applyKey: {
      return updateEach(path, nav.data(object), pathIndex + 1, returnFn);
    }
    case $setKey:
      return updateEach(path, nav.data, pathIndex + 1, returnFn);
    case $navKey:
      return updateEach(
        nav.data, object, 0,
        (_object) => updateEach(path, _object, pathIndex + 1, returnFn)
      );
    case $noneKey:
      return $none;
  }
  if (nav[updateKey]) {
    updateFn = nav[updateKey];
  } else if (nav['@@qim/nav']) {
    updateFn = nav['@@qim/nav'][updateKey];
  } else if (Array.isArray(nav)) {
    mutationMarker = mutationMarker || {
      hasMutated: false
    };
    const nestedResult = undefinedIfNone(updateEach(nav, object, 0, undefined, mutationMarker));
    return updateEach(path, nestedResult, pathIndex + 1, returnFn, mutationMarker);
  }
  if (!updateFn) {
    throw new Error(`invalid navigator at path index ${pathIndex}`);
  }
  return continueUpdateEach(updateFn, nav, object, path, pathIndex, returnFn);
};

continueUpdateEach = (updateFn, nav, object, path, pathIndex, returnFn) =>
  updateFn(nav, object, (subObject) => updateEach(path, subObject, pathIndex + 1, returnFn), path, pathIndex);

update = function (path, obj) {
  if (!Array.isArray(path)) {
    path = [path];
  }
  return undefinedIfNone(updateEach(path, obj, 0));
};

export default curry2(update);
<commit_msg>remove commented code<commit_after>import objectAssign from 'object-assign';

import isInteger from './utils/isInteger';
import {updateKey} from './createNavigator';
import {curry2} from './utils/curry';
import {$setKey} from './$set';
import {$navKey} from './$nav';
import {$applyKey} from './$apply';
import $none, {$noneKey, isNone, undefinedIfNone} from './$none';

let continueUpdateEach;
let update;

export const updateEach = (path, object, pathIndex, returnFn, mutationMarker) => {
  if (pathIndex >= path.length) {
    if (returnFn) {
      return returnFn(object);
    }
    return object;
  }
  const nav = path[pathIndex];
  if (!nav || typeof nav === 'string' || typeof nav === 'number' || typeof nav === 'boolean') {
    if (object && typeof object === 'object') {
      const value = object[nav];
      const newValue = updateEach(path, value, pathIndex + 1, returnFn);
      if (isNone(newValue)) {
        if (!(nav in object)) {
          return object;
        }
        if (Array.isArray(object)) {
          const newObject = object.slice(0);
          // Could have a silly edge here where nav was a property and not an index, which means it disappears.
          // If it's still here, that means it's an index.
          if (nav in object) {
            newObject.splice(nav, 1);
          }
          return newObject;
        }
        const newObject = objectAssign({}, object);
        delete newObject[nav];
        return newObject;
      }
      if (value === newValue) {
        return object;
      }
      if (mutationMarker) {
        if (mutationMarker.hasMutated) {
          object[nav] = newValue;
          return object;
        } else {
          mutationMarker.hasMutated = true;
        }
      }
      if (Array.isArray(object)) {
        const newObject = object.slice(0);
        newObject[nav] = newValue;
        return newObject;
      }
      return objectAssign({}, object, {[nav]: newValue});
    } else {
      object = isInteger(nav) ? [] : {};
      const value = object[nav];
      const newValue = updateEach(path, value, pathIndex + 1, returnFn);
      object[nav] = newValue;
      return object;
    }
  }
  if (typeof nav === 'function') {
    if (nav(object)) {
      return updateEach(path, object, pathIndex + 1, returnFn);
    } else {
      return object;
    }
  }
  let updateFn;
  switch (nav['@@qim/nav']) {
    case $applyKey: {
      return updateEach(path, nav.data(object), pathIndex + 1, returnFn);
    }
    case $setKey:
      return updateEach(path, nav.data, pathIndex + 1, returnFn);
    case $navKey:
      return updateEach(
        nav.data, object, 0,
        (_object) => updateEach(path, _object, pathIndex + 1, returnFn)
      );
    case $noneKey:
      return $none;
  }
  if (nav[updateKey]) {
    updateFn = nav[updateKey];
  } else if (nav['@@qim/nav']) {
    updateFn = nav['@@qim/nav'][updateKey];
  } else if (Array.isArray(nav)) {
    mutationMarker = mutationMarker || {
      hasMutated: false
    };
    const nestedResult = undefinedIfNone(updateEach(nav, object, 0, undefined, mutationMarker));
    return updateEach(path, nestedResult, pathIndex + 1, returnFn, mutationMarker);
  }
  if (!updateFn) {
    throw new Error(`invalid navigator at path index ${pathIndex}`);
  }
  return continueUpdateEach(updateFn, nav, object, path, pathIndex, returnFn);
};

continueUpdateEach = (updateFn, nav, object, path, pathIndex, returnFn) =>
  updateFn(nav, object, (subObject) => updateEach(path, subObject, pathIndex + 1, returnFn), path, pathIndex);

update = function (path, obj) {
  if (!Array.isArray(path)) {
    path = [path];
  }
  return undefinedIfNone(updateEach(path, obj, 0));
};

export default curry2(update);
<|endoftext|>


<commit_before>/*global document, Polymer, window*/

(function () {

	'use strict';

	var
		BOTTOM_BAR_TOOLBAR_SLOT = 'bottom-bar-toolbar',
		BOTTOM_BAR_MENU_SLOT = 'bottom-bar-menu';

	Polymer({

		is: 'cosmoz-bottom-bar',

		behaviors: [
			Polymer.IronResizableBehavior
		],

		listeners: {
			'iron-resize': '_onResize',
			'iron-overlay-closed': '_dropdownClosed',
			'transitionend': '_onTransitionEnd'
		},

		properties: {

			/** Whether the bar is active/shown (always active when fixed) */

			active: {
				type: Boolean,
				value: false,
				reflectToAttribute: true
			},

			/** Whether the bar is fixed (and take up space) or shows/hides from the bottom when needed
			 ** usually fixed on desktop and not mobile
			 */

			fixed: {
				type: Boolean,
				value: false,
				reflectToAttribute: true
			},

			/** Bar height (not applicable when "matchParent" or "matchElementHeight" is set) */

			barHeight: {
				type: Number,
				value: 64
			},

			/** Reference element from which to inherit height */

			matchElementHeight: {
				type: Object,
				computed: 'computeMatchElementHeight(matchParent)'
			},

			/** Whether to match the height of parent (set reference element to parent) */

			matchParent: {
				type: Boolean,
				value: false
			},

			/** Scroller element to listen to when deciding whether or not to show the bar.
			 * 	Bar will be shown while scrolling up or when reaching bottom
			 */

			scroller: {
				type: Object,
				observer: '_scrollerChanged'
			},

			scrollerOverflow: {
				type: Boolean,
				value: false,
				notify: true
			},

			/**
			 * Indicates wether this bottom bar has items distributed to the menu.
			 */

			hasMenuItems: {
				type: Boolean,
				value: false
			},

			/**
			 * Class applied the the selected item
			 */
			selectedClass: {
				type: String,
				value: 'cosmoz-bottom-bar-selected-item'
			},

			/**
			 * Class applied to items distributed to the toolbar
			 */
			toolbarClass: {
				type: String,
				value: 'cosmoz-bottom-bar-toolbar'
			},

			/**
			 * Class applied to items distributed to the menu
			 */
			menuClass: {
				type: String,
				value: 'cosmoz-bottom-bar-menu'
			},

			maxToolbarItems: {
				type: Number,
				value: 3
			},

			computedBarHeight: {
				type: Number,
				computed: '_computeComputedBarHeight(matchElementHeight, barHeight, _computedBarHeightKicker)',
				notify: true
			},

			_computedBarHeightKicker: {
				type: Number
			},

			visible: {
				type: Boolean,
				notify: true,
				readOnly: true,
				computed: '_computeVisible(hasActions, active, fixed)'
			},

			/**
			 * Whether we have any visible actions
			 */

			hasActions: {
				type: Boolean,
				value: false,
				readOnly: true,
				notify: true
			},

			_nodeObserver: {
				type: Object
			},

			_hiddenMutationObserver: {
				type: Object
			},

			_scrollHandler: {
				type: Object
			}

		},

		observers: [
			'_showHideBottomBar(visible, computedBarHeight)'
		],

		attached: function () {
			var context = this;

			this._hiddenMutationObserver = new MutationObserver(function (mutations) {
				var layoutingChange = mutations.some(function (mutation) {
					return mutation.attributeName === 'hidden';
				});
				if (layoutingChange) {
					context._forceLayout();
				}
			});
			this._nodeObserver = Polymer.dom(this).observeNodes(this._childrenUpdated.bind(this));
			this._computedBarHeightKicker = 0;
		},

		detached: function () {
			Polymer.dom(this).unobserveNodes(this._nodeObserver);
			this._hiddenMutationObserver.disconnect();
			if (this.scroller) {
				this.scroller.removeEventListener('scroll', this._scrollHandler);
			}
		},

		created: function () {
			this._scrollHandler = this._scrollManagement.bind(this);
		},

		_computeVisible: function (hasActions, active, fixed) {
			return hasActions && (active || fixed);
		},

		computeMatchElementHeight: function (matchParent) {
			if (matchParent) {
				return this.parentElement;
			}

			return null;
		},

		_scrollerChanged: function (newScroller, oldScroller) {
			if (oldScroller) {
				oldScroller.removeEventListener('scroll', this._scrollHandler);
			}

			if (!newScroller) {
				return;
			}

			if (!newScroller.addEventListener) {
				console.warn('New scroller does not have addEventListener', newScroller);
				return;
			}

			newScroller.addEventListener('scroll', this._scrollHandler);
			this.lastScroll = newScroller.scrollTop;
		},

		_computeComputedBarHeight: function (matchElementHeight, barHeight, kicker) {
			if (matchElementHeight) {
				return matchElementHeight.offsetHeight;
			}
			return barHeight;
		},

		_getHeightStyle: function (height) {
			return 'height: ' + height + 'px;';
		},

		_onResize: function () {
			this._computedBarHeightKicker += 1;
			this._scrollManagement();
			this._debounceLayoutActions();
		},

		_scrollManagement: function () {

			if (!this.scroller) {
				return;
			}

			var scrollTop = this.scroller.scrollTop,
				isScrollingUp = this.lastScroll > scrollTop,
				scrollerHeight = this.scroller.clientHeight,
				scrollerScrollHeight = this.scroller.scrollHeight,
				isAtBottom = scrollTop + scrollerHeight + this.barHeight * 0.7 >= scrollerScrollHeight,
				isAtTop = scrollTop === 0;

			this.active = isAtTop || isScrollingUp || isAtBottom;
			this.scrollerOverflow = scrollerScrollHeight > scrollerHeight;
			this.lastScroll = scrollTop;
		},

		_showHideBottomBar: function (visible, barHeight) {
			var	translateY = visible ? 0 : barHeight;

			this.style['height'] = 'auto';
			this.style['overflow'] = 'initial';
			this.translate3d('0px', translateY + 'px', '0px');
		},

		_onTransitionEnd: function (event) {
			if (Polymer.dom(event).rootTarget === this
				&& !this.visible) {
				this.style['height'] = '0px';
				this.style['overflow'] = 'hidden';
			}
		},

		_childrenUpdated: function (info) {
			info.addedNodes.forEach(function (node) {
				if (node.nodeType === Node.ELEMENT_NODE) {
					this._hiddenMutationObserver.observe(node, {
						attributes: true
					});
				}
			}, this);

			// Initially distribute elements to the menu.
			this._getElementToDistribute().forEach(function (element) {
				var slot = element.getAttribute('slot');
				if (slot !== BOTTOM_BAR_MENU_SLOT && slot !== BOTTOM_BAR_TOOLBAR_SLOT) {
					this._moveElement(element, false);
				}
			}, this);
			this._debounceLayoutActions();
		},

		_getElementToDistribute: function () {
			return this.getEffectiveChildren()
				.filter(function (element) {
					return !element.hidden && element.getAttribute('slot') !== 'info' && element.tagName !== 'TEMPLATE';
				}, this);
		},

		_dropdownClosed: function () {
			this.$.dropdownButton.active = false;
		},

		/**
		 * Causes a new layouting of all the bottom bar elements after visibility of one of the element has changed.
		 * This is implemented by placing all elements in the menu and trying to fill the toolbar.
		 * @returns {void}
		 */

		_forceLayout: function () {
			this._overflowWidth = undefined;
			this._getElementToDistribute().forEach(function (element) {
				this._moveElement(element, false);
			}, this);
			this._debounceLayoutActions();
		},

		/**
		 * Layout the actions available as buttons or menu items
		 *
		 * If the window is resizing down, just make sure that all buttons fits, and if not,
		 * move one to menu and call itself async (to allow re-rendering) and see if we fit.
		 * Repeat until the button fits or no buttons are left.
		 *
		 * If the window is sizing up, try to place a menu item out as a button, call itself
		 * async (to allow re-rendering) and see if we fit - if we don't, remove the button again.
		 *
		 * We also need to keep track of `_scalingUp` between calls since the resize might fire
		 * a lot of events, and we don't want to be starting multiple "calculation processes"
		 * since this will result in an infinite loop.
		 *
		 * The actual layouting of actions will be performed by adding or removing the 'button'
		 * attribute from the action, which will cause it to match different content insertion
		 * points.
		 *
		 * @param  {Boolean} bigger If we're sizing up
		 *
		 */

		_layoutActions: function () {
			var elements = this._getElementToDistribute(),
				toolbarElements,
				menuElements,
				toolbar = this.$.toolbar,
				currentWidth,
				fits,
				newToolbarElement,
				newMenuElement;

			this._setHasActions(elements.length > 0);
			if (!this.hasActions) {
				// No need to render if we don't have any actions
				return;
			}

			currentWidth = toolbar.clientWidth;
			fits = toolbar.scrollWidth <= currentWidth + 1;

			toolbarElements = elements.filter(function (element) {
				if (element.getAttribute('slot') === BOTTOM_BAR_TOOLBAR_SLOT) {
					// make sure we only read scrollWidth and clientWidth until
					// know that we don't fit
					fits = fits && element.scrollWidth <= element.clientWidth;
					return true;
				}
			});

			if (fits && toolbarElements.length === this.maxToolbarItems) {
				return;
			}

			fits = fits && toolbarElements.length < this.maxToolbarItems;

			menuElements = elements.filter(function (e) {
				return e.getAttribute('slot') === BOTTOM_BAR_MENU_SLOT;
			});

			if (fits) {
				if (this._canAddMoreButtonToBar(currentWidth, toolbarElements, menuElements)) {
					newToolbarElement = menuElements[0];
					this._moveElement(newToolbarElement, true);
					// (pasleq) If we are moving the focused element from the menu to the toolbar
					// while the toolbar is open, this will cause an error in iron-control-state
					// that tries to handle lost of focus on an element that has been removed.
					if (toolbarElements.length > 0) {
						toolbarElements[0].focus();
					} else {
						newToolbarElement.focus();
					}
					this.$.menu.close();
					this.distributeContent();
					this._debounceLayoutActions();
					this.hasMenuItems = menuElements.length > 1;
				} else {
					this.hasMenuItems = menuElements.length > 0;
				}
				return;
			}

			this._overflowWidth = currentWidth;

			if (toolbarElements.length < 1) {
				return;
			}

			newMenuElement = toolbarElements[toolbarElements.length - 1];
			this._moveElement(newMenuElement, false);
			this.hasMenuItems = true;
			this.distributeContent();
			this._debounceLayoutActions();
		},

		_moveElement: function (element, toToolbar) {
			var slot = toToolbar ? BOTTOM_BAR_TOOLBAR_SLOT : BOTTOM_BAR_MENU_SLOT,
				tabindex = toToolbar ? '0' : '-1';

			element.setAttribute('slot', slot);
			element.setAttribute('tabindex', tabindex);
			this.toggleClass(this.menuClass, !toToolbar, element);
			this.toggleClass(this.toolbarClass, toToolbar, element);
		},

		_debounceLayoutActions: function () {
			this.debounce('layoutActions', this._layoutActions, 30);
		},

		_canAddMoreButtonToBar: function (width, bottomBarElements, menuElements) {

			var hasSpace = width > this._overflowWidth || this._overflowWidth === undefined,
				hasPlace = bottomBarElements.length < this.maxToolbarItems,
				hasCandidates = menuElements.length > 0;

			return hasSpace && hasPlace && hasCandidates;
		},

		_onActionSelected: function (event, detail) {
			this._fireAction(detail.item);
			event.currentTarget.selected = undefined;
		},

		_fireAction: function (item) {

			if (!item || !item.dispatchEvent) {
				return;
			}

			item.dispatchEvent(new window.CustomEvent('action', {
				bubbles: true,
				cancelable: true,
				detail: {
					item: item
				}
			}));
		}
	});
}());
<commit_msg>Make sure property docs are found by wc.org<commit_after>/*global document, Polymer, window*/

(function () {

	'use strict';

	var
		BOTTOM_BAR_TOOLBAR_SLOT = 'bottom-bar-toolbar',
		BOTTOM_BAR_MENU_SLOT = 'bottom-bar-menu';

	Polymer({

		is: 'cosmoz-bottom-bar',

		behaviors: [
			Polymer.IronResizableBehavior
		],

		listeners: {
			'iron-resize': '_onResize',
			'iron-overlay-closed': '_dropdownClosed',
			'transitionend': '_onTransitionEnd'
		},

		properties: {

			/** Whether the bar is active/shown (always active when fixed) */
			active: {
				type: Boolean,
				value: false,
				reflectToAttribute: true
			},

			/** Whether the bar is fixed (and take up space) or shows/hides from the bottom when needed
			 ** usually fixed on desktop and not mobile
			 */
			fixed: {
				type: Boolean,
				value: false,
				reflectToAttribute: true
			},

			/** Bar height (not applicable when "matchParent" or "matchElementHeight" is set) */
			barHeight: {
				type: Number,
				value: 64
			},

			/** Reference element from which to inherit height */
			matchElementHeight: {
				type: Object,
				computed: 'computeMatchElementHeight(matchParent)'
			},

			/** Whether to match the height of parent (set reference element to parent) */
			matchParent: {
				type: Boolean,
				value: false
			},

			/** Scroller element to listen to when deciding whether or not to show the bar.
			 * 	Bar will be shown while scrolling up or when reaching bottom
			 */
			scroller: {
				type: Object,
				observer: '_scrollerChanged'
			},

			scrollerOverflow: {
				type: Boolean,
				value: false,
				notify: true
			},

			/**
			 * Indicates wether this bottom bar has items distributed to the menu.
			 */
			hasMenuItems: {
				type: Boolean,
				value: false
			},

			/**
			 * Class applied the the selected item
			 */
			selectedClass: {
				type: String,
				value: 'cosmoz-bottom-bar-selected-item'
			},

			/**
			 * Class applied to items distributed to the toolbar
			 */
			toolbarClass: {
				type: String,
				value: 'cosmoz-bottom-bar-toolbar'
			},

			/**
			 * Class applied to items distributed to the menu
			 */
			menuClass: {
				type: String,
				value: 'cosmoz-bottom-bar-menu'
			},

			maxToolbarItems: {
				type: Number,
				value: 3
			},

			computedBarHeight: {
				type: Number,
				computed: '_computeComputedBarHeight(matchElementHeight, barHeight, _computedBarHeightKicker)',
				notify: true
			},

			_computedBarHeightKicker: {
				type: Number
			},

			visible: {
				type: Boolean,
				notify: true,
				readOnly: true,
				computed: '_computeVisible(hasActions, active, fixed)'
			},

			/**
			 * Whether we have any visible actions
			 */
			hasActions: {
				type: Boolean,
				value: false,
				readOnly: true,
				notify: true
			}
		},

		/**
		 * Non-Polymer properties
		 */
		_nodeObserver: undefined,
		_hiddenMutationObserver: undefined,
		_scrollHandler: undefined,

		observers: [
			'_showHideBottomBar(visible, computedBarHeight)'
		],

		attached: function () {
			var context = this;

			this._hiddenMutationObserver = new MutationObserver(function (mutations) {
				var layoutingChange = mutations.some(function (mutation) {
					return mutation.attributeName === 'hidden';
				});
				if (layoutingChange) {
					context._forceLayout();
				}
			});
			this._nodeObserver = Polymer.dom(this).observeNodes(this._childrenUpdated.bind(this));
			this._computedBarHeightKicker = 0;
		},

		detached: function () {
			Polymer.dom(this).unobserveNodes(this._nodeObserver);
			this._hiddenMutationObserver.disconnect();
			if (this.scroller) {
				this.scroller.removeEventListener('scroll', this._scrollHandler);
			}
		},

		created: function () {
			this._scrollHandler = this._scrollManagement.bind(this);
		},

		_computeVisible: function (hasActions, active, fixed) {
			return hasActions && (active || fixed);
		},

		computeMatchElementHeight: function (matchParent) {
			if (matchParent) {
				return this.parentElement;
			}

			return null;
		},

		_scrollerChanged: function (newScroller, oldScroller) {
			if (oldScroller) {
				oldScroller.removeEventListener('scroll', this._scrollHandler);
			}

			if (!newScroller) {
				return;
			}

			if (!newScroller.addEventListener) {
				console.warn('New scroller does not have addEventListener', newScroller);
				return;
			}

			newScroller.addEventListener('scroll', this._scrollHandler);
			this.lastScroll = newScroller.scrollTop;
		},

		_computeComputedBarHeight: function (matchElementHeight, barHeight, kicker) {
			if (matchElementHeight) {
				return matchElementHeight.offsetHeight;
			}
			return barHeight;
		},

		_getHeightStyle: function (height) {
			return 'height: ' + height + 'px;';
		},

		_onResize: function () {
			this._computedBarHeightKicker += 1;
			this._scrollManagement();
			this._debounceLayoutActions();
		},

		_scrollManagement: function () {

			if (!this.scroller) {
				return;
			}

			var scrollTop = this.scroller.scrollTop,
				isScrollingUp = this.lastScroll > scrollTop,
				scrollerHeight = this.scroller.clientHeight,
				scrollerScrollHeight = this.scroller.scrollHeight,
				isAtBottom = scrollTop + scrollerHeight + this.barHeight * 0.7 >= scrollerScrollHeight,
				isAtTop = scrollTop === 0;

			this.active = isAtTop || isScrollingUp || isAtBottom;
			this.scrollerOverflow = scrollerScrollHeight > scrollerHeight;
			this.lastScroll = scrollTop;
		},

		_showHideBottomBar: function (visible, barHeight) {
			var	translateY = visible ? 0 : barHeight;

			this.style['height'] = 'auto';
			this.style['overflow'] = 'initial';
			this.translate3d('0px', translateY + 'px', '0px');
		},

		_onTransitionEnd: function (event) {
			if (Polymer.dom(event).rootTarget === this
				&& !this.visible) {
				this.style['height'] = '0px';
				this.style['overflow'] = 'hidden';
			}
		},

		_childrenUpdated: function (info) {
			info.addedNodes.forEach(function (node) {
				if (node.nodeType === Node.ELEMENT_NODE) {
					this._hiddenMutationObserver.observe(node, {
						attributes: true
					});
				}
			}, this);

			// Initially distribute elements to the menu.
			this._getElementToDistribute().forEach(function (element) {
				var slot = element.getAttribute('slot');
				if (slot !== BOTTOM_BAR_MENU_SLOT && slot !== BOTTOM_BAR_TOOLBAR_SLOT) {
					this._moveElement(element, false);
				}
			}, this);
			this._debounceLayoutActions();
		},

		_getElementToDistribute: function () {
			return this.getEffectiveChildren()
				.filter(function (element) {
					return !element.hidden && element.getAttribute('slot') !== 'info' && element.tagName !== 'TEMPLATE';
				}, this);
		},

		_dropdownClosed: function () {
			this.$.dropdownButton.active = false;
		},

		/**
		 * Causes a new layouting of all the bottom bar elements after visibility of one of the element has changed.
		 * This is implemented by placing all elements in the menu and trying to fill the toolbar.
		 * @returns {void}
		 */

		_forceLayout: function () {
			this._overflowWidth = undefined;
			this._getElementToDistribute().forEach(function (element) {
				this._moveElement(element, false);
			}, this);
			this._debounceLayoutActions();
		},

		/**
		 * Layout the actions available as buttons or menu items
		 *
		 * If the window is resizing down, just make sure that all buttons fits, and if not,
		 * move one to menu and call itself async (to allow re-rendering) and see if we fit.
		 * Repeat until the button fits or no buttons are left.
		 *
		 * If the window is sizing up, try to place a menu item out as a button, call itself
		 * async (to allow re-rendering) and see if we fit - if we don't, remove the button again.
		 *
		 * We also need to keep track of `_scalingUp` between calls since the resize might fire
		 * a lot of events, and we don't want to be starting multiple "calculation processes"
		 * since this will result in an infinite loop.
		 *
		 * The actual layouting of actions will be performed by adding or removing the 'button'
		 * attribute from the action, which will cause it to match different content insertion
		 * points.
		 *
		 * @param  {Boolean} bigger If we're sizing up
		 *
		 */

		_layoutActions: function () {
			var elements = this._getElementToDistribute(),
				toolbarElements,
				menuElements,
				toolbar = this.$.toolbar,
				currentWidth,
				fits,
				newToolbarElement,
				newMenuElement;

			this._setHasActions(elements.length > 0);
			if (!this.hasActions) {
				// No need to render if we don't have any actions
				return;
			}

			currentWidth = toolbar.clientWidth;
			fits = toolbar.scrollWidth <= currentWidth + 1;

			toolbarElements = elements.filter(function (element) {
				if (element.getAttribute('slot') === BOTTOM_BAR_TOOLBAR_SLOT) {
					// make sure we only read scrollWidth and clientWidth until
					// know that we don't fit
					fits = fits && element.scrollWidth <= element.clientWidth;
					return true;
				}
			});

			if (fits && toolbarElements.length === this.maxToolbarItems) {
				return;
			}

			fits = fits && toolbarElements.length < this.maxToolbarItems;

			menuElements = elements.filter(function (e) {
				return e.getAttribute('slot') === BOTTOM_BAR_MENU_SLOT;
			});

			if (fits) {
				if (this._canAddMoreButtonToBar(currentWidth, toolbarElements, menuElements)) {
					newToolbarElement = menuElements[0];
					this._moveElement(newToolbarElement, true);
					// (pasleq) If we are moving the focused element from the menu to the toolbar
					// while the toolbar is open, this will cause an error in iron-control-state
					// that tries to handle lost of focus on an element that has been removed.
					if (toolbarElements.length > 0) {
						toolbarElements[0].focus();
					} else {
						newToolbarElement.focus();
					}
					this.$.menu.close();
					this.distributeContent();
					this._debounceLayoutActions();
					this.hasMenuItems = menuElements.length > 1;
				} else {
					this.hasMenuItems = menuElements.length > 0;
				}
				return;
			}

			this._overflowWidth = currentWidth;

			if (toolbarElements.length < 1) {
				return;
			}

			newMenuElement = toolbarElements[toolbarElements.length - 1];
			this._moveElement(newMenuElement, false);
			this.hasMenuItems = true;
			this.distributeContent();
			this._debounceLayoutActions();
		},

		_moveElement: function (element, toToolbar) {
			var slot = toToolbar ? BOTTOM_BAR_TOOLBAR_SLOT : BOTTOM_BAR_MENU_SLOT,
				tabindex = toToolbar ? '0' : '-1';

			element.setAttribute('slot', slot);
			element.setAttribute('tabindex', tabindex);
			this.toggleClass(this.menuClass, !toToolbar, element);
			this.toggleClass(this.toolbarClass, toToolbar, element);
		},

		_debounceLayoutActions: function () {
			this.debounce('layoutActions', this._layoutActions, 30);
		},

		_canAddMoreButtonToBar: function (width, bottomBarElements, menuElements) {

			var hasSpace = width > this._overflowWidth || this._overflowWidth === undefined,
				hasPlace = bottomBarElements.length < this.maxToolbarItems,
				hasCandidates = menuElements.length > 0;

			return hasSpace && hasPlace && hasCandidates;
		},

		_onActionSelected: function (event, detail) {
			this._fireAction(detail.item);
			event.currentTarget.selected = undefined;
		},

		_fireAction: function (item) {

			if (!item || !item.dispatchEvent) {
				return;
			}

			item.dispatchEvent(new window.CustomEvent('action', {
				bubbles: true,
				cancelable: true,
				detail: {
					item: item
				}
			}));
		}
	});
}());
<|endoftext|>


<commit_before>/*globals define, $*/
/*jshint browser: true*/
/**
 * Provides dialog helpers with backend support for handling libraries.
 * @author kecso / https://github.com/kecso
 */

define([
    'js/logger',
    'js/Dialogs/Confirm/ConfirmDialog',
    'js/Dialogs/AddOrUpdateLibrary/AddOrUpdateLibraryDialog',
    'js/Loader/ProgressNotification',
    'common/regexp',
    'common/storage/constants',
    'common/core/constants'
], function (Logger, ConfirmDialog, AddOrUpdateLibraryDialog, ProgressNotification, REGEXP, CONSTANTS, CORECONSTANTS) {
    'use strict';

    var LibraryManager = function (client, mainLogger) {
        this._add = new AddOrUpdateLibraryDialog(client, true);
        this._update = new AddOrUpdateLibraryDialog(client);
        this._remove = new ConfirmDialog();
        this._getName = new ConfirmDialog();
        this._doNotAskRemove = false;
        this._client = client;
        this._logger = mainLogger ? mainLogger.fork('LibraryManager') :
            Logger.createWithGmeConfig('Utils:LibraryManager', client.gmeConfig);
        this._libraryInfos = {};
        this._followedProjects = {};
        this._currentProjectId = null;
        this._currentBranchName = null;
    };

    LibraryManager.prototype.add = function () {
        var self = this,
            forbiddenNames = this._client.getLibraryNames();

        this._getName.show({
            title: 'Add Library',
            iconClass: 'glyphicon glyphicon-folder-close',
            question: 'Give a unique name of the new library..',
            input: {
                label: 'Name',
                placeHolder: 'Enter name of new library...',
                required: true,
                checkFn: function (value) {
                    if (forbiddenNames.indexOf(value) !== -1) {
                        return false;
                    }

                    return REGEXP.DOCUMENT_KEY.test(value);
                }
            },
            severity: 'info'
        }, function (_dummy, newName) {
            self._add.show(newName);
        });
    };

    LibraryManager.prototype.update = function (nodeId) {
        var self = this,
            client = this._client,
            node = client.getNode(nodeId),
            libraryName = node.getFullyQualifiedName(),
            libraryInfo = this._libraryInfos[libraryName];

        this._update.show(nodeId, function (newCommitHash) {
            if (libraryInfo && libraryInfo.commitHash && newCommitHash &&
                libraryInfo.commitHash !== newCommitHash) {
                libraryInfo.commitHash = newCommitHash;
                libraryInfo.notified = false;
                self._watchProject(libraryInfo.projectId);
            }
        });
    };

    LibraryManager.prototype.remove = function (nodeId) {
        var libraryRoot = this._client.getNode(nodeId),
            self = this,
            name;

        if (libraryRoot) {
            name = libraryRoot.getFullyQualifiedName();
            if (this._doNotAskRemove) {
                this._client.removeLibrary(name);
                return;
            }

            this._remove.show({
                enableDontAskAgain: true,
                deleteItem: libraryRoot.getFullyQualifiedName() + ' library'
            }, function (doNotAsk) {
                self._doNotAskRemove = doNotAsk;
                self._client.removeLibrary(name);
            });
        }
    };

    LibraryManager.prototype.rename = function (nodeId) {
        var self = this,
            libraryRoot = this._client.getNode(nodeId),
            ownName,
            forbiddenNames;

        if (libraryRoot) {
            ownName = libraryRoot.getFullyQualifiedName();
            forbiddenNames = this._client.getLibraryNames();
            forbiddenNames.splice(forbiddenNames.indexOf(ownName), 1);

            this._getName.show({
                title: 'Rename Library',
                iconClass: 'glyphicon glyphicon-folder-close',
                question: 'Would you like to rename "' + ownName + '"?',
                input: {
                    label: 'Name',
                    placeHolder: 'Enter new library name...',
                    required: true,
                    checkFn: function (value) {
                        if (forbiddenNames.indexOf(value) !== -1) {
                            return false;
                        }

                        return REGEXP.DOCUMENT_KEY.test(value);
                    }
                },
                severity: 'info'
            }, function (_dummy, newName) {
                self._client.renameLibrary(ownName, newName);
            });
        }

    };

    LibraryManager.prototype.check = function (name, callback) {
        var client = this._client,
            availableNames = client.getLibraryNames(),
            libraryInfo;

        if (availableNames.indexOf(name) !== -1) {
            libraryInfo = client.getLibraryInfo(name);
            if (libraryInfo && libraryInfo.projectId && libraryInfo.branchName) {
                client.getBranches(libraryInfo.projectId, function (err, branches) {
                    if (err) {
                        callback(err);
                    } else if (branches[libraryInfo.branchName] &&
                        branches[libraryInfo.branchName] !== libraryInfo.commitHash) {
                        this._notifyNewLibraryVersion(name);
                        callback(null, true);
                    } else {
                        callback(null, false);
                    }
                });
            }
        }
    };

    LibraryManager.prototype._clearWatchers = function () {
        var projectId;

        for (projectId in this._followedProjects) {
            this._client.unwatchProject(projectId, this._followedProjects[projectId]);
        }
        this._followedProjects = {};
    };

    LibraryManager.prototype._unwatchProject = function (projectId) {
        var libraryName,
            stillWatching = false;

        for (libraryName in this._libraryInfos) {
            if (this._libraryInfos[libraryName].projectId === projectId &&
                this._libraryInfos[libraryName].notified === false) {
                stillWatching = true;
            }
        }

        if (stillWatching === false) {
            this._client.unwatchProject(projectId, this._followedProjects[projectId]);
            delete this._followedProjects[projectId];
        }
    };

    LibraryManager.prototype._watchProject = function (projectId) {
        var self = this,
            eventFunction = function (websocket, event) {
                var libraryName;

                if (event.etype === CONSTANTS.BRANCH_HASH_UPDATED && self._followedProjects[event.projectId]) {
                    for (libraryName in self._libraryInfos) {
                        if (self._libraryInfos[libraryName].projectId === event.projectId &&
                            event.branchName === self._libraryInfos[libraryName].branchName &&
                            self._libraryInfos[libraryName].notified === false) {

                            self._notifyNewLibraryVersion(libraryName);
                            self._libraryInfos[libraryName].notified = true;
                            self._unwatchProject(self._libraryInfos[libraryName].projectId);
                        }
                    }
                }
            };

        if (!this._followedProjects[projectId]) {
            this._followedProjects[projectId] = eventFunction;
            this._client.watchProject(projectId, eventFunction);
        }
    };

    LibraryManager.prototype._checkLibrary = function (name) {
        var self = this,
            client = this._client,
            libraryInfo = this._libraryInfos[name];
        if (libraryInfo) {
            client.getBranches(libraryInfo.projectId, function (err, branches) {
                if (err) {
                    self._logger.error(err);
                } else if (branches[libraryInfo.branchName] &&
                    branches[libraryInfo.branchName] !== libraryInfo.commitHash) {
                    self._notifyNewLibraryVersion(name);
                    libraryInfo.notified = true;
                    self._unwatchProject(libraryInfo.projectId);
                }
            });
        }
    };

    LibraryManager.prototype._notifyNewLibraryVersion = function (libName) {
        var self = this,
            projectId = this._client.getActiveProjectId(),
            branchName = this._client.getActiveBranchName(),
            note;

        if (this._client.isReadOnly()) {
            this._client.notifyUser({message: 'New version of [' + libName + '] library available'});
        } else {
            note = $.notify({
                icon: 'glyphicon glyphicon-folder-close',
                message: 'A new version of the ' + libName + ' library is available for the ' +
                'current project. Click here to update!'
            }, {
                delay: 10000,
                hideDuration: 0,
                type: 'info',
                offset: {
                    x: 20,
                    y: 37
                },
                mouse_over: 'pause',
                onClose: function () {
                    note.$ele.off();
                }
            });

            note.$ele.css('cursor', 'pointer');

            note.$ele.on('click', function () {
                note.close();
                var progress = ProgressNotification.start('Updating library ' + libName + ',' +
                    'please stand by...');

                if (projectId === self._client.getActiveProjectId() &&
                    branchName === self._client.getActiveBranchName()) {
                    self._client.workerRequests.updateLibrary(libName, null, function (err/*, res*/) {
                        if (err) {
                            clearInterval(progress.intervalId);
                            progress.note.update({
                                message: 'Library updated failed with error: ' + err.message,
                                type: 'danger',
                                progress: 100
                            });
                        } else {
                            clearInterval(progress.intervalId);
                            progress.note.update({
                                message: 'Library updated successfully!',
                                type: 'success',
                                progress: 100
                            });

                            progress.note.close();
                        }
                    });
                } else {
                    clearInterval(progress.intervalId);
                    progress.note.update({
                        message: 'Project and or branch changed before update was invoked.',
                        type: 'danger',
                        progress: 100
                    });
                }
            });
        }
    };

    LibraryManager.prototype.follow = function () {
        var client = this._client,
            projectId = client.getActiveProjectId(),
            branchName = client.getActiveBranchName(),
            availableNames,
            info,
            name,
            i;

        if (typeof projectId !== 'string' || typeof branchName !== 'string') {
            // No project or branch -> clear everything.

            this._currentProjectId = projectId;
            this._libraryInfos = {};
            this._clearWatchers();
            return;
        } else if (projectId === this._currentProjectId && branchName === this._currentBranchName) {
            //Same project and branch -> check if there is any change in the set of libraries.

            availableNames = client.getLibraryNames();

            //removals
            for (name in this._libraryInfos) {
                if (availableNames.indexOf(name) === -1) {
                    this._unwatchProject(this._libraryInfos[name].projectId);
                    delete this._libraryInfos[name];
                }
            }

            //additions
            for (i = 0; i < availableNames.length; i += 1) {
                if (!this._libraryInfos[availableNames[i]]) {
                    info = client.getLibraryInfo(availableNames[i]);
                    if (info && info.projectId && info.branchName &&
                        availableNames[i].indexOf(CORECONSTANTS.NAMESPACE_SEPARATOR) === -1) {
                        this._watchProject(info.projectId);
                        this._libraryInfos[availableNames[i]] = {
                            projectId: info.projectId,
                            branchName: info.branchName,
                            commitHash: info.commitHash,
                            notified: false
                        };
                        this._checkLibrary(availableNames[i]);
                    }
                }
            }

        } else {
            // Project and/or branch changed -> reset the library watchers.

            this._currentProjectId = projectId;
            this._currentBranchName = branchName;
            this._libraryInfos = {};
            availableNames = client.getLibraryNames();
            this._clearWatchers();
            for (i = 0; i < availableNames.length; i += 1) {
                info = client.getLibraryInfo(availableNames[i]);
                if (info && info.projectId && info.branchName) {
                    this._watchProject(info.projectId);
                    this._libraryInfos[availableNames[i]] = {
                        projectId: info.projectId,
                        branchName: info.branchName,
                        commitHash: info.commitHash,
                        notified: false
                    };
                    this._checkLibrary(availableNames[i]);
                }
            }
        }
    };

    LibraryManager.prototype.openLibraryOriginInNewWindow = function (libraryRootId, followBranch) {
        var nodeObj = this._client.getNode(libraryRootId),
            address,
            info;

        if (!nodeObj) {
            this._logger.error('Library node is not loaded and cannot be followed [', libraryRootId, ']');
            return;
        }

        info = this._client.getLibraryInfo(nodeObj.getFullyQualifiedName());

        if (!info) {
            this._logger.error('Library node has no available info and cannot be followed [', libraryRootId, ']');
            return;
        }

        if (!info.projectId) {
            this._logger.error('Library info did not a have projectId in info and cannot be followed [',
                libraryRootId, ']');
            return;
        }

        address = window.location.origin + '/?project=' + encodeURIComponent(info.projectId);

        if (info.branchName && followBranch) {
            address += '&branch=' + encodeURIComponent(info.branchName);
        } else if (info.commitHash) {
            address += '&commit=' + encodeURIComponent(info.commitHash);
        }

        window.open(address, '_blank');
        window.focus();
    };

    return LibraryManager;
});<commit_msg>WIP - #1597 fix missed scenario of opening/chaning project (#1602)<commit_after>/*globals define, $*/
/*jshint browser: true*/
/**
 * Provides dialog helpers with backend support for handling libraries.
 * @author kecso / https://github.com/kecso
 */

define([
    'js/logger',
    'js/Dialogs/Confirm/ConfirmDialog',
    'js/Dialogs/AddOrUpdateLibrary/AddOrUpdateLibraryDialog',
    'js/Loader/ProgressNotification',
    'common/regexp',
    'common/storage/constants',
    'common/core/constants'
], function (Logger, ConfirmDialog, AddOrUpdateLibraryDialog, ProgressNotification, REGEXP, CONSTANTS, CORECONSTANTS) {
    'use strict';

    var LibraryManager = function (client, mainLogger) {
        this._add = new AddOrUpdateLibraryDialog(client, true);
        this._update = new AddOrUpdateLibraryDialog(client);
        this._remove = new ConfirmDialog();
        this._getName = new ConfirmDialog();
        this._doNotAskRemove = false;
        this._client = client;
        this._logger = mainLogger ? mainLogger.fork('LibraryManager') :
            Logger.createWithGmeConfig('Utils:LibraryManager', client.gmeConfig);
        this._libraryInfos = {};
        this._followedProjects = {};
        this._currentProjectId = null;
        this._currentBranchName = null;
    };

    LibraryManager.prototype.add = function () {
        var self = this,
            forbiddenNames = this._client.getLibraryNames();

        this._getName.show({
            title: 'Add Library',
            iconClass: 'glyphicon glyphicon-folder-close',
            question: 'Give a unique name of the new library..',
            input: {
                label: 'Name',
                placeHolder: 'Enter name of new library...',
                required: true,
                checkFn: function (value) {
                    if (forbiddenNames.indexOf(value) !== -1) {
                        return false;
                    }

                    return REGEXP.DOCUMENT_KEY.test(value);
                }
            },
            severity: 'info'
        }, function (_dummy, newName) {
            self._add.show(newName);
        });
    };

    LibraryManager.prototype.update = function (nodeId) {
        var self = this,
            client = this._client,
            node = client.getNode(nodeId),
            libraryName = node.getFullyQualifiedName(),
            libraryInfo = this._libraryInfos[libraryName];

        this._update.show(nodeId, function (newCommitHash) {
            if (libraryInfo && libraryInfo.commitHash && newCommitHash &&
                libraryInfo.commitHash !== newCommitHash) {
                libraryInfo.commitHash = newCommitHash;
                libraryInfo.notified = false;
                self._watchProject(libraryInfo.projectId);
            }
        });
    };

    LibraryManager.prototype.remove = function (nodeId) {
        var libraryRoot = this._client.getNode(nodeId),
            self = this,
            name;

        if (libraryRoot) {
            name = libraryRoot.getFullyQualifiedName();
            if (this._doNotAskRemove) {
                this._client.removeLibrary(name);
                return;
            }

            this._remove.show({
                enableDontAskAgain: true,
                deleteItem: libraryRoot.getFullyQualifiedName() + ' library'
            }, function (doNotAsk) {
                self._doNotAskRemove = doNotAsk;
                self._client.removeLibrary(name);
            });
        }
    };

    LibraryManager.prototype.rename = function (nodeId) {
        var self = this,
            libraryRoot = this._client.getNode(nodeId),
            ownName,
            forbiddenNames;

        if (libraryRoot) {
            ownName = libraryRoot.getFullyQualifiedName();
            forbiddenNames = this._client.getLibraryNames();
            forbiddenNames.splice(forbiddenNames.indexOf(ownName), 1);

            this._getName.show({
                title: 'Rename Library',
                iconClass: 'glyphicon glyphicon-folder-close',
                question: 'Would you like to rename "' + ownName + '"?',
                input: {
                    label: 'Name',
                    placeHolder: 'Enter new library name...',
                    required: true,
                    checkFn: function (value) {
                        if (forbiddenNames.indexOf(value) !== -1) {
                            return false;
                        }

                        return REGEXP.DOCUMENT_KEY.test(value);
                    }
                },
                severity: 'info'
            }, function (_dummy, newName) {
                self._client.renameLibrary(ownName, newName);
            });
        }

    };

    LibraryManager.prototype.check = function (name, callback) {
        var client = this._client,
            availableNames = client.getLibraryNames(),
            libraryInfo;

        if (availableNames.indexOf(name) !== -1) {
            libraryInfo = client.getLibraryInfo(name);
            if (libraryInfo && libraryInfo.projectId && libraryInfo.branchName) {
                client.getBranches(libraryInfo.projectId, function (err, branches) {
                    if (err) {
                        callback(err);
                    } else if (branches[libraryInfo.branchName] &&
                        branches[libraryInfo.branchName] !== libraryInfo.commitHash) {
                        this._notifyNewLibraryVersion(name);
                        callback(null, true);
                    } else {
                        callback(null, false);
                    }
                });
            }
        }
    };

    LibraryManager.prototype._clearWatchers = function () {
        var projectId;

        for (projectId in this._followedProjects) {
            this._client.unwatchProject(projectId, this._followedProjects[projectId]);
        }
        this._followedProjects = {};
    };

    LibraryManager.prototype._unwatchProject = function (projectId) {
        var libraryName,
            stillWatching = false;

        for (libraryName in this._libraryInfos) {
            if (this._libraryInfos[libraryName].projectId === projectId &&
                this._libraryInfos[libraryName].notified === false) {
                stillWatching = true;
            }
        }

        if (stillWatching === false) {
            this._client.unwatchProject(projectId, this._followedProjects[projectId]);
            delete this._followedProjects[projectId];
        }
    };

    LibraryManager.prototype._watchProject = function (projectId) {
        var self = this,
            eventFunction = function (websocket, event) {
                var libraryName;

                if (event.etype === CONSTANTS.BRANCH_HASH_UPDATED && self._followedProjects[event.projectId]) {
                    for (libraryName in self._libraryInfos) {
                        if (self._libraryInfos[libraryName].projectId === event.projectId &&
                            event.branchName === self._libraryInfos[libraryName].branchName &&
                            self._libraryInfos[libraryName].notified === false) {

                            self._notifyNewLibraryVersion(libraryName);
                            self._libraryInfos[libraryName].notified = true;
                            self._unwatchProject(self._libraryInfos[libraryName].projectId);
                        }
                    }
                }
            };

        if (!this._followedProjects[projectId]) {
            this._followedProjects[projectId] = eventFunction;
            this._client.watchProject(projectId, eventFunction);
        }
    };

    LibraryManager.prototype._checkLibrary = function (name) {
        var self = this,
            client = this._client,
            libraryInfo = this._libraryInfos[name];
        if (libraryInfo) {
            client.getBranches(libraryInfo.projectId, function (err, branches) {
                if (err) {
                    self._logger.error(err);
                } else if (branches[libraryInfo.branchName] &&
                    branches[libraryInfo.branchName] !== libraryInfo.commitHash) {
                    self._notifyNewLibraryVersion(name);
                    libraryInfo.notified = true;
                    self._unwatchProject(libraryInfo.projectId);
                }
            });
        }
    };

    LibraryManager.prototype._notifyNewLibraryVersion = function (libName) {
        var self = this,
            projectId = this._client.getActiveProjectId(),
            branchName = this._client.getActiveBranchName(),
            note;

        if (this._client.isReadOnly()) {
            this._client.notifyUser({message: 'New version of [' + libName + '] library available'});
        } else {
            note = $.notify({
                icon: 'glyphicon glyphicon-folder-close',
                message: 'A new version of the ' + libName + ' library is available for the ' +
                'current project. Click here to update!'
            }, {
                delay: 10000,
                hideDuration: 0,
                type: 'info',
                offset: {
                    x: 20,
                    y: 37
                },
                mouse_over: 'pause',
                onClose: function () {
                    note.$ele.off();
                }
            });

            note.$ele.css('cursor', 'pointer');

            note.$ele.on('click', function () {
                note.close();
                var progress = ProgressNotification.start('Updating library ' + libName + ',' +
                    'please stand by...');

                if (projectId === self._client.getActiveProjectId() &&
                    branchName === self._client.getActiveBranchName()) {
                    self._client.workerRequests.updateLibrary(libName, null, function (err/*, res*/) {
                        if (err) {
                            clearInterval(progress.intervalId);
                            progress.note.update({
                                message: 'Library updated failed with error: ' + err.message,
                                type: 'danger',
                                progress: 100
                            });
                        } else {
                            clearInterval(progress.intervalId);
                            progress.note.update({
                                message: 'Library updated successfully!',
                                type: 'success',
                                progress: 100
                            });

                            progress.note.close();
                        }
                    });
                } else {
                    clearInterval(progress.intervalId);
                    progress.note.update({
                        message: 'Project and or branch changed before update was invoked.',
                        type: 'danger',
                        progress: 100
                    });
                }
            });
        }
    };

    LibraryManager.prototype.follow = function () {
        var client = this._client,
            projectId = client.getActiveProjectId(),
            branchName = client.getActiveBranchName(),
            availableNames,
            info,
            name,
            i;

        if (typeof projectId !== 'string' || typeof branchName !== 'string') {
            // No project or branch -> clear everything.

            this._currentProjectId = projectId;
            this._libraryInfos = {};
            this._clearWatchers();
            return;
        } else if (projectId === this._currentProjectId && branchName === this._currentBranchName) {
            //Same project and branch -> check if there is any change in the set of libraries.

            availableNames = client.getLibraryNames();

            //removals
            for (name in this._libraryInfos) {
                if (availableNames.indexOf(name) === -1) {
                    this._unwatchProject(this._libraryInfos[name].projectId);
                    delete this._libraryInfos[name];
                }
            }

            //additions
            for (i = 0; i < availableNames.length; i += 1) {
                if (!this._libraryInfos[availableNames[i]]) {
                    info = client.getLibraryInfo(availableNames[i]);
                    if (info && info.projectId && info.branchName &&
                        availableNames[i].indexOf(CORECONSTANTS.NAMESPACE_SEPARATOR) === -1) {
                        this._watchProject(info.projectId);
                        this._libraryInfos[availableNames[i]] = {
                            projectId: info.projectId,
                            branchName: info.branchName,
                            commitHash: info.commitHash,
                            notified: false
                        };
                        this._checkLibrary(availableNames[i]);
                    }
                }
            }

        } else {
            // Project and/or branch changed -> reset the library watchers.

            this._currentProjectId = projectId;
            this._currentBranchName = branchName;
            this._libraryInfos = {};
            availableNames = client.getLibraryNames();
            this._clearWatchers();
            for (i = 0; i < availableNames.length; i += 1) {
                info = client.getLibraryInfo(availableNames[i]);
                if (info && info.projectId && info.branchName &&
                    availableNames[i].indexOf(CORECONSTANTS.NAMESPACE_SEPARATOR) === -1) {
                    this._watchProject(info.projectId);
                    this._libraryInfos[availableNames[i]] = {
                        projectId: info.projectId,
                        branchName: info.branchName,
                        commitHash: info.commitHash,
                        notified: false
                    };
                    this._checkLibrary(availableNames[i]);
                }
            }
        }
    };

    LibraryManager.prototype.openLibraryOriginInNewWindow = function (libraryRootId, followBranch) {
        var nodeObj = this._client.getNode(libraryRootId),
            address,
            info;

        if (!nodeObj) {
            this._logger.error('Library node is not loaded and cannot be followed [', libraryRootId, ']');
            return;
        }

        info = this._client.getLibraryInfo(nodeObj.getFullyQualifiedName());

        if (!info) {
            this._logger.error('Library node has no available info and cannot be followed [', libraryRootId, ']');
            return;
        }

        if (!info.projectId) {
            this._logger.error('Library info did not a have projectId in info and cannot be followed [',
                libraryRootId, ']');
            return;
        }

        address = window.location.origin + '/?project=' + encodeURIComponent(info.projectId);

        if (info.branchName && followBranch) {
            address += '&branch=' + encodeURIComponent(info.branchName);
        } else if (info.commitHash) {
            address += '&commit=' + encodeURIComponent(info.commitHash);
        }

        window.open(address, '_blank');
        window.focus();
    };

    return LibraryManager;
});<|endoftext|>


<commit_before>import { SIMULATE_API , CAR_LIST_API } from '../constants/WebServiceConstants';
import bluebird from 'bluebird';
import axios from 'axios';
import ApiService from './ApiService';

var _ = require('lodash');

class ProvideService {

    updateSimulator(newValue) {
        dispatch(ProvideConstants.UPDATE_SIMULATOR, { newValue });
    }
    /**
     * calculate - description
     *
     * @param  {type} profile description
     * @param  {type} simulatorInfo description
     * @return {type}              description
     */
    calculate(profile, simulatorInfo) {
      return new bluebird( (resolve, reject) => {
          ApiService.post(
             `SIMULATE_API${JSON.stringify(simulateData)}/${profile.user_id}`,
          ).then(response => {
            if (!_.isNil(response)) {
                return resolve(response.data);
            }
          }).catch( err => {
            return reject(err);
          });
      });
    }

    getCarList() {
      return new bluebird( (resolve, reject) => {
          ApiService.get(
             CAR_LIST_API ,
          ).then(response => {
            if (!_.isNil(response)) {
                return resolve(response.data);
            }
          }).catch( err => {
            return reject(err);
          });
      });
    }
}

export default new ProvideService()
<commit_msg>Update ProvideService.js<commit_after>import { SIMULATE_API , CAR_LIST_API } from '../constants/WebServiceConstants';
import bluebird from 'bluebird';
import axios from 'axios';
import ApiService from './ApiService';

var _ = require('lodash');

class ProvideService {

    updateSimulator(newValue) {
        dispatch(ProvideConstants.UPDATE_SIMULATOR, { newValue });
    }
    /**
     * calculate - description
     *
     * @param  {type} profile description
     * @param  {type} simulatorInfo description
     * @return {type}              description
     */
    calculate(profile, simulatorInfo) {
      return new bluebird( (resolve, reject) => {
          ApiService.post(
             `SIMULATE_API${JSON.stringify(simulatorInfo)}/${profile.user_id}`,
          ).then(response => {
            if (!_.isNil(response)) {
                return resolve(response.data);
            }
          }).catch( err => {
            return reject(err);
          });
      });
    }

    getCarList() {
      return new bluebird( (resolve, reject) => {
          ApiService.get(
             CAR_LIST_API ,
          ).then(response => {
            if (!_.isNil(response)) {
                return resolve(response.data);
            }
          }).catch( err => {
            return reject(err);
          });
      });
    }
}

export default new ProvideService()
<|endoftext|>


<commit_before>var DocumentSettings = Backbone.View.extend({
  events: {
    'submit form': 'invite',
    'change select.change-mode': 'changeMode',
    'click a.remove-collaborator': 'removeCollaborator'
  },

  changeMode: function(e) {
    var collaboratorId = $(e.currentTarget).attr('collaborator');
    var mode = $(e.currentTarget).val();

    graph.get(collaboratorId).set({
      mode: mode
    });
    // trigger immediate sync
    graph.sync();

    return false;
  },

  removeCollaborator: function(e) {
    var collaboratorId = $(e.currentTarget).attr('collaborator');
    graph.del(collaboratorId);
    // trigger immediate sync
    graph.sync();
    this.collaborators.del(collaboratorId);
    this.render();
    return false;
  },

  invite: function() {
    var that = this;
    $.ajax({
      type: "POST",
      url: "/invite",
      data: {
        email: $('#collaborator_email').val(),
        document: app.document.model._id,
        mode: $('#collaborator_mode').val()
      },
      dataType: "json",
      success: function(res) {
        if (res.error) return alert(res.error);
        that.load();
      },
      error: function(err) {
        alert("Unknown error occurred");
      }
    });

    return false;
  },

  load: function() {
    var that = this;
    graph.fetch({"type": "/type/collaborator", "document": app.document.model._id}, function(err, nodes) {
      that.collaborators = nodes;
      that.render();
    });
  },

  initialize: function() {
    this.el = '#document_settings';
  },

  render: function() {
    $(this.el).html(_.tpl('document_settings', {
      collaborators: this.collaborators,
      document: app.document.model
    }));
    this.delegateEvents();
  }
});
<commit_msg>Use immediate sync for changeMode() and removeCollaborator().<commit_after>var DocumentSettings = Backbone.View.extend({
  events: {
    'submit form': 'invite',
    'change select.change-mode': 'changeMode',
    'click a.remove-collaborator': 'removeCollaborator'
  },

  changeMode: function(e) {
    var collaboratorId = $(e.currentTarget).attr('collaborator');
    var mode = $(e.currentTarget).val();
    var that = this;

    window.pendingSync = true;

    graph.get(collaboratorId).set({
      mode: mode
    });

    // trigger immediate sync
    graph.sync(function(err) {
      window.pendingSync = false;
      that.render();
    });

    return false;
  },

  removeCollaborator: function(e) {
    var collaboratorId = $(e.currentTarget).attr('collaborator');
    var that = this;

    window.pendingSync = true;
    graph.del(collaboratorId);

    // trigger immediate sync
    graph.sync(function(err) {
      window.pendingSync = false;
      that.collaborators.del(collaboratorId);
      that.render();
    });

    return false;
  },

  invite: function() {
    var that = this;
    $.ajax({
      type: "POST",
      url: "/invite",
      data: {
        email: $('#collaborator_email').val(),
        document: app.document.model._id,
        mode: $('#collaborator_mode').val()
      },
      dataType: "json",
      success: function(res) {
        if (res.error) return alert(res.error);
        that.load();
      },
      error: function(err) {
        alert("Unknown error occurred");
      }
    });

    return false;
  },

  load: function() {
    var that = this;
    graph.fetch({"type": "/type/collaborator", "document": app.document.model._id}, function(err, nodes) {
      that.collaborators = nodes;
      that.render();
    });
  },

  initialize: function() {
    this.el = '#document_settings';
  },

  render: function() {
    $(this.el).html(_.tpl('document_settings', {
      collaborators: this.collaborators,
      document: app.document.model
    }));
    this.delegateEvents();
  }
});
<|endoftext|>


<commit_before>import { h, Component } 				from 'preact/preact';

import Notification						from 'com/content-notifications/notification';

import $Node							from '../../shrub/js/node/node';
import $Note							from '../../shrub/js/note/note';
import $Notification					from '../../shrub/js/notification/notification';

export default class NotificationsBase extends Component {

	constructor( props ) {
		super(props);

		this.state = {
			notifications: null,
			notificationIds: [],
			notificationsTotal: -1,
			count: 0,
			status: null,
			feed: [],
			loading: true,
			highestRead: -1,
		};
	}

	hasUnreadNotifications() {
		const highestInFeed = this.getHighestNotificationInFeed();
		if (highestInFeed !== null) {
			if (highestInFeed > this.state.highestRead) {
				return true;
			}
		}
		return false;
	}

	markReadHighest() {
		// TODO: Triggering this should immidiately update the counter on the
		// icon in the top bar
		if ( this.hasUnreadNotifications() ) {
			const highestInFeed = this.getHighestNotificationInFeed();
			$Notification.SetMarkRead(highestInFeed).then((r) => {
				if (r.status == 200) {
					this.setState({highestRead: highestInFeed});
				}
			});
		}
	}

	getHighestNotificationInFeed() {
		const notificationsOrder = this.getNotificationsOrder();
		if (notificationsOrder && notificationsOrder.length > 0) {
			return notificationsOrder[0];
		}
		return null;
	}

	processNotificationFeed(r) {

		const caller_id = r.caller_id;
		this.collectAllNodesAndNodes(r.feed, caller_id);
		let highestRead = r.max_read !== undefined ? r.max_read : this.state.highestRead;
		this.setState({
			feed: r.feed,
			caller_id: caller_id,
			status: r.status,
			count: r.count,
			loading: true,
			highestRead: highestRead,
		});
	}

	collectAllNodesAndNodes(feed, caller_id) {

		let nodeLookup = new Map();
		let nodes = [];
		let notificationLookup = new Map();
		let social = {};

		let soicialPromise = $Node.GetMy().then((response) => {

			social.following = response.star ? response.star : [];
			social.followers = response.refs.star ? response.refs.star : [];
			social.friends = social.followers.filter((i) => social.following.indexOf(i) > -1);
		});

		feed.forEach((notification) => {
			if (nodes.indexOf(notification.node) < 0) {
				nodes.push(notification.node);
			}
			notificationLookup.set(notification.id, notification);
		});

		let node2notes = new Map();
		let notification2nodeAndNote = new Map();
		let noteLookup = new Map();
		let users = [];
		let usersLookup = new Map();

		users.push(caller_id);
		let notes = [];

		feed.forEach(({id, node, note}) => {
			if ( note ) {
				// Only fetch nonzero notes. Don't add zero notes to the list of notes in a node
				if (node2notes.has(node)) {
					node2notes.get(node).push(note);
				} else {
					node2notes.set(node, [note]);
				}
				notes.push(note);
				notification2nodeAndNote.set(id, {node: node, note: note});
			}
		});

		let nodesPromise = $Node.Get(nodes)
			.then((response) => {
				//console.log('[Notifications:Nodes]', response.node);
				if (response.node) {
					response.node.forEach((node) => {
						node.authors = [];
						if (node.author > 0 && users.indexOf(node.author) < 0) {
							users.push(node.author);
							node.authors.push(node.author);
						}
						if (node.meta && node.meta.author) {
							node.meta.author.forEach((author) => {
								if (author > 0 && users.indexOf(author) < 0) {
									users.push(author);
								}
								if (author > 0 && node.authors.indexOf(author) < 0) {
									node.authors.push(author);
								}
							});
						}
						nodeLookup.set(node.id, node);
					});
				}

			});

		let notesPromise = $Note.Pick(notes).then((response) => {
			//console.log('[Notifications:Notes]', response.note);
			if (response.notes) {
				response.notes.forEach((note) => {
					noteLookup.set(note.id, note);

					if (note.author > 0 && users.indexOf(note.author) < 0) {
						users.push(note.author);
					}
				});
			}
		});

		Promise.all([nodesPromise, notesPromise, soicialPromise])
			.then(() => {
				noteLookup.forEach((note, id) => {
					note.isNodeAuthor = nodeLookup.get(note.node).authors.indexOf(note.author) > -1;
				});
				return $Node.Get(users);
			})
			.then((response) => {
				//console.log('[Notifications:Users]', response.node);
				if (response.node) {
					response.node.forEach((node) => {
						if (node.type == 'user') {
							node.isFriend = social.friends.indexOf(node.id) > -1;
							node.isFollowing = node.isFriend && social.following.indexOf(node.id) > -1;
							node.isFollower = node.isFriend && social.followers.indexOf(node.id) > -1;

							usersLookup.set(node.id, node);
						}
					});
				}

				this.composeNotifications(feed, caller_id, notification2nodeAndNote, node2notes, nodeLookup, noteLookup, usersLookup, notificationLookup, social);
			});
	}

	composeNotifications(feed, caller_id, notification2nodeAndNote, node2notes, nodeLookup, noteLookup, usersLookup, notificationLookup, social) {

		const myAtName = '@' + usersLookup.get(caller_id).name;
		noteLookup.forEach((note, id) => {
			note.selfauthored = note.author == caller_id;
			note.mention = note.body.indexOf(myAtName) >= 0;
		});

		nodeLookup.forEach((node, id) => {
			node.selfauthored = false;
			if (node.author == caller_id) {
				node.selfauthored = true;
			} else if (node.meta && node.meta.author) {
				node.meta.author.forEach((author) => {
					if (author == caller_id) {
						node.selfauthored = true;
					}
				});
			}
			node.mention = node.body.indexOf(myAtName) >= 0 || node.name.indexOf(myAtName) >= 0;
		});

		let notifications = this.state.notifications ? this.state.notifications : new Map();
		let notificationIds = this.state.notificationIds;

		let processedNotifications = [];

		feed.forEach((notification) => {
			if (processedNotifications.indexOf(notification.id) < 0) {
				let node = nodeLookup.get(notification.node);
				let data = {
					node: node,
					note: undefined,
					notification: [notification],
					multi: false,
					users: new Map(),
					social: social,
				};

				data.users.set(caller_id, usersLookup.get(caller_id));
				data.users.set(node.author, usersLookup.get(node.author));

				if (node.meta && node.meta.author) {
					node.meta.author.forEach((author) => {
						if (!data.users.has(author)) {
							data.users.set(author, usersLookup.get(author));
						}
					});
				}

				if (notification.note) {
					let firstNote = noteLookup.get(notification.note);
					data.note = [firstNote];
					if (!data.users.has(firstNote.author)) {
						data.users.set(firstNote.author, usersLookup.get(firstNote.author));
					}

					if (!firstNote.mention && !firstNote.selfauthored && !firstNote.isNodeAuthor) {
						node2notes.get(notification.node).forEach((note) => {
							if (note != notification.note) {
								let noteData = noteLookup.get(note);
								if (!noteData.mention && !noteData.selfauthored && !noteData.isNodeAuthor) {
									notification2nodeAndNote.forEach((otherData, otherNotification) => {
										if (otherData.note == note) {
											processedNotifications.push(otherNotification);
											data.notification.push(notificationLookup.get(otherNotification));
											data.note.push(noteData);
											if (!data.users.has(noteData.author)) {
												data.users.set(noteData.author, noteData);
											}
											data.multi = true;
										}
									});
								}
							}
						});
					}
				}
				let notificationId = data.notification.sort()[0].id;
				notificationIds.push(notificationId);
				notifications.set(notificationId, data);
				processedNotifications.push(notification.id);
			}
		});

		this.setState({
			notifications: notifications,
			notificationIds: notificationIds.sort((a, b) => b - a),
			loading: false,
			feedSize: (this.state.feedSize ? this.state.feedSize : 0) + processedNotifications.length,
		});
	}

	updateCallback() {
		//For subclass to do stuff with
	}

	isLoading() {
		return this.state.loading;
	};

	getNotificationsOrder() {
		return this.state.notificationIds;
	}

	getNotifications() {
		let Notifications = [];
		const notifications = this.state.notifications;
		const notificationsOrder = this.getNotificationsOrder();
		const caller_id = this.state.caller_id;
		if (!this.state.loading) {

			notificationsOrder.forEach((id) => {
				let notification = notifications.get(id);
				Notifications.push([id, <Notification caller_id={caller_id} notification={notification} />]);
			});

		}

		return Notifications;
	}

}
<commit_msg>Lint tweak<commit_after>import { h, Component } 				from 'preact/preact';

import Notification						from 'com/content-notifications/notification';

import $Node							from '../../shrub/js/node/node';
import $Note							from '../../shrub/js/note/note';
import $Notification					from '../../shrub/js/notification/notification';

export default class NotificationsBase extends Component {

	constructor( props ) {
		super(props);

		this.state = {
			notifications: null,
			notificationIds: [],
			notificationsTotal: -1,
			count: 0,
			status: null,
			feed: [],
			loading: true,
			highestRead: -1,
		};
	}

	hasUnreadNotifications() {
		const highestInFeed = this.getHighestNotificationInFeed();
		if (highestInFeed !== null) {
			if (highestInFeed > this.state.highestRead) {
				return true;
			}
		}
		return false;
	}

	markReadHighest() {
		// TODO: Triggering this should immidiately update the counter on the
		// icon in the top bar
		if ( this.hasUnreadNotifications() ) {
			const highestInFeed = this.getHighestNotificationInFeed();
			$Notification.SetMarkRead(highestInFeed).then((r) => {
				if (r.status == 200) {
					this.setState({highestRead: highestInFeed});
				}
			});
		}
	}

	getHighestNotificationInFeed() {
		const notificationsOrder = this.getNotificationsOrder();
		if (notificationsOrder && notificationsOrder.length > 0) {
			return notificationsOrder[0];
		}
		return null;
	}

	processNotificationFeed(r) {

		const caller_id = r.caller_id;
		this.collectAllNodesAndNodes(r.feed, caller_id);
		let highestRead = r.max_read !== undefined ? r.max_read : this.state.highestRead;
		this.setState({
			feed: r.feed,
			caller_id: caller_id,
			status: r.status,
			count: r.count,
			loading: true,
			highestRead: highestRead,
		});
	}

	collectAllNodesAndNodes(feed, caller_id) {

		let nodeLookup = new Map();
		let nodes = [];
		let notificationLookup = new Map();
		let social = {};

		let soicialPromise = $Node.GetMy().then((response) => {

			social.following = response.star ? response.star : [];
			social.followers = response.refs.star ? response.refs.star : [];
			social.friends = social.followers.filter((i) => social.following.indexOf(i) > -1);
		});

		feed.forEach((notification) => {
			if (nodes.indexOf(notification.node) < 0) {
				nodes.push(notification.node);
			}
			notificationLookup.set(notification.id, notification);
		});

		let node2notes = new Map();
		let notification2nodeAndNote = new Map();
		let noteLookup = new Map();
		let users = [];
		let usersLookup = new Map();

		users.push(caller_id);
		let notes = [];

		feed.forEach(({id, node, note}) => {
			if ( note ) {
				// Only fetch nonzero notes. Don't add zero notes to the list of notes in a node
				if ( node2notes.has(node) ) {
					node2notes.get(node).push(note);
				}
				else {
					node2notes.set(node, [note]);
				}
				notes.push(note);
				notification2nodeAndNote.set(id, {node: node, note: note});
			}
		});

		let nodesPromise = $Node.Get(nodes)
			.then((response) => {
				//console.log('[Notifications:Nodes]', response.node);
				if (response.node) {
					response.node.forEach((node) => {
						node.authors = [];
						if (node.author > 0 && users.indexOf(node.author) < 0) {
							users.push(node.author);
							node.authors.push(node.author);
						}
						if (node.meta && node.meta.author) {
							node.meta.author.forEach((author) => {
								if (author > 0 && users.indexOf(author) < 0) {
									users.push(author);
								}
								if (author > 0 && node.authors.indexOf(author) < 0) {
									node.authors.push(author);
								}
							});
						}
						nodeLookup.set(node.id, node);
					});
				}

			});

		let notesPromise = $Note.Pick(notes).then((response) => {
			//console.log('[Notifications:Notes]', response.note);
			if (response.notes) {
				response.notes.forEach((note) => {
					noteLookup.set(note.id, note);

					if (note.author > 0 && users.indexOf(note.author) < 0) {
						users.push(note.author);
					}
				});
			}
		});

		Promise.all([nodesPromise, notesPromise, soicialPromise])
			.then(() => {
				noteLookup.forEach((note, id) => {
					note.isNodeAuthor = nodeLookup.get(note.node).authors.indexOf(note.author) > -1;
				});
				return $Node.Get(users);
			})
			.then((response) => {
				//console.log('[Notifications:Users]', response.node);
				if (response.node) {
					response.node.forEach((node) => {
						if (node.type == 'user') {
							node.isFriend = social.friends.indexOf(node.id) > -1;
							node.isFollowing = node.isFriend && social.following.indexOf(node.id) > -1;
							node.isFollower = node.isFriend && social.followers.indexOf(node.id) > -1;

							usersLookup.set(node.id, node);
						}
					});
				}

				this.composeNotifications(feed, caller_id, notification2nodeAndNote, node2notes, nodeLookup, noteLookup, usersLookup, notificationLookup, social);
			});
	}

	composeNotifications(feed, caller_id, notification2nodeAndNote, node2notes, nodeLookup, noteLookup, usersLookup, notificationLookup, social) {

		const myAtName = '@' + usersLookup.get(caller_id).name;
		noteLookup.forEach((note, id) => {
			note.selfauthored = note.author == caller_id;
			note.mention = note.body.indexOf(myAtName) >= 0;
		});

		nodeLookup.forEach((node, id) => {
			node.selfauthored = false;
			if (node.author == caller_id) {
				node.selfauthored = true;
			} else if (node.meta && node.meta.author) {
				node.meta.author.forEach((author) => {
					if (author == caller_id) {
						node.selfauthored = true;
					}
				});
			}
			node.mention = node.body.indexOf(myAtName) >= 0 || node.name.indexOf(myAtName) >= 0;
		});

		let notifications = this.state.notifications ? this.state.notifications : new Map();
		let notificationIds = this.state.notificationIds;

		let processedNotifications = [];

		feed.forEach((notification) => {
			if (processedNotifications.indexOf(notification.id) < 0) {
				let node = nodeLookup.get(notification.node);
				let data = {
					node: node,
					note: undefined,
					notification: [notification],
					multi: false,
					users: new Map(),
					social: social,
				};

				data.users.set(caller_id, usersLookup.get(caller_id));
				data.users.set(node.author, usersLookup.get(node.author));

				if (node.meta && node.meta.author) {
					node.meta.author.forEach((author) => {
						if (!data.users.has(author)) {
							data.users.set(author, usersLookup.get(author));
						}
					});
				}

				if (notification.note) {
					let firstNote = noteLookup.get(notification.note);
					data.note = [firstNote];
					if (!data.users.has(firstNote.author)) {
						data.users.set(firstNote.author, usersLookup.get(firstNote.author));
					}

					if (!firstNote.mention && !firstNote.selfauthored && !firstNote.isNodeAuthor) {
						node2notes.get(notification.node).forEach((note) => {
							if (note != notification.note) {
								let noteData = noteLookup.get(note);
								if (!noteData.mention && !noteData.selfauthored && !noteData.isNodeAuthor) {
									notification2nodeAndNote.forEach((otherData, otherNotification) => {
										if (otherData.note == note) {
											processedNotifications.push(otherNotification);
											data.notification.push(notificationLookup.get(otherNotification));
											data.note.push(noteData);
											if (!data.users.has(noteData.author)) {
												data.users.set(noteData.author, noteData);
											}
											data.multi = true;
										}
									});
								}
							}
						});
					}
				}
				let notificationId = data.notification.sort()[0].id;
				notificationIds.push(notificationId);
				notifications.set(notificationId, data);
				processedNotifications.push(notification.id);
			}
		});

		this.setState({
			notifications: notifications,
			notificationIds: notificationIds.sort((a, b) => b - a),
			loading: false,
			feedSize: (this.state.feedSize ? this.state.feedSize : 0) + processedNotifications.length,
		});
	}

	updateCallback() {
		//For subclass to do stuff with
	}

	isLoading() {
		return this.state.loading;
	};

	getNotificationsOrder() {
		return this.state.notificationIds;
	}

	getNotifications() {
		let Notifications = [];
		const notifications = this.state.notifications;
		const notificationsOrder = this.getNotificationsOrder();
		const caller_id = this.state.caller_id;
		if (!this.state.loading) {

			notificationsOrder.forEach((id) => {
				let notification = notifications.get(id);
				Notifications.push([id, <Notification caller_id={caller_id} notification={notification} />]);
			});

		}

		return Notifications;
	}

}
<|endoftext|>


<commit_before>'use strict';

const Command = require('../../models/Command.js');
const EarthCycleEmbed = require('../../embeds/EarthCycleEmbed.js');

/**
 * Displays the current stage in Earth's cycle
 */
class EarthCycle extends Command {
  /**
   * Constructs a callable command
   * @param {Genesis} bot  The bot object
   */
  constructor(bot) {
    super(bot, 'warframe.misc.cycle', 'cycle', 'Current and remaining time in cycle of Earth or Cetus rotations.');
    this.regex = new RegExp(`^${this.call}\\s?(earth)?`, 'i');
    this.usages = [
      {
        description: 'Display Cetus\'s current cycle progress',
        parameters: [],
      },
      {
        description: 'Display Earth\'s current cycle progress',
        parameters: ['earth'],
      },
    ];
  }

  /**
   * Run the command
   * @param {Message} message Message with a command to handle, reply to,
   *                          or perform an action based on parameters.
   * @returns {string} success status
   */
  async run(message) {
    let cycleData;
    const earth = (/earth/ig).test(message.strippedContent);
    const ws = JSON.parse(await this.bot.worldStates.pc.getData());
    if (earth) {
      cycleData = ws.earthCycle;
    } else {
      cycleData = ws.cetusCycle;
      const ostrons = ws.syndicateMissions.filter(mission => mission.syndicate === 'Ostrons')[0];
      if (ostrons) {
        cycleData.bountyExpiry = ostrons.expiry;
      }
    }
    const embed = new EarthCycleEmbed(this.bot, cycleData);
    await this.messageManager.embed(message, embed, true, true);
    return this.messageManager.statuses.SUCCESS;
  }
}

module.exports = EarthCycle;
<commit_msg>chore: consistent worldstate data fetch<commit_after>'use strict';

const Command = require('../../models/Command.js');
const EarthCycleEmbed = require('../../embeds/EarthCycleEmbed.js');

/**
 * Displays the current stage in Earth's cycle
 */
class EarthCycle extends Command {
  /**
   * Constructs a callable command
   * @param {Genesis} bot  The bot object
   */
  constructor(bot) {
    super(bot, 'warframe.misc.cycle', 'cycle', 'Current and remaining time in cycle of Earth or Cetus rotations.');
    this.regex = new RegExp(`^${this.call}\\s?(earth)?`, 'i');
    this.usages = [
      {
        description: 'Display Cetus\'s current cycle progress',
        parameters: [],
      },
      {
        description: 'Display Earth\'s current cycle progress',
        parameters: ['earth'],
      },
    ];
  }

  /**
   * Run the command
   * @param {Message} message Message with a command to handle, reply to,
   *                          or perform an action based on parameters.
   * @returns {string} success status
   */
  async run(message) {
    let cycleData;
    const earth = (/earth/ig).test(message.strippedContent);
    const ws = await this.bot.worldStates[platform.toLowerCase()].getData();
    if (earth) {
      cycleData = ws.earthCycle;
    } else {
      cycleData = ws.cetusCycle;
      const ostrons = ws.syndicateMissions.filter(mission => mission.syndicate === 'Ostrons')[0];
      if (ostrons) {
        cycleData.bountyExpiry = ostrons.expiry;
      }
    }
    const embed = new EarthCycleEmbed(this.bot, cycleData);
    await this.messageManager.embed(message, embed, true, true);
    return this.messageManager.statuses.SUCCESS;
  }
}

module.exports = EarthCycle;
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { View, Text, StyleSheet, ListView } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import _ from 'lodash';
import i18n from 'react-native-i18n';
import moment from 'moment';
import Accordion from 'react-native-collapsible/Accordion';

import HeaderWrapper from '../Header/HeaderWrapper';
import { mapArrayOfIdsToCategories } from '../../services/mapIdToObject';
import { translateMany } from '../../services/translator';
import { CATEGORY_PROP } from '../../constants/TranslationProps';
import ThresholdItemHeader from './ThresholdItemHeader';
import colors from '../../themes/Colors';

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

    this.state = {
      activeSection: false
    };
  }

  onChange = (index) => {
    this.setState({
      activeSection: index,
    });
  }

  renderHeader = ({
    title,
    icon,
    color,
    value,
    incomes,
    expenses
  }) => {
    const { currency } = this.props;
    const currentExpense = _.sumBy(expenses, 'value');

    const header = (
      <View>
        <ThresholdItemHeader
          color={color}
          currency={currency}
          currentExpense={currentExpense}
          icon={icon}
          title={title}
          value={value}
        />
      </View>
    );
    return header;
  }

  renderContent = ({
    title,
    icon,
    color,
    value,
    incomes,
    expenses
  }) => {
    const events = incomes.concat(expenses);
    const orderedEvents = _.sortBy(events, 'date');

    var content = (
      <View>
        {orderedEvents.map((e, index) => (
          <Text key={index}>{e.value} | {moment(e.date).format('DD MM YYYY')}</Text>
        ))
        }
        {orderedEvents.length === 0 &&
          <Text> No events for this category</Text>
        }
      </View>
    );

    return content;
  }

  render() {
    const { thresholdCategories, routeIndex } = this.props;
    const { activeSection } = this.state;
    let active = activeSection;

    if (routeIndex !== 0) {
      active = false;
    }

    return (
      <View >
        <HeaderWrapper title={i18n.t('CATEGORIES_THRESHOLD')} />
        <Accordion
          activeSection={active}
          onChange={this.onChange}
          renderContent={this.renderContent}
          renderHeader={this.renderHeader}
          sections={thresholdCategories}
        />
      </View >
    );
  }
}

function isCurrentMonth(date) {
  const eventDate = moment(date);
  const currentDate = moment();
  return eventDate.month() === currentDate.month() &&
    eventDate.year() === currentDate.year();
}

function mapStateToProps(state) {
  const { thresholds, expenses, incomes, currency } = state.ebudgie;
  const currentThreshold = thresholds[thresholds.length - 1] || {};
  const categories = currentThreshold.categories || [];
  const mappedCategories = mapArrayOfIdsToCategories(state.ebudgie.categories, categories);

  const thresholdCategories = _.map(mappedCategories, (c) => {
    return {
      ...c,
      expenses: _.filter(expenses, (e) => e.categoryId === c.id && isCurrentMonth(e.date)) || [],
      incomes: _.filter(incomes, (i) => i.categoryId === c.id && isCurrentMonth(i.date)) || [],
    };
  });

  return {
    thresholdCategories: translateMany(thresholdCategories, CATEGORY_PROP),
    currency,
    routeIndex: state.navigation.index,
  };
}

function mapDispatchToProps(dispatch) {
  return {

  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ThresholdList);
<commit_msg>Make ThresholdList vertical scrollable<commit_after>import React, { Component, PropTypes } from 'react';
import { ScrollView, View, Text, StyleSheet, ListView } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import _ from 'lodash';
import i18n from 'react-native-i18n';
import moment from 'moment';
import Accordion from 'react-native-collapsible/Accordion';

import HeaderWrapper from '../Header/HeaderWrapper';
import { mapArrayOfIdsToCategories } from '../../services/mapIdToObject';
import { translateMany } from '../../services/translator';
import { CATEGORY_PROP } from '../../constants/TranslationProps';
import ThresholdItemHeader from './ThresholdItemHeader';
import colors from '../../themes/Colors';

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

    this.state = {
      activeSection: false
    };
  }

  onChange = (index) => {
    this.setState({
      activeSection: index,
    });
  }

  renderHeader = ({
    title,
    icon,
    color,
    value,
    incomes,
    expenses
  }) => {
    const { currency } = this.props;
    const currentExpense = _.sumBy(expenses, 'value');

    const header = (
      <View>
        <ThresholdItemHeader
          color={color}
          currency={currency}
          currentExpense={currentExpense}
          icon={icon}
          title={title}
          value={value}
        />
      </View>
    );
    return header;
  }

  renderContent = ({
    title,
    icon,
    color,
    value,
    incomes,
    expenses
  }) => {
    const events = incomes.concat(expenses);
    const orderedEvents = _.sortBy(events, 'date');

    var content = (
      <View>
        {orderedEvents.map((e, index) => (
          <Text key={index}>{e.value} | {moment(e.date).format('DD MM YYYY')}</Text>
        ))
        }
        {orderedEvents.length === 0 &&
          <Text> No events for this category</Text>
        }
      </View>
    );

    return content;
  }

  render() {
    const { thresholdCategories, routeIndex } = this.props;
    const { activeSection } = this.state;
    let active = activeSection;

    if (routeIndex !== 0) {
      active = false;
    }

    return (
      <ScrollView >
        <HeaderWrapper title={i18n.t('CATEGORIES_THRESHOLD')} />
        <Accordion
          activeSection={active}
          onChange={this.onChange}
          renderContent={this.renderContent}
          renderHeader={this.renderHeader}
          sections={thresholdCategories}
        />
      </ScrollView >
    );
  }
}

function isCurrentMonth(date) {
  const eventDate = moment(date);
  const currentDate = moment();
  return eventDate.month() === currentDate.month() &&
    eventDate.year() === currentDate.year();
}

function mapStateToProps(state) {
  const { thresholds, expenses, incomes, currency } = state.ebudgie;
  const currentThreshold = thresholds[thresholds.length - 1] || {};
  const categories = currentThreshold.categories || [];
  const mappedCategories = mapArrayOfIdsToCategories(state.ebudgie.categories, categories);

  const thresholdCategories = _.map(mappedCategories, (c) => {
    return {
      ...c,
      expenses: _.filter(expenses, (e) => e.categoryId === c.id && isCurrentMonth(e.date)) || [],
      incomes: _.filter(incomes, (i) => i.categoryId === c.id && isCurrentMonth(i.date)) || [],
    };
  });

  return {
    thresholdCategories: translateMany(thresholdCategories, CATEGORY_PROP),
    currency,
    routeIndex: state.navigation.index,
  };
}

function mapDispatchToProps(dispatch) {
  return {

  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ThresholdList);
<|endoftext|>


<commit_before>import cx from 'classnames';
import React from 'react';
import YouTube from 'react-youtube';
import VideoBackdrop from './VideoBackdrop';

const debug = require('debug')('uwave:component:video:youtube');

export default class YouTubePlayer extends React.Component {
  static propTypes = {
    className: React.PropTypes.string,
    size: React.PropTypes.string,
    media: React.PropTypes.object,
    seek: React.PropTypes.number,
    volume: React.PropTypes.number
  };

  componentDidUpdate(prevProps) {
    const { player } = this.refs;
    if (player) {
      // only set volume after the YT API is fully initialised.
      // if it fails here because the API isn't ready, the the volume will still
      // be set in onYTReady().
      if (player._internalPlayer && prevProps.volume !== this.props.volume) {
        debug('YT: setting volume', this.props.volume);
        player._internalPlayer.setVolume(this.props.volume);
      }
    }
  }

  onYTReady(event) {
    event.target.setVolume(this.props.volume);
  }

  render() {
    const { className, size, media, seek } = this.props;
    const sizeClass = `YouTubePlayer--${size}`;

    const opts = {
      width: '100%',
      height: '100%',
      playerVars: {
        autoplay: 1,
        controls: 0,        // do not show player controls in the bottom
        rel: 0,             // do not show related videos after the video finishes
        showinfo: 0,        // do not show video title etc in the frame
        iv_load_policy: 3,  // disable annotations
        modestbranding: 1,  // hide youtube logo
        start: (seek || 0) + (media.start || 0)
      }
    };

    let backdrop;
    if (size === 'small') {
      backdrop = <VideoBackdrop url={media.thumbnail} />;
    }
    // Wrapper span so the backdrop can be full-size…
    return (

        {backdrop}

          <YouTube
            ref="player"
            videoId={media.sourceID}
            opts={opts}
            onReady={::this.onYTReady}
          />

    );
  }
}
<commit_msg>video: avoid rerendering youtube-player when volume changes<commit_after>import cx from 'classnames';
import React from 'react';
import YouTube from 'react-youtube';
import VideoBackdrop from './VideoBackdrop';

const debug = require('debug')('uwave:component:video:youtube');

export default class YouTubePlayer extends React.Component {
  static propTypes = {
    className: React.PropTypes.string,
    size: React.PropTypes.string,
    media: React.PropTypes.object,
    seek: React.PropTypes.number,
    volume: React.PropTypes.number
  };

  componentWillReceiveProps(nextProps) {
    const { player } = this.refs;
    if (player) {
      // only set volume after the YT API is fully initialised.
      // if it fails here because the API isn't ready, the the volume will still
      // be set in onYTReady().
      if (player._internalPlayer && this.props.volume !== nextProps.volume) {
        debug('YT: setting volume', nextProps.volume);
        player._internalPlayer.setVolume(nextProps.volume);
      }
    }
  }

  shouldComponentUpdate(nextProps) {
    // Only rerender when the media changes. This avoids react-youtube resetting
    // the YouTube player when only the volume changes.
    return this.props.media !== nextProps.media ||
      this.props.media._id !== nextProps.media._id;
  }

  onYTReady(event) {
    event.target.setVolume(this.props.volume);
  }

  render() {
    const { className, size, media, seek } = this.props;
    const sizeClass = `YouTubePlayer--${size}`;

    const opts = {
      width: '100%',
      height: '100%',
      playerVars: {
        autoplay: 1,
        controls: 0,        // do not show player controls in the bottom
        rel: 0,             // do not show related videos after the video finishes
        showinfo: 0,        // do not show video title etc in the frame
        iv_load_policy: 3,  // disable annotations
        modestbranding: 1,  // hide youtube logo
        start: (seek || 0) + (media.start || 0)
      }
    };

    let backdrop;
    if (size === 'small') {
      backdrop = <VideoBackdrop url={media.thumbnail} />;
    }
    // Wrapper span so the backdrop can be full-size…
    return (

        {backdrop}

          <YouTube
            ref="player"
            videoId={media.sourceID}
            opts={opts}
            onReady={::this.onYTReady}
          />

    );
  }
}
<|endoftext|>