<commit_before>/**
 * WebThings Gateway App.
 *
 * Front end main script.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
'use strict';

// eslint-disable-next-line prefer-const
let API;
// eslint-disable-next-line prefer-const
let AssistantScreen;
// eslint-disable-next-line prefer-const
let GatewayModel;
// eslint-disable-next-line prefer-const
let ThingsScreen;
// eslint-disable-next-line prefer-const
let AddThingScreen;
// eslint-disable-next-line prefer-const
let Menu;
// eslint-disable-next-line prefer-const
let ContextMenu;
// eslint-disable-next-line prefer-const
let SettingsScreen;
// eslint-disable-next-line prefer-const
let FloorplanScreen;
// eslint-disable-next-line prefer-const
let Router;
// eslint-disable-next-line prefer-const
let RulesScreen;
// eslint-disable-next-line prefer-const
let RuleScreen;
// eslint-disable-next-line prefer-const
let LogsScreen;
// eslint-disable-next-line prefer-const
let Speech;

const shaka = require('shaka-player/dist/shaka-player.compiled');
const MobileDragDrop = require('mobile-drag-drop/index.min');
const ScrollBehavior = require('mobile-drag-drop/scroll-behaviour.min');
const Notifications = require('./notifications');
const Utils = require('./utils');

const App = {
  /**
   * Current server host.
   */
  HOST: window.location.host,

  /**
   * Current server origin.
   */
  ORIGIN: window.location.origin,

  /**
   * Maximum number of allowed ping failures.
   */
  MAX_PING_FAILURES: 3,

  /**
   * Interval at which to ping to check for connectivity.
   */
  PING_INTERVAL: 20 * 1000,

  /**
   * Start WebThings Gateway app.
   */
  init: function() {
    // Load the shaka player polyfills
    shaka.polyfill.installAll();
    MobileDragDrop.polyfill({
      holdToDrag: 500,
      // eslint-disable-next-line max-len
      dragImageTranslateOverride: ScrollBehavior.scrollBehaviourDragImageTranslateOverride,
    });

    AddThingScreen.init();
    ContextMenu.init();
    AssistantScreen.init();
    ThingsScreen.init();
    SettingsScreen.init();
    FloorplanScreen.init();

    if (navigator.mediaDevices && window.MediaRecorder) {
      Speech.init();
    }

    RulesScreen.init();
    RuleScreen.init();

    LogsScreen.init();

    this.views = [];
    this.views.things = document.getElementById('things-view');
    this.views.floorplan = document.getElementById('floorplan-view');
    this.views.settings = document.getElementById('settings-view');
    this.views.rules = document.getElementById('rules-view');
    this.views.rule = document.getElementById('rule-view');
    this.views.assistant = document.getElementById('assistant-view');
    this.views.logs = document.getElementById('logs-view');
    this.currentView = 'things';
    this.menuButton = document.getElementById('menu-button');
    this.menuButton.addEventListener('click', Menu.toggle.bind(Menu));
    this.overflowButton = document.getElementById('overflow-button');
    this.overflowButton.addEventListener('click',
                                         this.toggleOverflowMenu.bind(this));
    this.overflowMenu = document.getElementById('overflow-menu');
    this.blockMessages = false;
    this.messageArea = document.getElementById('message-area');
    this.messageTimeout = null;

    this.connectivityOverlay = document.getElementById('connectivity-scrim');
    this.pingerInterval = null;
    this.pingerLastStatus = null;
    this.failedPings = 0;
    this.startPinger();

    this.gatewayModel = new GatewayModel();

    this.wsBackoff = 1000;
    this.initWebSocket();

    Menu.init();
    Router.init();
  },

  initWebSocket() {
    const path = `${this.ORIGIN.replace(/^http/, 'ws')}/internal-logs?jwt=${API.jwt}`;
    this.messageSocket = new WebSocket(path);

    this.messageSocket.addEventListener('open', () => {
      // Reset the backoff period
      this.wsBackoff = 1000;
    }, {once: true});

    const onMessage = (msg) => {
      const message = JSON.parse(msg.data);

      this.showMessage(message.message, 5000, message.url);
    };

    const cleanup = () => {
      this.messageSocket.removeEventListener('message', onMessage);
      this.messageSocket.removeEventListener('close', cleanup);
      this.messageSocket.removeEventListener('error', cleanup);
      this.messageSocket.close();
      this.messageSocket = null;

      setTimeout(() => {
        this.wsBackoff *= 2;
        if (this.wsBackoff > 30000) {
          this.wsBackoff = 30000;
        }
        this.initWebSocket();
      }, this.wsBackoff);
    };

    this.messageSocket.addEventListener('message', onMessage);
    this.messageSocket.addEventListener('close', cleanup);
    this.messageSocket.addEventListener('error', cleanup);
  },

  startPinger() {
    fetch(
      `${this.ORIGIN}/ping`,
      {
        headers: {
          Accept: 'application/json',
        },
      }
    ).then((res) => {
      if (!res.ok) {
        throw new Error(`Bad return status: ${res.status}`);
      }

      if (this.pingerLastStatus === 'offline') {
        window.location.reload();
      } else {
        this.failedPings = 0;
        this.pingerLastStatus = 'online';
        this.connectivityOverlay.classList.add('hidden');
        this.messageArea.classList.remove('disconnected');

        if (this.messageArea.innerText === 'Gateway Unreachable') {
          this.hidePersistentMessage();
        }
      }
    }).catch((e) => {
      console.error('Gateway unreachable:', e);
      if (++this.failedPings >= this.MAX_PING_FAILURES) {
        this.connectivityOverlay.classList.remove('hidden');
        this.messageArea.classList.add('disconnected');
        this.showPersistentMessage('Gateway Unreachable');
        this.pingerLastStatus = 'offline';
      }
    });

    if (!this.pingerInterval) {
      this.pingerInterval =
        setInterval(this.startPinger.bind(this), this.PING_INTERVAL);
    }
  },

  showAssistant: function() {
    AssistantScreen.show();
    this.selectView('assistant');
  },

  showThings: function(context) {
    const events = context.pathname.split('/').pop() === 'events';
    ThingsScreen.show(context.params.thingId || null,
                      context.params.actionName || null,
                      events,
                      context.querystring);
    this.selectView('things');
  },

  showSettings: function(context) {
    SettingsScreen.show(context.params.section || null,
                        context.params.subsection || null,
                        context.params.id || null);
    this.selectView('settings');
  },

  showFloorplan: function() {
    FloorplanScreen.show();
    this.selectView('floorplan');
  },

  showRules: function() {
    RulesScreen.show();
    this.selectView('rules');
  },

  showRule: function(context) {
    RuleScreen.show(context.params.rule);
    this.selectView('rule');
  },

  showLogs: function(context) {
    if (context.params.thingId) {
      const descr = {
        thing: context.params.thingId,
        property: context.params.propId,
        type: 'property',
      };
      LogsScreen.show(descr);
    } else {
      LogsScreen.show();
    }
    this.selectView('logs');
  },

  selectView: function(view) {
    if (!this.views[view]) {
      console.error('Tried to select view that didnt exist');
      return;
    }
    this.views[this.currentView].classList.remove('selected');
    this.views[view].classList.add('selected');
    Menu.selectItem(view);
    this.currentView = view;
  },

  showMenuButton: function() {
    this.menuButton.classList.remove('hidden');
  },

  hideMenuButton: function() {
    this.menuButton.classList.add('hidden');
  },

  showOverflowButton: function() {
    this.overflowButton.classList.remove('hidden');
  },

  hideOverflowButton: function() {
    this.overflowMenu.classList.add('hidden');
    this.overflowButton.classList.add('hidden');
  },

  buildOverflowMenu: function(links) {
    this.overflowMenu.innerHTML = '';

    for (const link of links) {
      const element = document.createElement('a');
      element.innerText = link.name;

      if (link.listener) {
        element.href = '#';
        element.addEventListener('click', (e) => {
          e.preventDefault();
          this.toggleOverflowMenu();
          link.listener();
        });
      } else {
        element.href = link.href;
        element.addEventListener('click', () => {
          this.toggleOverflowMenu();
        });
      }

      if (link.icon) {
        const image = document.createElement('img');
        image.src = link.icon;
        image.alt = `${link.name} icon`;
        element.insertBefore(image, element.childNodes[0]);
      }

      this.overflowMenu.appendChild(element);
    }
  },

  toggleOverflowMenu: function() {
    if (this.overflowMenu.classList.contains('hidden')) {
      this.overflowMenu.classList.remove('hidden');
    } else {
      this.overflowMenu.classList.add('hidden');
    }
  },

  showPersistentMessage: function(message) {
    this.showMessage(message);
    this.blockMessages = true;
  },

  hidePersistentMessage: function() {
    if (this.blockMessages) {
      this.hideMessageArea();
      this.blockMessages = false;
    }
  },

  showMessageArea: function() {
    this.messageArea.classList.remove('hidden');
  },

  hideMessageArea: function() {
    this.messageArea.classList.add('hidden');
  },

  showMessage: function(message, duration, extraUrl = null) {
    if (this.messageTimeout !== null) {
      clearTimeout(this.messageTimeout);
      this.messageTimeout = null;
    }

    if (this.blockMessages) {
      return;
    }

    if (extraUrl) {
      message += `

          More Information
        `;
    }

    this.messageArea.innerHTML = message;
    this.showMessageArea();

    if (typeof duration === 'number') {
      this.messageTimeout = setTimeout(() => {
        this.messageTimeout = null;
        this.hideMessageArea();
      }, duration);
    }
  },
};

module.exports = App;

// avoid circular dependency
API = require('./api');
AssistantScreen = require('./views/assistant');
GatewayModel = require('./models/gateway-model');
ThingsScreen = require('./views/things');
AddThingScreen = require('./views/add-thing');
Menu = require('./views/menu');
ContextMenu = require('./context-menu');
SettingsScreen = require('./views/settings');
FloorplanScreen = require('./views/floorplan');
Router = require('./router');
RulesScreen = require('./views/rules-screen');
RuleScreen = require('./views/rule-screen');
LogsScreen = require('./views/logs-screen');
Speech = require('./speech');

// load web components
require('@webcomponents/webcomponentsjs/webcomponents-bundle');
require('./components/capability/alarm');
require('./components/capability/binary-sensor');
require('./components/capability/camera');
require('./components/capability/color-control');
require('./components/capability/custom');
require('./components/capability/door-sensor');
require('./components/capability/energy-monitor');
require('./components/capability/label');
require('./components/capability/leak-sensor');
require('./components/capability/light');
require('./components/capability/motion-sensor');
require('./components/capability/multi-level-sensor');
require('./components/capability/multi-level-switch');
require('./components/capability/on-off-switch');
require('./components/capability/push-button');
require('./components/capability/smart-plug');
require('./components/capability/temperature-sensor');
require('./components/capability/video-camera');
require('./components/icon/custom');
require('./components/property/action');
require('./components/property/alarm');
require('./components/property/boolean');
require('./components/property/brightness');
require('./components/property/color');
require('./components/property/color-temperature');
require('./components/property/current');
require('./components/property/enum');
require('./components/property/frequency');
require('./components/property/image');
require('./components/property/instantaneous-power');
require('./components/property/leak');
require('./components/property/level');
require('./components/property/motion');
require('./components/property/number');
require('./components/property/numeric-label');
require('./components/property/on-off');
require('./components/property/open');
require('./components/property/pushed');
require('./components/property/slider');
require('./components/property/string');
require('./components/property/string-label');
require('./components/property/switch');
require('./components/property/temperature');
require('./components/property/video');
require('./components/property/voltage');

