<commit_before>/*
 * Copyright 2017-present, Converse.AI
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

class ChannelMessage {
  constructor() {}
}

class ChannelMessageText extends ChannelMessage {
  constructor(text) {
    super();
    this.text = text;
  }

  setText(text) {
    this.text = text;
  }
}

class ChannelMessageMedia extends ChannelMessage {
  constructor(media) {
    super();
    this.media = media;
  }

  setMedia(media) {
    this.media = media;
  }
}

class ChannelMessageConversation extends ChannelMessage {
  constructor(conversation) {
    super();
    this.conversation = conversation;
  }

  setConversation(conversation) {
    this.conversation = conversation;
  }
}

module.exports = {
  Text: ChannelMessageText,
  Media: ChannelMessageMedia,
  Conversation: ChannelMessageConversation,
}
<commit_msg>adding top level setters for media and conversation messages.<commit_after>/*
 * Copyright 2017-present, Converse.AI
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

class ChannelMessage {
  constructor() {}
}

class ChannelMessageText extends ChannelMessage {
  constructor(text) {
    super();
    this.text = text;
  }

  setText(text) {
    this.text = text;
  }
}

class ChannelMessageMedia extends ChannelMessage {
  constructor(media) {
    super();
    this.media = media;
  }

  setType(type) {
    this.media.type = type;
  }

  setEntity(entity) {
    this.media.entity = entity;
  }
}

class ChannelMessageConversation extends ChannelMessage {
  constructor(conversation) {
    super();
    this.conversation = conversation;
  }

  setIntent(intent, isNew = false) {
    if (isNew) {
      this.conversation.new = intent;
    } else {
      this.conversation.existing = intent;
    }
  }

  setEntityData(data) {
    this.conversation.entityData = data;
  }

  setRuntimeCTX(runtimeCTX) {
    this.conversation.runtimeCTX = runtimeCTX;
  }
}

module.exports = {
  Text: ChannelMessageText,
  Media: ChannelMessageMedia,
  Conversation: ChannelMessageConversation,
}
<|endoftext|>


<commit_before>import _ from 'messages'
import Component from 'base-component'
import Icon from 'icon'
import Link from 'react-router/lib/Link'
import map from 'lodash/map'
import React from 'react'
import Tooltip from 'tooltip'
import { Button } from 'react-bootstrap-4/lib'
import { connectStore } from 'utils'

@connectStore([
  'user'
], {
  withRef: true
})
export default class Menu extends Component {
  get height () {
    return this.refs.content.offsetHeight
  }

  _toggleCollapsed = () => this.setState({ collapsed: !this.state.collapsed })

  render () {
    const { user } = this.props
    const items = [
      { to: '/home', icon: 'home', label: 'homePage' },
      { to: '/dashboard/overview', icon: 'dashboard', label: 'dashboardPage', subMenu: [
        { to: '/dashboard/overview', icon: 'dashboard-overview', label: 'overviewDashboardPage' },
        { to: '/dashboard/visualization', icon: 'dashboard-visualization', label: 'overviewVisualizationDashboardPage' },
        { to: '/dashboard/stats', icon: 'dashboard-stats', label: 'overviewStatsDashboardPage' },
        { to: '/dashboard/health', icon: 'dashboard-health', label: 'overviewHealthDashboardPage' }
      ]},
      { to: '/self/dashboard', icon: 'self-service', label: 'selfServicePage', subMenu: [
        { to: '/self/dashboard', icon: 'self-service-dashboard', label: 'selfServiceDashboardPage' },
        { to: '/self/admin', icon: 'self-service-admin', label: 'selfServiceAdminPage' }
      ]},
      { to: '/backup/overview', icon: 'backup', label: 'backupPage', subMenu: [
        { to: '/backup/overview', icon: 'backup-overview', label: 'backupOverviewPage' },
        { to: '/backup/new', icon: 'backup-new', label: 'backupNewPage' },
        { to: '/backup/restore', icon: 'backup-restore', label: 'backupRestorePage' }
      ]},
      { to: '/update', icon: 'update', label: 'updatePage' },
      { to: '/settings/servers', icon: 'settings', label: 'settingsPage', subMenu: [
        { to: '/settings/servers', icon: 'settings-servers', label: 'settingsServersPage' },
        { to: '/settings/users', icon: 'settings-users', label: 'settingsUsersPage' },
        { to: '/settings/groups', icon: 'settings-groups', label: 'settingsGroupsPage' },
        { to: '/settings/acls', icon: 'settings-acls', label: 'settingsAclsPage' },
        { to: '/settings/remotes', icon: 'backup-remotes', label: 'backupRemotesPage' },
        { to: '/settings/plugins', icon: 'settings-plugins', label: 'settingsPluginsPage' }
      ]},
      { to: '/about', icon: 'about', label: 'aboutPage' },
      { to: '/new/vm', icon: 'new', label: 'newMenu', subMenu: [
        { to: '/new/vm', icon: 'new-vm', label: 'newVmPage' },
        { to: '/new/sr', icon: 'new-sr', label: 'newSrPage' },
        { to: '/settings/servers', icon: 'settings-servers', label: 'newServerPage' },
        { to: '/import', icon: 'new-import', label: 'newImport' }
      ]}
    ]

    return

            {this.state.collapsed
              ? XO
              : Xen Orchestra
            }

          <Button onClick={this._toggleCollapsed}>
            <Icon icon='menu-collapse' size='lg' fixedWidth />
          </Button>

        {map(items, (item, index) =>
          <MenuLinkItem key={index} item={item} collapsed={this.state.collapsed} />
        )}

          <Button className='nav-link' style={{width: '100%'}}>
            <Icon icon='sign-out' size='lg' fixedWidth />
            {!this.state.collapsed &&    }
            {!this.state.collapsed && _('signOut')}
          </Button>

          <Link className='nav-link' style={{display: 'flex'}} to={'/user'}>

              <Tooltip content={user ? user.email : ''}>
                <Icon icon='user' size='lg' />
              </Tooltip>

          </Link>

  }
}

const MenuLinkItem = props => {
  const { item, collapsed } = props
  const { to, icon, label, subMenu } = item

  return

    <Link activeClassName='active' className='nav-link' to={to}>
      <Icon icon={`menu-${icon}`} size='lg' fixedWidth />
      {!collapsed &&    }
      {!collapsed && _(label)}
    </Link>
    {subMenu && <SubMenu items={subMenu} />}

}

const SubMenu = props => {
  return

    {map(props.items, (item, index) => (

        <Link activeClassName='active' className='nav-link' to={item.to}>
          <Icon icon={`menu-${item.icon}`} size='lg' fixedWidth />
          {_(item.label)}
        </Link>

    ))}

}
<commit_msg>fix(Menu): always update to avoid issues with router and intl<commit_after>import _ from 'messages'
import Component from 'base-component'
import Icon from 'icon'
import Link from 'react-router/lib/Link'
import map from 'lodash/map'
import React from 'react'
import Tooltip from 'tooltip'
import { Button } from 'react-bootstrap-4/lib'
import { connectStore } from 'utils'

@connectStore([
  'user'
], {
  withRef: true
})
export default class Menu extends Component {
  get height () {
    return this.refs.content.offsetHeight
  }

  // FIXME: remove when fixed in React.
  //
  // There are currently issues between context updates (used by
  // router and intl) and pure components.
  shouldComponentUpdate () {
    return true
  }

  _toggleCollapsed = () => this.setState({ collapsed: !this.state.collapsed })

  render () {
    const { user } = this.props
    const items = [
      { to: '/home', icon: 'home', label: 'homePage' },
      { to: '/dashboard/overview', icon: 'dashboard', label: 'dashboardPage', subMenu: [
        { to: '/dashboard/overview', icon: 'dashboard-overview', label: 'overviewDashboardPage' },
        { to: '/dashboard/visualization', icon: 'dashboard-visualization', label: 'overviewVisualizationDashboardPage' },
        { to: '/dashboard/stats', icon: 'dashboard-stats', label: 'overviewStatsDashboardPage' },
        { to: '/dashboard/health', icon: 'dashboard-health', label: 'overviewHealthDashboardPage' }
      ]},
      { to: '/self/dashboard', icon: 'self-service', label: 'selfServicePage', subMenu: [
        { to: '/self/dashboard', icon: 'self-service-dashboard', label: 'selfServiceDashboardPage' },
        { to: '/self/admin', icon: 'self-service-admin', label: 'selfServiceAdminPage' }
      ]},
      { to: '/backup/overview', icon: 'backup', label: 'backupPage', subMenu: [
        { to: '/backup/overview', icon: 'backup-overview', label: 'backupOverviewPage' },
        { to: '/backup/new', icon: 'backup-new', label: 'backupNewPage' },
        { to: '/backup/restore', icon: 'backup-restore', label: 'backupRestorePage' }
      ]},
      { to: '/update', icon: 'update', label: 'updatePage' },
      { to: '/settings/servers', icon: 'settings', label: 'settingsPage', subMenu: [
        { to: '/settings/servers', icon: 'settings-servers', label: 'settingsServersPage' },
        { to: '/settings/users', icon: 'settings-users', label: 'settingsUsersPage' },
        { to: '/settings/groups', icon: 'settings-groups', label: 'settingsGroupsPage' },
        { to: '/settings/acls', icon: 'settings-acls', label: 'settingsAclsPage' },
        { to: '/settings/remotes', icon: 'backup-remotes', label: 'backupRemotesPage' },
        { to: '/settings/plugins', icon: 'settings-plugins', label: 'settingsPluginsPage' }
      ]},
      { to: '/about', icon: 'about', label: 'aboutPage' },
      { to: '/new/vm', icon: 'new', label: 'newMenu', subMenu: [
        { to: '/new/vm', icon: 'new-vm', label: 'newVmPage' },
        { to: '/new/sr', icon: 'new-sr', label: 'newSrPage' },
        { to: '/settings/servers', icon: 'settings-servers', label: 'newServerPage' },
        { to: '/import', icon: 'new-import', label: 'newImport' }
      ]}
    ]

    return

            {this.state.collapsed
              ? XO
              : Xen Orchestra
            }

          <Button onClick={this._toggleCollapsed}>
            <Icon icon='menu-collapse' size='lg' fixedWidth />
          </Button>

        {map(items, (item, index) =>
          <MenuLinkItem key={index} item={item} collapsed={this.state.collapsed} />
        )}

          <Button className='nav-link' style={{width: '100%'}}>
            <Icon icon='sign-out' size='lg' fixedWidth />
            {!this.state.collapsed &&    }
            {!this.state.collapsed && _('signOut')}
          </Button>

          <Link className='nav-link' style={{display: 'flex'}} to={'/user'}>

              <Tooltip content={user ? user.email : ''}>
                <Icon icon='user' size='lg' />
              </Tooltip>

          </Link>

  }
}

const MenuLinkItem = props => {
  const { item, collapsed } = props
  const { to, icon, label, subMenu } = item

  return

    <Link activeClassName='active' className='nav-link' to={to}>
      <Icon icon={`menu-${icon}`} size='lg' fixedWidth />
      {!collapsed &&    }
      {!collapsed && _(label)}
    </Link>
    {subMenu && <SubMenu items={subMenu} />}

}

const SubMenu = props => {
  return

    {map(props.items, (item, index) => (

        <Link activeClassName='active' className='nav-link' to={item.to}>
          <Icon icon={`menu-${item.icon}`} size='lg' fixedWidth />
          {_(item.label)}
        </Link>

    ))}

}
<|endoftext|>


<commit_before>(function ($, Vel) {
  'use strict';

  let _defaults = {
    data: [],
    placeholder: '',
    secondaryPlaceholder: '',
    autocompleteOptions: {},
    limit: Infinity,
    onChipAdd: null,
    onChipSelect: null,
    onChipDelete: null
  };

  /**
   * @typedef {Object} chip
   * @property {String} tag  chip tag string
   * @property {String} [image]  chip avatar image string
   */

  /**
   * @class
   *
   */
  class Chips {
    /**
     * Construct Chips instance and set up overlay
     * @constructor
     * @param {Element} el
     * @param {Object} options
     */
    constructor(el, options) {

      // If exists, destroy and reinitialize
      if (!!el.M_Chips) {
        el.M_Chips.destroy();
      }

      this.el = el;
      this.$el = $(el);
      this.el.M_Chips = this;

      /**
       * Options for the modal
       * @member Chips#options
       * @prop {Array} data
       * @prop {String} placeholder
       * @prop {String} secondaryPlaceholder
       * @prop {Object} autocompleteOptions
       */
      this.options = $.extend({}, Chips.defaults, options);

      this.$el.addClass('chips input-field');
      this.chipsData = [];
      this.$chips = $();
      this.$input = this.$el.find('input');
      this.$input.addClass('input');
      this.hasAutocomplete = Object.keys(this.options.autocompleteOptions).length > 0;

      // Set input id
      if (!this.$input.attr('id')) {
        this.$input.attr('id', M.guid());
      }

      // Render initial chips
      if (this.options.data.length) {
        this.chipsData = this.options.data;
        this._renderChips(this.chipsData);
      }

      // Setup autocomplete if needed
      if (this.hasAutocomplete) {
        this._setupAutocomplete();
      }

      this._setPlaceholder();
      this._setupLabel();
      this._setupEventHandlers();
    }

    static get defaults() {
      return _defaults;
    }

    static init($els, options) {
      let arr = [];
      $els.each(function() {
        arr.push(new Chips(this, options));
      });
      return arr;
    }

    /**
     * Get Instance
     */
    static getInstance(el) {
      let domElem = !!el.jquery ? el[0] : el;
      return domElem.M_Chips;
    }

    /**
     * Get Chips Data
     */
    getData() {
      return this.chipsData;
    }

    /**
     * Teardown component
     */
    destroy() {
      this._removeEventHandlers();
      this.$chips.remove();
      this.el.M_Chips = undefined;
    }

    /**
     * Setup Event Handlers
     */
    _setupEventHandlers() {
      this._handleChipClickBound = this._handleChipClick.bind(this);
      this._handleInputKeydownBound = this._handleInputKeydown.bind(this);
      this._handleInputFocusBound = this._handleInputFocus.bind(this);
      this._handleInputBlurBound = this._handleInputBlur.bind(this);

      this.el.addEventListener('click', this._handleChipClickBound);
      document.addEventListener('keydown', Chips._handleChipsKeydown);
      document.addEventListener('keyup', Chips._handleChipsKeyup);
      this.el.addEventListener('blur', Chips._handleChipsBlur, true);
      this.$input[0].addEventListener('focus', this._handleInputFocusBound);
      this.$input[0].addEventListener('blur', this._handleInputBlurBound);
      this.$input[0].addEventListener('keydown', this._handleInputKeydownBound);
    }

    /**
     * Remove Event Handlers
     */
    _removeEventHandlers() {
      this.el.removeEventListener('click', this._handleChipClickBound);
      document.removeEventListener('keydown', Chips._handleChipsKeydown);
      document.removeEventListener('keyup', Chips._handleChipsKeyup);
      this.el.removeEventListener('blur', Chips._handleChipsBlur, true);
      this.$input[0].removeEventListener('focus', this._handleInputFocusBound);
      this.$input[0].removeEventListener('blur', this._handleInputBlurBound);
      this.$input[0].removeEventListener('keydown', this._handleInputKeydownBound);
    }

    /**
     * Handle Chip Click
     * @param {Event} e
     */
    _handleChipClick(e) {
      let $chip = $(e.target).closest('.chip');
      let clickedClose = $(e.target).is('.close');
      if ($chip.length) {
        let index = $chip.index();
        if (clickedClose) {
          // delete chip
          this.deleteChip(index);
          this.$input[0].focus();

        } else {
          // select chip
          this.selectChip(index);
        }

      // Default handle click to focus on input
      } else {
        this.$input[0].focus();
      }
    }

    /**
     * Handle Chips Keydown
     * @param {Event} e
     */
    static _handleChipsKeydown(e) {
      Chips._keydown = true;

      let $chips = $(e.target).closest('.chips');
      let chipsKeydown = e.target && $chips.length;

      // Don't handle keydown inputs on input and textarea
      if ($(e.target).is('input, textarea') || !chipsKeydown) {
        return;
      }

      let currChips = $chips[0].M_Chips;

      // backspace and delete
      if (e.keyCode === 8 || e.keyCode === 46) {
        e.preventDefault();

        let selectIndex = currChips.chipsData.length;
        if (currChips._selectedChip) {
          let index = currChips._selectedChip.index();
          currChips.deleteChip(index);
          currChips._selectedChip = null;
          selectIndex = index - 1;
        }

        if (currChips.chipsData.length) {
          currChips.selectChip(selectIndex);
        }

      // left arrow key
      } else if (e.keyCode === 37) {
        if (currChips._selectedChip) {
          let selectIndex = currChips._selectedChip.index() - 1;
          if (selectIndex < 0) {
            return;
          }
          currChips.selectChip(selectIndex);
        }

      // right arrow key
      } else if (e.keyCode === 39) {
        if (currChips._selectedChip) {
          let selectIndex = currChips._selectedChip.index() + 1;

          if (selectIndex >= currChips.chipsData.length) {
            currChips.$input[0].focus();
          } else {
            currChips.selectChip(selectIndex);
          }
        }
      }
    }

    /**
     * Handle Chips Keyup
     * @param {Event} e
     */
    static _handleChipsKeyup(e) {
      Chips._keydown = false;
    }

    /**
     * Handle Chips Blur
     * @param {Event} e
     */
    static _handleChipsBlur(e) {
      if (!Chips._keydown) {
        let $chips = $(e.target).closest('.chips');
        let currChips = $chips[0].M_Chips;

        currChips._selectedChip = null;
      }
    }

    /**
     * Handle Input Focus
     */
    _handleInputFocus() {
      this.$el.addClass('focus');
    }

    /**
     * Handle Input Blur
     */
    _handleInputBlur() {
      this.$el.removeClass('focus');
    }

    /**
     * Handle Input Keydown
     * @param {Event} e
     */
    _handleInputKeydown(e) {
      Chips._keydown = true;

      // enter
      if (e.keyCode === 13) {
        // Override enter if autocompleting.
        if (this.hasAutocomplete &&
            this.autocomplete &&
            this.autocomplete.isOpen) {
          return;
        }

        e.preventDefault();
        this.addChip({tag: this.$input[0].value});
        this.$input[0].value = '';

      // delete or left
      } else if ((e.keyCode === 8 || e.keyCode === 37) && this.$input[0].value === '' && this.chipsData.length) {
        e.preventDefault();
        this.selectChip(this.chipsData.length - 1);
      }
    }

    /**
     * Render Chip
     * @param {chip} chip
     * @return {Element}
     */
    _renderChip(chip) {
      if (!chip.tag) {
        return;
      }

      let renderedChip = document.createElement('div');
      let closeIcon = document.createElement('i');
      renderedChip.classList.add('chip');
      renderedChip.textContent = chip.tag;
      renderedChip.setAttribute('tabindex', 0);
      $(closeIcon).addClass('material-icons close');
      closeIcon.textContent = 'close';

      // attach image if needed
      if (chip.image) {
        let img = document.createElement('img');
        img.setAttribute('src', chip.image);
        renderedChip.insertBefore(img, renderedChip.firstChild);
      }

      renderedChip.appendChild(closeIcon);
      return renderedChip;
    }

    /**
     * Render Chips
     */
    _renderChips() {
      this.$chips.remove();
      for (let i = 0; i < this.chipsData.length; i++) {
        let chipEl = this._renderChip(this.chipsData[i]);
        this.$el.append(chipEl);
        this.$chips.add(chipEl);
      }

      // move input to end
      this.$el.append(this.$input);
    }

    /**
     * Setup Autocomplete
     */
    _setupAutocomplete() {
      this.options.autocompleteOptions.onAutocomplete = (val) => {
        this.addChip({tag: val});
        this.$input[0].value = '';
        this.$input[0].focus();
      };

      this.autocomplete = M.Autocomplete.init(this.$input, this.options.autocompleteOptions)[0];
    }

    /**
     * Setup Label
     */
    _setupLabel() {
      this.$label = this.$el.find('label');
      if (this.$label.length) {
        this.$label.setAttribute('for', this.$input.attr('id'));
      }
    }

    /**
     * Set placeholder
     */
    _setPlaceholder() {
      if ((this.chipsData !== undefined && !this.chipsData.length) && this.options.placeholder) {
        $(this.$input).prop('placeholder', this.options.placeholder);

      } else if ((this.chipsData === undefined || !!this.chipsData.length) && this.options.secondaryPlaceholder) {
        $(this.$input).prop('placeholder', this.options.secondaryPlaceholder);
      }
    }

    /**
     * Check if chip is valid
     * @param {chip} chip
     */
    _isValid(chip) {
      if (chip.hasOwnProperty('tag') && chip.tag !== '') {
        let exists = false;
        for (let i = 0; i < this.chipsData.length; i++) {
          if (this.chipsData[i].tag === chip.tag) {
            exists = true;
            break;
          }
        }
        return !exists;

      } else {
        return false;
      }
    }

    /**
     * Add chip
     * @param {chip} chip
     */
    addChip(chip) {
      if (!this._isValid(chip) ||
          this.chipsData.length >= this.options.limit) {
        return;
      }

      let renderedChip = this._renderChip(chip);
      this.$chips.add(renderedChip);
      this.chipsData.push(chip);
      $(this.$input).before(renderedChip);
      this._setPlaceholder();

      // fire chipAdd callback
      if (typeof(this.options.onChipAdd) === 'function') {
        this.options.onChipAdd.call(this, this.$el, renderedChip);
      }
    }

    /**
     * Delete chip
     * @param {Number} chip
     */
    deleteChip(chipIndex) {
      // let chip = this.chips[chipIndex];
      this.$chips.eq(chipIndex).remove();
      this.$chips = this.$chips.filter(function(el) {
        return $(el).index() >= 0;
      });
      this.chipsData.splice(chipIndex, 1);
      this._setPlaceholder();

      // fire chipDelete callback
      if (typeof(this.options.onChipDelete) === 'function') {
        this.options.onChipDelete.call(this, this.$el, this.$chip);
      }
    }

    /**
     * Select chip
     * @param {Number} chip
     */
    selectChip(chipIndex) {
      let $chip = this.$chips.eq(chipIndex);
      this._selectedChip = $chip;
      $chip[0].focus();

      // fire chipSelect callback
      if (typeof(this.options.onChipSelect) === 'function') {
        this.options.onChipSelect.call(this, this.$el, this.$chip);
      }
    }

    /**
     * Deselect chip
     * @param {Number} chip
     */
    deselectChip(chipIndex) {
      let $chip = this.$chips.eq(chipIndex);
      this._selectedChip = null;
    }
  }

  /**
   * @static
   * @memberof Chips
   */
  Chips._keydown = false;

  M.Chips = Chips;

  if (M.jQueryLoaded) {
    M.initializeJqueryWrapper(Chips, 'chips', 'M_Chips');
  }

  $(document).ready(function() {
    // Handle removal of static chips.
    $(document.body).on('click', '.chip .close', function() {
      let $chips = $(this).closest('.chips');
      if ($chips.length && $chips[0].M_Chips) {
        return;
      }
      $(this).closest('.chip').remove();
    });
  });
}( cash ));
<commit_msg>Removed unused deselect chip function<commit_after>(function ($, Vel) {
  'use strict';

  let _defaults = {
    data: [],
    placeholder: '',
    secondaryPlaceholder: '',
    autocompleteOptions: {},
    limit: Infinity,
    onChipAdd: null,
    onChipSelect: null,
    onChipDelete: null
  };

  /**
   * @typedef {Object} chip
   * @property {String} tag  chip tag string
   * @property {String} [image]  chip avatar image string
   */

  /**
   * @class
   *
   */
  class Chips {
    /**
     * Construct Chips instance and set up overlay
     * @constructor
     * @param {Element} el
     * @param {Object} options
     */
    constructor(el, options) {

      // If exists, destroy and reinitialize
      if (!!el.M_Chips) {
        el.M_Chips.destroy();
      }

      this.el = el;
      this.$el = $(el);
      this.el.M_Chips = this;

      /**
       * Options for the modal
       * @member Chips#options
       * @prop {Array} data
       * @prop {String} placeholder
       * @prop {String} secondaryPlaceholder
       * @prop {Object} autocompleteOptions
       */
      this.options = $.extend({}, Chips.defaults, options);

      this.$el.addClass('chips input-field');
      this.chipsData = [];
      this.$chips = $();
      this.$input = this.$el.find('input');
      this.$input.addClass('input');
      this.hasAutocomplete = Object.keys(this.options.autocompleteOptions).length > 0;

      // Set input id
      if (!this.$input.attr('id')) {
        this.$input.attr('id', M.guid());
      }

      // Render initial chips
      if (this.options.data.length) {
        this.chipsData = this.options.data;
        this._renderChips(this.chipsData);
      }

      // Setup autocomplete if needed
      if (this.hasAutocomplete) {
        this._setupAutocomplete();
      }

      this._setPlaceholder();
      this._setupLabel();
      this._setupEventHandlers();
    }

    static get defaults() {
      return _defaults;
    }

    static init($els, options) {
      let arr = [];
      $els.each(function() {
        arr.push(new Chips(this, options));
      });
      return arr;
    }

    /**
     * Get Instance
     */
    static getInstance(el) {
      let domElem = !!el.jquery ? el[0] : el;
      return domElem.M_Chips;
    }

    /**
     * Get Chips Data
     */
    getData() {
      return this.chipsData;
    }

    /**
     * Teardown component
     */
    destroy() {
      this._removeEventHandlers();
      this.$chips.remove();
      this.el.M_Chips = undefined;
    }

    /**
     * Setup Event Handlers
     */
    _setupEventHandlers() {
      this._handleChipClickBound = this._handleChipClick.bind(this);
      this._handleInputKeydownBound = this._handleInputKeydown.bind(this);
      this._handleInputFocusBound = this._handleInputFocus.bind(this);
      this._handleInputBlurBound = this._handleInputBlur.bind(this);

      this.el.addEventListener('click', this._handleChipClickBound);
      document.addEventListener('keydown', Chips._handleChipsKeydown);
      document.addEventListener('keyup', Chips._handleChipsKeyup);
      this.el.addEventListener('blur', Chips._handleChipsBlur, true);
      this.$input[0].addEventListener('focus', this._handleInputFocusBound);
      this.$input[0].addEventListener('blur', this._handleInputBlurBound);
      this.$input[0].addEventListener('keydown', this._handleInputKeydownBound);
    }

    /**
     * Remove Event Handlers
     */
    _removeEventHandlers() {
      this.el.removeEventListener('click', this._handleChipClickBound);
      document.removeEventListener('keydown', Chips._handleChipsKeydown);
      document.removeEventListener('keyup', Chips._handleChipsKeyup);
      this.el.removeEventListener('blur', Chips._handleChipsBlur, true);
      this.$input[0].removeEventListener('focus', this._handleInputFocusBound);
      this.$input[0].removeEventListener('blur', this._handleInputBlurBound);
      this.$input[0].removeEventListener('keydown', this._handleInputKeydownBound);
    }

    /**
     * Handle Chip Click
     * @param {Event} e
     */
    _handleChipClick(e) {
      let $chip = $(e.target).closest('.chip');
      let clickedClose = $(e.target).is('.close');
      if ($chip.length) {
        let index = $chip.index();
        if (clickedClose) {
          // delete chip
          this.deleteChip(index);
          this.$input[0].focus();

        } else {
          // select chip
          this.selectChip(index);
        }

      // Default handle click to focus on input
      } else {
        this.$input[0].focus();
      }
    }

    /**
     * Handle Chips Keydown
     * @param {Event} e
     */
    static _handleChipsKeydown(e) {
      Chips._keydown = true;

      let $chips = $(e.target).closest('.chips');
      let chipsKeydown = e.target && $chips.length;

      // Don't handle keydown inputs on input and textarea
      if ($(e.target).is('input, textarea') || !chipsKeydown) {
        return;
      }

      let currChips = $chips[0].M_Chips;

      // backspace and delete
      if (e.keyCode === 8 || e.keyCode === 46) {
        e.preventDefault();

        let selectIndex = currChips.chipsData.length;
        if (currChips._selectedChip) {
          let index = currChips._selectedChip.index();
          currChips.deleteChip(index);
          currChips._selectedChip = null;
          selectIndex = index - 1;
        }

        if (currChips.chipsData.length) {
          currChips.selectChip(selectIndex);
        }

      // left arrow key
      } else if (e.keyCode === 37) {
        if (currChips._selectedChip) {
          let selectIndex = currChips._selectedChip.index() - 1;
          if (selectIndex < 0) {
            return;
          }
          currChips.selectChip(selectIndex);
        }

      // right arrow key
      } else if (e.keyCode === 39) {
        if (currChips._selectedChip) {
          let selectIndex = currChips._selectedChip.index() + 1;

          if (selectIndex >= currChips.chipsData.length) {
            currChips.$input[0].focus();
          } else {
            currChips.selectChip(selectIndex);
          }
        }
      }
    }

    /**
     * Handle Chips Keyup
     * @param {Event} e
     */
    static _handleChipsKeyup(e) {
      Chips._keydown = false;
    }

    /**
     * Handle Chips Blur
     * @param {Event} e
     */
    static _handleChipsBlur(e) {
      if (!Chips._keydown) {
        let $chips = $(e.target).closest('.chips');
        let currChips = $chips[0].M_Chips;

        currChips._selectedChip = null;
      }
    }

    /**
     * Handle Input Focus
     */
    _handleInputFocus() {
      this.$el.addClass('focus');
    }

    /**
     * Handle Input Blur
     */
    _handleInputBlur() {
      this.$el.removeClass('focus');
    }

    /**
     * Handle Input Keydown
     * @param {Event} e
     */
    _handleInputKeydown(e) {
      Chips._keydown = true;

      // enter
      if (e.keyCode === 13) {
        // Override enter if autocompleting.
        if (this.hasAutocomplete &&
            this.autocomplete &&
            this.autocomplete.isOpen) {
          return;
        }

        e.preventDefault();
        this.addChip({tag: this.$input[0].value});
        this.$input[0].value = '';

      // delete or left
      } else if ((e.keyCode === 8 || e.keyCode === 37) && this.$input[0].value === '' && this.chipsData.length) {
        e.preventDefault();
        this.selectChip(this.chipsData.length - 1);
      }
    }

    /**
     * Render Chip
     * @param {chip} chip
     * @return {Element}
     */
    _renderChip(chip) {
      if (!chip.tag) {
        return;
      }

      let renderedChip = document.createElement('div');
      let closeIcon = document.createElement('i');
      renderedChip.classList.add('chip');
      renderedChip.textContent = chip.tag;
      renderedChip.setAttribute('tabindex', 0);
      $(closeIcon).addClass('material-icons close');
      closeIcon.textContent = 'close';

      // attach image if needed
      if (chip.image) {
        let img = document.createElement('img');
        img.setAttribute('src', chip.image);
        renderedChip.insertBefore(img, renderedChip.firstChild);
      }

      renderedChip.appendChild(closeIcon);
      return renderedChip;
    }

    /**
     * Render Chips
     */
    _renderChips() {
      this.$chips.remove();
      for (let i = 0; i < this.chipsData.length; i++) {
        let chipEl = this._renderChip(this.chipsData[i]);
        this.$el.append(chipEl);
        this.$chips.add(chipEl);
      }

      // move input to end
      this.$el.append(this.$input);
    }

    /**
     * Setup Autocomplete
     */
    _setupAutocomplete() {
      this.options.autocompleteOptions.onAutocomplete = (val) => {
        this.addChip({tag: val});
        this.$input[0].value = '';
        this.$input[0].focus();
      };

      this.autocomplete = M.Autocomplete.init(this.$input, this.options.autocompleteOptions)[0];
    }

    /**
     * Setup Label
     */
    _setupLabel() {
      this.$label = this.$el.find('label');
      if (this.$label.length) {
        this.$label.setAttribute('for', this.$input.attr('id'));
      }
    }

    /**
     * Set placeholder
     */
    _setPlaceholder() {
      if ((this.chipsData !== undefined && !this.chipsData.length) && this.options.placeholder) {
        $(this.$input).prop('placeholder', this.options.placeholder);

      } else if ((this.chipsData === undefined || !!this.chipsData.length) && this.options.secondaryPlaceholder) {
        $(this.$input).prop('placeholder', this.options.secondaryPlaceholder);
      }
    }

    /**
     * Check if chip is valid
     * @param {chip} chip
     */
    _isValid(chip) {
      if (chip.hasOwnProperty('tag') && chip.tag !== '') {
        let exists = false;
        for (let i = 0; i < this.chipsData.length; i++) {
          if (this.chipsData[i].tag === chip.tag) {
            exists = true;
            break;
          }
        }
        return !exists;

      } else {
        return false;
      }
    }

    /**
     * Add chip
     * @param {chip} chip
     */
    addChip(chip) {
      if (!this._isValid(chip) ||
          this.chipsData.length >= this.options.limit) {
        return;
      }

      let renderedChip = this._renderChip(chip);
      this.$chips.add(renderedChip);
      this.chipsData.push(chip);
      $(this.$input).before(renderedChip);
      this._setPlaceholder();

      // fire chipAdd callback
      if (typeof(this.options.onChipAdd) === 'function') {
        this.options.onChipAdd.call(this, this.$el, renderedChip);
      }
    }

    /**
     * Delete chip
     * @param {Number} chip
     */
    deleteChip(chipIndex) {
      // let chip = this.chips[chipIndex];
      this.$chips.eq(chipIndex).remove();
      this.$chips = this.$chips.filter(function(el) {
        return $(el).index() >= 0;
      });
      this.chipsData.splice(chipIndex, 1);
      this._setPlaceholder();

      // fire chipDelete callback
      if (typeof(this.options.onChipDelete) === 'function') {
        this.options.onChipDelete.call(this, this.$el, this.$chip);
      }
    }

    /**
     * Select chip
     * @param {Number} chip
     */
    selectChip(chipIndex) {
      let $chip = this.$chips.eq(chipIndex);
      this._selectedChip = $chip;
      $chip[0].focus();

      // fire chipSelect callback
      if (typeof(this.options.onChipSelect) === 'function') {
        this.options.onChipSelect.call(this, this.$el, this.$chip);
      }
    }
  }

  /**
   * @static
   * @memberof Chips
   */
  Chips._keydown = false;

  M.Chips = Chips;

  if (M.jQueryLoaded) {
    M.initializeJqueryWrapper(Chips, 'chips', 'M_Chips');
  }

  $(document).ready(function() {
    // Handle removal of static chips.
    $(document.body).on('click', '.chip .close', function() {
      let $chips = $(this).closest('.chips');
      if ($chips.length && $chips[0].M_Chips) {
        return;
      }
      $(this).closest('.chip').remove();
    });
  });
}( cash ));
<|endoftext|>


