<commit_before>import {Animator} from './animator';
import {View} from './view';
import {ShadowDOM} from './shadow-dom';

function getAnimatableElement(view) {
  if (view.animatableElement !== undefined) {
    return view.animatableElement;
  }

  let current = view.firstChild;

  while (current && current.nodeType !== 1) {
    current = current.nextSibling;
  }

  if (current && current.nodeType === 1) {
    return (view.animatableElement = current.classList.contains('au-animate') ? current : null);
  }

  return (view.animatableElement = null);
}

/**
* Represents a slot or location within the DOM to which views can be added and removed.
* Manages the view lifecycle for its children.
*/
export class ViewSlot {
  /**
  * Creates an instance of ViewSlot.
  * @param anchor The DOM node which will server as the anchor or container for insertion.
  * @param anchorIsContainer Indicates whether the node is a container.
  * @param animator The animator that will controll enter/leave transitions for this slot.
  */
  constructor(anchor: Node, anchorIsContainer: boolean, animator?: Animator = Animator.instance) {
    this.anchor = anchor;
    this.anchorIsContainer = anchorIsContainer;
    this.bindingContext = null;
    this.overrideContext = null;
    this.animator = animator;
    this.children = [];
    this.isBound = false;
    this.isAttached = false;
    this.contentSelectors = null;
    anchor.viewSlot = this;
    anchor.isContentProjectionSource = false;
  }

  /**
   *   Runs the animator against the first animatable element found within the view's fragment
   *   @param  view       The view to use when searching for the element.
   *   @param  direction  The animation direction enter|leave.
   *   @returns An animation complete Promise or undefined if no animation was run.
   */
  animateView(view: View, direction: string = 'enter'): void | Promise<any> {
    let animatableElement = getAnimatableElement(view);

    if (animatableElement !== null) {
      switch (direction) {
      case 'enter':
        return this.animator.enter(animatableElement);
      case 'leave':
        return this.animator.leave(animatableElement);
      default:
        throw new Error('Invalid animation direction: ' + direction);
      }
    }
  }

  /**
  * Takes the child nodes of an existing element that has been converted into a ViewSlot
  * and makes those nodes into a View within the slot.
  */
  transformChildNodesIntoView(): void {
    let parent = this.anchor;

    this.children.push({
      fragment: parent,
      firstChild: parent.firstChild,
      lastChild: parent.lastChild,
      returnToCache() {},
      removeNodes() {
        let last;

        while (last = parent.lastChild) {
          parent.removeChild(last);
        }
      },
      created() {},
      bind() {},
      unbind() {},
      attached() {},
      detached() {}
    });
  }

  /**
  * Binds the slot and it's children.
  * @param bindingContext The binding context to bind to.
  * @param overrideContext A secondary binding context that can override the standard context.
  */
  bind(bindingContext: Object, overrideContext: Object): void {
    let i;
    let ii;
    let children;

    if (this.isBound) {
      if (this.bindingContext === bindingContext) {
        return;
      }

      this.unbind();
    }

    this.isBound = true;
    this.bindingContext = bindingContext = bindingContext || this.bindingContext;
    this.overrideContext = overrideContext = overrideContext || this.overrideContext;

    children = this.children;
    for (i = 0, ii = children.length; i < ii; ++i) {
      children[i].bind(bindingContext, overrideContext, true);
    }
  }

  /**
  * Unbinds the slot and its children.
  */
  unbind(): void {
    if (this.isBound) {
      let i;
      let ii;
      let children = this.children;

      this.isBound = false;
      this.bindingContext = null;
      this.overrideContext = null;

      for (i = 0, ii = children.length; i < ii; ++i) {
        children[i].unbind();
      }
    }
  }

  /**
  * Adds a view to the slot.
  * @param view The view to add.
  * @return May return a promise if the view addition triggered an animation.
  */
  add(view: View): void | Promise<any> {
    if (this.anchorIsContainer) {
      view.appendNodesTo(this.anchor);
    } else {
      view.insertNodesBefore(this.anchor);
    }

    this.children.push(view);

    if (this.isAttached) {
      view.attached();
      return this.animateView(view, 'enter');
    }
  }

  /**
  * Inserts a view into the slot.
  * @param index The index to insert the view at.
  * @param view The view to insert.
  * @return May return a promise if the view insertion triggered an animation.
  */
  insert(index: number, view: View): void | Promise<any> {
    let children = this.children;
    let length = children.length;

    if ((index === 0 && length === 0) || index >= length) {
      return this.add(view);
    }

    view.insertNodesBefore(children[index].firstChild);
    children.splice(index, 0, view);

    if (this.isAttached) {
      view.attached();
      return this.animateView(view, 'enter');
    }
  }

  /**
   * Moves a view across the slot.
   * @param sourceIndex The index the view is currently at.
   * @param targetIndex The index to insert the view at.
   */
  move(sourceIndex, targetIndex) {
    if (sourceIndex === targetIndex) {
      return;
    }

    const children = this.children;
    const view = children[sourceIndex];

    view.removeNodes();
    view.insertNodesBefore(children[targetIndex].firstChild);
    children.splice(sourceIndex, 1);
    children.splice(targetIndex, 0, view);
  }

  /**
  * Removes a view from the slot.
  * @param view The view to remove.
  * @param returnToCache Should the view be returned to the view cache?
  * @param skipAnimation Should the removal animation be skipped?
  * @return May return a promise if the view removal triggered an animation.
  */
  remove(view: View, returnToCache?: boolean, skipAnimation?: boolean): void | Promise<View> {
    return this.removeAt(this.children.indexOf(view), returnToCache, skipAnimation);
  }

  /**
  * Removes many views from the slot.
  * @param viewsToRemove The array of views to remove.
  * @param returnToCache Should the views be returned to the view cache?
  * @param skipAnimation Should the removal animation be skipped?
  * @return May return a promise if the view removal triggered an animation.
  */
  removeMany(viewsToRemove: View[], returnToCache?: boolean, skipAnimation?: boolean): void | Promise<View> {
    const children = this.children;
    let ii = viewsToRemove.length;
    let i;
    let rmPromises = [];

    viewsToRemove.forEach(child => {
      if (skipAnimation) {
        child.removeNodes();
        return;
      }

      let animation = this.animateView(child, 'leave');
      if (animation) {
        rmPromises.push(animation.then(() => child.removeNodes()));
      } else {
        child.removeNodes();
      }
    });

    let removeAction = () => {
      if (this.isAttached) {
        for (i = 0; i < ii; ++i) {
          viewsToRemove[i].detached();
        }
      }

      if (returnToCache) {
        for (i = 0; i < ii; ++i) {
          viewsToRemove[i].returnToCache();
        }
      }

      for (i = 0; i < ii; ++i) {
        const index = children.indexOf(viewsToRemove[i]);
        if (index >= 0) {
          children.splice(index, 1);
        }
      }
    };

    if (rmPromises.length > 0) {
      return Promise.all(rmPromises).then(() => removeAction());
    }

    return removeAction();
  }

  /**
  * Removes a view an a specified index from the slot.
  * @param index The index to remove the view at.
  * @param returnToCache Should the view be returned to the view cache?
  * @param skipAnimation Should the removal animation be skipped?
  * @return May return a promise if the view removal triggered an animation.
  */
  removeAt(index: number, returnToCache?: boolean, skipAnimation?: boolean): View | Promise<View> {
    let view = this.children[index];

    let removeAction = () => {
      index = this.children.indexOf(view);
      view.removeNodes();
      this.children.splice(index, 1);

      if (this.isAttached) {
        view.detached();
      }

      if (returnToCache) {
        view.returnToCache();
      }

      return view;
    };

    if (!skipAnimation) {
      let animation = this.animateView(view, 'leave');
      if (animation) {
        return animation.then(() => removeAction());
      }
    }

    return removeAction();
  }

  /**
  * Removes all views from the slot.
  * @param returnToCache Should the view be returned to the view cache?
  * @param skipAnimation Should the removal animation be skipped?
  * @return May return a promise if the view removals triggered an animation.
  */
  removeAll(returnToCache?: boolean, skipAnimation?: boolean): void | Promise<any> {
    let children = this.children;
    let ii = children.length;
    let i;
    let rmPromises = [];

    children.forEach(child => {
      if (skipAnimation) {
        child.removeNodes();
        return;
      }

      let animation = this.animateView(child, 'leave');
      if (animation) {
        rmPromises.push(animation.then(() => child.removeNodes()));
      } else {
        child.removeNodes();
      }
    });

    let removeAction = () => {
      if (this.isAttached) {
        for (i = 0; i < ii; ++i) {
          children[i].detached();
        }
      }

      if (returnToCache) {
        for (i = 0; i < ii; ++i) {
          children[i].returnToCache();
        }
      }

      this.children = [];
    };

    if (rmPromises.length > 0) {
      return Promise.all(rmPromises).then(() => removeAction());
    }

    return removeAction();
  }

  /**
  * Triggers the attach for the slot and its children.
  */
  attached(): void {
    let i;
    let ii;
    let children;
    let child;

    if (this.isAttached) {
      return;
    }

    this.isAttached = true;

    children = this.children;
    for (i = 0, ii = children.length; i < ii; ++i) {
      child = children[i];
      child.attached();
      this.animateView(child, 'enter');
    }
  }

  /**
  * Triggers the detach for the slot and its children.
  */
  detached(): void {
    let i;
    let ii;
    let children;

    if (this.isAttached) {
      this.isAttached = false;
      children = this.children;
      for (i = 0, ii = children.length; i < ii; ++i) {
        children[i].detached();
      }
    }
  }

  projectTo(slots: Object): void {
    this.projectToSlots = slots;
    this.add = this._projectionAdd;
    this.insert = this._projectionInsert;
    this.move = this._projectionMove;
    this.remove = this._projectionRemove;
    this.removeAt = this._projectionRemoveAt;
    this.removeMany = this._projectionRemoveMany;
    this.removeAll = this._projectionRemoveAll;
    this.children.forEach(view => ShadowDOM.distributeView(view, slots, this));
  }

  _projectionAdd(view) {
    ShadowDOM.distributeView(view, this.projectToSlots, this);

    this.children.push(view);

    if (this.isAttached) {
      view.attached();
    }
  }

  _projectionInsert(index, view) {
    if ((index === 0 && !this.children.length) || index >= this.children.length) {
      this.add(view);
    } else {
      ShadowDOM.distributeView(view, this.projectToSlots, this, index);

      this.children.splice(index, 0, view);

      if (this.isAttached) {
        view.attached();
      }
    }
  }

  _projectionMove(sourceIndex, targetIndex) {
    if (sourceIndex === targetIndex) {
      return;
    }

    const children = this.children;
    const view = children[sourceIndex];

    ShadowDOM.undistributeView(view, this.projectToSlots, this);
    ShadowDOM.distributeView(view, this.projectToSlots, this, targetIndex);

    children.splice(sourceIndex, 1);
    children.splice(targetIndex, 0, view);
  }

  _projectionRemove(view, returnToCache) {
    ShadowDOM.undistributeView(view, this.projectToSlots, this);
    this.children.splice(this.children.indexOf(view), 1);

    if (this.isAttached) {
      view.detached();
    }
  }

  _projectionRemoveAt(index, returnToCache) {
    let view = this.children[index];

    ShadowDOM.undistributeView(view, this.projectToSlots, this);
    this.children.splice(index, 1);

    if (this.isAttached) {
      view.detached();
    }
  }

  _projectionRemoveMany(viewsToRemove, returnToCache?) {
    viewsToRemove.forEach(view => this.remove(view, returnToCache));
  }

  _projectionRemoveAll(returnToCache) {
    ShadowDOM.undistributeAll(this.projectToSlots, this);

    let children = this.children;

    if (this.isAttached) {
      for (let i = 0, ii = children.length; i < ii; ++i) {
        children[i].detached();
      }
    }

    this.children = [];
  }
}
<commit_msg>fix(view-slot): check for undefined child<commit_after>import {Animator} from './animator';
import {View} from './view';
import {ShadowDOM} from './shadow-dom';