if (navigator.serviceWorker) {
  navigator.serviceWorker.register('/service-worker.js', {
    scope: '/',
  });
  navigator.serviceWorker.ready.then(Notifications.onReady);
}

/**
  * Start app on page load.
  */
window.addEventListener('load', function app_onLoad() {
  window.removeEventListener('load', app_onLoad);
  App.init();
});
<commit_msg>Guard on existence of message<commit_after>/**
 * WebThings Gateway App.
 *
 * Front end main script.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
'use strict';

// eslint-disable-next-line prefer-const
let API;
// eslint-disable-next-line prefer-const
let AssistantScreen;
// eslint-disable-next-line prefer-const
let GatewayModel;
// eslint-disable-next-line prefer-const
let ThingsScreen;
// eslint-disable-next-line prefer-const
let AddThingScreen;
// eslint-disable-next-line prefer-const
let Menu;
// eslint-disable-next-line prefer-const
let ContextMenu;
// eslint-disable-next-line prefer-const
let SettingsScreen;
// eslint-disable-next-line prefer-const
let FloorplanScreen;
// eslint-disable-next-line prefer-const
let Router;
// eslint-disable-next-line prefer-const
let RulesScreen;
// eslint-disable-next-line prefer-const
let RuleScreen;
// eslint-disable-next-line prefer-const
let LogsScreen;
// eslint-disable-next-line prefer-const
let Speech;

const shaka = require('shaka-player/dist/shaka-player.compiled');
const MobileDragDrop = require('mobile-drag-drop/index.min');
const ScrollBehavior = require('mobile-drag-drop/scroll-behaviour.min');
const Notifications = require('./notifications');
const Utils = require('./utils');

const App = {
  /**
   * Current server host.
   */
  HOST: window.location.host,

  /**
   * Current server origin.
   */
  ORIGIN: window.location.origin,

  /**
   * Maximum number of allowed ping failures.
   */
  MAX_PING_FAILURES: 3,

  /**
   * Interval at which to ping to check for connectivity.
   */
  PING_INTERVAL: 20 * 1000,

  /**
   * Start WebThings Gateway app.
   */
  init: function() {
    // Load the shaka player polyfills
    shaka.polyfill.installAll();
    MobileDragDrop.polyfill({
      holdToDrag: 500,
      // eslint-disable-next-line max-len
      dragImageTranslateOverride: ScrollBehavior.scrollBehaviourDragImageTranslateOverride,
    });

    AddThingScreen.init();
    ContextMenu.init();
    AssistantScreen.init();
    ThingsScreen.init();
    SettingsScreen.init();
    FloorplanScreen.init();

    if (navigator.mediaDevices && window.MediaRecorder) {
      Speech.init();
    }

    RulesScreen.init();
    RuleScreen.init();

    LogsScreen.init();

    this.views = [];
    this.views.things = document.getElementById('things-view');
    this.views.floorplan = document.getElementById('floorplan-view');
    this.views.settings = document.getElementById('settings-view');
    this.views.rules = document.getElementById('rules-view');
    this.views.rule = document.getElementById('rule-view');
    this.views.assistant = document.getElementById('assistant-view');
    this.views.logs = document.getElementById('logs-view');
    this.currentView = 'things';
    this.menuButton = document.getElementById('menu-button');
    this.menuButton.addEventListener('click', Menu.toggle.bind(Menu));
    this.overflowButton = document.getElementById('overflow-button');
    this.overflowButton.addEventListener('click',
                                         this.toggleOverflowMenu.bind(this));
    this.overflowMenu = document.getElementById('overflow-menu');
    this.blockMessages = false;
    this.messageArea = document.getElementById('message-area');
    this.messageTimeout = null;

    this.connectivityOverlay = document.getElementById('connectivity-scrim');
    this.pingerInterval = null;
    this.pingerLastStatus = null;
    this.failedPings = 0;
    this.startPinger();

    this.gatewayModel = new GatewayModel();

    this.wsBackoff = 1000;
    this.initWebSocket();

    Menu.init();
    Router.init();
  },

  initWebSocket() {
    const path = `${this.ORIGIN.replace(/^http/, 'ws')}/internal-logs?jwt=${API.jwt}`;
    this.messageSocket = new WebSocket(path);

    this.messageSocket.addEventListener('open', () => {
      // Reset the backoff period
      this.wsBackoff = 1000;
    }, {once: true});

    const onMessage = (msg) => {
      const message = JSON.parse(msg.data);
      if (message && message.message) {
        this.showMessage(message.message, 5000, message.url);
      }
    };

    const cleanup = () => {
      this.messageSocket.removeEventListener('message', onMessage);
      this.messageSocket.removeEventListener('close', cleanup);
      this.messageSocket.removeEventListener('error', cleanup);
      this.messageSocket.close();
      this.messageSocket = null;

      setTimeout(() => {
        this.wsBackoff *= 2;
        if (this.wsBackoff > 30000) {
          this.wsBackoff = 30000;
        }
        this.initWebSocket();
      }, this.wsBackoff);
    };

    this.messageSocket.addEventListener('message', onMessage);
    this.messageSocket.addEventListener('close', cleanup);
    this.messageSocket.addEventListener('error', cleanup);
  },

  startPinger() {
    fetch(
      `${this.ORIGIN}/ping`,
      {
        headers: {
          Accept: 'application/json',
        },
      }
    ).then((res) => {
      if (!res.ok) {
        throw new Error(`Bad return status: ${res.status}`);
      }

      if (this.pingerLastStatus === 'offline') {
        window.location.reload();
      } else {
        this.failedPings = 0;
        this.pingerLastStatus = 'online';
        this.connectivityOverlay.classList.add('hidden');
        this.messageArea.classList.remove('disconnected');

        if (this.messageArea.innerText === 'Gateway Unreachable') {
          this.hidePersistentMessage();
        }
      }
    }).catch((e) => {
      console.error('Gateway unreachable:', e);
      if (++this.failedPings >= this.MAX_PING_FAILURES) {
        this.connectivityOverlay.classList.remove('hidden');
        this.messageArea.classList.add('disconnected');
        this.showPersistentMessage('Gateway Unreachable');
        this.pingerLastStatus = 'offline';
      }
    });

    if (!this.pingerInterval) {
      this.pingerInterval =
        setInterval(this.startPinger.bind(this), this.PING_INTERVAL);
    }
  },

  showAssistant: function() {
    AssistantScreen.show();
    this.selectView('assistant');
  },

  showThings: function(context) {
    const events = context.pathname.split('/').pop() === 'events';
    ThingsScreen.show(context.params.thingId || null,
                      context.params.actionName || null,
                      events,
                      context.querystring);
    this.selectView('things');
  },

  showSettings: function(context) {
    SettingsScreen.show(context.params.section || null,
                        context.params.subsection || null,
                        context.params.id || null);
    this.selectView('settings');
  },

  showFloorplan: function() {
    FloorplanScreen.show();
    this.selectView('floorplan');
  },

  showRules: function() {
    RulesScreen.show();
    this.selectView('rules');
  },

  showRule: function(context) {
    RuleScreen.show(context.params.rule);
    this.selectView('rule');
  },

  showLogs: function(context) {
    if (context.params.thingId) {
      const descr = {
        thing: context.params.thingId,
        property: context.params.propId,
        type: 'property',
      };
      LogsScreen.show(descr);
    } else {
      LogsScreen.show();
    }
    this.selectView('logs');
  },

  selectView: function(view) {
    if (!this.views[view]) {
      console.error('Tried to select view that didnt exist');
      return;
    }
    this.views[this.currentView].classList.remove('selected');
    this.views[view].classList.add('selected');
    Menu.selectItem(view);
    this.currentView = view;
  },

  showMenuButton: function() {
    this.menuButton.classList.remove('hidden');
  },

  hideMenuButton: function() {
    this.menuButton.classList.add('hidden');
  },

  showOverflowButton: function() {
    this.overflowButton.classList.remove('hidden');
  },

  hideOverflowButton: function() {
    this.overflowMenu.classList.add('hidden');
    this.overflowButton.classList.add('hidden');
  },

  buildOverflowMenu: function(links) {
    this.overflowMenu.innerHTML = '';

    for (const link of links) {
      const element = document.createElement('a');
      element.innerText = link.name;

      if (link.listener) {
        element.href = '#';
        element.addEventListener('click', (e) => {
          e.preventDefault();
          this.toggleOverflowMenu();
          link.listener();
        });
      } else {
        element.href = link.href;
        element.addEventListener('click', () => {
          this.toggleOverflowMenu();
        });
      }

      if (link.icon) {
        const image = document.createElement('img');
        image.src = link.icon;
        image.alt = `${link.name} icon`;
        element.insertBefore(image, element.childNodes[0]);
      }

      this.overflowMenu.appendChild(element);
    }
  },

  toggleOverflowMenu: function() {
    if (this.overflowMenu.classList.contains('hidden')) {
      this.overflowMenu.classList.remove('hidden');
    } else {
      this.overflowMenu.classList.add('hidden');
    }
  },

  showPersistentMessage: function(message) {
    this.showMessage(message);
    this.blockMessages = true;
  },

  hidePersistentMessage: function() {
    if (this.blockMessages) {
      this.hideMessageArea();
      this.blockMessages = false;
    }
  },

  showMessageArea: function() {
    this.messageArea.classList.remove('hidden');
  },

  hideMessageArea: function() {
    this.messageArea.classList.add('hidden');
  },

  showMessage: function(message, duration, extraUrl = null) {
    if (this.messageTimeout !== null) {
      clearTimeout(this.messageTimeout);
      this.messageTimeout = null;
    }

    if (this.blockMessages) {
      return;
    }

    if (extraUrl) {
      message += `

          More Information
        `;
    }

    this.messageArea.innerHTML = message;
    this.showMessageArea();

    if (typeof duration === 'number') {
      this.messageTimeout = setTimeout(() => {
        this.messageTimeout = null;
        this.hideMessageArea();
      }, duration);
    }
  },
};

module.exports = App;

// avoid circular dependency
API = require('./api');
AssistantScreen = require('./views/assistant');
GatewayModel = require('./models/gateway-model');
ThingsScreen = require('./views/things');
AddThingScreen = require('./views/add-thing');
Menu = require('./views/menu');
ContextMenu = require('./context-menu');
SettingsScreen = require('./views/settings');
FloorplanScreen = require('./views/floorplan');
Router = require('./router');
RulesScreen = require('./views/rules-screen');
RuleScreen = require('./views/rule-screen');
LogsScreen = require('./views/logs-screen');
Speech = require('./speech');