<commit_before>function Gauge(placeholderName, configuration)
{
	this.placeholderName = placeholderName;

	var self = this; // for internal d3 functions

	this.configure = function(configuration)
	{
		this.config = configuration;

		this.config.size = this.config.size * 0.9;

		this.config.raduis = this.config.size * 0.97 / 2;
		this.config.cx = this.config.size / 2;
		this.config.cy = this.config.size / 2;

		this.config.min = undefined != configuration.min ? configuration.min : 0;
		this.config.max = undefined != configuration.max ? configuration.max : 100;
		this.config.range = this.config.max - this.config.min;

		this.config.majorTicks = configuration.majorTicks || 5;
		this.config.minorTicks = configuration.minorTicks || 2;

		this.config.greenColor 	= configuration.greenColor || "#109618";
		this.config.yellowColor = configuration.yellowColor || "#FF9900";
		this.config.redColor 	= configuration.redColor || "#DC3912";

		this.config.transitionDuration = configuration.transitionDuration || 500;
	}

	this.render = function()
	{
		this.body = d3.select("#" + this.placeholderName)
							.append("svg:svg")
							.attr("class", "gauge")
							.attr("width", this.config.size)
							.attr("height", this.config.size);

		this.body.append("svg:circle")
					.attr("cx", this.config.cx)
					.attr("cy", this.config.cy)
					.attr("r", this.config.raduis)
					.style("fill", "#ccc")
					.style("stroke", "#000")
					.style("stroke-width", "0.5px");

		this.body.append("svg:circle")
					.attr("cx", this.config.cx)
					.attr("cy", this.config.cy)
					.attr("r", 0.9 * this.config.raduis)
					.style("fill", "#fff")
					.style("stroke", "#e0e0e0")
					.style("stroke-width", "2px");

		for (var index in this.config.greenZones)
		{
			this.drawBand(this.config.greenZones[index].from, this.config.greenZones[index].to, self.config.greenColor);
		}

		for (var index in this.config.yellowZones)
		{
			this.drawBand(this.config.yellowZones[index].from, this.config.yellowZones[index].to, self.config.yellowColor);
		}

		for (var index in this.config.redZones)
		{
			this.drawBand(this.config.redZones[index].from, this.config.redZones[index].to, self.config.redColor);
		}

		if (undefined != this.config.label)
		{
			var fontSize = Math.round(this.config.size / 9);
			this.body.append("svg:text")
						.attr("x", this.config.cx)
						.attr("y", this.config.cy / 2 + fontSize / 2)
						.attr("dy", fontSize / 2)
						.attr("text-anchor", "middle")
						.text(this.config.label)
						.style("font-size", fontSize + "px")
						.style("fill", "#333")
						.style("stroke-width", "0px");
		}

		var fontSize = Math.round(this.config.size / 16);
		var majorDelta = this.config.range / (this.config.majorTicks - 1);
		for (var major = this.config.min; major <= this.config.max; major += majorDelta)
		{
			var minorDelta = majorDelta / this.config.minorTicks;
			for (var minor = major + minorDelta; minor < Math.min(major + majorDelta, this.config.max); minor += minorDelta)
			{
				var point1 = this.valueToPoint(minor, 0.75);
				var point2 = this.valueToPoint(minor, 0.85);

				this.body.append("svg:line")
							.attr("x1", point1.x)
							.attr("y1", point1.y)
							.attr("x2", point2.x)
							.attr("y2", point2.y)
							.style("stroke", "#666")
							.style("stroke-width", "1px");
			}

			var point1 = this.valueToPoint(major, 0.7);
			var point2 = this.valueToPoint(major, 0.85);

			this.body.append("svg:line")
						.attr("x1", point1.x)
						.attr("y1", point1.y)
						.attr("x2", point2.x)
						.attr("y2", point2.y)
						.style("stroke", "#333")
						.style("stroke-width", "2px");

			if (major == this.config.min || major == this.config.max)
			{
				var point = this.valueToPoint(major, 0.63);

				this.body.append("svg:text")
				 			.attr("x", point.x)
				 			.attr("y", point.y)
				 			.attr("dy", fontSize / 3)
				 			.attr("text-anchor", major == this.config.min ? "start" : "end")
				 			.text(major)
				 			.style("font-size", fontSize + "px")
							.style("fill", "#333")
							.style("stroke-width", "0px");
			}
		}

		var pointerContainer = this.body.append("svg:g").attr("class", "pointerContainer");

		var midValue = (this.config.min + this.config.max) / 2;

		var pointerPath = this.buildPointerPath(midValue);

		var pointerLine = d3.svg.line()
									.x(function(d) { return d.x })
									.y(function(d) { return d.y })
									.interpolate("basis");

		pointerContainer.selectAll("path")
							.data([pointerPath])
							.enter()
								.append("svg:path")
									.attr("d", pointerLine)
									.style("fill", "#dc3912")
									.style("stroke", "#c63310")
									.style("fill-opacity", 0.7)

		pointerContainer.append("svg:circle")
							.attr("cx", this.config.cx)
							.attr("cy", this.config.cy)
							.attr("r", 0.12 * this.config.raduis)
							.style("fill", "#4684EE")
							.style("stroke", "#666")
							.style("opacity", 1);

		var fontSize = Math.round(this.config.size / 10);
		pointerContainer.selectAll("text")
							.data([midValue])
							.enter()
								.append("svg:text")
									.attr("x", this.config.cx)
									.attr("y", this.config.size - this.config.cy / 4 - fontSize)
									.attr("dy", fontSize / 2)
									.attr("text-anchor", "middle")
									.style("font-size", fontSize + "px")
									.style("fill", "#000")
									.style("stroke-width", "0px");

		this.redraw(this.config.min, 0);
	}

	this.buildPointerPath = function(value)
	{
		var delta = this.config.range / 13;

		var head = valueToPoint(value, 0.85);
		var head1 = valueToPoint(value - delta, 0.12);
		var head2 = valueToPoint(value + delta, 0.12);

		var tailValue = value - (this.config.range * (1/(270/360)) / 2);
		var tail = valueToPoint(tailValue, 0.28);
		var tail1 = valueToPoint(tailValue - delta, 0.12);
		var tail2 = valueToPoint(tailValue + delta, 0.12);

		return [head, head1, tail2, tail, tail1, head2, head];

		function valueToPoint(value, factor)
		{
			var point = self.valueToPoint(value, factor);
			point.x -= self.config.cx;
			point.y -= self.config.cy;
			return point;
		}
	}

	this.drawBand = function(start, end, color)
	{
		if (0 >= end - start) return;

		this.body.append("svg:path")
					.style("fill", color)
					.attr("d", d3.svg.arc()
						.startAngle(this.valueToRadians(start))
						.endAngle(this.valueToRadians(end))
						.innerRadius(0.65 * this.config.raduis)
						.outerRadius(0.85 * this.config.raduis))
					.attr("transform", function() { return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(270)" });
	}

	this.redraw = function(value, transitionDuration)
	{
		var pointerContainer = this.body.select(".pointerContainer");

		pointerContainer.selectAll("text").text(Math.round(value));

		var pointer = pointerContainer.selectAll("path");
		pointer.transition()
					.duration(undefined != transitionDuration ? transitionDuration : this.config.transitionDuration)
					//.delay(0)
					//.ease("linear")
					//.attr("transform", function(d)
					.attrTween("transform", function()
					{
						var pointerValue = value;
						if (value > self.config.max) pointerValue = self.config.max + 0.02*self.config.range;
						else if (value < self.config.min) pointerValue = self.config.min - 0.02*self.config.range;
						var targetRotation = (self.valueToDegrees(pointerValue) - 90);
						var currentRotation = self._currentRotation || targetRotation;
						self._currentRotation = targetRotation;

						return function(step)
						{
							var rotation = currentRotation + (targetRotation-currentRotation)*step;
							return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(" + rotation + ")";
						}
					});
	}

	this.valueToDegrees = function(value)
	{
		// thanks @closealert
		//return value / this.config.range * 270 - 45;
		return value / this.config.range * 270 - (this.config.min / this.config.range * 270 + 45);
	}

	this.valueToRadians = function(value)
	{
		return this.valueToDegrees(value) * Math.PI / 180;
	}

	this.valueToPoint = function(value, factor)
	{
		return { 	x: this.config.cx - this.config.raduis * factor * Math.cos(this.valueToRadians(value)),
					y: this.config.cy - this.config.raduis * factor * Math.sin(this.valueToRadians(value)) 		};
	}

	// initialization
	this.configure(configuration);
}<commit_msg>Prettified gauge.js<commit_after>function Gauge(placeholderName, configuration) {

  this.placeholderName = placeholderName;

  var self = this; // for internal d3 functions

  this.configure = function(configuration) {
    this.config = configuration;

    this.config.size = this.config.size * 0.9;

    this.config.raduis = this.config.size * 0.97 / 2;
    this.config.cx = this.config.size / 2;
    this.config.cy = this.config.size / 2;

    this.config.min = undefined != configuration.min ? configuration.min : 0;
    this.config.max = undefined != configuration.max ? configuration.max : 100;
    this.config.range = this.config.max - this.config.min;

    this.config.majorTicks = configuration.majorTicks || 5;
    this.config.minorTicks = configuration.minorTicks || 2;

    this.config.greenColor   = configuration.greenColor || "#109618";
    this.config.yellowColor = configuration.yellowColor || "#FF9900";
    this.config.redColor   = configuration.redColor || "#DC3912";

    this.config.transitionDuration = configuration.transitionDuration || 500;
  };

  this.render = function() {
    this.body = d3.select("#" + this.placeholderName)
              .append("svg:svg")
              .attr("class", "gauge")
              .attr("width", this.config.size)
              .attr("height", this.config.size);

    this.body.append("svg:circle")
          .attr("cx", this.config.cx)
          .attr("cy", this.config.cy)
          .attr("r", this.config.raduis)
          .style("fill", "#ccc")
          .style("stroke", "#000")
          .style("stroke-width", "0.5px");

    this.body.append("svg:circle")
          .attr("cx", this.config.cx)
          .attr("cy", this.config.cy)
          .attr("r", 0.9 * this.config.raduis)
          .style("fill", "#fff")
          .style("stroke", "#e0e0e0")
          .style("stroke-width", "2px");

    for (var index in this.config.greenZones) {
      this.drawBand(this.config.greenZones[index].from, this.config.greenZones[index].to, self.config.greenColor);
    }

    for (var index in this.config.yellowZones) {
      this.drawBand(this.config.yellowZones[index].from, this.config.yellowZones[index].to, self.config.yellowColor);
    }

    for (var index in this.config.redZones) {
      this.drawBand(this.config.redZones[index].from, this.config.redZones[index].to, self.config.redColor);
    }

    if (undefined != this.config.label) {
      var fontSize = Math.round(this.config.size / 9);
      this.body.append("svg:text")
            .attr("x", this.config.cx)
            .attr("y", this.config.cy / 2 + fontSize / 2)
            .attr("dy", fontSize / 2)
            .attr("text-anchor", "middle")
            .text(this.config.label)
            .style("font-size", fontSize + "px")
            .style("fill", "#333")
            .style("stroke-width", "0px");
    }

    var fontSize = Math.round(this.config.size / 16);
    var majorDelta = this.config.range / (this.config.majorTicks - 1);
    for (var major = this.config.min; major <= this.config.max; major += majorDelta) {
      var minorDelta = majorDelta / this.config.minorTicks;
      for (var minor = major + minorDelta; minor < Math.min(major + majorDelta, this.config.max); minor += minorDelta) {
        var point1 = this.valueToPoint(minor, 0.75);
        var point2 = this.valueToPoint(minor, 0.85);

        this.body.append("svg:line")
              .attr("x1", point1.x)
              .attr("y1", point1.y)
              .attr("x2", point2.x)
              .attr("y2", point2.y)
              .style("stroke", "#666")
              .style("stroke-width", "1px");
      }

      var point1 = this.valueToPoint(major, 0.7);
      var point2 = this.valueToPoint(major, 0.85);

      this.body.append("svg:line")
            .attr("x1", point1.x)
            .attr("y1", point1.y)
            .attr("x2", point2.x)
            .attr("y2", point2.y)
            .style("stroke", "#333")
            .style("stroke-width", "2px");

      if (major == this.config.min || major == this.config.max) {
        var point = this.valueToPoint(major, 0.63);

        this.body.append("svg:text")
               .attr("x", point.x)
               .attr("y", point.y)
               .attr("dy", fontSize / 3)
               .attr("text-anchor", major == this.config.min ? "start" : "end")
               .text(major)
               .style("font-size", fontSize + "px")
              .style("fill", "#333")
              .style("stroke-width", "0px");
      }
    }

    var pointerContainer = this.body.append("svg:g").attr("class", "pointerContainer");

    var midValue = (this.config.min + this.config.max) / 2;

    var pointerPath = this.buildPointerPath(midValue);

    var pointerLine = d3.svg.line()
                  .x(function(d) { return d.x })
                  .y(function(d) { return d.y })
                  .interpolate("basis");

    pointerContainer.selectAll("path")
              .data([pointerPath])
              .enter()
                .append("svg:path")
                  .attr("d", pointerLine)
                  .style("fill", "#dc3912")
                  .style("stroke", "#c63310")
                  .style("fill-opacity", 0.7);

    pointerContainer.append("svg:circle")
              .attr("cx", this.config.cx)
              .attr("cy", this.config.cy)
              .attr("r", 0.12 * this.config.raduis)
              .style("fill", "#4684EE")
              .style("stroke", "#666")
              .style("opacity", 1);

    var fontSize = Math.round(this.config.size / 10);
    pointerContainer.selectAll("text")
              .data([midValue])
              .enter()
                .append("svg:text")
                  .attr("x", this.config.cx)
                  .attr("y", this.config.size - this.config.cy / 4 - fontSize)
                  .attr("dy", fontSize / 2)
                  .attr("text-anchor", "middle")
                  .style("font-size", fontSize + "px")
                  .style("fill", "#000")
                  .style("stroke-width", "0px");

    this.redraw(this.config.min, 0);
  };

  this.buildPointerPath = function(value) {
    var delta = this.config.range / 13;

    var head = valueToPoint(value, 0.85);
    var head1 = valueToPoint(value - delta, 0.12);
    var head2 = valueToPoint(value + delta, 0.12);

    var tailValue = value - (this.config.range * (1/(270/360)) / 2);
    var tail = valueToPoint(tailValue, 0.28);
    var tail1 = valueToPoint(tailValue - delta, 0.12);
    var tail2 = valueToPoint(tailValue + delta, 0.12);

    return [head, head1, tail2, tail, tail1, head2, head];

    function valueToPoint(value, factor)
    {
      var point = self.valueToPoint(value, factor);
      point.x -= self.config.cx;
      point.y -= self.config.cy;
      return point;
    }
  };

  this.drawBand = function(start, end, color) {
    if (0 >= end - start) return;

    this.body.append("svg:path")
          .style("fill", color)
          .attr("d", d3.svg.arc()
            .startAngle(this.valueToRadians(start))
            .endAngle(this.valueToRadians(end))
            .innerRadius(0.65 * this.config.raduis)
            .outerRadius(0.85 * this.config.raduis))
          .attr("transform", function() { return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(270)" });
  };

  this.redraw = function(value, transitionDuration) {
    var pointerContainer = this.body.select(".pointerContainer");

    pointerContainer.selectAll("text").text(Math.round(value));

    var pointer = pointerContainer.selectAll("path");
    pointer.transition()
          .duration(undefined != transitionDuration ? transitionDuration : this.config.transitionDuration)
          //.delay(0)
          //.ease("linear")
          //.attr("transform", function(d)
          .attrTween("transform", function() {
            var pointerValue = value;
            if (value > self.config.max) pointerValue = self.config.max + 0.02*self.config.range;
            else if (value < self.config.min) pointerValue = self.config.min - 0.02*self.config.range;
            var targetRotation = (self.valueToDegrees(pointerValue) - 90);
            var currentRotation = self._currentRotation || targetRotation;
            self._currentRotation = targetRotation;
            return function(step) {
              var rotation = currentRotation + (targetRotation-currentRotation)*step;
              return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(" + rotation + ")";
            };
          });
  };

  this.valueToDegrees = function(value) {
    // thanks @closealert
    //return value / this.config.range * 270 - 45;
    return value / this.config.range * 270 - (this.config.min / this.config.range * 270 + 45);
  };

  this.valueToRadians = function(value) {
    return this.valueToDegrees(value) * Math.PI / 180;
  };

  this.valueToPoint = function(value, factor) {
    return {
      x: this.config.cx - this.config.raduis * factor * Math.cos(this.valueToRadians(value)),
      y: this.config.cy - this.config.raduis * factor * Math.sin(this.valueToRadians(value))
    };
  };

  // initialization
  this.configure(configuration);
}
<|endoftext|>


<commit_before>var glide = glide || {};

/******************************************************************************
 *** Slideshow Param Defaults ***
 *****************************************************************************/
glide.param = {};
glide.param.name = "Slideshow";
glide.param.showDuration = 5000;
glide.param.animDuration = 2000;
glide.param.autoForward = true;
glide.param.bgColor = "#1e2426";
glide.param.animation = "NONE";
glide.param.picsJsonURI = "pics.json";   // pic catalog URI

/******************************************************************************
 *** Internal Vars ***
 *****************************************************************************/
glide.showTimeoutId = null;
glide.lastAnimation = null;
glide.animIntervalId = null;
glide.cancelAnimation = null;
glide.animations = {};

/******************************************************************************
 *** Helper Functions ***
 *****************************************************************************/
glide.isScalar = function (obj) {
    return (/string|number|boolean/).test(typeof obj);
};

glide.randSplice = function(array) {
    var i = Math.floor(Math.random() * array.length);
    var removed = array.splice(i, 1);
    return removed;
};

glide.randKey = function(obj) {
    var keys = Object.keys(obj);
    var i = Math.floor(Math.random() * keys.length);
    return keys[i];
};

/******************************************************************************
 *** Glide Functions ***
 *****************************************************************************/
glide.nextPic = function () {
    if (glide.i == glide.max) {
        glide.i = 0;
    } else {
        glide.i++;
    }
    return [{"key": glide.i, "src": glide.dataset.pics[glide.i]}];
};

glide.prevPic = function () {
    if (glide.i === 0) {
        glide.i = glide.max;
    } else {
        glide.i--;
    }
    return [{"key": glide.i, "src": glide.dataset.pics[glide.i]}];
};

glide.key = function (d) {
    return d.key;
};

glide.calcImgPosAndDims = function (img) {

    var height, width, x, y;

    if (img !== null) {  // new image
        glide.naturalHeight = img.naturalHeight;
        glide.naturalWidth = img.naturalWidth;
    }

    var wh = window.innerHeight;
    var ww = window.innerWidth;

    if (glide.naturalHeight <= wh && glide.naturalWidth <= ww) {  // image fits
        height = glide.naturalHeight;
        width = glide.naturalWidth;
    } else {    // scale
        var widthScale = ww / glide.naturalWidth;
        var heightScale = wh / glide.naturalHeight;
        var scale = Math.min(widthScale, heightScale);
        width = Math.round(glide.naturalWidth * scale);
        height = Math.round(glide.naturalHeight * scale);
    }

    x = (ww - width) / 2;
    y = (wh - height) / 2;

    return { "width": width,
             "height": height,
             "x": x,
             "y": y };
};

glide.updateParams = function () {
    Object.keys(glide.param).forEach(function(key, index) {
        if (glide.dataset.hasOwnProperty(key)) {
            var val = glide.dataset[key];
            if (glide.isScalar(val)) {
                console.log("Using custom slideshow param '" + key + "' : " + val);
                glide.param[key] = val;
            } else {
                console.log("Ignoring invalid slideshow param '" + key + "'.");
            }
        }
    });
};

glide.getRandAnimation = function () {
    var anim = glide.lastAnimation;
    while (anim == glide.lastAnimation) {
        anim = glide.randKey(glide.animations);
    }
    glide.lastAnimation = anim;
    return anim;
};

glide.setupCanvas = function () {
    d3.select("title")
      .text(glide.param.name);

    glide.svg = d3.select("body")
                  .attr("style", "background-color: " + glide.param.bgColor)
                  .append("svg")
                  .attr("width", window.innerWidth)
                  .attr("height", window.innerHeight)
                  .attr("id", "canvas");
};

glide.nextSlide = function () {
    // get pic...
    var nextPicSpec = glide.nextPic();

    // show slide...
    glide.showSlide(nextPicSpec);
};

glide.prevSlide = function () {
    // get pic...
    var nextPicSpec = glide.prevPic();

    // show slide...
    glide.showSlide(nextPicSpec);
};

glide.showSlide = function (nextPicSpec) {

    // cancel old timeout
    if (glide.param.autoForward
        && glide.showTimeoutId !== null) {
        clearTimeout(glide.showTimeoutId);
    }

    // cancel old animation
    if (glide.cancelAnimation !== null) {
        glide.cancelAnimation();
    }

    // select all image objects
    var pics = glide.svg.selectAll("image")
                        .data(nextPicSpec, glide.key);

    // show next pic using random or selected animation
    if (glide.param.animation == "RANDOM") {
        var anim = glide.getRandAnimation();
        glide.animations[anim](nextPicSpec, pics);
    } else if (glide.animations.hasOwnProperty(glide.param.animation)) {
        glide.animations[glide.param.animation](nextPicSpec, pics);
    } else {    // no animation
        glide.aniNone(nextPicSpec, pics);
    }
};

glide.picId = function (d) { return "pic_" + d.key; };

glide.picSrc = function (d) { return d.src; };

glide.triggerNextSlide = function (millis) {
    if (glide.param.autoForward) {
        glide.showTimeoutId = setTimeout(glide.nextSlide, millis);
    }
};

glide.aniNotImplemented = function (nextPicSpec, pics) {
    console.log("This animation is not yet implemented!");
    glide.aniNone(nextPicSpec, pics);
};

glide.aniNone = function (nextPicSpec, pics) {
    // (1) remove old pic (if exists)
    pics.exit()
        .remove();

    // (2) load new pic
    var img = new Image();
    img.onload = function () {
        var posAndDims = glide.calcImgPosAndDims(img);
        pics.enter()
           .append("image")
           .attr("xlink:href", glide.picSrc)
           .attr("id", glide.picId)
           .attr("width", posAndDims.width)
           .attr("height", posAndDims.height)
           .attr("x", posAndDims.x)
           .attr("y", posAndDims.y);

        glide.triggerNextSlide(glide.param.showDuration);
    };
    img.src = nextPicSpec[0].src;
};

glide.aniZoomIn = function (nextPicSpec, pics) {
    // (1) remove old pic (if exists)
    pics.exit()
        .remove();

    // (2) load new pic
    var img = new Image();
    img.onload = function () {
        var posAndDims = glide.calcImgPosAndDims(img);
        pics.enter()
           .append("image")
           .attr("xlink:href", glide.picSrc)
           .attr("id", glide.picId)
           .attr("width", 1)
           .attr("height", 1)
           .attr("x", window.innerWidth / 2)
           .attr("y", window.innerHeight / 2)
           .transition()
           .duration(glide.param.animDuration)
           .attr("width", posAndDims.width)
           .attr("height", posAndDims.height)
           .attr("x", posAndDims.x)
           .attr("y", posAndDims.y);

        glide.triggerNextSlide(glide.param.animDuration
            + glide.param.showDuration);
    };
    img.src = nextPicSpec[0].src;
};

glide.aniSlideRight = function (nextPicSpec, pics) {
    // (1) remove old pic (if exists)
    pics.exit()
        .transition()
        .duration(glide.param.animDuration)
        .attr("x", window.innerWidth)
        .remove();

    // (2) load new pic
    var img = new Image();
    img.onload = function () {
        var posAndDims = glide.calcImgPosAndDims(img);
        pics.enter()
            .append("image")
            .attr("xlink:href", glide.picSrc)
            .attr("id", glide.picId)
            .attr("width", posAndDims.width)
            .attr("height", posAndDims.height)
            .attr("x", 0 - posAndDims.width)
            .attr("y", posAndDims.y)
            .transition()
            .duration(glide.param.animDuration)
            .attr("x", posAndDims.x);

            glide.triggerNextSlide(glide.param.animDuration
                + glide.param.showDuration);
    };
    img.src = nextPicSpec[0].src;
};

glide.aniSlideTop = function (nextPicSpec, pics) {
    // (1) remove old pic (if exists)
    pics.exit()
        .transition()
        .duration(glide.param.animDuration)
        .attr("y", window.innerHeight)
        .remove();

    // (2) load new pic
    var img = new Image();
    img.onload = function () {
        var posAndDims = glide.calcImgPosAndDims(img);
        pics.enter()
            .append("image")
            .attr("xlink:href", glide.picSrc)
            .attr("id", glide.picId)
            .attr("width", posAndDims.width)
            .attr("height", posAndDims.height)
            .attr("x", posAndDims.x)
            .attr("y", 0 - posAndDims.height)
            .transition()
            .duration(glide.param.animDuration)
            .attr("y", posAndDims.y);

        glide.triggerNextSlide(glide.param.animDuration
            + glide.param.showDuration);
    };
    img.src = nextPicSpec[0].src;
};

glide.getSquareDims = function (picPosAndDims) {

    var squaresPerRow = 8;
    var positions = [];
    var sqWidth, numSqWide, numSqHigh, planeWidth, planeHeight;

    if (picPosAndDims.width > picPosAndDims.height) {
        // fill complete width with squares
        sqWidth = Math.ceil(picPosAndDims.width / squaresPerRow);
        numSqWide = squaresPerRow;
        numSqHigh = Math.ceil(picPosAndDims.height / sqWidth);
    } else {
        // fill complete height with squares
        sqWidth = Math.ceil(picPosAndDims.height / squaresPerRow);
        numSqHigh = squaresPerRow;
        numSqWide = Math.ceil(picPosAndDims.width / sqWidth);
    }

    planeWidth = sqWidth * numSqWide;
    planeHeight = sqWidth * numSqHigh;

    // calculate positions
    var startX = picPosAndDims.x - ((planeWidth - picPosAndDims.width) / 2);
    var startY = picPosAndDims.y - ((planeHeight - picPosAndDims.height) / 2);
    var count = 0;
    var xPos = startX;
    var yPos = startY;
    for (var i=1; i <= numSqHigh; i++) {
        for (var j=1; j <= numSqWide; j++) {
            count++;
            positions.push({ key: count, x: xPos, y: yPos });
            xPos += sqWidth;
        }
        yPos += sqWidth;
        xPos = startX;
    }

    return {
        width: sqWidth,
        squares: positions
    };
};

glide.aniMemory = function (nextPicSpec, pics) {
    // (1) remove old pic (if exists)
    pics.exit()
        .remove();

    // (2) load new pic
    var img = new Image();
    img.onload = function () {
        var posAndDims = glide.calcImgPosAndDims(img);
        pics.enter()
           .append("image")
           .attr("xlink:href", glide.picSrc)
           .attr("id", glide.picId)
           .attr("width", posAndDims.width)
           .attr("height", posAndDims.height)
           .attr("x", posAndDims.x)
           .attr("y", posAndDims.y);

       // (3) immediately draw squares
       var squareDims = glide.getSquareDims(posAndDims);
       var squareData = squareDims.squares;
       var squareShowTime = 100;
       var animDuration = squareData.length * squareShowTime;
       glide.svg.selectAll("rect")
                .data(squareData, glide.key)
                .enter()
                .append("rect")
                .attr("width", squareDims.width)
                .attr("height", squareDims.width)
                .attr("x", function (d) { return d.x; })
                .attr("y", function (d) { return d.y; })
                .attr("id", function (d) { return "sq_" + d.key; })
                .attr("stroke", glide.param.bgColor)
                .attr("fill", glide.param.bgColor);

       // (4) define animation stopper
       glide.cancelAnimation = function () {
           if (glide.animIntervalId !== null) {
               clearInterval(glide.animIntervalId);
               glide.animIntervalId = null;

               glide.svg.selectAll("rect")
                        .data([], glide.key)
                        .exit()
                        .remove();

               glide.cancelAnimation = null;
           }
       };

       // (5) start animation: remove squares (one by one)
       glide.animIntervalId = setInterval(function () {
               if ( squareData.length > 0 ) {
                   glide.randSplice(squareData);
                   glide.svg.selectAll("rect")
                         .data(squareData, glide.key)
                         .exit()
                         .remove();
               } else {
                   clearInterval(glide.animIntervalId);
                   glide.animIntervalId = null;
                   glide.cancelAnimation = null;
               }
       },
       squareShowTime);

       glide.triggerNextSlide(animDuration + glide.param.showDuration);
    };
    img.src = nextPicSpec[0].src;
};

glide.registerAnimations = function () {
    glide.animations.ZOOM_IN = glide.aniZoomIn;
    glide.animations.SLIDE_RIGHT = glide.aniSlideRight;
    glide.animations.SLIDE_TOP = glide.aniSlideTop;
    glide.animations.MEMORY = glide.aniMemory;
};

glide.startShow = function (data) {
    glide.dataset = data;
    glide.max = glide.dataset.pics.length - 1;
    glide.i = -1;
    glide.updateParams();
    glide.registerAnimations();
    glide.setupCanvas();
    glide.nextSlide();
};

glide.stopShow = function () {
    if (glide.showTimeoutId !== null) {
        clearTimeout(glide.showTimeoutId);
        glide.showTimeoutId = null;
    }
};

/******************************************************************************
 *** Event Handler ***
 *****************************************************************************/
 // On click on body:
d3.select("body").on("click", function(e) {
    glide.nextSlide();
});

// On key press:
document.onkeypress = function (e) {
    e = e || window.event;
    switch (e.key) {
    case " ":   // Space
        glide.nextSlide();
      break;
    case "Backspace":
      glide.prevSlide();
      break;
    case "ArrowDown":
      glide.prevSlide();
      break;
    case "ArrowUp":
      glide.nextSlide();
      break;
    case "ArrowLeft":
      glide.prevSlide();
      break;
    case "ArrowRight":
      glide.nextSlide();
      break;
    case "Enter":
      glide.nextSlide();
      break;
    case "Escape":
      glide.stopShow();
      break;
    default:
      return;
  }
};

// On window resize:
window.onresize = function(e) {

    // resize canvas
    glide.svg.attr("width", window.innerWidth)
             .attr("height", window.innerHeight);

    // resize pic
    var posAndDims = glide.calcImgPosAndDims();
    glide.svg.selectAll("image")
           .attr("width", posAndDims.width)
           .attr("height", posAndDims.height)
           .attr("x", posAndDims.x)
           .attr("y", posAndDims.y);
};

// On page load:
d3.json(glide.param.picsJsonURI, function(data) {
    glide.startShow(data);
});
<commit_msg>fixed null pointer issue on window resize<commit_after>var glide = glide || {};

/******************************************************************************
 *** Slideshow Param Defaults ***
 *****************************************************************************/
glide.param = {};
glide.param.name = "Slideshow";
glide.param.showDuration = 5000;
glide.param.animDuration = 2000;
glide.param.autoForward = true;
glide.param.bgColor = "#1e2426";
glide.param.animation = "NONE";
glide.param.picsJsonURI = "pics.json";   // pic catalog URI

/******************************************************************************
 *** Internal Vars ***
 *****************************************************************************/
glide.showTimeoutId = null;
glide.lastAnimation = null;
glide.animIntervalId = null;
glide.cancelAnimation = null;
glide.animations = {};

/******************************************************************************
 *** Helper Functions ***
 *****************************************************************************/
glide.isScalar = function (obj) {
    return (/string|number|boolean/).test(typeof obj);
};

glide.randSplice = function(array) {
    var i = Math.floor(Math.random() * array.length);
    var removed = array.splice(i, 1);
    return removed;
};

glide.randKey = function(obj) {
    var keys = Object.keys(obj);
    var i = Math.floor(Math.random() * keys.length);
    return keys[i];
};

/******************************************************************************
 *** Glide Functions ***
 *****************************************************************************/
glide.nextPic = function () {
    if (glide.i == glide.max) {
        glide.i = 0;
    } else {
        glide.i++;
    }
    return [{"key": glide.i, "src": glide.dataset.pics[glide.i]}];
};

glide.prevPic = function () {
    if (glide.i === 0) {
        glide.i = glide.max;
    } else {
        glide.i--;
    }
    return [{"key": glide.i, "src": glide.dataset.pics[glide.i]}];
};

glide.key = function (d) {
    return d.key;
};

glide.calcImgPosAndDims = function (img) {

    var height, width, x, y;

    if (img != null) {  // new image
        glide.naturalHeight = img.naturalHeight;
        glide.naturalWidth = img.naturalWidth;
    }

    var wh = window.innerHeight;
    var ww = window.innerWidth;

    if (glide.naturalHeight <= wh && glide.naturalWidth <= ww) {  // image fits
        height = glide.naturalHeight;
        width = glide.naturalWidth;
    } else {    // scale
        var widthScale = ww / glide.naturalWidth;
        var heightScale = wh / glide.naturalHeight;
        var scale = Math.min(widthScale, heightScale);
        width = Math.round(glide.naturalWidth * scale);
        height = Math.round(glide.naturalHeight * scale);
    }

    x = (ww - width) / 2;
    y = (wh - height) / 2;

    return { "width": width,
             "height": height,
             "x": x,
             "y": y };
};

glide.updateParams = function () {
    Object.keys(glide.param).forEach(function(key, index) {
        if (glide.dataset.hasOwnProperty(key)) {
            var val = glide.dataset[key];
            if (glide.isScalar(val)) {
                console.log("Using custom slideshow param '" + key + "' : " + val);
                glide.param[key] = val;
            } else {
                console.log("Ignoring invalid slideshow param '" + key + "'.");
            }
        }
    });
};

glide.getRandAnimation = function () {
    var anim = glide.lastAnimation;
    while (anim == glide.lastAnimation) {
        anim = glide.randKey(glide.animations);
    }
    glide.lastAnimation = anim;
    return anim;
};

glide.setupCanvas = function () {
    d3.select("title")
      .text(glide.param.name);

    glide.svg = d3.select("body")
                  .attr("style", "background-color: " + glide.param.bgColor)
                  .append("svg")
                  .attr("width", window.innerWidth)
                  .attr("height", window.innerHeight)
                  .attr("id", "canvas");
};

glide.nextSlide = function () {
    // get pic...
    var nextPicSpec = glide.nextPic();

    // show slide...
    glide.showSlide(nextPicSpec);
};

glide.prevSlide = function () {
    // get pic...
    var nextPicSpec = glide.prevPic();

    // show slide...
    glide.showSlide(nextPicSpec);
};

glide.showSlide = function (nextPicSpec) {

    // cancel old timeout
    if (glide.param.autoForward
        && glide.showTimeoutId != null) {
        clearTimeout(glide.showTimeoutId);
    }

    // cancel old animation
    if (glide.cancelAnimation != null) {
        glide.cancelAnimation();
    }

    // select all image objects
    var pics = glide.svg.selectAll("image")
                        .data(nextPicSpec, glide.key);

    // show next pic using random or selected animation
    if (glide.param.animation == "RANDOM") {
        var anim = glide.getRandAnimation();
        glide.animations[anim](nextPicSpec, pics);
    } else if (glide.animations.hasOwnProperty(glide.param.animation)) {
        glide.animations[glide.param.animation](nextPicSpec, pics);
    } else {    // no animation
        glide.aniNone(nextPicSpec, pics);
    }
};

glide.picId = function (d) { return "pic_" + d.key; };

glide.picSrc = function (d) { return d.src; };

glide.triggerNextSlide = function (millis) {
    if (glide.param.autoForward) {
        glide.showTimeoutId = setTimeout(glide.nextSlide, millis);
    }
};

glide.aniNotImplemented = function (nextPicSpec, pics) {
    console.log("This animation is not yet implemented!");
    glide.aniNone(nextPicSpec, pics);
};

glide.aniNone = function (nextPicSpec, pics) {
    // (1) remove old pic (if exists)
    pics.exit()
        .remove();

    // (2) load new pic
    var img = new Image();
    img.onload = function () {
        var posAndDims = glide.calcImgPosAndDims(img);
        pics.enter()
           .append("image")
           .attr("xlink:href", glide.picSrc)
           .attr("id", glide.picId)
           .attr("width", posAndDims.width)
           .attr("height", posAndDims.height)
           .attr("x", posAndDims.x)
           .attr("y", posAndDims.y);

        glide.triggerNextSlide(glide.param.showDuration);
    };
    img.src = nextPicSpec[0].src;
};

glide.aniZoomIn = function (nextPicSpec, pics) {
    // (1) remove old pic (if exists)
    pics.exit()
        .remove();

    // (2) load new pic
    var img = new Image();
    img.onload = function () {
        var posAndDims = glide.calcImgPosAndDims(img);
        pics.enter()
           .append("image")
           .attr("xlink:href", glide.picSrc)
           .attr("id", glide.picId)
           .attr("width", 1)
           .attr("height", 1)
           .attr("x", window.innerWidth / 2)
           .attr("y", window.innerHeight / 2)
           .transition()
           .duration(glide.param.animDuration)
           .attr("width", posAndDims.width)
           .attr("height", posAndDims.height)
           .attr("x", posAndDims.x)
           .attr("y", posAndDims.y);

        glide.triggerNextSlide(glide.param.animDuration
            + glide.param.showDuration);
    };
    img.src = nextPicSpec[0].src;
};

glide.aniSlideRight = function (nextPicSpec, pics) {
    // (1) remove old pic (if exists)
    pics.exit()
        .transition()
        .duration(glide.param.animDuration)
        .attr("x", window.innerWidth)
        .remove();

    // (2) load new pic
    var img = new Image();
    img.onload = function () {
        var posAndDims = glide.calcImgPosAndDims(img);
        pics.enter()
            .append("image")
            .attr("xlink:href", glide.picSrc)
            .attr("id", glide.picId)
            .attr("width", posAndDims.width)
            .attr("height", posAndDims.height)
            .attr("x", 0 - posAndDims.width)
            .attr("y", posAndDims.y)
            .transition()
            .duration(glide.param.animDuration)
            .attr("x", posAndDims.x);

            glide.triggerNextSlide(glide.param.animDuration
                + glide.param.showDuration);
    };
    img.src = nextPicSpec[0].src;
};

glide.aniSlideTop = function (nextPicSpec, pics) {
    // (1) remove old pic (if exists)
    pics.exit()
        .transition()
        .duration(glide.param.animDuration)
        .attr("y", window.innerHeight)
        .remove();

    // (2) load new pic
    var img = new Image();
    img.onload = function () {
        var posAndDims = glide.calcImgPosAndDims(img);
        pics.enter()
            .append("image")
            .attr("xlink:href", glide.picSrc)
            .attr("id", glide.picId)
            .attr("width", posAndDims.width)
            .attr("height", posAndDims.height)
            .attr("x", posAndDims.x)
            .attr("y", 0 - posAndDims.height)
            .transition()
            .duration(glide.param.animDuration)
            .attr("y", posAndDims.y);

        glide.triggerNextSlide(glide.param.animDuration
            + glide.param.showDuration);
    };
    img.src = nextPicSpec[0].src;
};

glide.getSquareDims = function (picPosAndDims) {

    var squaresPerRow = 8;
    var positions = [];
    var sqWidth, numSqWide, numSqHigh, planeWidth, planeHeight;

    if (picPosAndDims.width > picPosAndDims.height) {
        // fill complete width with squares
        sqWidth = Math.ceil(picPosAndDims.width / squaresPerRow);
        numSqWide = squaresPerRow;
        numSqHigh = Math.ceil(picPosAndDims.height / sqWidth);
    } else {
        // fill complete height with squares
        sqWidth = Math.ceil(picPosAndDims.height / squaresPerRow);
        numSqHigh = squaresPerRow;
        numSqWide = Math.ceil(picPosAndDims.width / sqWidth);
    }

    planeWidth = sqWidth * numSqWide;
    planeHeight = sqWidth * numSqHigh;

    // calculate positions
    var startX = picPosAndDims.x - ((planeWidth - picPosAndDims.width) / 2);
    var startY = picPosAndDims.y - ((planeHeight - picPosAndDims.height) / 2);
    var count = 0;
    var xPos = startX;
    var yPos = startY;
    for (var i=1; i <= numSqHigh; i++) {
        for (var j=1; j <= numSqWide; j++) {
            count++;
            positions.push({ key: count, x: xPos, y: yPos });
            xPos += sqWidth;
        }
        yPos += sqWidth;
        xPos = startX;
    }

    return {
        width: sqWidth,
        squares: positions
    };
};

glide.aniMemory = function (nextPicSpec, pics) {
    // (1) remove old pic (if exists)
    pics.exit()
        .remove();

    // (2) load new pic
    var img = new Image();
    img.onload = function () {
        var posAndDims = glide.calcImgPosAndDims(img);
        pics.enter()
           .append("image")
           .attr("xlink:href", glide.picSrc)
           .attr("id", glide.picId)
           .attr("width", posAndDims.width)
           .attr("height", posAndDims.height)
           .attr("x", posAndDims.x)
           .attr("y", posAndDims.y);

       // (3) immediately draw squares
       var squareDims = glide.getSquareDims(posAndDims);
       var squareData = squareDims.squares;
       var squareShowTime = 100;
       var animDuration = squareData.length * squareShowTime;
       glide.svg.selectAll("rect")
                .data(squareData, glide.key)
                .enter()
                .append("rect")
                .attr("width", squareDims.width)
                .attr("height", squareDims.width)
                .attr("x", function (d) { return d.x; })
                .attr("y", function (d) { return d.y; })
                .attr("id", function (d) { return "sq_" + d.key; })
                .attr("stroke", glide.param.bgColor)
                .attr("fill", glide.param.bgColor);

       // (4) define animation stopper
       glide.cancelAnimation = function () {
           if (glide.animIntervalId != null) {
               clearInterval(glide.animIntervalId);
               glide.animIntervalId = null;

               glide.svg.selectAll("rect")
                        .data([], glide.key)
                        .exit()
                        .remove();

               glide.cancelAnimation = null;
           }
       };

       // (5) start animation: remove squares (one by one)
       glide.animIntervalId = setInterval(function () {
               if ( squareData.length > 0 ) {
                   glide.randSplice(squareData);
                   glide.svg.selectAll("rect")
                         .data(squareData, glide.key)
                         .exit()
                         .remove();
               } else {
                   clearInterval(glide.animIntervalId);
                   glide.animIntervalId = null;
                   glide.cancelAnimation = null;
               }
       },
       squareShowTime);

       glide.triggerNextSlide(animDuration + glide.param.showDuration);
    };
    img.src = nextPicSpec[0].src;
};

glide.registerAnimations = function () {
    glide.animations.ZOOM_IN = glide.aniZoomIn;
    glide.animations.SLIDE_RIGHT = glide.aniSlideRight;
    glide.animations.SLIDE_TOP = glide.aniSlideTop;
    glide.animations.MEMORY = glide.aniMemory;
};

glide.startShow = function (data) {
    glide.dataset = data;
    glide.max = glide.dataset.pics.length - 1;
    glide.i = -1;
    glide.updateParams();
    glide.registerAnimations();
    glide.setupCanvas();
    glide.nextSlide();
};

glide.stopShow = function () {
    if (glide.showTimeoutId != null) {
        clearTimeout(glide.showTimeoutId);
        glide.showTimeoutId = null;
    }
};

/******************************************************************************
 *** Event Handler ***
 *****************************************************************************/
 // On click on body:
d3.select("body").on("click", function(e) {
    glide.nextSlide();
});

// On key press:
document.onkeypress = function (e) {
    e = e || window.event;
    switch (e.key) {
    case " ":   // Space
        glide.nextSlide();
      break;
    case "Backspace":
      glide.prevSlide();
      break;
    case "ArrowDown":
      glide.prevSlide();
      break;
    case "ArrowUp":
      glide.nextSlide();
      break;
    case "ArrowLeft":
      glide.prevSlide();
      break;
    case "ArrowRight":
      glide.nextSlide();
      break;
    case "Enter":
      glide.nextSlide();
      break;
    case "Escape":
      glide.stopShow();
      break;
    default:
      return;
  }
};

// On window resize:
window.onresize = function(e) {

    // resize canvas
    glide.svg.attr("width", window.innerWidth)
             .attr("height", window.innerHeight);

    // resize pic
    var posAndDims = glide.calcImgPosAndDims();
    glide.svg.selectAll("image")
           .attr("width", posAndDims.width)
           .attr("height", posAndDims.height)
           .attr("x", posAndDims.x)
           .attr("y", posAndDims.y);
};

// On page load:
d3.json(glide.param.picsJsonURI, function(data) {
    glide.startShow(data);
});
<|endoftext|>