function getAnimatableElement(view) {
  if (view.animatableElement !== undefined) {
    return view.animatableElement;
  }

  let current = view.firstChild;

  while (current && current.nodeType !== 1) {
    current = current.nextSibling;
  }

  if (current && current.nodeType === 1) {
    return (view.animatableElement = current.classList.contains('au-animate') ? current : null);
  }

  return (view.animatableElement = null);
}

/**
* Represents a slot or location within the DOM to which views can be added and removed.
* Manages the view lifecycle for its children.
*/
export class ViewSlot {
  /**
  * Creates an instance of ViewSlot.
  * @param anchor The DOM node which will server as the anchor or container for insertion.
  * @param anchorIsContainer Indicates whether the node is a container.
  * @param animator The animator that will controll enter/leave transitions for this slot.
  */
  constructor(anchor: Node, anchorIsContainer: boolean, animator?: Animator = Animator.instance) {
    this.anchor = anchor;
    this.anchorIsContainer = anchorIsContainer;
    this.bindingContext = null;
    this.overrideContext = null;
    this.animator = animator;
    this.children = [];
    this.isBound = false;
    this.isAttached = false;
    this.contentSelectors = null;
    anchor.viewSlot = this;
    anchor.isContentProjectionSource = false;
  }

  /**
   *   Runs the animator against the first animatable element found within the view's fragment
   *   @param  view       The view to use when searching for the element.
   *   @param  direction  The animation direction enter|leave.
   *   @returns An animation complete Promise or undefined if no animation was run.
   */
  animateView(view: View, direction: string = 'enter'): void | Promise<any> {
    let animatableElement = getAnimatableElement(view);

    if (animatableElement !== null) {
      switch (direction) {
      case 'enter':
        return this.animator.enter(animatableElement);
      case 'leave':
        return this.animator.leave(animatableElement);
      default:
        throw new Error('Invalid animation direction: ' + direction);
      }
    }
  }

  /**
  * Takes the child nodes of an existing element that has been converted into a ViewSlot
  * and makes those nodes into a View within the slot.
  */
  transformChildNodesIntoView(): void {
    let parent = this.anchor;

    this.children.push({
      fragment: parent,
      firstChild: parent.firstChild,
      lastChild: parent.lastChild,
      returnToCache() {},
      removeNodes() {
        let last;

        while (last = parent.lastChild) {
          parent.removeChild(last);
        }
      },
      created() {},
      bind() {},
      unbind() {},
      attached() {},
      detached() {}
    });
  }

  /**
  * Binds the slot and it's children.
  * @param bindingContext The binding context to bind to.
  * @param overrideContext A secondary binding context that can override the standard context.
  */
  bind(bindingContext: Object, overrideContext: Object): void {
    let i;
    let ii;
    let children;

    if (this.isBound) {
      if (this.bindingContext === bindingContext) {
        return;
      }

      this.unbind();
    }

    this.isBound = true;
    this.bindingContext = bindingContext = bindingContext || this.bindingContext;
    this.overrideContext = overrideContext = overrideContext || this.overrideContext;

    children = this.children;
    for (i = 0, ii = children.length; i < ii; ++i) {
      children[i].bind(bindingContext, overrideContext, true);
    }
  }

  /**
  * Unbinds the slot and its children.
  */
  unbind(): void {
    if (this.isBound) {
      let i;
      let ii;
      let children = this.children;

      this.isBound = false;
      this.bindingContext = null;
      this.overrideContext = null;

      for (i = 0, ii = children.length; i < ii; ++i) {
        children[i].unbind();
      }
    }
  }

  /**
  * Adds a view to the slot.
  * @param view The view to add.
  * @return May return a promise if the view addition triggered an animation.
  */
  add(view: View): void | Promise<any> {
    if (this.anchorIsContainer) {
      view.appendNodesTo(this.anchor);
    } else {
      view.insertNodesBefore(this.anchor);
    }

    this.children.push(view);

    if (this.isAttached) {
      view.attached();
      return this.animateView(view, 'enter');
    }
  }

  /**
  * Inserts a view into the slot.
  * @param index The index to insert the view at.
  * @param view The view to insert.
  * @return May return a promise if the view insertion triggered an animation.
  */
  insert(index: number, view: View): void | Promise<any> {
    let children = this.children;
    let length = children.length;

    if ((index === 0 && length === 0) || index >= length) {
      return this.add(view);
    }

    view.insertNodesBefore(children[index].firstChild);
    children.splice(index, 0, view);

    if (this.isAttached) {
      view.attached();
      return this.animateView(view, 'enter');
    }
  }

  /**
   * Moves a view across the slot.
   * @param sourceIndex The index the view is currently at.
   * @param targetIndex The index to insert the view at.
   */
  move(sourceIndex, targetIndex) {
    if (sourceIndex === targetIndex) {
      return;
    }

    const children = this.children;
    const view = children[sourceIndex];

    view.removeNodes();
    view.insertNodesBefore(children[targetIndex].firstChild);
    children.splice(sourceIndex, 1);
    children.splice(targetIndex, 0, view);
  }

  /**
  * Removes a view from the slot.
  * @param view The view to remove.
  * @param returnToCache Should the view be returned to the view cache?
  * @param skipAnimation Should the removal animation be skipped?
  * @return May return a promise if the view removal triggered an animation.
  */
  remove(view: View, returnToCache?: boolean, skipAnimation?: boolean): void | Promise<View> {
    return this.removeAt(this.children.indexOf(view), returnToCache, skipAnimation);
  }

  /**
  * Removes many views from the slot.
  * @param viewsToRemove The array of views to remove.
  * @param returnToCache Should the views be returned to the view cache?
  * @param skipAnimation Should the removal animation be skipped?
  * @return May return a promise if the view removal triggered an animation.
  */
  removeMany(viewsToRemove: View[], returnToCache?: boolean, skipAnimation?: boolean): void | Promise<View> {
    const children = this.children;
    let ii = viewsToRemove.length;
    let i;
    let rmPromises = [];

    viewsToRemove.forEach(child => {
      if (skipAnimation) {
        child.removeNodes();
        return;
      }

      let animation = this.animateView(child, 'leave');
      if (animation) {
        rmPromises.push(animation.then(() => child.removeNodes()));
      } else {
        child.removeNodes();
      }
    });

    let removeAction = () => {
      if (this.isAttached) {
        for (i = 0; i < ii; ++i) {
          viewsToRemove[i].detached();
        }
      }

      if (returnToCache) {
        for (i = 0; i < ii; ++i) {
          viewsToRemove[i].returnToCache();
        }
      }

      for (i = 0; i < ii; ++i) {
        const index = children.indexOf(viewsToRemove[i]);
        if (index >= 0) {
          children.splice(index, 1);
        }
      }
    };

    if (rmPromises.length > 0) {
      return Promise.all(rmPromises).then(() => removeAction());
    }

    return removeAction();
  }

  /**
  * Removes a view an a specified index from the slot.
  * @param index The index to remove the view at.
  * @param returnToCache Should the view be returned to the view cache?
  * @param skipAnimation Should the removal animation be skipped?
  * @return May return a promise if the view removal triggered an animation.
  */
  removeAt(index: number, returnToCache?: boolean, skipAnimation?: boolean): View | Promise<View> {
    let view = this.children[index];

    let removeAction = () => {
      index = this.children.indexOf(view);
      view.removeNodes();
      this.children.splice(index, 1);

      if (this.isAttached) {
        view.detached();
      }

      if (returnToCache) {
        view.returnToCache();
      }

      return view;
    };

    if (!skipAnimation) {
      let animation = this.animateView(view, 'leave');
      if (animation) {
        return animation.then(() => removeAction());
      }
    }

    return removeAction();
  }

  /**
  * Removes all views from the slot.
  * @param returnToCache Should the view be returned to the view cache?
  * @param skipAnimation Should the removal animation be skipped?
  * @return May return a promise if the view removals triggered an animation.
  */
  removeAll(returnToCache?: boolean, skipAnimation?: boolean): void | Promise<any> {
    let children = this.children;
    let ii = children.length;
    let i;
    let rmPromises = [];

    children.forEach(child => {
      if (skipAnimation) {
        child.removeNodes();
        return;
      }

      let animation = this.animateView(child, 'leave');
      if (animation) {
        rmPromises.push(animation.then(() => child.removeNodes()));
      } else {
        child.removeNodes();
      }
    });

    let removeAction = () => {
      if (this.isAttached) {
        for (i = 0; i < ii; ++i) {
          children[i].detached();
        }
      }

      if (returnToCache) {
        for (i = 0; i < ii; ++i) {
          if (children[i]) {
            children[i].returnToCache();
          }
        }
      }

      this.children = [];
    };

    if (rmPromises.length > 0) {
      return Promise.all(rmPromises).then(() => removeAction());
    }

    return removeAction();
  }

  /**
  * Triggers the attach for the slot and its children.
  */
  attached(): void {
    let i;
    let ii;
    let children;
    let child;

    if (this.isAttached) {
      return;
    }

    this.isAttached = true;

    children = this.children;
    for (i = 0, ii = children.length; i < ii; ++i) {
      child = children[i];
      child.attached();
      this.animateView(child, 'enter');
    }
  }

  /**
  * Triggers the detach for the slot and its children.
  */
  detached(): void {
    let i;
    let ii;
    let children;

    if (this.isAttached) {
      this.isAttached = false;
      children = this.children;
      for (i = 0, ii = children.length; i < ii; ++i) {
        children[i].detached();
      }
    }
  }

  projectTo(slots: Object): void {
    this.projectToSlots = slots;
    this.add = this._projectionAdd;
    this.insert = this._projectionInsert;
    this.move = this._projectionMove;
    this.remove = this._projectionRemove;
    this.removeAt = this._projectionRemoveAt;
    this.removeMany = this._projectionRemoveMany;
    this.removeAll = this._projectionRemoveAll;
    this.children.forEach(view => ShadowDOM.distributeView(view, slots, this));
  }

  _projectionAdd(view) {
    ShadowDOM.distributeView(view, this.projectToSlots, this);

    this.children.push(view);

    if (this.isAttached) {
      view.attached();
    }
  }

  _projectionInsert(index, view) {
    if ((index === 0 && !this.children.length) || index >= this.children.length) {
      this.add(view);
    } else {
      ShadowDOM.distributeView(view, this.projectToSlots, this, index);

      this.children.splice(index, 0, view);

      if (this.isAttached) {
        view.attached();
      }
    }
  }

  _projectionMove(sourceIndex, targetIndex) {
    if (sourceIndex === targetIndex) {
      return;
    }

    const children = this.children;
    const view = children[sourceIndex];

    ShadowDOM.undistributeView(view, this.projectToSlots, this);
    ShadowDOM.distributeView(view, this.projectToSlots, this, targetIndex);

    children.splice(sourceIndex, 1);
    children.splice(targetIndex, 0, view);
  }

  _projectionRemove(view, returnToCache) {
    ShadowDOM.undistributeView(view, this.projectToSlots, this);
    this.children.splice(this.children.indexOf(view), 1);

    if (this.isAttached) {
      view.detached();
    }
  }

  _projectionRemoveAt(index, returnToCache) {
    let view = this.children[index];

    ShadowDOM.undistributeView(view, this.projectToSlots, this);
    this.children.splice(index, 1);

    if (this.isAttached) {
      view.detached();
    }
  }

  _projectionRemoveMany(viewsToRemove, returnToCache?) {
    viewsToRemove.forEach(view => this.remove(view, returnToCache));
  }

  _projectionRemoveAll(returnToCache) {
    ShadowDOM.undistributeAll(this.projectToSlots, this);

    let children = this.children;

    if (this.isAttached) {
      for (let i = 0, ii = children.length; i < ii; ++i) {
        children[i].detached();
      }
    }

    this.children = [];
  }
}
<|endoftext|>