// load web components
require('@webcomponents/webcomponentsjs/webcomponents-bundle');
require('./components/capability/alarm');
require('./components/capability/binary-sensor');
require('./components/capability/camera');
require('./components/capability/color-control');
require('./components/capability/custom');
require('./components/capability/door-sensor');
require('./components/capability/energy-monitor');
require('./components/capability/label');
require('./components/capability/leak-sensor');
require('./components/capability/light');
require('./components/capability/motion-sensor');
require('./components/capability/multi-level-sensor');
require('./components/capability/multi-level-switch');
require('./components/capability/on-off-switch');
require('./components/capability/push-button');
require('./components/capability/smart-plug');
require('./components/capability/temperature-sensor');
require('./components/capability/video-camera');
require('./components/icon/custom');
require('./components/property/action');
require('./components/property/alarm');
require('./components/property/boolean');
require('./components/property/brightness');
require('./components/property/color');
require('./components/property/color-temperature');
require('./components/property/current');
require('./components/property/enum');
require('./components/property/frequency');
require('./components/property/image');
require('./components/property/instantaneous-power');
require('./components/property/leak');
require('./components/property/level');
require('./components/property/motion');
require('./components/property/number');
require('./components/property/numeric-label');
require('./components/property/on-off');
require('./components/property/open');
require('./components/property/pushed');
require('./components/property/slider');
require('./components/property/string');
require('./components/property/string-label');
require('./components/property/switch');
require('./components/property/temperature');
require('./components/property/video');
require('./components/property/voltage');

if (navigator.serviceWorker) {
  navigator.serviceWorker.register('/service-worker.js', {
    scope: '/',
  });
  navigator.serviceWorker.ready.then(Notifications.onReady);
}

/**
  * Start app on page load.
  */
window.addEventListener('load', function app_onLoad() {
  window.removeEventListener('load', app_onLoad);
  App.init();
});
<|endoftext|>


<commit_before>import parse from 'try-json-parse'
import handleAuth from './auth'
import KiteError from './error'
import { Event } from '../constants'

export default function handleIncomingMessage(proto, message) {
  this.emit(Event.debug, `Receiving: ${message}`)

  const req = parse(message)

  if (req == null) {
    this.emit(Event.warning, new KiteError(`Invalid payload! (${message})`))
    return
  }

  if (!isKiteReq(req)) {
    this.emit(Event.debug, 'Handling a normal dnode message')
    return proto.handle(req)
  }

  const { links, method, callbacks } = req
  const {
    withArgs = [],
    authentication: auth,
    responseCallback,
  } = parseKiteReq(req)

  this.emit(Event.debug, 'Authenticating request')

  return handleAuth(this, method, auth, this.key)
    .then(token => {
      this.emit(Event.debug, 'Authentication passed')

      // set this as the current token for the duration of the synchronous
      // method call.
      // NOTE: this mechanism may be changed at some point in the future.
      this.currentToken = token

      proto.handle(
        toProtoReq({
          method,
          withArgs,
          responseCallback,
          links,
          callbacks,
        })
      )

      this.currentToken = null
    })
    .catch(err => {
      this.emit(Event.debug, 'Authentication failed', err)

      return proto.handle({
        method: 'kite.echo',
        arguments: [err, responseCallback],
        links,
        callbacks: mungeCallbacks(callbacks, 1),
      })
    })
}

const isKiteReq = req =>
  req.arguments.length &&
  req.arguments[0] &&
  req.arguments[0].responseCallback &&
  req.arguments[0].withArgs

const parseKiteReq = req => req.arguments[0]

const isResponseCallback = callback =>
  callback[0] === '0' && callback[1] === 'responseCallback'

const mungeCallbacks = (callbacks, n) => {
  // FIXME: this is an ugly hack; there must be a better way to implement it:

  for (let key of Object.keys(callbacks || {})) {
    const callback = callbacks[key]
    if (isResponseCallback(callback)) {
      callbacks[key] = [n]
    }
    if (callback[1] === 'withArgs') {
      // since we're rewriting the protocol for the withArgs case,
      // we need to remove everything up to withArgs
      callbacks[key] = callback.slice(2)
    }
  }

  return callbacks
}

const toProtoReq = ({
  method,
  withArgs,
  responseCallback,
  links,
  callbacks,
}) => {
  if (!Array.isArray(withArgs)) {
    withArgs = [withArgs]
  }

  mungeCallbacks(callbacks, withArgs.length)

  return {
    method,
    arguments: [...Array.from(withArgs), responseCallback],
    links,
    callbacks,
  }
}
<commit_msg>kite/handleMessage: fix is response callback fn<commit_after>import parse from 'try-json-parse'
import handleAuth from './auth'
import KiteError from './error'
import { Event } from '../constants'

export default function handleIncomingMessage(proto, message) {
  this.emit(Event.debug, `Receiving: ${message}`)

  const req = parse(message)

  if (req == null) {
    this.emit(Event.warning, new KiteError(`Invalid payload! (${message})`))
    return
  }

  if (!isKiteReq(req)) {
    this.emit(Event.debug, 'Handling a normal dnode message')
    return proto.handle(req)
  }

  const { links, method, callbacks } = req
  const {
    withArgs = [],
    authentication: auth,
    responseCallback,
  } = parseKiteReq(req)

  this.emit(Event.debug, 'Authenticating request')

  return handleAuth(this, method, auth, this.key)
    .then(token => {
      this.emit(Event.debug, 'Authentication passed')

      // set this as the current token for the duration of the synchronous
      // method call.
      // NOTE: this mechanism may be changed at some point in the future.
      this.currentToken = token

      proto.handle(
        toProtoReq({
          method,
          withArgs,
          responseCallback,
          links,
          callbacks,
        })
      )

      this.currentToken = null
    })
    .catch(err => {
      this.emit(Event.debug, 'Authentication failed', err)

      return proto.handle({
        method: 'kite.echo',
        arguments: [err, responseCallback],
        links,
        callbacks: mungeCallbacks(callbacks, 1),
      })
    })
}

const isKiteReq = req =>
  req.arguments.length &&
  req.arguments[0] &&
  req.arguments[0].responseCallback &&
  req.arguments[0].withArgs

const parseKiteReq = req => req.arguments[0]

const isResponseCallback = callback =>
  Array.isArray(callback) && callback.join('.') === '0.responseCallback'

const mungeCallbacks = (callbacks, n) => {
  // FIXME: this is an ugly hack; there must be a better way to implement it:

  for (let key of Object.keys(callbacks || {})) {
    const callback = callbacks[key]
    if (isResponseCallback(callback)) {
      callbacks[key] = [n]
    }
    if (callback[1] === 'withArgs') {
      // since we're rewriting the protocol for the withArgs case,
      // we need to remove everything up to withArgs
      callbacks[key] = callback.slice(2)
    }
  }

  return callbacks
}

const toProtoReq = ({
  method,
  withArgs,
  responseCallback,
  links,
  callbacks,
}) => {
  if (!Array.isArray(withArgs)) {
    withArgs = [withArgs]
  }

  mungeCallbacks(callbacks, withArgs.length)

  return {
    method,
    arguments: [...Array.from(withArgs), responseCallback],
    links,
    callbacks,
  }
}
<|endoftext|>


<commit_before>function init() {
    var stage = new createjs.Stage("demoCanvas");
    var circle = new createjs.Shape();
    circle.graphics.beginFill("DeepSkyBlue").drawCircle(0, 0, 50);
    circle.x = 100;
    circle.y = 100;
    stage.addChild(circle);
    stage.update();
    //createjs.Tween.get(circle, {loop: true})
    //    .to({x: 1100}, 1000, createjs.Ease.getPowInOut(4))
    //    .to({alpha: 0, y: 475}, 500, createjs.Ease.getPowInOut(2))
    //    .to({alpha: 0, y: 425}, 100)
    //    .to({alpha: 1, y: 200}, 500, createjs.Ease.getPowInOut(2))
    //    .to({x: 100}, 800, createjs.Ease.getPowInOut(2));
    //createjs.Ticker.setFPS(60);
    //createjs.Ticker.addEventListener("tick", stage);
}<commit_msg>add connect web socket<commit_after>function init() {
    var stage = new createjs.Stage("demoCanvas");
    var circle = new createjs.Shape();
    circle.graphics.beginFill("DeepSkyBlue").drawCircle(0, 0, 50);
    circle.x = 100;
    circle.y = 100;
    stage.addChild(circle);
    stage.update();
    //createjs.Tween.get(circle, {loop: true})
    //    .to({x: 1100}, 1000, createjs.Ease.getPowInOut(4))
    //    .to({alpha: 0, y: 475}, 500, createjs.Ease.getPowInOut(2))
    //    .to({alpha: 0, y: 425}, 100)
    //    .to({alpha: 1, y: 200}, 500, createjs.Ease.getPowInOut(2))
    //    .to({x: 100}, 800, createjs.Ease.getPowInOut(2));
    //createjs.Ticker.setFPS(60);
    //createjs.Ticker.addEventListener("tick", stage);
    connectWebSocket();
}<|endoftext|>


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

require.config({
    baseUrl: STATIC_FILES_URL+'js/',
    paths: {
        jquery: 'libs/jquery-1.11.1.min',
        bootstrap: 'libs/bootstrap.min',
        jqueryui: 'libs/jquery-ui.min',
        session_security: 'session_security',
        underscore: 'libs/underscore-min',
        igv_lib: 'libs/igv'
    },
    shim: {
        'session_security': ['jquery'],
        'bootstrap': ['jquery'],
        'jqueryui': ['jquery'],
        'igv_lib': ['jquery', 'jqueryui']
    }
});

require([

    'jquery',
    'jqueryui',
    'session_security',
    'bootstrap',
    'igv_lib'

], function($, jqueryui, session_security, bs, igv_lib) {
    var browser;
    var tracks = [];
    var readgroupset_divs = $('.readgroupset-data');
    var bam_divs = $('.bam-data');

    for (var i = 0; i < readgroupset_divs.length; i++) {
        var row = $(readgroupset_divs[i]);
        var readgroupset_id = row.data('readgroupset');
        var sample_barcode = row.data('sample');
        tracks.push({
            sourceType: 'ga4gh',
            type: 'bam',
            url: 'https://genomics.googleapis.com/v1',
            readGroupSetIds: readgroupset_id,
            name: sample_barcode + ': Google Genomics',
            referenceName: '1',
            withCredentials: true
        });
    }

    for (var i = 0; i < bam_divs.length; i++) {
        var row = $(bam_divs[i]);
        var bam_path = row.data('gcs');
        var sample_barcode = row.data('sample');
        var obj = {
            sourceType: 'gcs',
            type: 'bam',
            url: bam_path, // gs:// url to .bam file
            name: sample_barcode + ': GCS bam file',
            withCredentials: true
        };
        if(row['program'] === 'TCGA' && genome_build === 'HG38'){
            obj.indexURL = bam_path.replace(/\.bam/,'.bai');
        }
        tracks.push(obj);
    }

    var genes_obj = {
        name: "Genes: Gencode v18",
        url: "https://s3.amazonaws.com/igv.broadinstitute.org/data/hg19/genes/gencode.v18.collapsed.bed",
        indexURL: "https://s3.amazonaws.com/igv.broadinstitute.org/data/hg19/genes/gencode.v18.collapsed.bed.idx",
        order: Number.MAX_VALUE,
        displayMode: "EXPANDED"
    };

    if(genome_build === 'HG38') {
        genes_obj.url = 'https://s3.amazonaws.com/igv.broadinstitute.org/data/hg38/gencode.v24.annotation.sorted.gtf.gz';
        genes_obj.indexURL = 'https://s3.amazonaws.com/igv.broadinstitute.org/data/hg38/gencode.v24.annotation.sorted.gtf.gz.tbi';
        genes_obj.name = 'Genes: Gencode v24';
        genes_obj.format = 'gtf';
    }

    tracks.push(genes_obj);

    var options = {
        showNavigation: true,
        genome: genome_build.toLowerCase(),
        locus: "EGFR",
        tracks: tracks,
        withCredentials: true
    };

    $('#igv-div').empty();
    igv.browser = null;
    igv.oauth.google.setRedirectUrl(BASE_URL, service_account);
    igv.oauth.google.login();

    browser = igv.createBrowser($('#igv-div')[0], options);

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

require.config({
    baseUrl: STATIC_FILES_URL+'js/',
    paths: {
        jquery: 'libs/jquery-1.11.1.min',
        bootstrap: 'libs/bootstrap.min',
        jqueryui: 'libs/jquery-ui.min',
        session_security: 'session_security',
        underscore: 'libs/underscore-min',
        igv_lib: 'libs/igv'
    },
    shim: {
        'session_security': ['jquery'],
        'bootstrap': ['jquery'],
        'jqueryui': ['jquery'],
        'igv_lib': ['jquery', 'jqueryui']
    }
});

require([

    'jquery',
    'jqueryui',
    'session_security',
    'bootstrap',
    'igv_lib'

], function($, jqueryui, session_security, bs, igv_lib) {
    var browser;
    var tracks = [];
    var readgroupset_divs = $('.readgroupset-data');
    var bam_divs = $('.bam-data');

    for (var i = 0; i < readgroupset_divs.length; i++) {
        var row = $(readgroupset_divs[i]);
        var readgroupset_id = row.data('readgroupset');
        var sample_barcode = row.data('sample');
        tracks.push({
            sourceType: 'ga4gh',
            type: 'bam',
            url: 'https://genomics.googleapis.com/v1',
            readGroupSetIds: readgroupset_id,
            name: sample_barcode + ': Google Genomics',
            referenceName: '1',
            withCredentials: true
        });
    }

    for (var i = 0; i < bam_divs.length; i++) {
        var row = $(bam_divs[i]);
        var bam_path = row.data('gcs');
        var sample_barcode = row.data('sample');
        var obj = {
            sourceType: 'gcs',
            type: 'bam',
            url: bam_path, // gs:// url to .bam file
            name: sample_barcode + ': GCS bam file',
            withCredentials: true
        };
        if(row['program'] === 'TCGA' && genome_build === 'HG38'){
            obj.indexURL = bam_path.replace(/\.bam/,'.bai');
        }
        tracks.push(obj);
    }

    var genes_obj = {
        name: "Genes: Gencode v18",
        url: "https://s3.amazonaws.com/igv.broadinstitute.org/annotations/hg19/genes/gencode.v18.collapsed.bed",
        indexURL: "https://s3.amazonaws.com/igv.broadinstitute.org/annotations/hg19/genes/gencode.v18.collapsed.bed.idx",
        order: Number.MAX_VALUE,
        displayMode: "EXPANDED"
    };

    if(genome_build === 'HG38') {
        genes_obj.url = 'https://s3.amazonaws.com/igv.broadinstitute.org/annotations/hg38/genes/gencode.v24.annotation.sorted.gtf.gz';
        genes_obj.indexURL = 'https://s3.amazonaws.com/igv.broadinstitute.org/annotations/hg38/genes/gencode.v24.annotation.sorted.gtf.gz.tbi';
        genes_obj.name = 'Genes: Gencode v24';
        genes_obj.format = 'gtf';
    }

    tracks.push(genes_obj);

    var options = {
        showNavigation: true,
        genome: genome_build.toLowerCase(),
        locus: "EGFR",
        tracks: tracks,
        withCredentials: true
    };

    $('#igv-div').empty();
    igv.browser = null;
    igv.oauth.google.setRedirectUrl(BASE_URL, service_account);
    igv.oauth.google.login();

    browser = igv.createBrowser($('#igv-div')[0], options);

});<|endoftext|>


<commit_before>var Backend = {};
/**
 * Backend.Api makes it possible to make calls to a backend in a simplified manner. It gives supplies defaults for GET, POST, PUT, PATCH and DELETE requests
 *
 * @param baseUrl The base url of the backend. If you want to PUT something to https://server.io/api/object then you can supply https://server.io/api as your base url. In your request methods your can supply the /object uri. Alternatively you can omit it and supply the whole url to the request method
 */
Backend.Api = function(baseUrl){

    var version = function(){return 'v1.0'},
    baseUrl = baseUrl||'',
    defaults = { xhrFields: {withCredentials: true}, contentType: 'application/x-www-form-urlencoded', type: 'GET', success: function(data, status, xhr){console.info('API request ' + xhr.status + " " + xhr.statusText + " Bytes: " + xhr.response.length);}, error: function(xhr, errorType, error){alert('XHR call failed...');}},
    get = function(uri, r){
        request( prepareRequest(uri, r, 'GET') );
    },
    post = function(uri, r){
        request( prepareRequest(uri, r, 'POST') );
    },
    put = function(uri, r){
        request( prepareRequest(uri, r, 'PUT') );
    },
    patch = function(uri, r){
        request( prepareRequest(uri, r, 'PATCH') );
    },
    del = function(uri, r){
        request( prepareRequest(uri, r, 'DELETE') );
    },
    prepareRequest = function(uri, request, type){
        request = request || {};
        request.type = type;
        request.url = uri;

        extend(request, defaults);

        return request;
    };

    function extend(a, b){
        for(var p in b){
            if (!a[p]){
                a[p] = b[p];
            }
        }
    };

    function request( r ){

        if ( typeof r.url === "string" && r.url.substring(0, baseUrl.length) !== baseUrl ){
            r.url = baseUrl + r.url;
        }
        var http = new XMLHttpRequest();
        http.open(r.type, r.url, true);

        http.setRequestHeader("Content-type", r.contentType);

        http.onreadystatechange = function() {//Call a function when the state changes.
            if(http.readyState == 4 && http.status == 200) {
                r.success( decode(http), http.status, http);
            }
            else if(http.readyState == 4) {
                r.error( decode(http), http.status, http);
            }
        }
        http.send();
    };

    /**
     * Encodes the response into the right format
     *
     * @param XmlHttpRequest the ajax request object
     *
     * @return string|object The encoded response depending on the content type, eg. application/json delivers a JSON object, text/html returns a string
     */
    function decode(xhr){
        var response = xhr.responseText;
        if (xhr.getResponseHeader('content-type').match(/json/)){
            response = JSON.parse(response);
        }
        return response;
    }

    return {
        get : get,
        post : post,
        put : put,
        patch : patch,
        del : del,
        version:version
    }
};

<commit_msg>comments<commit_after>/**
 * Main object to make backend calls
 */
var Backend = {};
/**
 * Backend.Api makes it possible to make calls to a backend in a simplified manner. It gives supplies defaults for GET, POST, PUT, PATCH and DELETE requests
 *
 * @param baseUrl The base url of the backend. If you want to PUT something to https://server.io/api/object then you can supply https://server.io/api as your base url. In your request methods your can supply the /object uri. Alternatively you can omit it and supply the whole url to the request method
 */
Backend.Api = function(baseUrl){

    var version = function(){return 'v1.0'},
    baseUrl = baseUrl||'',
    defaults = { xhrFields: {withCredentials: true}, contentType: 'application/x-www-form-urlencoded', type: 'GET', success: function(data, status, xhr){console.info('API request ' + xhr.status + " " + xhr.statusText + " Bytes: " + xhr.response.length);}, error: function(xhr, errorType, error){alert('XHR call failed...');}},
    get = function(uri, r){
        request( prepareRequest(uri, r, 'GET') );
    },
    post = function(uri, r){
        request( prepareRequest(uri, r, 'POST') );
    },
    put = function(uri, r){
        request( prepareRequest(uri, r, 'PUT') );
    },
    patch = function(uri, r){
        request( prepareRequest(uri, r, 'PATCH') );
    },
    del = function(uri, r){
        request( prepareRequest(uri, r, 'DELETE') );
    },
    prepareRequest = function(uri, request, type){
        request = request || {};
        request.type = type;
        request.url = uri;

        extend(request, defaults);

        return request;
    };

    function extend(a, b){
        for(var p in b){
            if (!a[p]){
                a[p] = b[p];
            }
        }
    };

    function request( r ){

        if ( typeof r.url === "string" && r.url.substring(0, baseUrl.length) !== baseUrl ){
            r.url = baseUrl + r.url;
        }
        var http = new XMLHttpRequest();
        http.open(r.type, r.url, true);

        http.setRequestHeader("Content-type", r.contentType);

        http.onreadystatechange = function() {//Call a function when the state changes.
            if(http.readyState == 4 && http.status == 200) {
                r.success( decode(http), http.status, http);
            }
            else if(http.readyState == 4) {
                r.error( decode(http), http.status, http);
            }
        }
        http.send();
    };

    /**
     * Encodes the response into the right format
     *
     * @param XmlHttpRequest the ajax request object
     *
     * @return string|object The encoded response depending on the content type, eg. application/json delivers a JSON object, text/html returns a string
     */
    function decode(xhr){
        var response = xhr.responseText;
        if (xhr.getResponseHeader('content-type').match(/json/)){
            response = JSON.parse(response);
        }
        return response;
    }

    return {
        get : get,
        post : post,
        put : put,
        patch : patch,
        del : del,
        version:version
    }
};

<|endoftext|>


<commit_before>(function( $ ) {
	$.widget( "custom.combobox", {
		options: {
			list: [],
			select: function(e,data){}
		},
		_map: {},
		_create: function() {
			this.wrapper = $( "<span>" )
				.addClass( "custom-combobox" )
				.insertAfter( this.element );

			this._createOptions();
			this.element.hide();
			this._createAutocomplete();
			this._createShowAllButton();
		},
		_createOptions: function() {
			$( "<option>" )
				.appendTo(this.element)
				.val( "" )
				.text( "select unit..." );
			for(var i=0; i<this.options.list.length; i++){
				var item = this.options.list[i];
				$( "<option>" )
					.appendTo(this.element)
					.val( item.id )
					.text( item.label_detail );
				this._map[item.id] = i;
			}
		},
		_createAutocomplete: function() {
			var selected = this.element.children( ":selected" ),
				value = selected.val() ? selected.text() : "";

			this.input = $( "<input>" )
				.appendTo( this.wrapper )
				.val( value )
				.attr( "title", "" )
				.addClass( "custom-combobox-input ui-widget ui-widget-content ui-state-default ui-corner-left" )
				.autocomplete({
					delay: 0,
					minLength: 0,
					source: $.proxy( this, "_source" )
				})
				.tooltip({
					tooltipClass: "ui-state-highlight"
				});

			this._on( this.input, {
				autocompleteselect: function( event, ui ) {
					ui.item.option.selected = true;
					this._trigger( "select", event, {
						item: ui.item.option
					});
				},

				autocompletechange: "_removeIfInvalid"
			});
		},

		_createShowAllButton: function() {
			var input = this.input,
				wasOpen = false;

			$( "<a>" )
				.attr( "tabIndex", -1 )
				.attr( "title", "Show All Items" )
				.tooltip()
				.appendTo( this.wrapper )
				.button({
					icons: {
						primary: "ui-icon-triangle-1-s"
					},
					text: false
				})
				.removeClass( "ui-corner-all" )
				.addClass( "custom-combobox-toggle ui-corner-right" )
				.mousedown(function() {
					wasOpen = input.autocomplete( "widget" ).is( ":visible" );
				})
				.click(function() {
					input.focus();

					// Close if already visible
					if ( wasOpen ) {
						return;
					}

					// Pass empty string as value to search for, displaying all results
					input.autocomplete( "search", "" );
				});
		},

		_source: function( request, response ) {
			var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
			var _self = this;
			response( this.element.children( "option" ).map(function() {
				var data = this.value?_self.options.list[_self._map[this.value]]:{};
				if ( this.value && ( !request.term || (function(list){
					for(var i=0; i<list.length; i++){
						if(matcher.test(list[i])) return true;
					}
					if(matcher.test(data.label_detail)) return true;
					return false;
				}( data.pronunciation )) ) )
					return {
						label: data.label_detail,
						value: data.value,
						option: this
					};
			}) );
		},
		_removeIfInvalid: function( event, ui ) {

			// Selected an item, nothing to do
			if ( ui.item ) {
				return;
			}

			// Search for a match (case-insensitive)
			var value = this.input.val(),
				valueLowerCase = value.toLowerCase(),
				valid = false;
			this.element.children( "option" ).each(function() {
				if ( $( this ).text().toLowerCase() === valueLowerCase ) {
					this.selected = valid = true;
					return false;
				}
			});

			// Found a match, nothing to do
			if ( valid ) {
				return;
			}

			// the value is "", do not open tooltip
			if(!value) {
				this.input.val("");
				this.element.val( "" );
				return;
			}

			// Remove invalid value
			this.input
				.val( "" )
				.attr( "title", value + " didn't match any item" )
				.tooltip( "open" );
			this.element.val( "" );
			this._delay(function() {
				this.input.tooltip( "close" ).attr( "title", "" );
			}, 2500 );
			this.input.autocomplete( "instance" ).term = "";
		},

		_destroy: function() {
			this.wrapper.remove();
			this.element.show();
		}
	});
	$.widget( "custom.dataselect", {
		options: {
			list: [],
			select: function(e,data){}
		},
		_create: function() {
			this._createOptions();
			var _self = this;
			this.element.selectmenu({
				select: function( event, data ){
					_self.options.select(event, data);
				}
			});
			// 最初はselectイベントが発火されないので手動で行う
			var options = this.element.children("option");
			if(options.length > 0) {
				this._trigger( "select", event, {
					item: options[0]
				});
			}
		},
		_createOptions: function() {
			for(var i=0; i<this.options.list.length; i++){
				var item = this.options.list[i];
				var option = $( "<option>" )
					.appendTo(this.element)
					.val( item.id )
					.text( item.label );
			}
		}
	});
})( jQuery );

$(function() {

	$( document ).tooltip();
});
<commit_msg>prefix search on unit combobox<commit_after>(function( $ ) {
	$.widget( "custom.combobox", {
		options: {
			list: [],
			select: function(e,data){}
		},
		_map: {},
		_create: function() {
			this.wrapper = $( "<span>" )
				.addClass( "custom-combobox" )
				.insertAfter( this.element );

			this._createOptions();
			this.element.hide();
			this._createAutocomplete();
			this._createShowAllButton();
		},
		_createOptions: function() {
			$( "<option>" )
				.appendTo(this.element)
				.val( "" )
				.text( "select unit..." );
			for(var i=0; i<this.options.list.length; i++){
				var item = this.options.list[i];
				$( "<option>" )
					.appendTo(this.element)
					.val( item.id )
					.text( item.label_detail );
				this._map[item.id] = i;
			}
		},
		_createAutocomplete: function() {
			var selected = this.element.children( ":selected" ),
				value = selected.val() ? selected.text() : "";

			this.input = $( "<input>" )
				.appendTo( this.wrapper )
				.val( value )
				.attr( "title", "" )
				.addClass( "custom-combobox-input ui-widget ui-widget-content ui-state-default ui-corner-left" )
				.autocomplete({
					delay: 0,
					minLength: 0,
					source: $.proxy( this, "_source" )
				})
				.tooltip({
					tooltipClass: "ui-state-highlight"
				});

			this._on( this.input, {
				autocompleteselect: function( event, ui ) {
					ui.item.option.selected = true;
					this._trigger( "select", event, {
						item: ui.item.option
					});
				},

				autocompletechange: "_removeIfInvalid"
			});
		},

		_createShowAllButton: function() {
			var input = this.input,
				wasOpen = false;

			$( "<a>" )
				.attr( "tabIndex", -1 )
				.attr( "title", "Show All Items" )
				.tooltip()
				.appendTo( this.wrapper )
				.button({
					icons: {
						primary: "ui-icon-triangle-1-s"
					},
					text: false
				})
				.removeClass( "ui-corner-all" )
				.addClass( "custom-combobox-toggle ui-corner-right" )
				.mousedown(function() {
					wasOpen = input.autocomplete( "widget" ).is( ":visible" );
				})
				.click(function() {
					input.focus();

					// Close if already visible
					if ( wasOpen ) {
						return;
					}

					// Pass empty string as value to search for, displaying all results
					input.autocomplete( "search", "" );
				});
		},

		_source: function( request, response ) {
			var matcher = new RegExp( "^"+$.ui.autocomplete.escapeRegex(request.term), "i" );
			var _self = this;
			response( this.element.children( "option" ).map(function() {
				var data = this.value?_self.options.list[_self._map[this.value]]:{};
				if ( this.value && ( !request.term || (function(list){
					for(var i=0; i<list.length; i++){
						if(matcher.test(list[i])) return true;
					}
					if(matcher.test(data.label_detail)) return true;
					return false;
				}( data.pronunciation )) ) )
					return {
						label: data.label_detail,
						value: data.value,
						option: this
					};
			}) );
		},
		_removeIfInvalid: function( event, ui ) {

			// Selected an item, nothing to do
			if ( ui.item ) {
				return;
			}

			// Search for a match (case-insensitive)
			var value = this.input.val(),
				valueLowerCase = value.toLowerCase(),
				valid = false;
			this.element.children( "option" ).each(function() {
				if ( $( this ).text().toLowerCase() === valueLowerCase ) {
					this.selected = valid = true;
					return false;
				}
			});

			// Found a match, nothing to do
			if ( valid ) {
				return;
			}

			// the value is "", do not open tooltip
			if(!value) {
				this.input.val("");
				this.element.val( "" );
				return;
			}

			// Remove invalid value
			this.input
				.val( "" )
				.attr( "title", value + " didn't match any item" )
				.tooltip( "open" );
			this.element.val( "" );
			this._delay(function() {
				this.input.tooltip( "close" ).attr( "title", "" );
			}, 2500 );
			this.input.autocomplete( "instance" ).term = "";
		},

		_destroy: function() {
			this.wrapper.remove();
			this.element.show();
		}
	});
	$.widget( "custom.dataselect", {
		options: {
			list: [],
			select: function(e,data){}
		},
		_create: function() {
			this._createOptions();
			var _self = this;
			this.element.selectmenu({
				select: function( event, data ){
					_self.options.select(event, data);
				}
			});
			// 最初はselectイベントが発火されないので手動で行う
			var options = this.element.children("option");
			if(options.length > 0) {
				this._trigger( "select", event, {
					item: options[0]
				});
			}
		},
		_createOptions: function() {
			for(var i=0; i<this.options.list.length; i++){
				var item = this.options.list[i];
				var option = $( "<option>" )
					.appendTo(this.element)
					.val( item.id )
					.text( item.label );
			}
		}
	});
})( jQuery );

$(function() {

	$( document ).tooltip();
});
<|endoftext|>


<commit_before>import {Component} from 'substance'

const MAX_ROWS = 10

export default
class TableValueComponent extends Component {
  render($$) {
    let nrows
    const table = this.props.value
    let el = $$('div').addClass('sc-table-value')

    let tableEl = $$('table').addClass('sc-table-value')
    const columnNames = Object.keys(table.data)
    const thead = $$('thead')
    columnNames.forEach((name)=>{
      thead.append(
        $$('th').append(name)
      )
    })
    tableEl.append(thead)
    if (columnNames.length>0) {
      const tbody = $$('tbody')
      const data = table.data
      nrows = data[columnNames[0]].values.length

      for (let i = 0; i < nrows && i < MAX_ROWS; i++) {
        let tr = $$('tr')
        columnNames.forEach((name)=>{
          tr.append(
            $$('td').text(data[name].values[i])
          )
        })
        tbody.append(tr)
      }
      tableEl.append(tbody)
    }
    el.append(tableEl)

    if (nrows > MAX_ROWS) {
      el.append(
        $$('div').addClass('se-more-records').append(
          'Showing ',
          MAX_ROWS,
          ' out of ',
          nrows,
          ' rows'
        )
      )
    }

    return el
  }
}
<commit_msg>Fix TableValueComponent<commit_after>import {Component} from 'substance'

const MAX_ROWS = 10

export default
class TableValueComponent extends Component {
  render($$) {
    const table = this.props.value.data
    const data = table.data
    const rows = table.rows
    const cols = table.columns

    let el = $$('div').addClass('sc-table-value')

    let tableEl = $$('table').addClass('sc-table-value')

    const columnNames = Object.keys(data)
    const thead = $$('thead')
    columnNames.forEach((name)=>{
      thead.append(
        $$('th').append(name)
      )
    })
    tableEl.append(thead)

    if (cols > 0) {
      const tbody = $$('tbody')
      for (let row = 0; row < rows && row < MAX_ROWS; row++) {
        let tr = $$('tr')
        columnNames.forEach((name)=>{
          tr.append(
            $$('td').text(data[name][row])
          )
        })
        tbody.append(tr)
      }
      tableEl.append(tbody)
    }
    el.append(tableEl)

    if (rows > MAX_ROWS) {
      el.append(
        $$('div').addClass('se-more-records').append(
          `Showing ${MAX_ROWS} of ${rows} rows`
        )
      )
    }

    return el
  }
}
<|endoftext|>


<commit_before>const { filter: _filter, reduce: _reduce } = require('lodash');

Memory.sources || (Memory.sources = {});

function sourceMem(source, key, value) {
  Memory.sources[source.id] || (Memory.sources[source.id] = {});
  if(key !== undefined) {
    if(value !== undefined) {
      Memory.sources[source.id][key] = value;
    }

    return Memory.sources[source.id][key];
  }

  return Memory.sources[source.id];
}

function hasWorkableSpots(source) {
  if(!sourceMem(source, 'maxWorkableSpots')) {
    // determine number of workable spots
    var terrain = source.room.lookForAtArea(LOOK_TERRAIN, source.pos.y - 1, source.pos.x - 1, source.pos.y + 1, source.pos.x + 1, true);
    terrain = _filter(terrain, function(t) { return t.terrain != 'wall' });

    sourceMem(source, 'maxWorkableSpots', terrain.length);
    sourceMem(source, 'workableSpots', terrain.length);
  }
  else if(sourceMem(source, 'recalc')) {
    // count creeps that have reserved this source
    let reservedSpots = _reduce(Memory.creeps, (count, _, creep) => ( count + (creep.reservedSource === source.id )), 0);
    sourceMem(source, 'workableSpots', reservedSpots);
    sourceMem(source, 'recalc', false);
  }

  return sourceMem(source, 'workableSpots') > 0;
}

const { harvestResource, checkinSpot } = require('utils'),
      role = {

        /** @param {Creep} creep **/
        run: function(creep) {
          if(creep.carryCapacity !== 0 && creep.memory.harvesting && creep.carry.energy === creep.carryCapacity) {
            creep.memory.harvesting = false;
          }

          if(!creep.memory.harvesting && creep.carry.energy === 0) {
            creep.memory.harvesting = true;
          }

          if(creep.memory.harvesting) {
            role.harvestResource(creep);
          }
          else {
            // Find the nearest non-full container
            let target = creep.pos.findClosestByPath(FIND_STRUCTURES,
                                                     { filter: bldg => (
                                                       bldg.structureType === STRUCTURE_CONTAINER &&
                                                       bldg.store[RESOURCE_ENERGY] < bldg.storeCapacity
                                                     )
                                                     });

            if(target) {
              if(creep.transfer(target, RESOURCE_ENERGY) == ERR_NOT_IN_RANGE) {
                creep.doMove(target, {visualizePathStyle: {stroke: '#ffffff'}});
              }
            }
          }
        },

        // Path the creep to the nearest workable resource
        harvestResource: function(creep) {
          let source;

          if (creep.memory.reservedSource) {
            source = Game.getObjectById(creep.memory.reservedSource);
          }
          else {
            source = creep.pos.findClosestByPath(FIND_SOURCES,
                                                 { filter: (source) => source.energy > 0 && hasWorkableSpots(source) });
          }

          // reserve source if it's not already reserved
          if(creep.memory.reservedSource != source.id) {
            let workableSpots = sourceMem(source, 'workableSpots');
            if(workableSpots > 0) {
              sourceMem(source, 'workableSpots', workableSpots - 1);
              creep.memory.reservedSource = source.id;
            }
            else {
              source = null; // clear source if it's not workable
            }
          }

          if(source) {
            if(creep.harvest(source) == ERR_NOT_IN_RANGE) {
              creep.doMove(source, {visualizePathStyle: {stroke: '#00aaff'}});
            }
          }
        }
      };

module.exports = role;
<commit_msg>Check if source exists before checking its id<commit_after>const { filter: _filter, reduce: _reduce } = require('lodash');

Memory.sources || (Memory.sources = {});

function sourceMem(source, key, value) {
  Memory.sources[source.id] || (Memory.sources[source.id] = {});
  if(key !== undefined) {
    if(value !== undefined) {
      Memory.sources[source.id][key] = value;
    }

    return Memory.sources[source.id][key];
  }

  return Memory.sources[source.id];
}

function hasWorkableSpots(source) {
  if(!sourceMem(source, 'maxWorkableSpots')) {
    // determine number of workable spots
    var terrain = source.room.lookForAtArea(LOOK_TERRAIN, source.pos.y - 1, source.pos.x - 1, source.pos.y + 1, source.pos.x + 1, true);
    terrain = _filter(terrain, function(t) { return t.terrain != 'wall' });

    sourceMem(source, 'maxWorkableSpots', terrain.length);
    sourceMem(source, 'workableSpots', terrain.length);
  }
  else if(sourceMem(source, 'recalc')) {
    // count creeps that have reserved this source
    let reservedSpots = _reduce(Memory.creeps, (count, _, creep) => ( count + (creep.reservedSource === source.id )), 0);
    sourceMem(source, 'workableSpots', reservedSpots);
    sourceMem(source, 'recalc', false);
  }

  return sourceMem(source, 'workableSpots') > 0;
}

const { harvestResource, checkinSpot } = require('utils'),
      role = {

        /** @param {Creep} creep **/
        run: function(creep) {
          if(creep.carryCapacity !== 0 && creep.memory.harvesting && creep.carry.energy === creep.carryCapacity) {
            creep.memory.harvesting = false;
          }

          if(!creep.memory.harvesting && creep.carry.energy === 0) {
            creep.memory.harvesting = true;
          }

          if(creep.memory.harvesting) {
            role.harvestResource(creep);
          }
          else {
            // Find the nearest non-full container
            let target = creep.pos.findClosestByPath(FIND_STRUCTURES,
                                                     { filter: bldg => (
                                                       bldg.structureType === STRUCTURE_CONTAINER &&
                                                       bldg.store[RESOURCE_ENERGY] < bldg.storeCapacity
                                                     )
                                                     });

            if(target) {
              if(creep.transfer(target, RESOURCE_ENERGY) == ERR_NOT_IN_RANGE) {
                creep.doMove(target, {visualizePathStyle: {stroke: '#ffffff'}});
              }
            }
          }
        },

        // Path the creep to the nearest workable resource
        harvestResource: function(creep) {
          let source;

          if (creep.memory.reservedSource) {
            source = Game.getObjectById(creep.memory.reservedSource);
          }
          else {
            source = creep.pos.findClosestByPath(FIND_SOURCES,
                                                 { filter: (source) => source.energy > 0 && hasWorkableSpots(source) });
          }

          // reserve source if it's not already reserved
          if(source && creep.memory.reservedSource != source.id) {
            let workableSpots = sourceMem(source, 'workableSpots');
            if(workableSpots > 0) {
              sourceMem(source, 'workableSpots', workableSpots - 1);
              creep.memory.reservedSource = source.id;
            }
            else {
              source = null; // clear source if it's not workable
            }
          }

          if(source) {
            if(creep.harvest(source) == ERR_NOT_IN_RANGE) {
              creep.doMove(source, {visualizePathStyle: {stroke: '#00aaff'}});
            }
          }
        }
      };

module.exports = role;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright (c) 2015 MediaMath Inc. All rights reserved.
 * This code may only be used under the BSD style license found at http://mediamath.github.io/strand/LICENSE.txt

*/

(function (scope) {

	var _cache = [];
	var _instances = [];
	var _il = 0;
	var _backoff = 0;
	var _backoff_inc = 200;
	var _backoff_max = 2000;

	function _addInstance(instance) {
		_instances.push(instance);
		_cache.push({
			width:instance.offsetWidth,
			height:instance.offsetHeight,
		});
		_il++;
		requestAnimationFrame(_measure);
	}

	function _removeInstance(instance) {
		_instances.splice(_instances.indexOf(instance), 1);
		_il--;
	}

	function _measure() {
		_backoff += _backoff_inc;
		if (_backoff > _backoff_max) _backoff = _backoff_max;

		for(var i=0; i<_il; i++) {
			var inst = _instances[i];
			var w = inst.offsetWidth;
			var h = inst.offsetHeight;
			var cache = _cache[i];
			if (cache.width !== w) {
				cache.width = w;
				inst.elementResize(w,null);
				_backoff = 0;
			}
			if (cache.height !== h) {
				cache.height = h;
				inst.elementResize(null, h);
				_backoff = 0;
			}
		}
		if (_il) {
			setTimeout(function() {
				requestAnimationFrame(_measure);
			},_backoff);
		}
	}

	scope.Resizable = {

		properties: {
			resizeTarget:{
				type:Object,
				value: function() { return this; }
			}
		},

		attached:function() {
			_addInstance(this.resizeTarget);
		},

		detached:function() {
			_removeInstance(this.resizeTarget);
		},

		elementResize: function(e) {
			//stub for behavior
		}

	};

})(window.StrandTraits = window.StrandTraits || {});
<commit_msg>better target support on resizable<commit_after>/**
 * @license
 * Copyright (c) 2015 MediaMath Inc. All rights reserved.
 * This code may only be used under the BSD style license found at http://mediamath.github.io/strand/LICENSE.txt

*/

(function (scope) {

	var _instances = [];
	var _il = 0;
	var _backoff = 0;
	var _backoff_inc = 200;
	var _backoff_max = 2000;

	function _addInstance(instance, target) {
		_instances.push({
			target:target,
			instance: instance,
			width:target.offsetWidth,
			height:target.offsetHeight,
		});
		_il++;
		requestAnimationFrame(_measure);
	}

	function _removeInstance(instance) {
		_instances.filter(function(meta) {
			return meta.instance === instance;
		}).forEach(function(target) {
			_instances.splice(_instances.indexOf(target), 1);
			_il--;
		});
	}

	function _measure() {
		_backoff += _backoff_inc;
		if (_backoff > _backoff_max) _backoff = _backoff_max;

		for(var i=0; i<_il; i++) {
			var meta = _instances[i];
			var w = meta.target.offsetWidth;
			var h = meta.target.offsetHeight;
			if (meta.width !== w) {
				meta.width = w;
				meta.instance.elementResize(w,null,meta.target);
				_backoff = 0;
			}
			if (meta.height !== h) {
				meta.height = h;
				meta.instance.elementResize(null, h, meta.target);
				_backoff = 0;
			}
		}
		if (_il) {
			setTimeout(function() {
				requestAnimationFrame(_measure);
			},_backoff);
		}
	}

	scope.Resizable = {

		properties: {
			resizeTarget:{
				type:Object,
				value: function() { return this; }
			},
		},

		attached:function() {
			_addInstance(this, this.resizeTarget);
		},

		detached:function() {
			_removeInstance(this);
		},

		elementResize: function(e) {
			//stub for behavior
		}

	};

})(window.StrandTraits = window.StrandTraits || {});
<|endoftext|>


<commit_before>let objectValues = require('lodash').values;

class Versioning {
    /**
     * Create a new Versioning instance.
     *
     * @param {Array}  manualFiles
     * @param {object} manifest
     * @param {string} publicPath
     */
    constructor(manualFiles = [], manifest) {
        this.manualFiles = manualFiles.map(file => new File(file));
        this.manifest = manifest;

        this.registerEvents();
    }

    /**
     * Register all relevant event listeners.
     */
    registerEvents() {
        global.events.listen('standalone-sass-compiled', compiledFile => {
            compiledFile.rename(compiledFile.versionedPath());

            // this.prune();
        });

        global.events.listen(
            ['build', 'combined'], () => this.prune()
        );
    }

    /**
     * Register a watcher for any files that aren't
     * included in Webpack's core bundle process.
     */
    watch() {
        if (! process.argv.includes('--watch')) return this;

        this.manualFiles.forEach(file => {
            file.watch(file => {
                // Delete the old versioned file.
                File.find(
                    path.join(global.options.publicPath, this.manifest.get(file))
                ).delete();

                // And then whip up a new one.
                file.version();

                this.prune();
            });
        });

        return this;
    }

    /**
     * Create all hashed files requested by the user,
     * when they called mix.version(['file']);
     */
    writeHashedFiles() {
        this.manualFiles.forEach(file => file.version());

        return this;
    }

    /**
     * The user may optionally add extra files to be
     * versioned. Here, we'll manually add those to
     * Mix's manifest file.
     */
    addManualFilesToManifest() {
        this.manualFiles.forEach(file => this.manifest.add(file));
    }

    /**
     * Replace all old hashed files with the new versions.
     */
    prune() {
        this.writeHashedFiles().addManualFilesToManifest();

        let cachedFiles = objectValues(this.manifest.cache);
        let currentFiles = objectValues(this.manifest.get());

        cachedFiles
            .filter(file => ! currentFiles.includes(file))
            .map(file => {
                return file.startsWith(global.options.publicPath)
                    ? file
                    : path.join(global.options.publicPath, file);
            })
            .forEach(file => {
                console.log('removing ' + file);
                this.manifest.remove(file)
            });

        this.manifest.refresh();
        this.manifest.cache = currentFiles;

        return currentFiles;
    }
}

module.exports = Versioning;
<commit_msg>Uncomment<commit_after>let objectValues = require('lodash').values;

class Versioning {
    /**
     * Create a new Versioning instance.
     *
     * @param {Array}  manualFiles
     * @param {object} manifest
     * @param {string} publicPath
     */
    constructor(manualFiles = [], manifest) {
        this.manualFiles = manualFiles.map(file => new File(file));
        this.manifest = manifest;

        this.registerEvents();
    }

    /**
     * Register all relevant event listeners.
     */
    registerEvents() {
        global.events.listen('standalone-sass-compiled', compiledFile => {
            compiledFile.rename(compiledFile.versionedPath());

            this.prune();
        });

        global.events.listen(
            ['build', 'combined'], () => this.prune()
        );
    }

    /**
     * Register a watcher for any files that aren't
     * included in Webpack's core bundle process.
     */
    watch() {
        if (! process.argv.includes('--watch')) return this;

        this.manualFiles.forEach(file => {
            file.watch(file => {
                // Delete the old versioned file.
                File.find(
                    path.join(global.options.publicPath, this.manifest.get(file))
                ).delete();

                // And then whip up a new one.
                file.version();

                this.prune();
            });
        });

        return this;
    }

    /**
     * Create all hashed files requested by the user,
     * when they called mix.version(['file']);
     */
    writeHashedFiles() {
        this.manualFiles.forEach(file => file.version());

        return this;
    }

    /**
     * The user may optionally add extra files to be
     * versioned. Here, we'll manually add those to
     * Mix's manifest file.
     */
    addManualFilesToManifest() {
        this.manualFiles.forEach(file => this.manifest.add(file));
    }

    /**
     * Replace all old hashed files with the new versions.
     */
    prune() {
        this.writeHashedFiles().addManualFilesToManifest();

        let cachedFiles = objectValues(this.manifest.cache);
        let currentFiles = objectValues(this.manifest.get());

        cachedFiles
            .filter(file => ! currentFiles.includes(file))
            .map(file => {
                return file.startsWith(global.options.publicPath)
                    ? file
                    : path.join(global.options.publicPath, file);
            })
            .forEach(file => {
                console.log('removing ' + file);
                this.manifest.remove(file)
            });

        this.manifest.refresh();
        this.manifest.cache = currentFiles;

        return currentFiles;
    }
}

module.exports = Versioning;
<|endoftext|>


<commit_before>import React from 'react'
import SanityVision from './SanityVision'
import {route} from 'part:@sanity/base/router'

export default {
  router: route('/*'),
  name: 'vision',
  title: 'Vision',
  icon: function VisionIcon() {
    return <div />
  },
  component: SanityVision
}
<commit_msg>Remove icon from vision tool<commit_after>import SanityVision from './SanityVision'
import {route} from 'part:@sanity/base/router'

export default {
  router: route('/*'),
  name: 'vision',
  title: 'Vision',
  component: SanityVision
}
<|endoftext|>


<commit_before>/**
 * The following features are still outstanding: animation as a
 * function, placement as a function, inside, support for more triggers than
 * just mouse enter/leave, html tooltips, and selector delegation.
 */
angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )

/**
 * The $tooltip service creates tooltip- and popover-like directives as well as
 * houses global options for them.
 */
.provider( '$tooltip', function () {
  // The default options tooltip and popover.
  var defaultOptions = {
    placement: 'top',
    animation: true,
    popupDelay: 0
  };

  // Default hide triggers for each show trigger
  var triggerMap = {
    'mouseenter': 'mouseleave',
    'click': 'click',
    'focus': 'blur'
  };

  // The options specified to the provider globally.
  var globalOptions = {};

  /**
   * `options({})` allows global configuration of all tooltips in the
   * application.
   *
   *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
   *     // place tooltips left instead of top by default
   *     $tooltipProvider.options( { placement: 'left' } );
   *   });
   */
	this.options = function( value ) {
		angular.extend( globalOptions, value );
	};

  /**
   * This allows you to extend the set of trigger mappings available. E.g.:
   *
   *   $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
   */
  this.setTriggers = function setTriggers ( triggers ) {
    angular.extend( triggerMap, triggers );
  };

  /**
   * This is a helper function for translating camel-case to snake-case.
   */
  function snake_case(name){
    var regexp = /[A-Z]/g;
    var separator = '-';
    return name.replace(regexp, function(letter, pos) {
      return (pos ? separator : '') + letter.toLowerCase();
    });
  }

  /**
   * Returns the actual instance of the $tooltip service.
   * TODO support multiple triggers
   */
  this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) {
    return function $tooltip ( type, prefix, defaultTriggerShow ) {
      var options = angular.extend( {}, defaultOptions, globalOptions );

      /**
       * Returns an object of show and hide triggers.
       *
       * If a trigger is supplied,
       * it is used to show the tooltip; otherwise, it will use the `trigger`
       * option passed to the `$tooltipProvider.options` method; else it will
       * default to the trigger supplied to this directive factory.
       *
       * The hide trigger is based on the show trigger. If the `trigger` option
       * was passed to the `$tooltipProvider.options` method, it will use the
       * mapped trigger from `triggerMap` or the passed trigger if the map is
       * undefined; otherwise, it uses the `triggerMap` value of the show
       * trigger; else it will just use the show trigger.
       */
      function getTriggers ( trigger ) {
        var show = trigger || options.trigger || defaultTriggerShow;
        var hide = triggerMap[show] || show;
        return {
          show: show,
          hide: hide
        };
      }

      var directiveName = snake_case( type );

      var startSym = $interpolate.startSymbol();
      var endSym = $interpolate.endSymbol();
      var template =
        '
'+
        '
';

      return {
        restrict: 'EA',
        scope: true,
        compile: function (tElem, tAttrs) {
          var tooltipLinker = $compile( template );

          return function link ( scope, element, attrs ) {
            var tooltip;
            var transitionTimeout;
            var popupTimeout;
            var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
            var triggers = getTriggers( undefined );
            var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);

            var positionTooltip = function () {

              var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody);
              ttPosition.top += 'px';
              ttPosition.left += 'px';

              // Now set the calculated positioning.
              tooltip.css( ttPosition );
            };

            // By default, the tooltip is not open.
            // TODO add ability to start tooltip opened
            scope.tt_isOpen = false;

            function toggleTooltipBind () {
              if ( ! scope.tt_isOpen ) {
                showTooltipBind();
              } else {
                hideTooltipBind();
              }
            }

            // Show the tooltip with delay if specified, otherwise show it immediately
            function showTooltipBind() {
              if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
                return;
              }

              prepareTooltip();

              if ( scope.tt_popupDelay ) {
                // Do nothing if the tooltip was already scheduled to pop-up.
                // This happens if show is triggered multiple times before any hide is triggered.
                if (!popupTimeout) {
                  popupTimeout = $timeout( show, scope.tt_popupDelay, false );
                  popupTimeout.then(function(reposition){reposition();});
                }
              } else {
                show()();
              }
            }

            function hideTooltipBind () {
              scope.$apply(function () {
                hide();
              });
            }

            // Show the tooltip popup element.
            function show() {

              popupTimeout = null;

              // If there is a pending remove transition, we must cancel it, lest the
              // tooltip be mysteriously removed.
              if ( transitionTimeout ) {
                $timeout.cancel( transitionTimeout );
                transitionTimeout = null;
              }

              // Don't show empty tooltips.
              if ( ! scope.tt_content ) {
                return angular.noop;
              }

              createTooltip();

              // Set the initial positioning.
              tooltip.css({ top: 0, left: 0, display: 'block' });

              // Now we add it to the DOM because need some info about it. But it's not
              // visible yet anyway.
              if ( appendToBody ) {
                  $document.find( 'body' ).append( tooltip );
              } else {
                element.after( tooltip );
              }

              positionTooltip();

              // And show the tooltip.
              scope.tt_isOpen = true;
              scope.$digest(); // digest required as $apply is not called

              // Return positioning function as promise callback for correct
              // positioning after draw.
              return positionTooltip;
            }

            // Hide the tooltip popup element.
            function hide() {
              // First things first: we don't show it anymore.
              scope.tt_isOpen = false;

              //if tooltip is going to be shown after delay, we must cancel this
              $timeout.cancel( popupTimeout );
              popupTimeout = null;

              // And now we remove it from the DOM. However, if we have animation, we
              // need to wait for it to expire beforehand.
              // FIXME: this is a placeholder for a port of the transitions library.
              if ( scope.tt_animation ) {
                if (!transitionTimeout) {
                  transitionTimeout = $timeout(removeTooltip, 500);
                }
              } else {
                removeTooltip();
              }
            }

            function createTooltip() {
              // There can only be one tooltip element per directive shown at once.
              if (tooltip) {
                removeTooltip();
              }
              tooltip = tooltipLinker(scope);
            }

            function removeTooltip() {
              transitionTimeout = null;
              if (tooltip) {
                tooltip.remove();
                tooltip = null;
              }
            }

            function prepareTooltip() {
              prepPlacement();
              prepPopupDelay();
            }

            /**
             * Observe the relevant attributes.
             */
            attrs.$observe( type, function ( val ) {
              scope.tt_content = val;

              if (!val && scope.tt_isOpen ) {
                hide();
              }
            });

            attrs.$observe( prefix+'Title', function ( val ) {
              scope.tt_title = val;
            });

            function prepPlacement() {
              var val = attrs[ prefix + 'Placement' ];
              scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
            }

            function prepPopupDelay() {
              var val = attrs[ prefix + 'PopupDelay' ];
              var delay = parseInt( val, 10 );
              scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
            }

            var unregisterTriggers = function () {
              element.unbind(triggers.show, showTooltipBind);
              element.unbind(triggers.hide, hideTooltipBind);
            };

            function prepTriggers() {
              var val = attrs[ prefix + 'Trigger' ];
              unregisterTriggers();

              triggers = getTriggers( val );

              if ( triggers.show === triggers.hide ) {
                element.bind( triggers.show, toggleTooltipBind );
              } else {
                element.bind( triggers.show, showTooltipBind );
                element.bind( triggers.hide, hideTooltipBind );
              }
            }
            prepTriggers();

            var animation = scope.$eval(attrs[prefix + 'Animation']);
            scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation;

            attrs.$observe( prefix+'AppendToBody', function ( val ) {
              appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
            });

            // if a tooltip is attached to  we need to remove it on
            // location change as its parent scope will probably not be destroyed
            // by the change.
            if ( appendToBody ) {
              scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
              if ( scope.tt_isOpen ) {
                hide();
              }
            });
            }

            // Make sure tooltip is destroyed and removed.
            scope.$on('$destroy', function onDestroyTooltip() {
              $timeout.cancel( transitionTimeout );
              $timeout.cancel( popupTimeout );
              unregisterTriggers();
              removeTooltip();
            });
          };
        }
      };
    };
  }];
})