<commit_before>'use strict'

module.exports = [
  {
    user: '@michal.brasna',
    services: ['tankovna', 'lokal-hamburk', 'globus']
  },
  {
    user: '@jan.pavlovsky',
    services: ['peters', 'meat-market', 'globus', 'u-sani', 'lokal-hamburk']
  },
  {
    user: '@ottokovarik',
    services: ['tankovna', 'lokal-hamburk', 'globus']
  },
  {
    user: '@radim',
    services: ['tankovna', 'lokal-hamburk', 'u-zabranskych']
  },
  {
    user: '@tomas.pokorny',
    services: ['tankovna', 'lokal-hamburk']
  },
  {
    user: '@radek.carda',
    services: ['peters', 'globus', 'meat-market', 'lokal-hamburk', 'u-zabranskych']
  },
  {
    user: '@zdenek.cejka',
    services: ['peters', 'tankovna', 'u-sani', 'meat-market', 'globus']
  },
  {
    user: '@mafo',
    services: ['tankovna', 'lokal-hamburk', 'globus']
  },
  {
    user: '@martin.kadlec',
    services: ['tankovna', 'lokal-hamburk', 'globus']
  },
  {
    user: '@adamsobotka',
    services: ['tankovna', 'lokal-hamburk', 'meat-market', 'globus']
  },
  {
    user: '@tomasbauer',
    services: ['tankovna', 'lokal-hamburk', 'globus']
  },
  {
    user: '@navratj',
    services: ['tankovna', 'lokal-hamburk', 'u-zabranskych']
  },
  {
    user: '@snajby',
    services: ['peters', 'tankovna', 'meat-market', 'lokal-hamburk', 'globus', 'u-zabranskych']
  },
  {
    user: '@milanlepik',
    services: ['peters', 'tankovna', 'u-sani', 'meat-market', 'lokal-hamburk', 'globus']
  },
  {
    user: '@vaclavhosticka',
    services: ['peters', 'tankovna', 'meat-market', 'lokal-hamburk', 'globus', 'u-zabranskych']
  },
  {
    user: '@ango',
    services: ['peters', 'tankovna', 'meat-market', 'lokal-hamburk', 'globus', 'u-zabranskych']
  }
]
<commit_msg>add @ondram, update @ango<commit_after>'use strict'

module.exports = [
  {
    user: '@michal.brasna',
    services: ['tankovna', 'lokal-hamburk', 'globus']
  },
  {
    user: '@jan.pavlovsky',
    services: ['peters', 'meat-market', 'globus', 'u-sani', 'lokal-hamburk']
  },
  {
    user: '@ottokovarik',
    services: ['tankovna', 'lokal-hamburk', 'globus']
  },
  {
    user: '@radim',
    services: ['tankovna', 'lokal-hamburk', 'u-zabranskych']
  },
  {
    user: '@tomas.pokorny',
    services: ['tankovna', 'lokal-hamburk']
  },
  {
    user: '@radek.carda',
    services: ['peters', 'globus', 'meat-market', 'lokal-hamburk', 'u-zabranskych']
  },
  {
    user: '@zdenek.cejka',
    services: ['peters', 'tankovna', 'u-sani', 'meat-market', 'globus']
  },
  {
    user: '@mafo',
    services: ['tankovna', 'lokal-hamburk', 'globus']
  },
  {
    user: '@martin.kadlec',
    services: ['tankovna', 'lokal-hamburk', 'globus']
  },
  {
    user: '@adamsobotka',
    services: ['tankovna', 'lokal-hamburk', 'meat-market', 'globus']
  },
  {
    user: '@tomasbauer',
    services: ['tankovna', 'lokal-hamburk', 'globus']
  },
  {
    user: '@navratj',
    services: ['tankovna', 'lokal-hamburk', 'u-zabranskych']
  },
  {
    user: '@snajby',
    services: ['peters', 'tankovna', 'meat-market', 'lokal-hamburk', 'globus', 'u-zabranskych']
  },
  {
    user: '@milanlepik',
    services: ['peters', 'tankovna', 'u-sani', 'meat-market', 'lokal-hamburk', 'globus']
  },
  {
    user: '@vaclavhosticka',
    services: ['peters', 'tankovna', 'meat-market', 'lokal-hamburk', 'globus', 'u-zabranskych']
  },
  {
    user: '@ango',
    services: ['peters', 'tankovna', 'meat-market', 'lokal-hamburk', 'globus', 'u-zabranskych', 'u-sani', 'u-tunelu']
  },
  {
    user: '@ondram',
    services: ['peters', 'tankovna', 'meat-market', 'lokal-hamburk', 'globus', 'u-zabranskych', 'u-sani', 'u-tunelu']
  }
]
<|endoftext|>


<commit_before>const {absolute} = require('./helper')

const {DirectoryNode} = require('./directory-node')
const {RecursiveWatcherNode} = require('./recursive-watcher-node')

// Private: Map userland filesystem watcher subscriptions efficiently to deliver filesystem change notifications to
// each watcher with the most efficient coverage of native watchers.
//
// * If two watchers subscribe to the same directory, use a single native watcher for each.
// * Re-use a native watcher watching a parent directory for a watcher on a child directory. If the parent directory
//   watcher is removed, it will be split into child watchers.
// * If any child directories already being watched, stop and replace them with a watcher on the parent directory.
//
// Uses a trie whose structure mirrors the directory structure.
class Tree {
  // Private: Construct a tree with no native watchers.
  //
  // * `basePathSegments` the position of this tree's root relative to the filesystem's root as an {Array} of directory
  //   names.
  // * `createNative` {Function} used to construct new native watchers. It should accept an absolute path as an argument
  //   and return a new {NativeWatcher}.
  constructor (basePathSegments, createNative) {
    this.basePathSegments = basePathSegments
    this.root = new DirectoryNode()
    this.createNative = createNative
  }

  // Private: Identify the native watcher that should be used to produce events at a watched path, creating a new one
  // if necessary.
  //
  // * `pathSegments` the path to watch represented as an {Array} of directory names relative to this {Tree}'s
  //   root.
  // * `attachToNative` {Function} invoked with the appropriate native watcher and the absolute path to its watch root.
  add (pathSegments, attachToNative) {
    const absolutePathSegments = this.basePathSegments.concat(pathSegments)
    const absolutePath = absolute(...absolutePathSegments)

    const attachToNew = (childPaths) => {
      const native = this.createNative(absolutePath)
      const leaf = new RecursiveWatcherNode(native, absolutePathSegments, childPaths)
      this.root = this.root.insert(pathSegments, leaf)

      const sub = native.onWillStop(split => {
        sub.dispose()
        const createNative = split ? this.createNative : null
        this.root = this.root.remove(pathSegments, createNative) || new DirectoryNode()
      })

      attachToNative(native, absolutePath)
      return native
    }

    this.root.lookup(pathSegments).when({
      parent: (parent, remaining) => {
        // An existing NativeWatcher is watching the same directory or a parent directory of the requested path.
        // Attach this Watcher to it as a filtering watcher and record it as a dependent child path.
        const native = parent.getNativeWatcher()
        parent.addChildPath(remaining)
        attachToNative(native, absolute(...parent.getAbsolutePathSegments()))
      },
      children: children => {
        // One or more NativeWatchers exist on child directories of the requested path. Create a new native watcher
        // on the parent directory, note the subscribed child paths, and cleanly stop the child native watchers.
        const newNative = attachToNew(children.map(child => child.path))

        for (let i = 0; i < children.length; i++) {
          const childNode = children[i].node
          const childNative = childNode.getNativeWatcher()
          childNative.reattachTo(newNative, absolutePath)
          childNative.stop()
        }
      },
      missing: () => attachToNew([])
    })
  }

  // Private: Access the root node of the tree.
  getRoot () {
    return this.root
  }

  // Private: Return a {String} representation of this tree's structure for diagnostics and testing.
  print () {
    return this.root.print()
  }
}

module.exports = {Tree}
<commit_msg>Add nonrecursive watcher nodes into a Tree<commit_after>const {absolute} = require('./helper')

const {DirectoryNode} = require('./directory-node')
const {RecursiveWatcherNode} = require('./recursive-watcher-node')
const {NonrecursiveWatcherNode} = require('./nonrecursive-watcher-node')

// Private: Map userland filesystem watcher subscriptions efficiently to deliver filesystem change notifications to
// each watcher with the most efficient coverage of native watchers.
//
// * If two watchers subscribe to the same directory, use a single native watcher for each.
// * Re-use a native watcher watching a parent directory for a watcher on a child directory. If the parent directory
//   watcher is removed, it will be split into child watchers.
// * If any child directories already being watched, stop and replace them with a watcher on the parent directory.
//
// Uses a trie whose structure mirrors the directory structure.
class Tree {
  // Private: Construct a tree with no native watchers.
  //
  // * `basePathSegments` the position of this tree's root relative to the filesystem's root as an {Array} of directory
  //   names.
  // * `createNative` {Function} used to construct new native watchers. It should accept an absolute path and an options
  //   {Object} as arguments and return a new {NativeWatcher}.
  constructor (basePathSegments, createNative) {
    this.basePathSegments = basePathSegments
    this.root = new DirectoryNode()
    this.createNative = createNative
  }

  // Private: Identify the native watcher that should be used to produce events at a watched path, creating a new one
  // if necessary.
  //
  // * `pathSegments` the path to watch represented as an {Array} of directory names relative to this {Tree}'s
  //   root.
  // * `options` {Object} Options used to create a new {NativeWatcher}. This may also affect the kind of node inserted
  //   into the {Tree} and the way that existing and future nodes will be consolidated with it.
  // * `attachToNative` {Function} invoked with the appropriate native watcher and the absolute path to its watch root.
  add (pathSegments, options, attachToNative) {
    const absolutePathSegments = this.basePathSegments.concat(pathSegments)
    const absolutePath = absolute(...absolutePathSegments)

    const attachToNew = (children, immediate) => {
      const native = this.createNative(absolutePath, options)
      const node = options.recursive
        ? new RecursiveWatcherNode(native, absolutePathSegments, children)
        : new NonrecursiveWatcherNode(native, absolutePathSegments, immediate)
      this.root = this.root.insert(pathSegments, node)

      const sub = native.onWillStop(split => {
        sub.dispose()
        const createNative = split ? this.createNative : null
        this.root = this.root.remove(pathSegments, createNative) || new DirectoryNode()
      })

      attachToNative(native, absolutePath, options)
      return native
    }

    this.root.lookup(pathSegments).when({
      parent: (parent, remaining) => {
        // An existing NativeWatcher is watching the same directory or a parent directory of the requested path.
        const existingNative = parent.getNativeWatcher()
        const absoluteParentPath = absolute(...parent.getAbsolutePathSegments())

        if (parent.isCompatible(options)) {
          // Attach this Watcher to it as a filtering watcher and record it as a dependent child path.
          parent.addChildPath(remaining, options)
          attachToNative(existingNative, absoluteParentPath, parent.getOptions())
        } else {
          // Construct and attach a new {NativeWatcher} that will deliver events suitable for both the old and
          // new watchers. Reattach consumers of the existing {NativeWatcher} and stop it.
          const newNative = attachToNew([], {})
          existingNative.reattachTo(newNative, absoluteParentPath, options)
          existingNative.stop()
        }
      },
      children: (children, immediate) => {
        // One or more NativeWatchers exist on child directories of the requested path. Create a new native watcher
        // on the parent directory, note the subscribed child paths, and cleanly stop the child native watchers.
        const newNative = attachToNew(children, immediate)

        for (let i = 0; i < children.length; i++) {
          const childNode = children[i].node
          const childNative = childNode.getNativeWatcher()
          childNative.reattachTo(newNative, absolutePath, options)
          childNative.stop()
        }
      },
      missing: () => attachToNew([], {})
    })
  }