.directive( 'tooltipPopup', function () {
  return {
    restrict: 'EA',
    replace: true,
    scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
    templateUrl: 'template/tooltip/tooltip-popup.html'
  };
})

.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
  return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
}])

.directive( 'tooltipHtmlUnsafePopup', function () {
  return {
    restrict: 'EA',
    replace: true,
    scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
    templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
  };
})

.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
  return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
}]);
<commit_msg>fix(tooltip): evaluate appendToBody on init<commit_after>/**
 * The following features are still outstanding: animation as a
 * function, placement as a function, inside, support for more triggers than
 * just mouse enter/leave, html tooltips, and selector delegation.
 */
angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )

/**
 * The $tooltip service creates tooltip- and popover-like directives as well as
 * houses global options for them.
 */
.provider( '$tooltip', function () {
  // The default options tooltip and popover.
  var defaultOptions = {
    placement: 'top',
    animation: true,
    popupDelay: 0
  };

  // Default hide triggers for each show trigger
  var triggerMap = {
    'mouseenter': 'mouseleave',
    'click': 'click',
    'focus': 'blur'
  };

  // The options specified to the provider globally.
  var globalOptions = {};

  /**
   * `options({})` allows global configuration of all tooltips in the
   * application.
   *
   *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
   *     // place tooltips left instead of top by default
   *     $tooltipProvider.options( { placement: 'left' } );
   *   });
   */
	this.options = function( value ) {
		angular.extend( globalOptions, value );
	};

  /**
   * This allows you to extend the set of trigger mappings available. E.g.:
   *
   *   $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
   */
  this.setTriggers = function setTriggers ( triggers ) {
    angular.extend( triggerMap, triggers );
  };

  /**
   * This is a helper function for translating camel-case to snake-case.
   */
  function snake_case(name){
    var regexp = /[A-Z]/g;
    var separator = '-';
    return name.replace(regexp, function(letter, pos) {
      return (pos ? separator : '') + letter.toLowerCase();
    });
  }

  /**
   * Returns the actual instance of the $tooltip service.
   * TODO support multiple triggers
   */
  this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) {
    return function $tooltip ( type, prefix, defaultTriggerShow ) {
      var options = angular.extend( {}, defaultOptions, globalOptions );

      /**
       * Returns an object of show and hide triggers.
       *
       * If a trigger is supplied,
       * it is used to show the tooltip; otherwise, it will use the `trigger`
       * option passed to the `$tooltipProvider.options` method; else it will
       * default to the trigger supplied to this directive factory.
       *
       * The hide trigger is based on the show trigger. If the `trigger` option
       * was passed to the `$tooltipProvider.options` method, it will use the
       * mapped trigger from `triggerMap` or the passed trigger if the map is
       * undefined; otherwise, it uses the `triggerMap` value of the show
       * trigger; else it will just use the show trigger.
       */
      function getTriggers ( trigger ) {
        var show = trigger || options.trigger || defaultTriggerShow;
        var hide = triggerMap[show] || show;
        return {
          show: show,
          hide: hide
        };
      }

      var directiveName = snake_case( type );

      var startSym = $interpolate.startSymbol();
      var endSym = $interpolate.endSymbol();
      var template =
        '
'+
        '
';

      return {
        restrict: 'EA',
        scope: true,
        compile: function (tElem, tAttrs) {
          var tooltipLinker = $compile( template );

          return function link ( scope, element, attrs ) {
            var tooltip;
            var transitionTimeout;
            var popupTimeout;
            var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
            var triggers = getTriggers( undefined );
            var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);

            var positionTooltip = function () {

              var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody);
              ttPosition.top += 'px';
              ttPosition.left += 'px';

              // Now set the calculated positioning.
              tooltip.css( ttPosition );
            };

            // By default, the tooltip is not open.
            // TODO add ability to start tooltip opened
            scope.tt_isOpen = false;

            function toggleTooltipBind () {
              if ( ! scope.tt_isOpen ) {
                showTooltipBind();
              } else {
                hideTooltipBind();
              }
            }

            // Show the tooltip with delay if specified, otherwise show it immediately
            function showTooltipBind() {
              if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
                return;
              }

              prepareTooltip();

              if ( scope.tt_popupDelay ) {
                // Do nothing if the tooltip was already scheduled to pop-up.
                // This happens if show is triggered multiple times before any hide is triggered.
                if (!popupTimeout) {
                  popupTimeout = $timeout( show, scope.tt_popupDelay, false );
                  popupTimeout.then(function(reposition){reposition();});
                }
              } else {
                show()();
              }
            }

            function hideTooltipBind () {
              scope.$apply(function () {
                hide();
              });
            }

            // Show the tooltip popup element.
            function show() {

              popupTimeout = null;

              // If there is a pending remove transition, we must cancel it, lest the
              // tooltip be mysteriously removed.
              if ( transitionTimeout ) {
                $timeout.cancel( transitionTimeout );
                transitionTimeout = null;
              }

              // Don't show empty tooltips.
              if ( ! scope.tt_content ) {
                return angular.noop;
              }

              createTooltip();

              // Set the initial positioning.
              tooltip.css({ top: 0, left: 0, display: 'block' });

              // Now we add it to the DOM because need some info about it. But it's not
              // visible yet anyway.
              if ( appendToBody ) {
                  $document.find( 'body' ).append( tooltip );
              } else {
                element.after( tooltip );
              }

              positionTooltip();

              // And show the tooltip.
              scope.tt_isOpen = true;
              scope.$digest(); // digest required as $apply is not called

              // Return positioning function as promise callback for correct
              // positioning after draw.
              return positionTooltip;
            }

            // Hide the tooltip popup element.
            function hide() {
              // First things first: we don't show it anymore.
              scope.tt_isOpen = false;

              //if tooltip is going to be shown after delay, we must cancel this
              $timeout.cancel( popupTimeout );
              popupTimeout = null;

              // And now we remove it from the DOM. However, if we have animation, we
              // need to wait for it to expire beforehand.
              // FIXME: this is a placeholder for a port of the transitions library.
              if ( scope.tt_animation ) {
                if (!transitionTimeout) {
                  transitionTimeout = $timeout(removeTooltip, 500);
                }
              } else {
                removeTooltip();
              }
            }

            function createTooltip() {
              // There can only be one tooltip element per directive shown at once.
              if (tooltip) {
                removeTooltip();
              }
              tooltip = tooltipLinker(scope);
            }

            function removeTooltip() {
              transitionTimeout = null;
              if (tooltip) {
                tooltip.remove();
                tooltip = null;
              }
            }

            function prepareTooltip() {
              prepPlacement();
              prepPopupDelay();
            }

            /**
             * Observe the relevant attributes.
             */
            attrs.$observe( type, function ( val ) {
              scope.tt_content = val;

              if (!val && scope.tt_isOpen ) {
                hide();
              }
            });

            attrs.$observe( prefix+'Title', function ( val ) {
              scope.tt_title = val;
            });

            function prepPlacement() {
              var val = attrs[ prefix + 'Placement' ];
              scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
            }

            function prepPopupDelay() {
              var val = attrs[ prefix + 'PopupDelay' ];
              var delay = parseInt( val, 10 );
              scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
            }

            var unregisterTriggers = function () {
              element.unbind(triggers.show, showTooltipBind);
              element.unbind(triggers.hide, hideTooltipBind);
            };

            function prepTriggers() {
              var val = attrs[ prefix + 'Trigger' ];
              unregisterTriggers();

              triggers = getTriggers( val );

              if ( triggers.show === triggers.hide ) {
                element.bind( triggers.show, toggleTooltipBind );
              } else {
                element.bind( triggers.show, showTooltipBind );
                element.bind( triggers.hide, hideTooltipBind );
              }
            }
            prepTriggers();

            var animation = scope.$eval(attrs[prefix + 'Animation']);
            scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation;

            var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']);
            appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;

            // if a tooltip is attached to  we need to remove it on
            // location change as its parent scope will probably not be destroyed
            // by the change.
            if ( appendToBody ) {
              scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
              if ( scope.tt_isOpen ) {
                hide();
              }
            });
            }

            // Make sure tooltip is destroyed and removed.
            scope.$on('$destroy', function onDestroyTooltip() {
              $timeout.cancel( transitionTimeout );
              $timeout.cancel( popupTimeout );
              unregisterTriggers();
              removeTooltip();
            });
          };
        }
      };
    };
  }];
})

.directive( 'tooltipPopup', function () {
  return {
    restrict: 'EA',
    replace: true,
    scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
    templateUrl: 'template/tooltip/tooltip-popup.html'
  };
})

.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
  return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
}])

.directive( 'tooltipHtmlUnsafePopup', function () {
  return {
    restrict: 'EA',
    replace: true,
    scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
    templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
  };
})

.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
  return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
}]);
<|endoftext|>