  // Private: Access the root node of the tree.
  getRoot () {
    return this.root
  }

  // Private: Return a {String} representation of this tree's structure for diagnostics and testing.
  print () {
    return this.root.print()
  }
}

module.exports = {Tree}
<|endoftext|>


<commit_before>var _ = require('lodash');
var inflection = require('inflection');

module.exports = function (app, options) {
  //get remote methods.
  //set strong-remoting for more information
  //https://github.com/strongloop/strong-remoting
  var remotes = app.remotes();

  remotes.before('**', function (ctx, next) {

    var id = ctx.req.params.id;
    var data = ctx.args.data;

    relationships(id, data, app);

    next();
  });

  // for create
  remotes.after('**', function (ctx, next) {

    if (!ctx.result) {
      next();
    };
    var id = ctx.result.id;
    var data = ctx.req.body;

    relationships(id, data, app);

    next();
  });

};

function relationships (id, data, app) {

  if (!data || !data.data || !data.data.type || !id) {
    return;
  }

  var modelName = inflection.classify(data.data.type);

  var model = app.models[modelName];
  if (!model || !data.relationships) {
    return;
  }

  _.each(data.relationships, function (relationship, name) {

    var serverRelation = model.relations[name];
    var modelTo = serverRelation.modelTo;
    if (!modelTo) {
      return false;
    }
    var fk = serverRelation.keyTo;

    var setTo = {};
    setTo[fk] = null;
    var where = {};
    where[fk] = id;

    // remove all relations to the model (eg .: post/1)
    modelTo.updateAll(where, setTo, function (err, info) {
      if (err) {console.log(err);}
    });

    if (_.isArray(relationship.data)) {
      // find all instance from the relation data eg
      // [{type: "comments", id: 1}, {type: "comments", id: 2}]
      _.each(relationship.data, function (item) {
        createOrUpdateRelation(modelTo, item.id, where);
      });
    } else {
      // relationship: {data: {type: "comments": id: 1}}
      createOrUpdateRelation(modelTo, relationship.data.id, where);
    }

  });

  return;
}

// if the instance exist, then update it (create relationship),
// if not, then create one with the relation to the model
function createOrUpdateRelation (model, id, data) {
  model.findById(id, function (err, instance) {
    if (err) {console.log(err);}
    // instance does not exists yet, let's creat it
    if (instance === null || instance === undefined) {
      model.create(data);
    } else {
      instance.updateAttributes(data);
    }
  });
}
<commit_msg>fixes express' Error: Can't set headers after they are sent.<commit_after>var _ = require('lodash');
var inflection = require('inflection');

module.exports = function (app, options) {
  //get remote methods.
  //set strong-remoting for more information
  //https://github.com/strongloop/strong-remoting
  var remotes = app.remotes();

  remotes.before('**', function (ctx, next) {

    var id = ctx.req.params.id;
    var data = ctx.args.data;

    relationships(id, data, app);

    next();
  });

  // for create
  remotes.after('**', function (ctx, next) {

    if (ctx.result) {
      var id = ctx.result.id;
      var data = ctx.req.body;

      relationships(id, data, app);
    };

    next();
  });

};

function relationships (id, data, app) {

  if (!data || !data.data || !data.data.type || !id) {
    return;
  }

  var modelName = inflection.classify(data.data.type);

  var model = app.models[modelName];
  if (!model || !data.relationships) {
    return;
  }

  _.each(data.relationships, function (relationship, name) {

    var serverRelation = model.relations[name];
    var modelTo = serverRelation.modelTo;
    if (!modelTo) {
      return false;
    }
    var fk = serverRelation.keyTo;

    var setTo = {};
    setTo[fk] = null;
    var where = {};
    where[fk] = id;

    // remove all relations to the model (eg .: post/1)
    modelTo.updateAll(where, setTo, function (err, info) {
      if (err) {console.log(err);}
    });

    if (_.isArray(relationship.data)) {
      // find all instance from the relation data eg
      // [{type: "comments", id: 1}, {type: "comments", id: 2}]
      _.each(relationship.data, function (item) {
        createOrUpdateRelation(modelTo, item.id, where);
      });
    } else {
      // relationship: {data: {type: "comments": id: 1}}
      createOrUpdateRelation(modelTo, relationship.data.id, where);
    }

  });

  return;
}

// if the instance exist, then update it (create relationship),
// if not, then create one with the relation to the model
function createOrUpdateRelation (model, id, data) {
  model.findById(id, function (err, instance) {
    if (err) {console.log(err);}
    // instance does not exists yet, let's creat it
    if (instance === null || instance === undefined) {
      model.create(data);
    } else {
      instance.updateAttributes(data);
    }
  });
}
<|endoftext|>


<commit_before>/*!
 * OS.js - JavaScript Operating System
 *
 * Copyright (c) 2011-2014, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(CoreWM, Panel, PanelItem) {
  'use strict';

  /////////////////////////////////////////////////////////////////////////////
  // LOCALES
  /////////////////////////////////////////////////////////////////////////////

  var _Locales = {
    no_NO : {
      "Logging out user '{0}'.\nDo you want to save current session?" : "Logger ut bruker '{0}'.\nVil du lagre gjeldende sessjon?"
    },
    de_DE : {
      "Logging out user '{0}'.\nDo you want to save current session?" : "Benutzer wird abgemeldet '{0}'.\nWollen Sie die aktuelle Sitzung speichern?"
    }
  };

  function _() {
    var args = Array.prototype.slice.call(arguments, 0);
    args.unshift(_Locales);
    return OSjs.__.apply(this, args);
  }

  /////////////////////////////////////////////////////////////////////////////
  // ITEM
  /////////////////////////////////////////////////////////////////////////////

  /**
   * PanelItem: Buttons
   */
  var PanelItemButtons = function() {
    PanelItem.apply(this, ['PanelItemButtons PanelItemFill']);

    this.$container = null;
  };

  PanelItemButtons.prototype = Object.create(PanelItem.prototype);
  PanelItemButtons.Name = 'Buttons'; // Static name
  PanelItemButtons.Description = 'Button Bar'; // Static description
  PanelItemButtons.Icon = 'actions/stock_about.png'; // Static icon

  PanelItemButtons.prototype.init = function() {
    var root = PanelItem.prototype.init.apply(this, arguments);

    this.$container = document.createElement('ul');
    root.appendChild(this.$container);

    this.addButton(OSjs._('Applications'), 'categories/applications-other.png', function(ev) {
      ev.stopPropagation();
      var wm = OSjs.API.getWMInstance();
      if ( wm && wm.getSetting('menuCategories') ) {
        OSjs.Applications.CoreWM.BuildCategoryMenu(ev);
      } else {
        OSjs.Applications.CoreWM.BuildMenu(ev);
      }
      return false;
    });

    this.addButton(OSjs._('Settings'), 'categories/applications-system.png', function(ev) {
      var wm = OSjs.API.getWMInstance();
      if ( wm ) {
        wm.showSettings();
      }
      return false;
    });

    this.addButton(OSjs._('Log out (Exit)'), 'actions/exit.png', function(ev) {
      var user = OSjs.API.getHandlerInstance().getUserData() || {name: 'Unknown'};
      var wm = OSjs.API.getWMInstance();
      if ( wm ) {
        var conf = new OSjs.Dialogs.Confirm(_("Logging out user '{0}'.\nDo you want to save current session?", user.name), function(btn) {
          if ( btn == 'ok' ) {
            OSjs.Shutdown(true, false);
          } else if ( btn == 'cancel' ) {
            OSjs.Shutdown(false, false);
          }
        }, {title: OSjs._('Log out (Exit)'), buttonClose: true, buttonCloseLabel: OSjs._('Cancel'), buttonOkLabel: OSjs._('Yes'), buttonCancelLabel: OSjs._('No')});
        wm.addWindow(conf);
      }
    });

    return root;
  };

  PanelItemButtons.prototype.destroy = function() {
    PanelItem.prototype.destroy.apply(this, arguments);
  };

  PanelItemButtons.prototype.addButton = function(title, icon, callback) {
    icon = OSjs.API.getThemeResource(icon, 'icon');

    var sel = document.createElement('li');
    sel.className = 'Button';
    sel.title = title;
    sel.innerHTML = '';
    sel.onclick = callback;
    sel.oncontextmenu = function(ev) {
      ev.stopPropagation();
      return false;
    };

    this.$container.appendChild(sel);
  };

  /////////////////////////////////////////////////////////////////////////////
  // ITEM
  /////////////////////////////////////////////////////////////////////////////

  /**
   * PanelItem: WindowList
   */
  var PanelItemWindowList = function() {
    PanelItem.apply(this, ['PanelItemWindowList PanelItemWide']);

    this.$element = null;
  };

  PanelItemWindowList.prototype = Object.create(PanelItem.prototype);
  PanelItemWindowList.Name = 'Window List'; // Static name
  PanelItemWindowList.Description = 'Toggle between open windows'; // Static description
  PanelItemWindowList.Icon = 'apps/xfwm4.png'; // Static icon

  PanelItemWindowList.prototype.init = function() {
    var root = PanelItem.prototype.init.apply(this, arguments);

    this.$element = document.createElement('ul');
    root.appendChild(this.$element);

    var wm = OSjs.API.getWMInstance();
    if ( wm ) {
      var wins = wm.getWindows();
      for ( var i = 0; i < wins.length; i++ ) {
        if ( wins[i] ) {
          this.update('create', wins[i]);
        }
      }
    }

    return root;
  };

  PanelItemWindowList.prototype.destroy = function() {
    PanelItem.prototype.destroy.apply(this, arguments);
  };

  PanelItemWindowList.prototype.update = function(ev, win) {
    var self = this;
    if ( !this.$element || (win && win._properties.allow_windowlist === false) ) {
      return;
    }

    var cn = 'WindowList_Window_' + win._wid;
    var _change = function(cn, callback) {
      var els = self.$element.getElementsByClassName(cn);
      if ( els.length ) {
        for ( var i = 0, l = els.length; i < l; i++ ) {
          if ( els[i] && els[i].parentNode ) {
            callback(els[i]);
          }
        }
      }
    };

    if ( ev == 'create' ) {
      var className = className = 'Button WindowList_Window_' + win._wid;
      if ( this.$element.getElementsByClassName(className).length ) { return; }

      var el = document.createElement('li');
      el.innerHTML = '' + win._title + '';
      el.className = className;
      el.title = win._title;
      el.onclick = function() {
        win._restore(false, true);
      };
      el.oncontextmenu = function(ev) {
        ev.stopPropagation();
        return false;
      };

      if ( win._state.focused ) {
        el.className += ' Focused';
      }
      this.$element.appendChild(el);
    } else if ( ev == 'close' ) {
      _change(cn, function(el) {
        el.onclick = null;
        el.oncontextmenu = null;
        el.parentNode.removeChild(el);
      });
    } else if ( ev == 'focus' ) {
      _change(cn, function(el) {
        el.className += ' Focused';
      });
    } else if ( ev == 'blur' ) {
      _change(cn, function(el) {
        el.className = el.className.replace(/\s?Focused/, '');
      });
    } else if ( ev == 'title' ) {
      _change(cn, function(el) {
        el.getElementsByTagName('span')[0].innerHTML = win._title;
        el.title = win._title;
      });
    } else if ( ev == 'icon' ) {
      _change(cn, function(el) {
        el.getElementsByTagName('img')[0].src = win._icon;
      });
    } else if ( ev == 'attention_on' ) {
      _change(cn, function(el) {
        if ( !el.className.match(/Attention/) ) {
          el.className += ' Attention';
        }
      });
    } else if ( ev == 'attention_off' ) {
      _change(cn, function(el) {
        if ( !el.className.match(/Attention/) ) {
          el.className = el.className.replace(/\s?Attention/, '');
        }
      });
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // ITEM
  /////////////////////////////////////////////////////////////////////////////

  /**
   * PanelItem: Clock
   */
  var PanelItemClock = function() {
    PanelItem.apply(this, ['PanelItemClock PanelItemFill PanelItemRight']);
    this.clockInterval  = null;
  };

  PanelItemClock.prototype = Object.create(PanelItem.prototype);
  PanelItemClock.Name = 'Clock'; // Static name
  PanelItemClock.Description = 'View the time'; // Static description
  PanelItemClock.Icon = 'status/appointment-soon.png'; // Static icon

  PanelItemClock.prototype.init = function() {
    var root = PanelItem.prototype.init.apply(this, arguments);

    var clock = document.createElement('div');
    clock.innerHTML = '00:00:00';
    clock.oncontextmenu = function(ev) {
      ev.stopPropagation();
      return false;
    };
    var _updateClock = function() {
      var d = new Date();
      var t = ([
        (d.getHours() < 10 ? ("0" + d.getHours()) : d.getHours()),
        (d.getMinutes() < 10 ? ("0" + d.getMinutes()) : d.getMinutes()),
        (d.getSeconds() < 10 ? ("0" + d.getSeconds()) : d.getSeconds())
      ]).join(":");

      clock.innerHTML = t;
      clock.title     = t;
    };
    this.clockInterval = setInterval(_updateClock, 1000);
    _updateClock();

    root.appendChild(clock);

    return root;
  };

  PanelItemClock.prototype.destroy = function() {
    if ( this.clockInterval ) {
      clearInterval(this.clockInterval);
      this.clockInterval = null;
    }

    PanelItem.prototype.destroy.apply(this, arguments);
  };

  /////////////////////////////////////////////////////////////////////////////
  // ITEM
  /////////////////////////////////////////////////////////////////////////////

  var NotificationAreaItem = function(name, opts) {
    opts = opts || {};

    this.name           = name;
    this.opts           = opts;
    this.$container     = document.createElement("div");
    this.$inner         = document.createElement("div");
    this.onCreated      = opts.onCreated     || function() {};
    this.onInited       = opts.onInited      || function() {};
    this.onDestroy      = opts.onDestroy     || function() {};
    this.onClick        = opts.onClick       || function() {};
    this.onContextMenu  = opts.onContextMenu || function() {};

    var classNames = ["NotificationArea", "NotificationArea_" + name];
    if ( opts.className ) {
      classNames.push(opts.className);
    }

    this.$container.className = classNames.join(" ");
    if ( this.opts.tooltip ) {
      this.$container.title = this.opts.tooltip;
    }

    var self = this;
    this.$inner.addEventListener("click", function(ev) {
      self.onClick.apply(self, arguments);
    });
    this.$inner.addEventListener("contextmenu", function(ev) {
      ev.preventDefault();
      self.onContextMenu.apply(self, arguments);
    });

    this.$container.appendChild(this.$inner);

    this.onCreated.call(this);
  };

  NotificationAreaItem.prototype.init = function(root) {
    root.appendChild(this.$container);

    this.onInited.call(this, this.$container);
  };

  NotificationAreaItem.prototype.destroy = function() {
    this.onDestroy.call(this);

    if ( this.$container ) {
      if ( this.$container.parentNode ) {
        this.$container.parentNode.removeChild(this.$container);
      }
      this.$container = null;
    }
    this.$inner = null;
  };

  /**
   * PanelItem: NotificationArea
   */
  var PanelItemNotificationArea = function() {
    PanelItem.apply(this, ['PanelItemNotificationArea PanelItemFill PanelItemRight']);
    this.notifications = {};
  };

  PanelItemNotificationArea.prototype = Object.create(PanelItem.prototype);
  PanelItemNotificationArea.Name = 'NotificationArea'; // Static name
  PanelItemNotificationArea.Description = 'View notifications'; // Static description
  PanelItemNotificationArea.Icon = 'status/important.png'; // Static icon

  PanelItemNotificationArea.prototype.init = function() {
    var root = PanelItem.prototype.init.apply(this, arguments);

    return root;
  };

  PanelItemNotificationArea.prototype.createNotification = function(name, opts) {
    if ( this._$root ) {
      if ( !this.notifications[name] ) {
        var item = new NotificationAreaItem(name, opts);
        item.init(this._$root);
        this.notifications[name] = item;

        return item;
      }
    }
    return null;
  };

  PanelItemNotificationArea.prototype.removeNotification = function(name) {
    if ( this._$root ) {
      if ( this.notifications[name] ) {
        this.notifications[name].destroy();
        delete this.notifications[name];
        return true;
      }
    }

    return false;
  };

  PanelItemNotificationArea.prototype.destroy = function() {
    for ( var i in this.notifications ) {
      if ( this.notifications.hasOwnProperty(i) ) {
        if ( this.notifications[i] ) {
          this.notifications[i].destroy();
        }
        delete this.notifications[i];
      }
    }

    PanelItem.prototype.destroy.apply(this, arguments);
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  OSjs.Applications                                    = OSjs.Applications || {};
  OSjs.Applications.CoreWM                             = OSjs.Applications.CoreWM || {};
  OSjs.Applications.CoreWM.PanelItems                  = OSjs.Applications.CoreWM.PanelItems || {};
  OSjs.Applications.CoreWM.PanelItems.Buttons          = PanelItemButtons;
  OSjs.Applications.CoreWM.PanelItems.WindowList       = PanelItemWindowList;
  OSjs.Applications.CoreWM.PanelItems.Clock            = PanelItemClock;
  OSjs.Applications.CoreWM.PanelItems.NotificationArea = PanelItemNotificationArea;

})(OSjs.Applications.CoreWM.Class, OSjs.Applications.CoreWM.Panel, OSjs.Applications.CoreWM.PanelItem);

<commit_msg>CoreWM: Small fix for event bubbling in panel notification items<commit_after>/*!
 * OS.js - JavaScript Operating System
 *
 * Copyright (c) 2011-2014, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(CoreWM, Panel, PanelItem) {
  'use strict';

  /////////////////////////////////////////////////////////////////////////////
  // LOCALES
  /////////////////////////////////////////////////////////////////////////////

  var _Locales = {
    no_NO : {
      "Logging out user '{0}'.\nDo you want to save current session?" : "Logger ut bruker '{0}'.\nVil du lagre gjeldende sessjon?"
    },
    de_DE : {
      "Logging out user '{0}'.\nDo you want to save current session?" : "Benutzer wird abgemeldet '{0}'.\nWollen Sie die aktuelle Sitzung speichern?"
    }
  };

  function _() {
    var args = Array.prototype.slice.call(arguments, 0);
    args.unshift(_Locales);
    return OSjs.__.apply(this, args);
  }

  /////////////////////////////////////////////////////////////////////////////
  // ITEM
  /////////////////////////////////////////////////////////////////////////////

  /**
   * PanelItem: Buttons
   */
  var PanelItemButtons = function() {
    PanelItem.apply(this, ['PanelItemButtons PanelItemFill']);

    this.$container = null;
  };

  PanelItemButtons.prototype = Object.create(PanelItem.prototype);
  PanelItemButtons.Name = 'Buttons'; // Static name
  PanelItemButtons.Description = 'Button Bar'; // Static description
  PanelItemButtons.Icon = 'actions/stock_about.png'; // Static icon

  PanelItemButtons.prototype.init = function() {
    var root = PanelItem.prototype.init.apply(this, arguments);

    this.$container = document.createElement('ul');
    root.appendChild(this.$container);

    this.addButton(OSjs._('Applications'), 'categories/applications-other.png', function(ev) {
      ev.stopPropagation();
      var wm = OSjs.API.getWMInstance();
      if ( wm && wm.getSetting('menuCategories') ) {
        OSjs.Applications.CoreWM.BuildCategoryMenu(ev);
      } else {
        OSjs.Applications.CoreWM.BuildMenu(ev);
      }
      return false;
    });

    this.addButton(OSjs._('Settings'), 'categories/applications-system.png', function(ev) {
      var wm = OSjs.API.getWMInstance();
      if ( wm ) {
        wm.showSettings();
      }
      return false;
    });

    this.addButton(OSjs._('Log out (Exit)'), 'actions/exit.png', function(ev) {
      var user = OSjs.API.getHandlerInstance().getUserData() || {name: 'Unknown'};
      var wm = OSjs.API.getWMInstance();
      if ( wm ) {
        var conf = new OSjs.Dialogs.Confirm(_("Logging out user '{0}'.\nDo you want to save current session?", user.name), function(btn) {
          if ( btn == 'ok' ) {
            OSjs.Shutdown(true, false);
          } else if ( btn == 'cancel' ) {
            OSjs.Shutdown(false, false);
          }
        }, {title: OSjs._('Log out (Exit)'), buttonClose: true, buttonCloseLabel: OSjs._('Cancel'), buttonOkLabel: OSjs._('Yes'), buttonCancelLabel: OSjs._('No')});
        wm.addWindow(conf);
      }
    });

    return root;
  };

  PanelItemButtons.prototype.destroy = function() {
    PanelItem.prototype.destroy.apply(this, arguments);
  };

  PanelItemButtons.prototype.addButton = function(title, icon, callback) {
    icon = OSjs.API.getThemeResource(icon, 'icon');

    var sel = document.createElement('li');
    sel.className = 'Button';
    sel.title = title;
    sel.innerHTML = '';
    sel.onclick = callback;
    sel.oncontextmenu = function(ev) {
      ev.stopPropagation();
      return false;
    };

    this.$container.appendChild(sel);
  };

  /////////////////////////////////////////////////////////////////////////////
  // ITEM
  /////////////////////////////////////////////////////////////////////////////

  /**
   * PanelItem: WindowList
   */
  var PanelItemWindowList = function() {
    PanelItem.apply(this, ['PanelItemWindowList PanelItemWide']);

    this.$element = null;
  };

  PanelItemWindowList.prototype = Object.create(PanelItem.prototype);
  PanelItemWindowList.Name = 'Window List'; // Static name
  PanelItemWindowList.Description = 'Toggle between open windows'; // Static description
  PanelItemWindowList.Icon = 'apps/xfwm4.png'; // Static icon

  PanelItemWindowList.prototype.init = function() {
    var root = PanelItem.prototype.init.apply(this, arguments);

    this.$element = document.createElement('ul');
    root.appendChild(this.$element);

    var wm = OSjs.API.getWMInstance();
    if ( wm ) {
      var wins = wm.getWindows();
      for ( var i = 0; i < wins.length; i++ ) {
        if ( wins[i] ) {
          this.update('create', wins[i]);
        }
      }
    }

    return root;
  };

  PanelItemWindowList.prototype.destroy = function() {
    PanelItem.prototype.destroy.apply(this, arguments);
  };

  PanelItemWindowList.prototype.update = function(ev, win) {
    var self = this;
    if ( !this.$element || (win && win._properties.allow_windowlist === false) ) {
      return;
    }

    var cn = 'WindowList_Window_' + win._wid;
    var _change = function(cn, callback) {
      var els = self.$element.getElementsByClassName(cn);
      if ( els.length ) {
        for ( var i = 0, l = els.length; i < l; i++ ) {
          if ( els[i] && els[i].parentNode ) {
            callback(els[i]);
          }
        }
      }
    };

    if ( ev == 'create' ) {
      var className = className = 'Button WindowList_Window_' + win._wid;
      if ( this.$element.getElementsByClassName(className).length ) { return; }

      var el = document.createElement('li');
      el.innerHTML = '' + win._title + '';
      el.className = className;
      el.title = win._title;
      el.onclick = function() {
        win._restore(false, true);
      };
      el.oncontextmenu = function(ev) {
        ev.stopPropagation();
        return false;
      };

      if ( win._state.focused ) {
        el.className += ' Focused';
      }
      this.$element.appendChild(el);
    } else if ( ev == 'close' ) {
      _change(cn, function(el) {
        el.onclick = null;
        el.oncontextmenu = null;
        el.parentNode.removeChild(el);
      });
    } else if ( ev == 'focus' ) {
      _change(cn, function(el) {
        el.className += ' Focused';
      });
    } else if ( ev == 'blur' ) {
      _change(cn, function(el) {
        el.className = el.className.replace(/\s?Focused/, '');
      });
    } else if ( ev == 'title' ) {
      _change(cn, function(el) {
        el.getElementsByTagName('span')[0].innerHTML = win._title;
        el.title = win._title;
      });
    } else if ( ev == 'icon' ) {
      _change(cn, function(el) {
        el.getElementsByTagName('img')[0].src = win._icon;
      });
    } else if ( ev == 'attention_on' ) {
      _change(cn, function(el) {
        if ( !el.className.match(/Attention/) ) {
          el.className += ' Attention';
        }
      });
    } else if ( ev == 'attention_off' ) {
      _change(cn, function(el) {
        if ( !el.className.match(/Attention/) ) {
          el.className = el.className.replace(/\s?Attention/, '');
        }
      });
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // ITEM
  /////////////////////////////////////////////////////////////////////////////

  /**
   * PanelItem: Clock
   */
  var PanelItemClock = function() {
    PanelItem.apply(this, ['PanelItemClock PanelItemFill PanelItemRight']);
    this.clockInterval  = null;
  };

  PanelItemClock.prototype = Object.create(PanelItem.prototype);
  PanelItemClock.Name = 'Clock'; // Static name
  PanelItemClock.Description = 'View the time'; // Static description
  PanelItemClock.Icon = 'status/appointment-soon.png'; // Static icon

  PanelItemClock.prototype.init = function() {
    var root = PanelItem.prototype.init.apply(this, arguments);

    var clock = document.createElement('div');
    clock.innerHTML = '00:00:00';
    clock.oncontextmenu = function(ev) {
      ev.stopPropagation();
      return false;
    };
    var _updateClock = function() {
      var d = new Date();
      var t = ([
        (d.getHours() < 10 ? ("0" + d.getHours()) : d.getHours()),
        (d.getMinutes() < 10 ? ("0" + d.getMinutes()) : d.getMinutes()),
        (d.getSeconds() < 10 ? ("0" + d.getSeconds()) : d.getSeconds())
      ]).join(":");

      clock.innerHTML = t;
      clock.title     = t;
    };
    this.clockInterval = setInterval(_updateClock, 1000);
    _updateClock();

    root.appendChild(clock);

    return root;
  };

  PanelItemClock.prototype.destroy = function() {
    if ( this.clockInterval ) {
      clearInterval(this.clockInterval);
      this.clockInterval = null;
    }

    PanelItem.prototype.destroy.apply(this, arguments);
  };

  /////////////////////////////////////////////////////////////////////////////
  // ITEM
  /////////////////////////////////////////////////////////////////////////////

  var NotificationAreaItem = function(name, opts) {
    opts = opts || {};

    this.name           = name;
    this.opts           = opts;
    this.$container     = document.createElement("div");
    this.$inner         = document.createElement("div");
    this.onCreated      = opts.onCreated     || function() {};
    this.onInited       = opts.onInited      || function() {};
    this.onDestroy      = opts.onDestroy     || function() {};
    this.onClick        = opts.onClick       || function() {};
    this.onContextMenu  = opts.onContextMenu || function() {};

    var classNames = ["NotificationArea", "NotificationArea_" + name];
    if ( opts.className ) {
      classNames.push(opts.className);
    }

    this.$container.className = classNames.join(" ");
    if ( this.opts.tooltip ) {
      this.$container.title = this.opts.tooltip;
    }

    var self = this;
    this.$inner.addEventListener("click", function(ev) {
      self.onClick.apply(self, arguments);
    });
    this.$inner.addEventListener("contextmenu", function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      self.onContextMenu.apply(self, arguments);
      return false;
    });

    this.$container.appendChild(this.$inner);

    this.onCreated.call(this);
  };

  NotificationAreaItem.prototype.init = function(root) {
    root.appendChild(this.$container);

    this.onInited.call(this, this.$container);
  };

  NotificationAreaItem.prototype.destroy = function() {
    this.onDestroy.call(this);

    if ( this.$container ) {
      if ( this.$container.parentNode ) {
        this.$container.parentNode.removeChild(this.$container);
      }
      this.$container = null;
    }
    this.$inner = null;
  };

  /**
   * PanelItem: NotificationArea
   */
  var PanelItemNotificationArea = function() {
    PanelItem.apply(this, ['PanelItemNotificationArea PanelItemFill PanelItemRight']);
    this.notifications = {};
  };

  PanelItemNotificationArea.prototype = Object.create(PanelItem.prototype);
  PanelItemNotificationArea.Name = 'NotificationArea'; // Static name
  PanelItemNotificationArea.Description = 'View notifications'; // Static description
  PanelItemNotificationArea.Icon = 'status/important.png'; // Static icon

  PanelItemNotificationArea.prototype.init = function() {
    var root = PanelItem.prototype.init.apply(this, arguments);

    return root;
  };

  PanelItemNotificationArea.prototype.createNotification = function(name, opts) {
    if ( this._$root ) {
      if ( !this.notifications[name] ) {
        var item = new NotificationAreaItem(name, opts);
        item.init(this._$root);
        this.notifications[name] = item;

        return item;
      }
    }
    return null;
  };

  PanelItemNotificationArea.prototype.removeNotification = function(name) {
    if ( this._$root ) {
      if ( this.notifications[name] ) {
        this.notifications[name].destroy();
        delete this.notifications[name];
        return true;
      }
    }

    return false;
  };

  PanelItemNotificationArea.prototype.destroy = function() {
    for ( var i in this.notifications ) {
      if ( this.notifications.hasOwnProperty(i) ) {
        if ( this.notifications[i] ) {
          this.notifications[i].destroy();
        }
        delete this.notifications[i];
      }
    }

    PanelItem.prototype.destroy.apply(this, arguments);
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  OSjs.Applications                                    = OSjs.Applications || {};
  OSjs.Applications.CoreWM                             = OSjs.Applications.CoreWM || {};
  OSjs.Applications.CoreWM.PanelItems                  = OSjs.Applications.CoreWM.PanelItems || {};
  OSjs.Applications.CoreWM.PanelItems.Buttons          = PanelItemButtons;
  OSjs.Applications.CoreWM.PanelItems.WindowList       = PanelItemWindowList;
  OSjs.Applications.CoreWM.PanelItems.Clock            = PanelItemClock;
  OSjs.Applications.CoreWM.PanelItems.NotificationArea = PanelItemNotificationArea;

})(OSjs.Applications.CoreWM.Class, OSjs.Applications.CoreWM.Panel, OSjs.Applications.CoreWM.PanelItem);

<|endoftext|>


<commit_before>(function(){
	"use strict";

	KC3StrategyTabs.mstship = new KC3StrategyTab("mstship");

	KC3StrategyTabs.mstship.definition = {
		tabSelf: KC3StrategyTabs.mstship,

		lines: {
			"Intro" : 1,
			"Library" : 25,
			"Poke(1)" : 2,
			"Poke(2)" : 3,
			"Poke(3)" : 4,
			"Married" : 28,
			"Wedding" : 24,
			"Ranking" : 8,
			"Join" : 13,
			"Equip(1)" : 9,
			"Equip(2)" : 10,
			"Equip(3)" : 26,
			"Supply" : 27,
			"Docking(1)" : 11,
			"Docking(2)" : 12,
			"Construction" : 5,
			"Return" : 7,
			"Sortie" : 14,
			"Battle" : 15,
			"Attack" : 16,
			"Yasen(1)" : 18,
			"Yasen(2)" : 17,
			"MVP" : 23,
			"Damaged(1)" : 19,
			"Damaged(2)" : 20,
			"Damaged(3)" : 21,
			"Sunk" : 22,
			"Idle" : 29,
			"Repair" : 6
		},
		hourlies: {
			30: "0000",
			31: "0100",
			32: "0200",
			33: "0300",
			34: "0400",
			35: "0500",
			36: "0600",
			37: "0700",
			38: "0800",
			39: "0900",
			40: "1000",
			41: "1100",
			42: "1200",
			43: "1300",
			44: "1400",
			45: "1500",
			46: "1600",
			47: "1700",
			48: "1800",
			49: "1900",
			50: "2000",
			51: "2100",
			52: "2200",
			53: "2300"
		},

		currentGraph: "",
		audio: false,
		server_ip: "",

		/* INIT
		Prepares all data needed
		---------------------------------*/
		init :function(){
			var MyServer = (new KC3Server()).setNum( PlayerManager.hq.server );
			this.server_ip = MyServer.ip;
		},

		/* EXECUTE
		Places data onto the interface
		---------------------------------*/
		execute :function(){
			$(".tab_mstship .runtime_id").text(chrome.runtime.id);

			var self = this;

			/*
			// Add ship type filters
			$.each(KC3Meta._stype, function(index, stype_code){
				if(index === 0) return true;

				$("<div />")
					.addClass("stype")
					.text(stype_code)
					.data("id", index)
					.appendTo(".tab_mstship .filters");
			});
			$("<div />").addClass("clear").appendTo(".tab_mstship .filters");*/

			// List all ships
			var shipBox;
			$.each(KC3Master._ship, function(index, ShipData){
				if(!ShipData) { return true; }

				shipBox = $(".tab_mstship .factory .shipRecord").clone();
				shipBox.data("id", ShipData.api_id);
				shipBox.data("bs", ShipData.kc3_bship);

				if(ShipData.api_id<=500){
					$("img", shipBox).attr("src", KC3Meta.shipIcon(ShipData.api_id) );
				}else{
					$("img", shipBox).attr("src", KC3Meta.abyssIcon(ShipData.api_id) );
				}

				if(ConfigManager.salt_list.indexOf(ShipData.kc3_bship)>=0) {
					shipBox.addClass('salted');
				}

				$(".shipName", shipBox).text( "["+ShipData.api_id+"] "+KC3Meta.shipName(ShipData.api_name) );

				shipBox.appendTo(".tab_mstship .shipRecords");
			});

			// Select ship
			$(".tab_mstship .shipRecords .shipRecord").on("click", function(){
				self.showShip( $(this).data("id") );
			});

			// Play voice
			$(".tab_mstship .shipInfo .voice").on("click", function(){
				if(self.audio){ self.audio.pause(); }
				self.audio = new Audio("http://"+self.server_ip+"/kcs/sound/kc"+self.currentGraph+"/"+$(this).data("vnum")+".mp3");
				self.audio.play();
			});

			// On-click remodels
			$(".tab_mstship .shipInfo").on("click", ".remodel_name a", function(e){
				//console.log("clicked remodel");
				self.showShip( $(this).data("sid") );
				e.preventDefault();
				return false;
			});

			// Salt-toggle
			$(".tab_mstship .shipInfo").on("click", ".salty-zone", function(e){
				var
					saltList = ConfigManager.salt_list,
					saltPos  = saltList.indexOf(shipData.kc3_bship),
					shipBox  = $(".shipRecord").filter(function(i,x){
						return shipData.kc3_bship == $(x).data('bs');
					});
				if(saltPos >= 0) {
					saltList.splice(saltPos,1);
					shipBox.removeClass('salted');
				} else {
					saltList.push(shipData.kc3_bship);
					shipBox.addClass('salted');
				}
				ConfigManager.save();
				e.preventDefault();
				self.showShip( shipData.api_id );
				return false;
			});

			if(!!KC3StrategyTabs.pageParams[1]){
				this.showShip(KC3StrategyTabs.pageParams[1]);
			}else{
				this.showShip(405);
			}
		},

		showShip :function(ship_id){
			var self = this;
			var shipData = KC3Master.ship(ship_id);
			console.log(shipData);

			$(".tab_mstship .shipInfo .name").text( KC3Meta.shipName( shipData.api_name ) );

			// CG VIEWER
			var shipFile = KC3Master.graph_id(ship_id);
			this.currentGraph = shipFile;
			$(".tab_mstship .shipInfo .cgswf embed").remove();

			$("<embed/>")
				.attr("src", "../../../../assets/swf/card.swf?sip="+this.server_ip+"&shipFile="+shipFile+"&abyss="+(ship_id>500?1:0))
				.appendTo(".tab_mstship .shipInfo .cgswf");
			$(".tab_mstship .shipInfo").off('click',".salty-zone");
			$(".tab_mstship .shipInfo").removeClass('salted');

			if(ship_id<=500){
				// Ship-only, non abyssal
				$(".tab_mstship .shipInfo .stats").html("");
				$(".tab_mstship .shipInfo .intro").html( shipData.api_getmes );

				// Check saltiness
				if(ConfigManager.salt_list.indexOf(shipData.kc3_bship)>=0) {
					$(".tab_mstship .shipInfo").addClass('salted');
				}

				// STATS
				var statBox;
				$.each([
					["hp", "taik"],
					["fp", "houg"],
					["ar", "souk"],
					["tp", "raig"],
					["rn", "leng"],
					["aa", "tyku"],
					["sp", "soku"],
					["lk", "luck"],
				], function(index, stat){
					statBox = $(".tab_mstship .factory .ship_stat").clone();
					$("img", statBox).attr("src", "../../../../assets/img/stats/"+stat[0]+".png");
					$(".ship_stat_name", statBox).text(stat[1]);

					if(stat[0]=="rn"){
						$(".ship_stat_text", statBox).text(["","Short","Medium","Long","V.Long"][shipData.api_leng]);
						$(".ship_stat_text", statBox).show();
						$(".ship_stat_value", statBox).hide();

					}else if(stat[0]=="sp"){
						$(".ship_stat_text", statBox).text({"5":"Slow","10":"Fast"}[shipData.api_soku]);
						$(".ship_stat_text", statBox).show();
						$(".ship_stat_value", statBox).hide();

					}else{
						$(".ship_stat_min", statBox).text(shipData["api_"+stat[1]][0]);
						$(".ship_stat_max span", statBox).text(shipData["api_"+stat[1]][1]);
					}
					statBox.appendTo(".tab_mstship .shipInfo .stats");
				});

				var stockEquipments = WhoCallsTheFleetDb.getStockEquipment( ship_id );

				// EQUIPMENT
				$(".tab_mstship .equipments .equipment").each(function(index){
					$(".capacity", this).text( shipData.api_maxeq[index] );
					if(index >= shipData.api_slot_num){
						$(this).hide();
					}else{
						$(this).show();
					}

					if (stockEquipments) {
						var equipId = stockEquipments[index];
						if (equipId) {
							var equipment = KC3Master.slotitem( equipId );
							$(".slotitem", this).text(KC3Meta.gearName( equipment.api_name ) );
							$(".sloticon img", this)
								.attr("src","../../../../assets/img/items/"+equipment.api_type[3]+".png");
							console.log( equipment.api_type[3] );
						}
					}
				});

				// MORE INFO
				if(shipData.api_aftershipid>0){
					$(".tab_mstship .shipInfo .remodel_name a").text( KC3Meta.shipName(KC3Master.ship(shipData.api_aftershipid).api_name) );
					$(".tab_mstship .shipInfo .remodel_name a").data("sid", shipData.api_aftershipid);
					$(".tab_mstship .shipInfo .remodel_level span").text( shipData.api_afterlv );
					$(".tab_mstship .shipInfo .remodel_ammo .rsc_value").text( shipData.api_afterfuel );
					$(".tab_mstship .shipInfo .remodel_steel .rsc_value").text( shipData.api_afterbull );
					$(".tab_mstship .shipInfo .remodel").show();
				}else{
					$(".tab_mstship .shipInfo .remodel").hide();
				}
				$(".tab_mstship .scrap .rsc").each(function(index){
					$(".rsc_value", this).text( shipData.api_broken[index] );
				});
				$(".tab_mstship .modfods .rsc").each(function(index){
					$(".rsc_value", this).text( shipData.api_powup[index] );
				});
				$(".tab_mstship .shipInfo .consume_fuel .rsc_value").text( shipData.api_fuel_max );
				$(".tab_mstship .shipInfo .consume_ammo .rsc_value").text( shipData.api_bull_max );

				// VOICE LINES
				$(".tab_mstship .shipInfo .voices").html("");
				$.each(this.lines, function(vname, vnum){
					$("<div/>")
						.addClass("hover")
						.addClass("voice")
						.data("vnum", vnum)
						.text(vname)
						.appendTo(".tab_mstship .shipInfo .voices");
				});
				$("<div/>").addClass("clear").appendTo(".tab_mstship .shipInfo .voices");

				// HOURLIES
				$(".tab_mstship .shipInfo .hourlies").html("");
				if(shipData.api_voicef>1){
					$.each(this.hourlies, function(vnum, vname){
						var hhStr = vname.substring(0,2);
						var mmStr = vname.substring(2);
						$("<div/>")
							.addClass("hover")
							.addClass("voice")
							.data("vnum", vnum)
							.text(hhStr + ":" + mmStr)
							.appendTo(".tab_mstship .shipInfo .hourlies");
					});
					$("<div/>").addClass("clear").appendTo(".tab_mstship .shipInfo .hourlies");
				}

				$(".tab_mstship .shipInfo .stats").show();
				$(".tab_mstship .shipInfo .equipments").show();
				$(".tab_mstship .shipInfo .intro").show();
				$(".tab_mstship .shipInfo .more").show();
				$(".tab_mstship .shipInfo .json").hide();
				$(".tab_mstship .shipInfo .tokubest").show();
				if(ConfigManager.info_salt)
					$(".tab_mstship .shipInfo .tokubest .salty-zone").show();
				else
					$(".tab_mstship .shipInfo .tokubest .salty-zone").hide();
			}else{
				// abyssals, just show json
				$(".tab_mstship .shipInfo .json").text(JSON.stringify(shipData));

				$(".tab_mstship .shipInfo .stats").hide();
				$(".tab_mstship .shipInfo .equipments").hide();
				$(".tab_mstship .shipInfo .intro").hide();
				$(".tab_mstship .shipInfo .more").hide();
				$(".tab_mstship .shipInfo .json").show();
				$(".tab_mstship .shipInfo .tokubest").hide();
			}

		}

	};

})();
<commit_msg>unset equipment when necessary<commit_after>(function(){
	"use strict";

	KC3StrategyTabs.mstship = new KC3StrategyTab("mstship");

	KC3StrategyTabs.mstship.definition = {
		tabSelf: KC3StrategyTabs.mstship,

		lines: {
			"Intro" : 1,
			"Library" : 25,
			"Poke(1)" : 2,
			"Poke(2)" : 3,
			"Poke(3)" : 4,
			"Married" : 28,
			"Wedding" : 24,
			"Ranking" : 8,
			"Join" : 13,
			"Equip(1)" : 9,
			"Equip(2)" : 10,
			"Equip(3)" : 26,
			"Supply" : 27,
			"Docking(1)" : 11,
			"Docking(2)" : 12,
			"Construction" : 5,
			"Return" : 7,
			"Sortie" : 14,
			"Battle" : 15,
			"Attack" : 16,
			"Yasen(1)" : 18,
			"Yasen(2)" : 17,
			"MVP" : 23,
			"Damaged(1)" : 19,
			"Damaged(2)" : 20,
			"Damaged(3)" : 21,
			"Sunk" : 22,
			"Idle" : 29,
			"Repair" : 6
		},
		hourlies: {
			30: "0000",
			31: "0100",
			32: "0200",
			33: "0300",
			34: "0400",
			35: "0500",
			36: "0600",
			37: "0700",
			38: "0800",
			39: "0900",
			40: "1000",
			41: "1100",
			42: "1200",
			43: "1300",
			44: "1400",
			45: "1500",
			46: "1600",
			47: "1700",
			48: "1800",
			49: "1900",
			50: "2000",
			51: "2100",
			52: "2200",
			53: "2300"
		},

		currentGraph: "",
		audio: false,
		server_ip: "",

		/* INIT
		Prepares all data needed
		---------------------------------*/
		init :function(){
			var MyServer = (new KC3Server()).setNum( PlayerManager.hq.server );
			this.server_ip = MyServer.ip;
		},

		/* EXECUTE
		Places data onto the interface
		---------------------------------*/
		execute :function(){
			$(".tab_mstship .runtime_id").text(chrome.runtime.id);

			var self = this;

			/*
			// Add ship type filters
			$.each(KC3Meta._stype, function(index, stype_code){
				if(index === 0) return true;

				$("<div />")
					.addClass("stype")
					.text(stype_code)
					.data("id", index)
					.appendTo(".tab_mstship .filters");
			});
			$("<div />").addClass("clear").appendTo(".tab_mstship .filters");*/

			// List all ships
			var shipBox;
			$.each(KC3Master._ship, function(index, ShipData){
				if(!ShipData) { return true; }

				shipBox = $(".tab_mstship .factory .shipRecord").clone();
				shipBox.data("id", ShipData.api_id);
				shipBox.data("bs", ShipData.kc3_bship);

				if(ShipData.api_id<=500){
					$("img", shipBox).attr("src", KC3Meta.shipIcon(ShipData.api_id) );
				}else{
					$("img", shipBox).attr("src", KC3Meta.abyssIcon(ShipData.api_id) );
				}

				if(ConfigManager.salt_list.indexOf(ShipData.kc3_bship)>=0) {
					shipBox.addClass('salted');
				}

				$(".shipName", shipBox).text( "["+ShipData.api_id+"] "+KC3Meta.shipName(ShipData.api_name) );

				shipBox.appendTo(".tab_mstship .shipRecords");
			});

			// Select ship
			$(".tab_mstship .shipRecords .shipRecord").on("click", function(){
				self.showShip( $(this).data("id") );
			});

			// Play voice
			$(".tab_mstship .shipInfo .voice").on("click", function(){
				if(self.audio){ self.audio.pause(); }
				self.audio = new Audio("http://"+self.server_ip+"/kcs/sound/kc"+self.currentGraph+"/"+$(this).data("vnum")+".mp3");
				self.audio.play();
			});

			// On-click remodels
			$(".tab_mstship .shipInfo").on("click", ".remodel_name a", function(e){
				//console.log("clicked remodel");
				self.showShip( $(this).data("sid") );
				e.preventDefault();
				return false;
			});

			// Salt-toggle
			$(".tab_mstship .shipInfo").on("click", ".salty-zone", function(e){
				var
					saltList = ConfigManager.salt_list,
					saltPos  = saltList.indexOf(shipData.kc3_bship),
					shipBox  = $(".shipRecord").filter(function(i,x){
						return shipData.kc3_bship == $(x).data('bs');
					});
				if(saltPos >= 0) {
					saltList.splice(saltPos,1);
					shipBox.removeClass('salted');
				} else {
					saltList.push(shipData.kc3_bship);
					shipBox.addClass('salted');
				}
				ConfigManager.save();
				e.preventDefault();
				self.showShip( shipData.api_id );
				return false;
			});

			if(!!KC3StrategyTabs.pageParams[1]){
				this.showShip(KC3StrategyTabs.pageParams[1]);
			}else{
				this.showShip(405);
			}
		},

		showShip :function(ship_id){
			var self = this;
			var shipData = KC3Master.ship(ship_id);
			console.log(shipData);

			$(".tab_mstship .shipInfo .name").text( KC3Meta.shipName( shipData.api_name ) );

			// CG VIEWER
			var shipFile = KC3Master.graph_id(ship_id);
			this.currentGraph = shipFile;
			$(".tab_mstship .shipInfo .cgswf embed").remove();

			$("<embed/>")
				.attr("src", "../../../../assets/swf/card.swf?sip="+this.server_ip+"&shipFile="+shipFile+"&abyss="+(ship_id>500?1:0))
				.appendTo(".tab_mstship .shipInfo .cgswf");
			$(".tab_mstship .shipInfo").off('click',".salty-zone");
			$(".tab_mstship .shipInfo").removeClass('salted');

			if(ship_id<=500){
				// Ship-only, non abyssal
				$(".tab_mstship .shipInfo .stats").html("");
				$(".tab_mstship .shipInfo .intro").html( shipData.api_getmes );

				// Check saltiness
				if(ConfigManager.salt_list.indexOf(shipData.kc3_bship)>=0) {
					$(".tab_mstship .shipInfo").addClass('salted');
				}

				// STATS
				var statBox;
				$.each([
					["hp", "taik"],
					["fp", "houg"],
					["ar", "souk"],
					["tp", "raig"],
					["rn", "leng"],
					["aa", "tyku"],
					["sp", "soku"],
					["lk", "luck"],
				], function(index, stat){
					statBox = $(".tab_mstship .factory .ship_stat").clone();
					$("img", statBox).attr("src", "../../../../assets/img/stats/"+stat[0]+".png");
					$(".ship_stat_name", statBox).text(stat[1]);

					if(stat[0]=="rn"){
						$(".ship_stat_text", statBox).text(["","Short","Medium","Long","V.Long"][shipData.api_leng]);
						$(".ship_stat_text", statBox).show();
						$(".ship_stat_value", statBox).hide();

					}else if(stat[0]=="sp"){
						$(".ship_stat_text", statBox).text({"5":"Slow","10":"Fast"}[shipData.api_soku]);
						$(".ship_stat_text", statBox).show();
						$(".ship_stat_value", statBox).hide();

					}else{
						$(".ship_stat_min", statBox).text(shipData["api_"+stat[1]][0]);
						$(".ship_stat_max span", statBox).text(shipData["api_"+stat[1]][1]);
					}
					statBox.appendTo(".tab_mstship .shipInfo .stats");
				});

				var stockEquipments = WhoCallsTheFleetDb.getStockEquipment( ship_id );

				// EQUIPMENT
				$(".tab_mstship .equipments .equipment").each(function(index){
					$(".capacity", this).text( shipData.api_maxeq[index] );
					if(index >= shipData.api_slot_num){
						$(this).hide();
					}else{
						$(this).show();
					}

					if (stockEquipments) {
						var equipId = stockEquipments[index];
						if (equipId) {
							var equipment = KC3Master.slotitem( equipId );
							$(".slotitem", this).text(KC3Meta.gearName( equipment.api_name ) );
							$(".sloticon img", this)
								.attr("src","../../../../assets/img/items/"+equipment.api_type[3]+".png");
						} else {
							$(".slotitem", this).text( "" );
							$(".sloticon img", this).attr("src","");
						}
					}
				});

				// MORE INFO
				if(shipData.api_aftershipid>0){
					$(".tab_mstship .shipInfo .remodel_name a").text( KC3Meta.shipName(KC3Master.ship(shipData.api_aftershipid).api_name) );
					$(".tab_mstship .shipInfo .remodel_name a").data("sid", shipData.api_aftershipid);
					$(".tab_mstship .shipInfo .remodel_level span").text( shipData.api_afterlv );
					$(".tab_mstship .shipInfo .remodel_ammo .rsc_value").text( shipData.api_afterfuel );
					$(".tab_mstship .shipInfo .remodel_steel .rsc_value").text( shipData.api_afterbull );
					$(".tab_mstship .shipInfo .remodel").show();
				}else{
					$(".tab_mstship .shipInfo .remodel").hide();
				}
				$(".tab_mstship .scrap .rsc").each(function(index){
					$(".rsc_value", this).text( shipData.api_broken[index] );
				});
				$(".tab_mstship .modfods .rsc").each(function(index){
					$(".rsc_value", this).text( shipData.api_powup[index] );
				});
				$(".tab_mstship .shipInfo .consume_fuel .rsc_value").text( shipData.api_fuel_max );
				$(".tab_mstship .shipInfo .consume_ammo .rsc_value").text( shipData.api_bull_max );

				// VOICE LINES
				$(".tab_mstship .shipInfo .voices").html("");
				$.each(this.lines, function(vname, vnum){
					$("<div/>")
						.addClass("hover")
						.addClass("voice")
						.data("vnum", vnum)
						.text(vname)
						.appendTo(".tab_mstship .shipInfo .voices");
				});
				$("<div/>").addClass("clear").appendTo(".tab_mstship .shipInfo .voices");

				// HOURLIES
				$(".tab_mstship .shipInfo .hourlies").html("");
				if(shipData.api_voicef>1){
					$.each(this.hourlies, function(vnum, vname){
						var hhStr = vname.substring(0,2);
						var mmStr = vname.substring(2);
						$("<div/>")
							.addClass("hover")
							.addClass("voice")
							.data("vnum", vnum)
							.text(hhStr + ":" + mmStr)
							.appendTo(".tab_mstship .shipInfo .hourlies");
					});
					$("<div/>").addClass("clear").appendTo(".tab_mstship .shipInfo .hourlies");
				}

				$(".tab_mstship .shipInfo .stats").show();
				$(".tab_mstship .shipInfo .equipments").show();
				$(".tab_mstship .shipInfo .intro").show();
				$(".tab_mstship .shipInfo .more").show();
				$(".tab_mstship .shipInfo .json").hide();
				$(".tab_mstship .shipInfo .tokubest").show();
				if(ConfigManager.info_salt)
					$(".tab_mstship .shipInfo .tokubest .salty-zone").show();
				else
					$(".tab_mstship .shipInfo .tokubest .salty-zone").hide();
			}else{
				// abyssals, just show json
				$(".tab_mstship .shipInfo .json").text(JSON.stringify(shipData));

				$(".tab_mstship .shipInfo .stats").hide();
				$(".tab_mstship .shipInfo .equipments").hide();
				$(".tab_mstship .shipInfo .intro").hide();
				$(".tab_mstship .shipInfo .more").hide();
				$(".tab_mstship .shipInfo .json").show();
				$(".tab_mstship .shipInfo .tokubest").hide();
			}

		}

	};

})();
<|endoftext|>


<commit_before>import moment from 'moment';
import _ from 'lodash';
import { logger } from './config';

class WakeLight {
  constructor() {
    this._changeObservers = [];
  }

  /**
   * Event subscriber
   * @method on
   * @param {String}    event     the name of the event
   * @param {Function}  handler   the event handler
   */
  on(event, handler) {
    switch (event) {
      case 'change':
        this._changeObservers.push(handler);
        break;
      default:
        throw Error('event not available');
    }
  }

  /**
   * Change Event emitter
   * @method handleChange
   */
  handleChange(...args) {
    this._changeObservers.forEach(handler => {
      handler(...args);
    });
  }

  /**
   * add or update wakelight alarms. calling this method
   * will restart the alarms _timer and immediately check
   * if the alarms should be active
   * @method  addAlarms
   * @param   {Object}    An object where each key contains an alarm
   */
  addAlarms(alarms) {
    logger.debug('updating alarms');
    this._alarms = alarms;
  }

  /**
   * Check if the wakelight alarm should be active
   * @method checkAlarms
   * @param   [Object]    timeRef   optional momentjs time reference to check the alarm(s) against.
   *                                If no value is provided the system time is used.
   */
  checkAlarms(time) {
    if (!this._alarms) {
      throw Error('alarms not set =/');
    }

    // get current time reference
    const now = time || moment();

    // look for an active alarm
    const activeAlarm = _.find(this._alarms, alarm => {
      const { hour: alarmHour,
        minute: alarmMinute,
        duration: alarmDuration } = alarm;

      // create time reference for the alarm enable
      const alarmEnable = moment()
        .hour(alarmHour)
        .minute(alarmMinute);

      // create time reference for the alarm disable
      const alarmDisable = moment()
        .hour(alarmHour)
        .minute(alarmMinute)
        .add(alarmDuration, 'minutes');

      return (now.isAfter(alarmEnable) && now.isBefore(alarmDisable)) === true;
    });

    if (activeAlarm) {
      this.handleChange(true, now.format('dddd hh:mm:ss a'), activeAlarm.name);
    } else {
      this.handleChange(false, null, null);
    }
  }

  /**
   * start the wakelight time poller
   * @method run
   */
  run() {
    logger.debug('running wakelight');
    if (!this._alarms) {
      logger.error('alarms not set');
      return;
    }
    this.checkAlarms();

    /*
     * set an interval to check the time and see if the alarm
     * should be activated
     */
    this._timer = setInterval(() => {
      this.checkAlarms();
    }, 60000);
  }

  /**
   * stop the wakelight time poller
   * @method stop
   */
  stop() {
    clearTimeout(this._timer);
    this._timer = null;
  }

  /**
   * Restart the wakelight time poller
   * @method restart
   */
  restart() {
    this.stop();
    this.run();
  }
}

export default WakeLight;
<commit_msg>modifying callback<commit_after>import moment from 'moment';
import _ from 'lodash';
import { logger } from './config';

class WakeLight {
  constructor() {
    this._changeObservers = [];
  }

  /**
   * Event subscriber
   * @method on
   * @param {String}    event     the name of the event
   * @param {Function}  handler   the event handler
   */
  on(event, handler) {
    switch (event) {
      case 'change':
        this._changeObservers.push(handler);
        break;
      default:
        throw Error('event not available');
    }
  }

  /**
   * Change Event emitter
   * @method handleChange
   */
  handleChange(...args) {
    this._changeObservers.forEach(handler => {
      handler(...args);
    });
  }

  /**
   * add or update wakelight alarms. calling this method
   * will restart the alarms _timer and immediately check
   * if the alarms should be active
   * @method  addAlarms
   * @param   {Object}    An object where each key contains an alarm
   */
  addAlarms(alarms) {
    logger.debug('updating alarms');
    this._alarms = alarms;
  }

  /**
   * Check if the wakelight alarm should be active
   * @method checkAlarms
   * @param   [Object]    timeRef   optional momentjs time reference to check the alarm(s) against.
   *                                If no value is provided the system time is used.
   */
  checkAlarms(time) {
    if (!this._alarms) {
      throw Error('alarms not set =/');
    }

    // get current time reference
    const now = time || moment();

    // look for an active alarm
    const enableAlarm = _.find(this._alarms, alarm => {
      const { hour: alarmHour,
        minute: alarmMinute } = alarm;

      // create time reference for the alarm enable
      const alarmEnable = moment()
        .hour(alarmHour)
        .minute(alarmMinute);

      return now.isSame(alarmEnable, 'minute');
    });

    // look for an active alarm
    const disableAlarm = _.find(this._alarms, alarm => {
      const { hour: alarmHour,
        minute: alarmMinute,
        duration: alarmDuration } = alarm;

      // create time reference for the alarm disable
      const alarmDisable = moment()
        .hour(alarmHour)
        .minute(alarmMinute)
        .add(alarmDuration, 'minutes');

      return now.isSame(alarmDisable, 'minute');
    });

    if (enableAlarm) {
      this.handleChange(true, now.format('dddd hh:mm:ss a'), enableAlarm.name);
    }

    if (disableAlarm) {
      this.handleChange(false, now.format('dddd hh:mm:ss a'), disableAlarm.name);
    }
  }

  /**
   * start the wakelight time poller
   * @method run
   */
  run() {
    logger.debug('running wakelight');
    if (!this._alarms) {
      logger.error('alarms not set');
      return;
    }
    this.checkAlarms();

    /*
     * set an interval to check the time and see if the alarm
     * should be activated
     */
    this._timer = setInterval(() => {
      this.checkAlarms();
    }, 60000);
  }

  /**
   * stop the wakelight time poller
   * @method stop
   */
  stop() {
    clearTimeout(this._timer);
    this._timer = null;
  }

  /**
   * Restart the wakelight time poller
   * @method restart
   */
  restart() {
    this.stop();
    this.run();
  }
}

export default WakeLight;
<|endoftext|>