<commit_before>const path = require('path');
const { remarkProgramOutput } = require('./plugins/program_output');
const { rehypePlugins, remarkPlugins } = require('@rasahq/docusaurus-theme-tabula');

// FIXME: remove "next/" when releasing + remove the "next/" in
// http://github.com/RasaHQ/rasa-website/blob/master/netlify.toml
const BASE_URL = '/docs/rasa/next/';
const SITE_URL = 'https://rasa.com';

// NOTE: this allows switching between local dev instances of rasa/rasa-x
const isDev = process.env.NODE_ENV === 'development';
const SWAP_URL = isDev ? 'http://localhost:3001' : SITE_URL;

/* VERSIONING: WIP */

const routeBasePath = '/';
let versions = [];
try { versions = require('./versions.json'); } catch (ex) { console.info('no versions.json file found; assuming dev mode.') }

const legacyVersion = {
  label: 'Legacy 1.x',
  href: 'https://legacy-docs-v1.rasa.com',
  target: '_self',
};

const allVersions = {
  label: 'Versions',
  to: '/', // "fake" link
  position: 'left',
  items: versions.length > 0 ? [
    {
      label: versions[0],
      to: '/',
      activeBaseRegex: versions[0],
    },
    ...versions.slice(1).map((version) => ({
      label: version,
      to: `${version}/`,
      activeBaseRegex: version,
    })),
    {
      label: 'Master/Unreleased',
      to: 'next/',
      activeBaseRegex: `next`,
    },
    legacyVersion,
  ]
: [
    {
      label: 'Master/Unreleased',
      to: '/',
      activeBaseRegex: `/`,
    },
    legacyVersion,
  ],
}

module.exports = {
  customFields: {
    // NOTE: all non-standard options should go in this object
  },
  // FIXME: remove this option after launch
  onBrokenLinks: 'warn',
  title: 'Rasa Open Source Documentation',
  tagline: 'Cras justo odio, dapibus ac facilisis in, egestas eget quam.',
  url: SITE_URL,
  baseUrl: BASE_URL,
  favicon: '/img/favicon.ico',
  organizationName: 'RasaHQ',
  projectName: 'rasa',
  themeConfig: {
    colorMode: {
      defaultMode: 'light',
      disableSwitch: true,
    },
    navbar: {
      hideOnScroll: false,
      title: 'Rasa Open Source',
      logo: {
        alt: 'Rasa Logo',
        src: `/img/rasa-logo.svg`,
        href: SITE_URL,
      },
      items: [
        {
          label: 'Rasa Open Source',
          to: path.join('/', BASE_URL),
          position: 'left',
        },
        {
          label: 'Rasa X',
          position: 'left',
          href: `${SWAP_URL}/docs/rasa-x/next/`,
          target: '_self',
        },
        {
          target: '_self',
          href: 'http://blog.rasa.com/',
          label: 'Blog',
          position: 'right',
        },
        {
          target: '_self',
          href: `${SITE_URL}/community/join/`,
          label: 'Community',
          position: 'right',
        },
      ],
    },
    footer: {
      copyright: `Copyright © ${new Date().getFullYear()} Rasa Technologies GmbH`,
    },
    gtm: {
      containerID: 'GTM-PK448GB',
    },
  },
  themes: [
    '@rasahq/docusaurus-theme-tabula',
    path.resolve(__dirname, './themes/theme-custom')
  ],
  plugins: [
    ['@docusaurus/plugin-content-docs/', {
      routeBasePath,
      sidebarPath: require.resolve('./sidebars.js'),
      editUrl: 'https://github.com/rasahq/rasa/edit/master/docs/',
      showLastUpdateTime: true,
      showLastUpdateAuthor: true,
      rehypePlugins: [
        ...rehypePlugins,
      ],
      remarkPlugins: [
        ...remarkPlugins,
        remarkProgramOutput,
      ],
    }],
    ['@docusaurus/plugin-content-pages', {}],
    ['@docusaurus/plugin-sitemap',
      {
        cacheTime: 600 * 1000, // 600 sec - cache purge period
        changefreq: 'weekly',
        priority: 0.5,
      }],
    isDev && ['@docusaurus/plugin-debug', {}],
    [path.resolve(__dirname, './plugins/google-tagmanager'), {}],
  ].filter(Boolean),
};
<commit_msg>rename remark and rehype plugins from theme<commit_after>const path = require('path');
const { remarkProgramOutput } = require('./plugins/program_output');
const {
  rehypePlugins: themeRehypePlugins,
  remarkPlugins: themeRemarkPlugins,
} = require('@rasahq/docusaurus-theme-tabula');

// FIXME: remove "next/" when releasing + remove the "next/" in
// http://github.com/RasaHQ/rasa-website/blob/master/netlify.toml
const BASE_URL = '/docs/rasa/next/';
const SITE_URL = 'https://rasa.com';

// NOTE: this allows switching between local dev instances of rasa/rasa-x
const isDev = process.env.NODE_ENV === 'development';
const SWAP_URL = isDev ? 'http://localhost:3001' : SITE_URL;

/* VERSIONING: WIP */

const routeBasePath = '/';
let versions = [];
try { versions = require('./versions.json'); } catch (ex) { console.info('no versions.json file found; assuming dev mode.') }

const legacyVersion = {
  label: 'Legacy 1.x',
  href: 'https://legacy-docs-v1.rasa.com',
  target: '_self',
};

const allVersions = {
  label: 'Versions',
  to: '/', // "fake" link
  position: 'left',
  items: versions.length > 0 ? [
    {
      label: versions[0],
      to: '/',
      activeBaseRegex: versions[0],
    },
    ...versions.slice(1).map((version) => ({
      label: version,
      to: `${version}/`,
      activeBaseRegex: version,
    })),
    {
      label: 'Master/Unreleased',
      to: 'next/',
      activeBaseRegex: `next`,
    },
    legacyVersion,
  ]
: [
    {
      label: 'Master/Unreleased',
      to: '/',
      activeBaseRegex: `/`,
    },
    legacyVersion,
  ],
}

module.exports = {
  customFields: {
    // NOTE: all non-standard options should go in this object
  },
  // FIXME: set this to fail in CI after launch
  onBrokenLinks: 'warn',
  title: 'Rasa Open Source Documentation',
  tagline: 'Cras justo odio, dapibus ac facilisis in, egestas eget quam.',
  url: SITE_URL,
  baseUrl: BASE_URL,
  favicon: '/img/favicon.ico',
  organizationName: 'RasaHQ',
  projectName: 'rasa',
  themeConfig: {
    colorMode: {
      defaultMode: 'light',
      disableSwitch: true,
    },
    navbar: {
      hideOnScroll: false,
      title: 'Rasa Open Source',
      logo: {
        alt: 'Rasa Logo',
        src: `/img/rasa-logo.svg`,
        href: SITE_URL,
      },
      items: [
        {
          label: 'Rasa Open Source',
          to: path.join('/', BASE_URL),
          position: 'left',
        },
        {
          label: 'Rasa X',
          position: 'left',
          href: `${SWAP_URL}/docs/rasa-x/next/`,
          target: '_self',
        },
        {
          target: '_self',
          href: 'http://blog.rasa.com/',
          label: 'Blog',
          position: 'right',
        },
        {
          target: '_self',
          href: `${SITE_URL}/community/join/`,
          label: 'Community',
          position: 'right',
        },
      ],
    },
    footer: {
      copyright: `Copyright © ${new Date().getFullYear()} Rasa Technologies GmbH`,
    },
    gtm: {
      containerID: 'GTM-PK448GB',
    },
  },
  themes: [
    '@rasahq/docusaurus-theme-tabula',
    path.resolve(__dirname, './themes/theme-custom')
  ],
  plugins: [
    ['@docusaurus/plugin-content-docs/', {
      routeBasePath,
      sidebarPath: require.resolve('./sidebars.js'),
      editUrl: 'https://github.com/rasahq/rasa/edit/master/docs/',
      showLastUpdateTime: true,
      showLastUpdateAuthor: true,
      rehypePlugins: [
        ...themeRehypePlugins,
      ],
      remarkPlugins: [
        ...themeRemarkPlugins,
        remarkProgramOutput,
      ],
    }],
    ['@docusaurus/plugin-content-pages', {}],
    ['@docusaurus/plugin-sitemap',
      {
        cacheTime: 600 * 1000, // 600 sec - cache purge period
        changefreq: 'weekly',
        priority: 0.5,
      }],
    isDev && ['@docusaurus/plugin-debug', {}],
    [path.resolve(__dirname, './plugins/google-tagmanager'), {}],
  ].filter(Boolean),
};
<|endoftext|>


<commit_before>goog.provide('gmf.displayquerywindowComponent');

goog.require('gmf');
goog.require('ngeo.FeatureOverlay');
goog.require('ngeo.FeatureOverlayMgr');
/** @suppress {extraRequire} - required for `ngeoQueryResult` */
goog.require('ngeo.MapQuerent');
goog.require('ol.Collection');
goog.require('ol.style.Circle');
goog.require('ol.style.Fill');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');

ngeo.module.value('gmfDisplayquerywindowTemplateUrl',
  /**
     * @param {!angular.JQLite} $element Element.
     * @param {!angular.Attributes} $attrs Attributes.
     * @return {string} Template.
     */
  ($element, $attrs) => {
    const templateUrl = $attrs['gmfDisplayquerywindowTemplateurl'];
    return templateUrl !== undefined ? templateUrl :
      `${gmf.baseTemplateUrl}/displayquerywindow.html`;
  });

/**
 * @param {!angular.JQLite} $element Element.
 * @param {!angular.Attributes} $attrs Attributes.
 * @param {!function(!angular.JQLite, !angular.Attributes): string} gmfDisplayquerywindowTemplateUrl Template function.
 * @return {string} Template URL.
 * @ngInject
 */
function gmfDisplayquerywindowTemplateUrl($element, $attrs, gmfDisplayquerywindowTemplateUrl) {
  return gmfDisplayquerywindowTemplateUrl($element, $attrs);
}

/**
 * Provide a component to display results of the {@link ngeo.queryResult}
 * and shows related features on the map using the {@link ngeo.FeatureOverlayMgr}.
 *
 * You can override the default component's template by setting the
 * value `gmfDisplayquerywindowTemplateUrl`.
 *
 * Features displayed on the map use a default style but you can override these
 * styles by passing ol.style.Style objects as attributes of this component.
 *
 * Example:
 *
 *      <gmf-displayquerywindow
 *        gmf-displayquerywindow-featuresstyle="ctrl.styleForAllFeatures"
 *        gmf-displayquerywindow-selectedfeaturestyle="ctrl.styleForTheCurrentFeature">
 *      </gmf-displayquerywindow>
 *
 * @htmlAttribute {ol.style.Style} gmf-displayquerywindow-featuresstyle A style
 *     object for all features from the result of the query.
 * @htmlAttribute {ol.style.Style} selectedfeaturestyle A style
 *     object for the current displayed feature.
 * @htmlAttribute {boolean=} defaultcollapsed If the query result window is
 *     collapsed.
 * @htmlAttribute {boolean} desktop If the component is used in the desktop
 *     application.
 * @htmlAttribute {boolean} showunqueriedlayers If also layers, that have not
 *     been queried for the last query, should be shown in the filter.
 *
 * @ngdoc component
 * @ngname gmfDisplayquerywindow
 */
gmf.displayquerywindowComponent = {
  controller: 'GmfDisplayquerywindowController as ctrl',
  bindings: {
    'featuresStyleFn': '&gmfDisplayquerywindowFeaturesstyle',
    'selectedFeatureStyleFn': '&gmfDisplayquerywindowSelectedfeaturestyle',
    'defaultCollapsedFn': '&?gmfDisplayquerywindowDefaultcollapsed',
    'desktop': '=gmfDisplayquerywindowDesktop',
    'showUnqueriedLayers': '=gmfDisplayquerywindowShowunqueriedlayers'
  },
  templateUrl: gmfDisplayquerywindowTemplateUrl
};

gmf.module.component('gmfDisplayquerywindow', gmf.displayquerywindowComponent);

/**
 * @param {!angular.Scope} $scope Angular scope.
 * @param {!ngeox.QueryResult} ngeoQueryResult ngeo query result.
 * @param {!ngeo.FeatureOverlayMgr} ngeoFeatureOverlayMgr The ngeo feature
 *     overlay manager service.
 * @constructor
 * @private
 * @ngInject
 * @ngdoc controller
 * @ngname GmfDisplayquerywindowController
 */
gmf.DisplayquerywindowController = function($scope, ngeoQueryResult,
  ngeoFeatureOverlayMgr) {

  /**
   * @type {boolean}
   * @export
   */
  this.desktop = false;

  /**
   * Is the window currently collapsed?
   * When used for Desktop, it is shown non-collapsed.
   * @type {boolean}
   * @export
   */
  this.collapsed = !this.desktop;

  /**
   * @type {boolean}
   * @private
   */
  this.showUnqueriedLayers_ = false;

  /**
   * Object that is used to filter the source list in the template.
   * @type {Object}
   * @export
   */
  this.sourcesFilter = {'queried': true};

  /**
   * @type {ngeox.QueryResult}
   * @export
   */
  this.ngeoQueryResult = {
    sources: [],
    total: 0,
    pending: false
  };

  /**
   * @type {?ngeox.QueryResultSource}
   * @export
   */
  this.selectedSource = null;

  /**
   * @type {ol.Collection}
   * @private
   */
  this.features_ = new ol.Collection();

  /**
   * @type {!ngeo.FeatureOverlayMgr}
   * @private
   */
  this.ngeoFeatureOverlayMgr_ = ngeoFeatureOverlayMgr;

  /**
   * @type {ngeo.FeatureOverlay}
   * @private
   */
  this.highlightFeatureOverlay_ = ngeoFeatureOverlayMgr.getFeatureOverlay();

  /**
   * @type {ol.Collection}
   * @private
   */
  this.highlightFeatures_ = new ol.Collection();
  this.highlightFeatureOverlay_.setFeatures(this.highlightFeatures_);

  /**
   * @type {ngeox.QueryResultSource?}
   * @export
   */
  this.source = null;

  /**
   * @type {ol.Feature}
   * @export
   */
  this.feature = null;

  /**
   * @type {number}
   * @export
   */
  this.currentResult = -1;

  /**
   * @type {boolean}
   * @export
   */
  this.isNext = true;

  /**
   * @type {number}
   * @export
   */
  this.animate = 0;

  /**
   * @type {boolean}
   * @export
   */
  this.open = false;

  $scope.$watchCollection(
    () => ngeoQueryResult,
    (newQueryResult, oldQueryResult) => {
      this.updateQueryResult_(newQueryResult);
      if (newQueryResult.total > 0) {
        this.show();
      } else if (oldQueryResult !== newQueryResult) {
        this.close();
      }
    });
};

/**
 * Initialise the controller.
 */
gmf.DisplayquerywindowController.prototype.$onInit = function() {
  this.desktop = this.desktop;
  this.collapsed = this['defaultCollapsedFn'] ?
    this['defaultCollapsedFn']() === true : !this.desktop;

  this.showUnqueriedLayers_ = this['showUnqueriedLayers'] ?
    this['showUnqueriedLayers'] === true : false;

  this.sourcesFilter = this.showUnqueriedLayers_ ? {} : {'queried': true};

  const featuresOverlay = this.ngeoFeatureOverlayMgr_.getFeatureOverlay();
  const featuresStyle = this['featuresStyleFn']();
  if (featuresStyle !== undefined) {
    goog.asserts.assertInstanceof(featuresStyle, ol.style.Style);
    featuresOverlay.setStyle(featuresStyle);
  }
  featuresOverlay.setFeatures(this.features_);

  let highlightFeatureStyle = this['selectedFeatureStyleFn']();
  if (highlightFeatureStyle !== undefined) {
    goog.asserts.assertInstanceof(highlightFeatureStyle, ol.style.Style);
  } else {
    const fill = new ol.style.Fill({color: [255, 0, 0, 0.6]});
    const stroke = new ol.style.Stroke({color: [255, 0, 0, 1], width: 2});
    highlightFeatureStyle = new ol.style.Style({
      fill,
      image: new ol.style.Circle({fill, radius: 5, stroke}),
      stroke
    });
  }
  this.highlightFeatureOverlay_.setStyle(highlightFeatureStyle);
};

/**
 * Remove current displayed results then get new results from the
 * ngeoQueryResult service. Display all results on the map and display,
 * highlight the first feature.
 * @export
 */
gmf.DisplayquerywindowController.prototype.show = function() {
  this.clear();
  this.updateFeatures_();
};

/**
 * @private
 */
gmf.DisplayquerywindowController.prototype.updateFeatures_ = function() {
  this.setCurrentResult_(0, false);
  if (this.source !== null) {
    this.collectFeatures_();
    this.highlightCurrentFeature_();
    this.open = true;
  }
};

/**
 * Select a source and a feature depending of the given position.
 * @param {number} position The index of the feature. If the position is bigger
 * than the length of the first source, get it in the next source. Etc.
 * @param {boolean} setHighlight True to set the highlight automatically.
 * @return {boolean} True if result has changed. False else.
 * @private
 */
gmf.DisplayquerywindowController.prototype.setCurrentResult_ = function(
  position, setHighlight) {
  let hasChanged = false;
  if (position !== this.currentResult) {
    let i, source, features;
    const lastFeature = this.feature;
    const sources = this.ngeoQueryResult.sources;
    this.currentResult = position;
    for (i = 0; i < sources.length; i++) {
      source = sources[i];
      if (this.selectedSource !== null && this.selectedSource !== source) {
        // when filtering on a source, only consider features of the selected source
        continue;
      }
      features = source.features;
      if (position >= features.length) {
        position -= features.length;
      } else {
        this.source = source;
        this.feature = source.features[position];
        hasChanged = true;
        break;
      }
    }
    if (setHighlight) {
      this.highlightCurrentFeature_(lastFeature);
    }
  }
  return hasChanged;
};

/**
 * Select the logical previous source and feature then highlight this feature on
 * the map.
 * @export
 */
gmf.DisplayquerywindowController.prototype.previous = function() {
  let position = this.currentResult - 1;
  if (position < 0) {
    position = this.getResultLength() - 1;
  }
  const hasChanged = this.setCurrentResult_(position, true);
  if (hasChanged) {
    this.animate_(false);
  }
};

/**
 * Select the logical next source and feature then highlight this feature on
 * the map.
 * @export
 */
gmf.DisplayquerywindowController.prototype.next = function() {
  let position = this.currentResult + 1;
  const positionMax = this.getResultLength() - 1;
  if (position > positionMax) {
    position = 0;
  }
  const hasChanged = this.setCurrentResult_(position, true);
  if (hasChanged) {
    this.animate_(true);
  }
};

/**
 * Remove features without properties from the query result.
 * @param {ngeox.QueryResult} queryResult ngeo query result.
 * @private
 */
gmf.DisplayquerywindowController.prototype.updateQueryResult_ = function(queryResult) {
  this.ngeoQueryResult.total = 0;
  this.ngeoQueryResult.sources.length = 0;
  for (let i = 0; i < queryResult.sources.length; i++) {
    const source = queryResult.sources[i];
    source.features = source.features.filter(function(feature) {
      goog.asserts.assert(feature);
      return !ol.obj.isEmpty(ngeo.FeatureHelper.getFilteredFeatureValues(feature));
    }, this);
    this.ngeoQueryResult.sources.push(source);
    this.ngeoQueryResult.total += source.features.length;
  }
};

/**
 * Get the total count of features in the result of the query. If a source
 * has been select, only the number of features of that source are returned.
 * @return {number} Total number of features.
 * @export
 */
gmf.DisplayquerywindowController.prototype.getResultLength = function() {
  if (this.selectedSource === null) {
    return this.ngeoQueryResult.total;
  } else {
    return this.selectedSource.features.length;
  }
};

/**
 * @return {boolean} If the first result is active.
 * @export
 */
gmf.DisplayquerywindowController.prototype.isFirst = function() {
  return this.currentResult == 0;
};

/**
 * @return {boolean} If the last result is active.
 * @export
 */
gmf.DisplayquerywindowController.prototype.isLast = function() {
  return this.currentResult == this.getResultLength() - 1;
};

/**
 * Delete the unwanted ol3 properties from the current feature then return the
 * properties.
 * @return {Object?} Filtered properties of the current feature or null.
 * @export
 */
gmf.DisplayquerywindowController.prototype.getFeatureValues = function() {
  if (!this.feature) {
    return null;
  }
  return ngeo.FeatureHelper.getFilteredFeatureValues(this.feature);
};

/**
 * Special function that's used to set the "animation" value after to set the
 * "isNext" value. The aim is to wait on Angular to add a class (corresponding
 * to "isNext") on the DOM before to set the "animation" value and do the
 * animation.
 * @param {boolean} isNext used to indicate if the user wants to see the next
 * or the previous result.
 * @private
 */
gmf.DisplayquerywindowController.prototype.animate_ = function(isNext) {
  this.isNext = isNext;
  this.animate++;
};

/**
 * Collect all features in the queryResult object.
 * @private
 */
gmf.DisplayquerywindowController.prototype.collectFeatures_ = function() {
  const sources = this.ngeoQueryResult.sources;
  this.features_.clear();
  for (let i = 0; i < sources.length; i++) {
    const source = sources[i];
    if (this.selectedSource !== null && this.selectedSource !== source) {
      // when filtering on a source, only add features of the selected source
      continue;
    }
    const features = source.features;
    for (let ii = 0; ii < features.length; ii++) {
      this.features_.push(features[ii]);
    }
  }
};

/**
 * Highlight the current displayed feature.
 * @param {ol.Feature=} opt_lastFeature last highlighted feature. Require if
 * it exists because it must be added to the 'non-selected' features collection.
 * @private
 */
gmf.DisplayquerywindowController.prototype.highlightCurrentFeature_ =
function(opt_lastFeature) {
  this.highlightFeatures_.clear();
  this.features_.remove(this.feature);
  this.highlightFeatures_.push(this.feature);
  if (opt_lastFeature !== undefined) {
    this.features_.push(opt_lastFeature);
  }
};

/**
 * Remove the current selected feature and source and remove all features
 * from the map.
 * @export
 */
gmf.DisplayquerywindowController.prototype.close = function() {
  this.open = false;
  this.clear();
};

/**
 * Remove the current selected feature and source and remove all features
 * from the map.
 * @export
 */
gmf.DisplayquerywindowController.prototype.clear = function() {
  this.feature = null;
  this.source = null;
  this.currentResult = -1;
  this.features_.clear();
  this.highlightFeatures_.clear();
  this.selectedSource = null;
};

/**
 * @param {ngeox.QueryResultSource} source The source to select.
 * @export
 */
gmf.DisplayquerywindowController.prototype.setSelectedSource = function(source) {
  if (source !== null && source.features.length <= 0) {
    // sources with no results can not be selected
    return;
  }
  this.clear();
  this.selectedSource = source;
  this.updateFeatures_();
};

gmf.module.controller('GmfDisplayquerywindowController', gmf.DisplayquerywindowController);
<commit_msg>Linter<commit_after>goog.provide('gmf.displayquerywindowComponent');

goog.require('gmf');
goog.require('ngeo.FeatureOverlay');
goog.require('ngeo.FeatureOverlayMgr');
/** @suppress {extraRequire} - required for `ngeoQueryResult` */
goog.require('ngeo.MapQuerent');
goog.require('ol.Collection');
goog.require('ol.style.Circle');
goog.require('ol.style.Fill');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');

ngeo.module.value('gmfDisplayquerywindowTemplateUrl',
  /**
     * @param {!angular.JQLite} $element Element.
     * @param {!angular.Attributes} $attrs Attributes.
     * @return {string} Template.
     */
  ($element, $attrs) => {
    const templateUrl = $attrs['gmfDisplayquerywindowTemplateurl'];
    return templateUrl !== undefined ? templateUrl :
      `${gmf.baseTemplateUrl}/displayquerywindow.html`;
  });

/**
 * @param {!angular.JQLite} $element Element.
 * @param {!angular.Attributes} $attrs Attributes.
 * @param {!function(!angular.JQLite, !angular.Attributes): string} gmfDisplayquerywindowTemplateUrl Template function.
 * @return {string} Template URL.
 * @ngInject
 */
function gmfDisplayquerywindowTemplateUrl($element, $attrs, gmfDisplayquerywindowTemplateUrl) {
  return gmfDisplayquerywindowTemplateUrl($element, $attrs);
}

/**
 * Provide a component to display results of the {@link ngeo.queryResult}
 * and shows related features on the map using the {@link ngeo.FeatureOverlayMgr}.
 *
 * You can override the default component's template by setting the
 * value `gmfDisplayquerywindowTemplateUrl`.
 *
 * Features displayed on the map use a default style but you can override these
 * styles by passing ol.style.Style objects as attributes of this component.
 *
 * Example:
 *
 *      <gmf-displayquerywindow
 *        gmf-displayquerywindow-featuresstyle="ctrl.styleForAllFeatures"
 *        gmf-displayquerywindow-selectedfeaturestyle="ctrl.styleForTheCurrentFeature">
 *      </gmf-displayquerywindow>
 *
 * @htmlAttribute {ol.style.Style} gmf-displayquerywindow-featuresstyle A style
 *     object for all features from the result of the query.
 * @htmlAttribute {ol.style.Style} selectedfeaturestyle A style
 *     object for the current displayed feature.
 * @htmlAttribute {boolean=} defaultcollapsed If the query result window is
 *     collapsed.
 * @htmlAttribute {boolean} desktop If the component is used in the desktop
 *     application.
 * @htmlAttribute {boolean} showunqueriedlayers If also layers, that have not
 *     been queried for the last query, should be shown in the filter.
 *
 * @ngdoc component
 * @ngname gmfDisplayquerywindow
 */
gmf.displayquerywindowComponent = {
  controller: 'GmfDisplayquerywindowController as ctrl',
  bindings: {
    'featuresStyleFn': '&gmfDisplayquerywindowFeaturesstyle',
    'selectedFeatureStyleFn': '&gmfDisplayquerywindowSelectedfeaturestyle',
    'defaultCollapsedFn': '&?gmfDisplayquerywindowDefaultcollapsed',
    'desktop': '=gmfDisplayquerywindowDesktop',
    'showUnqueriedLayers': '=gmfDisplayquerywindowShowunqueriedlayers'
  },
  templateUrl: gmfDisplayquerywindowTemplateUrl
};

gmf.module.component('gmfDisplayquerywindow', gmf.displayquerywindowComponent);

/**
 * @param {!angular.Scope} $scope Angular scope.
 * @param {!ngeox.QueryResult} ngeoQueryResult ngeo query result.
 * @param {!ngeo.FeatureOverlayMgr} ngeoFeatureOverlayMgr The ngeo feature
 *     overlay manager service.
 * @constructor
 * @private
 * @ngInject
 * @ngdoc controller
 * @ngname GmfDisplayquerywindowController
 */
gmf.DisplayquerywindowController = function($scope, ngeoQueryResult,
  ngeoFeatureOverlayMgr) {

  /**
   * @type {boolean}
   * @export
   */
  this.desktop = false;

  /**
   * Is the window currently collapsed?
   * When used for Desktop, it is shown non-collapsed.
   * @type {boolean}
   * @export
   */
  this.collapsed = !this.desktop;

  /**
   * @type {boolean}
   * @private
   */
  this.showUnqueriedLayers_ = false;

  /**
   * Object that is used to filter the source list in the template.
   * @type {Object}
   * @export
   */
  this.sourcesFilter = {'queried': true};

  /**
   * @type {ngeox.QueryResult}
   * @export
   */
  this.ngeoQueryResult = {
    sources: [],
    total: 0,
    pending: false
  };

  /**
   * @type {?ngeox.QueryResultSource}
   * @export
   */
  this.selectedSource = null;

  /**
   * @type {ol.Collection}
   * @private
   */
  this.features_ = new ol.Collection();

  /**
   * @type {!ngeo.FeatureOverlayMgr}
   * @private
   */
  this.ngeoFeatureOverlayMgr_ = ngeoFeatureOverlayMgr;

  /**
   * @type {ngeo.FeatureOverlay}
   * @private
   */
  this.highlightFeatureOverlay_ = ngeoFeatureOverlayMgr.getFeatureOverlay();

  /**
   * @type {ol.Collection}
   * @private
   */
  this.highlightFeatures_ = new ol.Collection();
  this.highlightFeatureOverlay_.setFeatures(this.highlightFeatures_);

  /**
   * @type {ngeox.QueryResultSource?}
   * @export
   */
  this.source = null;

  /**
   * @type {ol.Feature}
   * @export
   */
  this.feature = null;

  /**
   * @type {number}
   * @export
   */
  this.currentResult = -1;

  /**
   * @type {boolean}
   * @export
   */
  this.isNext = true;

  /**
   * @type {number}
   * @export
   */
  this.animate = 0;

  /**
   * @type {boolean}
   * @export
   */
  this.open = false;

  $scope.$watchCollection(
    () => ngeoQueryResult,
    (newQueryResult, oldQueryResult) => {
      this.updateQueryResult_(newQueryResult);
      if (newQueryResult.total > 0) {
        this.show();
      } else if (oldQueryResult !== newQueryResult) {
        this.close();
      }
    });
};

/**
 * Initialise the controller.
 */
gmf.DisplayquerywindowController.prototype.$onInit = function() {
  this.desktop = this.desktop;
  this.collapsed = this['defaultCollapsedFn'] ?
    this['defaultCollapsedFn']() === true : !this.desktop;

  this.showUnqueriedLayers_ = this['showUnqueriedLayers'] ?
    this['showUnqueriedLayers'] === true : false;

  this.sourcesFilter = this.showUnqueriedLayers_ ? {} : {'queried': true};

  const featuresOverlay = this.ngeoFeatureOverlayMgr_.getFeatureOverlay();
  const featuresStyle = this['featuresStyleFn']();
  if (featuresStyle !== undefined) {
    goog.asserts.assertInstanceof(featuresStyle, ol.style.Style);
    featuresOverlay.setStyle(featuresStyle);
  }
  featuresOverlay.setFeatures(this.features_);

  let highlightFeatureStyle = this['selectedFeatureStyleFn']();
  if (highlightFeatureStyle !== undefined) {
    goog.asserts.assertInstanceof(highlightFeatureStyle, ol.style.Style);
  } else {
    const fill = new ol.style.Fill({color: [255, 0, 0, 0.6]});
    const stroke = new ol.style.Stroke({color: [255, 0, 0, 1], width: 2});
    highlightFeatureStyle = new ol.style.Style({
      fill,
      image: new ol.style.Circle({fill, radius: 5, stroke}),
      stroke
    });
  }
  this.highlightFeatureOverlay_.setStyle(highlightFeatureStyle);
};

/**
 * Remove current displayed results then get new results from the
 * ngeoQueryResult service. Display all results on the map and display,
 * highlight the first feature.
 * @export
 */
gmf.DisplayquerywindowController.prototype.show = function() {
  this.clear();
  this.updateFeatures_();
};

/**
 * @private
 */
gmf.DisplayquerywindowController.prototype.updateFeatures_ = function() {
  this.setCurrentResult_(0, false);
  if (this.source !== null) {
    this.collectFeatures_();
    this.highlightCurrentFeature_();
    this.open = true;
  }
};

/**
 * Select a source and a feature depending of the given position.
 * @param {number} position The index of the feature. If the position is bigger
 * than the length of the first source, get it in the next source. Etc.
 * @param {boolean} setHighlight True to set the highlight automatically.
 * @return {boolean} True if result has changed. False else.
 * @private
 */
gmf.DisplayquerywindowController.prototype.setCurrentResult_ = function(
  position, setHighlight) {
  let hasChanged = false;
  if (position !== this.currentResult) {
    let i, source, features;
    const lastFeature = this.feature;
    const sources = this.ngeoQueryResult.sources;
    this.currentResult = position;
    for (i = 0; i < sources.length; i++) {
      source = sources[i];
      if (this.selectedSource !== null && this.selectedSource !== source) {
        // when filtering on a source, only consider features of the selected source
        continue;
      }
      features = source.features;
      if (position >= features.length) {
        position -= features.length;
      } else {
        this.source = source;
        this.feature = source.features[position];
        hasChanged = true;
        break;
      }
    }
    if (setHighlight) {
      this.highlightCurrentFeature_(lastFeature);
    }
  }
  return hasChanged;
};

/**
 * Select the logical previous source and feature then highlight this feature on
 * the map.
 * @export
 */
gmf.DisplayquerywindowController.prototype.previous = function() {
  let position = this.currentResult - 1;
  if (position < 0) {
    position = this.getResultLength() - 1;
  }
  const hasChanged = this.setCurrentResult_(position, true);
  if (hasChanged) {
    this.animate_(false);
  }
};

/**
 * Select the logical next source and feature then highlight this feature on
 * the map.
 * @export
 */
gmf.DisplayquerywindowController.prototype.next = function() {
  let position = this.currentResult + 1;
  const positionMax = this.getResultLength() - 1;
  if (position > positionMax) {
    position = 0;
  }
  const hasChanged = this.setCurrentResult_(position, true);
  if (hasChanged) {
    this.animate_(true);
  }
};

/**
 * Remove features without properties from the query result.
 * @param {ngeox.QueryResult} queryResult ngeo query result.
 * @private
 */
gmf.DisplayquerywindowController.prototype.updateQueryResult_ = function(queryResult) {
  this.ngeoQueryResult.total = 0;
  this.ngeoQueryResult.sources.length = 0;
  for (let i = 0; i < queryResult.sources.length; i++) {
    const source = queryResult.sources[i];
    source.features = source.features.filter((feature) => {
      goog.asserts.assert(feature);
      return !ol.obj.isEmpty(ngeo.FeatureHelper.getFilteredFeatureValues(feature));
    });
    this.ngeoQueryResult.sources.push(source);
    this.ngeoQueryResult.total += source.features.length;
  }
};

/**
 * Get the total count of features in the result of the query. If a source
 * has been select, only the number of features of that source are returned.
 * @return {number} Total number of features.
 * @export
 */
gmf.DisplayquerywindowController.prototype.getResultLength = function() {
  if (this.selectedSource === null) {
    return this.ngeoQueryResult.total;
  } else {
    return this.selectedSource.features.length;
  }
};

/**
 * @return {boolean} If the first result is active.
 * @export
 */
gmf.DisplayquerywindowController.prototype.isFirst = function() {
  return this.currentResult == 0;
};

/**
 * @return {boolean} If the last result is active.
 * @export
 */
gmf.DisplayquerywindowController.prototype.isLast = function() {
  return this.currentResult == this.getResultLength() - 1;
};

/**
 * Delete the unwanted ol3 properties from the current feature then return the
 * properties.
 * @return {Object?} Filtered properties of the current feature or null.
 * @export
 */
gmf.DisplayquerywindowController.prototype.getFeatureValues = function() {
  if (!this.feature) {
    return null;
  }
  return ngeo.FeatureHelper.getFilteredFeatureValues(this.feature);
};

/**
 * Special function that's used to set the "animation" value after to set the
 * "isNext" value. The aim is to wait on Angular to add a class (corresponding
 * to "isNext") on the DOM before to set the "animation" value and do the
 * animation.
 * @param {boolean} isNext used to indicate if the user wants to see the next
 * or the previous result.
 * @private
 */
gmf.DisplayquerywindowController.prototype.animate_ = function(isNext) {
  this.isNext = isNext;
  this.animate++;
};

/**
 * Collect all features in the queryResult object.
 * @private
 */
gmf.DisplayquerywindowController.prototype.collectFeatures_ = function() {
  const sources = this.ngeoQueryResult.sources;
  this.features_.clear();
  for (let i = 0; i < sources.length; i++) {
    const source = sources[i];
    if (this.selectedSource !== null && this.selectedSource !== source) {
      // when filtering on a source, only add features of the selected source
      continue;
    }
    const features = source.features;
    for (let ii = 0; ii < features.length; ii++) {
      this.features_.push(features[ii]);
    }
  }
};

/**
 * Highlight the current displayed feature.
 * @param {ol.Feature=} opt_lastFeature last highlighted feature. Require if
 * it exists because it must be added to the 'non-selected' features collection.
 * @private
 */
gmf.DisplayquerywindowController.prototype.highlightCurrentFeature_ =
function(opt_lastFeature) {
  this.highlightFeatures_.clear();
  this.features_.remove(this.feature);
  this.highlightFeatures_.push(this.feature);
  if (opt_lastFeature !== undefined) {
    this.features_.push(opt_lastFeature);
  }
};

/**
 * Remove the current selected feature and source and remove all features
 * from the map.
 * @export
 */
gmf.DisplayquerywindowController.prototype.close = function() {
  this.open = false;
  this.clear();
};

/**
 * Remove the current selected feature and source and remove all features
 * from the map.
 * @export
 */
gmf.DisplayquerywindowController.prototype.clear = function() {
  this.feature = null;
  this.source = null;
  this.currentResult = -1;
  this.features_.clear();
  this.highlightFeatures_.clear();
  this.selectedSource = null;
};

/**
 * @param {ngeox.QueryResultSource} source The source to select.
 * @export
 */
gmf.DisplayquerywindowController.prototype.setSelectedSource = function(source) {
  if (source !== null && source.features.length <= 0) {
    // sources with no results can not be selected
    return;
  }
  this.clear();
  this.selectedSource = source;
  this.updateFeatures_();
};

gmf.module.controller('GmfDisplayquerywindowController', gmf.DisplayquerywindowController);
<|endoftext|>


<commit_before>import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

import Ember from 'ember';

const testTemplate = hbs`
  {{#ivy-tree as |tree|}}
    {{#tree.treeitem as |treeitem|}}
      Animals
      {{#treeitem.group as |group|}}
        {{#group.treeitem as |treeitem|}}
          Birds
        {{/group.treeitem}}
        {{#group.treeitem as |treeitem|}}
          Cats
          {{#treeitem.group as |group|}}
            {{#group.treeitem}}
              Siamese
            {{/group.treeitem}}
            {{#group.treeitem}}
              Tabby
            {{/group.treeitem}}
          {{/treeitem.group}}
        {{/group.treeitem}}
        {{#group.treeitem as |treeitem|}}
          Dogs
          {{#treeitem.group as |group|}}
            {{#group.treeitem as |treeitem|}}
              Small Breeds
              {{#treeitem.group as |group|}}
                {{#group.treeitem}}
                  Chihuahua
                {{/group.treeitem}}
                {{#group.treeitem}}
                  Italian Greyhound
                {{/group.treeitem}}
                {{#group.treeitem}}
                  Japanese Chin
                {{/group.treeitem}}
              {{/treeitem.group}}
            {{/group.treeitem}}
            {{#group.treeitem as |treeitem|}}
              Medium Breeds
              {{#treeitem.group as |group|}}
                {{#group.treeitem}}
                  Beagle
                {{/group.treeitem}}
                {{#group.treeitem}}
                  Cocker Spaniel
                {{/group.treeitem}}
                {{#group.treeitem}}
                  Pit Bull
                {{/group.treeitem}}
              {{/treeitem.group}}
            {{/group.treeitem}}
            {{#group.treeitem as |treeitem|}}
              Large Breeds
              {{#treeitem.group as |group|}}
                {{#group.treeitem}}
                  Afghan
                {{/group.treeitem}}
                {{#group.treeitem}}
                  Great Dane
                {{/group.treeitem}}
                {{#group.treeitem}}
                  Mastiff
                {{/group.treeitem}}
              {{/treeitem.group}}
            {{/group.treeitem}}
          {{/treeitem.group}}
        {{/group.treeitem}}
      {{/treeitem.group}}
    {{/tree.treeitem}}
    {{#tree.treeitem as |treeitem|}}
      Minerals
      {{#treeitem.group as |group|}}
        {{#group.treeitem}}
          Zinc
        {{/group.treeitem}}
        {{#group.treeitem as |treeitem|}}
          Gold
          {{#treeitem.group as |group|}}
            {{#group.treeitem}}
              Yellow Gold
            {{/group.treeitem}}
            {{#group.treeitem}}
              White Gold
            {{/group.treeitem}}
          {{/treeitem.group}}
        {{/group.treeitem}}
        {{#group.treeitem}}
          Silver
        {{/group.treeitem}}
      {{/treeitem.group}}
    {{/tree.treeitem}}
    {{#tree.treeitem as |treeitem|}}
      Vegetables
      {{#treeitem.group as |group|}}
        {{#group.treeitem}}
          Carrot
        {{/group.treeitem}}
        {{#group.treeitem}}
          Tomato
        {{/group.treeitem}}
        {{#group.treeitem}}
          Lettuce
        {{/group.treeitem}}
      {{/treeitem.group}}
    {{/tree.treeitem}}
  {{/ivy-tree}}
`;

moduleForComponent('ivy-tree', 'Integration | Component | ivy-tree', {
  integration: true
});

test('it applies an "aria-hidden" attribute to the children of a collapsed treeitem', function(assert) {
  this.render(testTemplate);

  assert.equal(this.$('[role="treeitem"]:eq(0)').attr('aria-expanded'), 'false');
  assert.equal(this.$('[role="treeitem"]:eq(0) [role="treeitem"]:eq(0)').attr('aria-hidden'), 'true');

  this.$('[role="treeitem"]:eq(0)').dblclick();
  assert.equal(this.$('[role="treeitem"]:eq(0) [role="treeitem"]:eq(0)').attr('aria-hidden'), 'false');
});

test('it applies an "aria-level" attribute to treeitems', function(assert) {
  this.render(testTemplate);

  assert.equal(this.$('[role="treeitem"][aria-level="1"]').length, 3);
  assert.equal(this.$('[role="treeitem"][aria-level="2"]').length, 9);
  assert.equal(this.$('[role="treeitem"][aria-level="3"]').length, 7);
  assert.equal(this.$('[role="treeitem"][aria-level="4"]').length, 9);
  assert.equal(this.$('[role="treeitem"][aria-level="5"]').length, 0);
});

test('it applies an "aria-selected" attribute to the active treeitem', function(assert) {
  this.render(testTemplate);

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-selected'), 'false');

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();
  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-selected'), 'true');
});

test('it activates a treeitem on click', function(assert) {
  this.render(testTemplate);

  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), undefined);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('id'));
});

test('it applies an "active" class to the active treeitem', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();

  assert.ok(this.$('[role="treeitem"][aria-level="1"]:eq(0)').hasClass('active'));
});

test('it applies a custom activeClass class to the active treeitem', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem activeClass="is-active"}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();

  assert.ok(this.$('[role="treeitem"][aria-level="1"]:eq(0)').hasClass('is-active'));
});

test('it toggles expansion of a treeitem on double-click', function(assert) {
  this.render(testTemplate);

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'false');

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').dblclick();
  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'true');
});

test('it toggles expansion of the focused treeitem when enter key is pressed', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'false');

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 13 }));
  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'true');
});

test('it selects the previous sibling treeitem when up arrow key is pressed', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(1)').click();

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 38 }));
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('id'));
});

test('it selects the parent treeitem when up arrow key is pressed', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(1)').dblclick();
  this.$('[role="treeitem"][aria-level="1"] [role="treeitem"][aria-level="2"]:eq(0)').click();

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 38 }));
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('id'));
});

test('it selects the next sibling treeitem when down arrow key is pressed', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 40 }));
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(1)').attr('id'));
});

test('it selects the next sibling of the parent treeitem when down arrow key is pressed', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').dblclick();
  this.$('[role="treeitem"][aria-level="1"]:eq(0) [role="treeitem"][aria-level="2"]:last-child').click();

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 40 }));
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(1)').attr('id'));
});

test('it collapses the (expanded) currently-focused node when left arrow key is pressed', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();
  this.$('[role="treeitem"][aria-level="1"]:eq(0)').dblclick();

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'true');
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('id'));

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 37 }));
  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'false');
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('id'));
});

test('it selects the parent of the (collapsed) currently-focused node when left arrow key is pressed', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').dblclick();
  this.$('[role="treeitem"][aria-level="1"] [role="treeitem"][aria-level="2"]:eq(0)').click();

  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"] [role="treeitem"][aria-level="2"]:eq(0)').attr('id'));

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 37 }));
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('id'));
});

test('it expands a collapsed treeitem and selects its first child when right arrow key is pressed', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'false');

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 39 }));
  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'true');
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"] [role="treeitem"][aria-level="2"]:eq(0)').attr('id'));
});

test('it expands all treeitems when asterisk (shift+8) key is pressed', function(assert) {
  this.render(testTemplate);

  assert.notEqual(this.$('[role="treeitem"][aria-expanded="false"]').length, 0);

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 56, shiftKey: true }));
  assert.equal(this.$('[role="treeitem"][aria-expanded="false"]').length, 0);
});

test('it selects the first, visible treeitem when home key is pressed', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(1)').click();

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-selected'), 'false');

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 36 }));

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-selected'), 'true');
});

test('it selects the last, visible treeitem when end key is pressed', function(assert) {
  this.render(testTemplate);

  this.$('[role="treeitem"][aria-level="1"]:eq(1)').click();

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(2)').attr('aria-selected'), 'false');

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 35 }));

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(2)').attr('aria-selected'), 'true');

  // expand the last item to make deeper items visible.
  this.$('[role="treeitem"][aria-level="1"]:eq(2)').dblclick();
  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 35 }));

  assert.equal(this.$('[role="treeitem"][aria-level="2"]:eq(8)').attr('aria-selected'), 'true');
});

test('it sends an "onSelect" action when a treeitem is selected', function(assert) {
  assert.expect(1);

  this.on('onSelect', function() {
    assert.ok(true);
  });
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem onSelect=(action "onSelect")}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();
});
<commit_msg>Remove shared test template<commit_after>import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

import Ember from 'ember';

moduleForComponent('ivy-tree', 'Integration | Component | ivy-tree', {
  integration: true
});

test('it applies an "aria-hidden" attribute to the children of a collapsed treeitem', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
    {{/ivy-tree}}
  `);

  assert.equal(this.$('[role="treeitem"]:eq(0)').attr('aria-expanded'), 'false');
  assert.equal(this.$('[role="treeitem"]:eq(0) [role="treeitem"]:eq(0)').attr('aria-hidden'), 'true');

  this.$('[role="treeitem"]:eq(0)').dblclick();
  assert.equal(this.$('[role="treeitem"]:eq(0) [role="treeitem"]:eq(0)').attr('aria-hidden'), 'false');
});

test('it applies an "aria-level" attribute to treeitems', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
    {{/ivy-tree}}
  `);

  assert.equal(this.$('[role="treeitem"]:eq(0)').attr('aria-level'), '1');
  assert.equal(this.$('[role="treeitem"]:eq(1)').attr('aria-level'), '2');
});

test('it applies an "aria-selected" attribute to the active treeitem', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem}}
    {{/ivy-tree}}
  `);

  assert.equal(this.$('[role="treeitem"]:eq(0)').attr('aria-selected'), 'false');

  this.$('[role="treeitem"]:eq(0)').click();
  assert.equal(this.$('[role="treeitem"]:eq(0)').attr('aria-selected'), 'true');
});

test('it activates a treeitem on click', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem}}
    {{/ivy-tree}}
  `);

  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), undefined);

  this.$('[role="treeitem"]:eq(0)').click();
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"]:eq(0)').attr('id'));
});

test('it applies an "active" class to the active treeitem', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"]:eq(0)').click();

  assert.ok(this.$('[role="treeitem"]:eq(0)').hasClass('active'));
});

test('it applies a custom activeClass class to the active treeitem', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem activeClass="is-active"}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();

  assert.ok(this.$('[role="treeitem"][aria-level="1"]:eq(0)').hasClass('is-active'));
});

test('it toggles expansion of a treeitem on double-click', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
    {{/ivy-tree}}
  `);

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'false');

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').dblclick();
  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'true');
});

test('it toggles expansion of the focused treeitem when enter key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'false');

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 13 }));
  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'true');
});

test('it selects the previous sibling treeitem when up arrow key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem}}
      {{tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"]:eq(1)').click();

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 38 }));
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"]:eq(0)').attr('id'));
});

test('it selects the parent treeitem when up arrow key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').dblclick();
  this.$('[role="treeitem"][aria-level="1"] [role="treeitem"][aria-level="2"]:eq(0)').click();

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 38 }));
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('id'));
});

test('it selects the next sibling treeitem when down arrow key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem}}
      {{tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"]:eq(0)').click();

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 40 }));
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"]:eq(1)').attr('id'));
});

test('it selects the next sibling of the parent treeitem when down arrow key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
      {{tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').dblclick();
  this.$('[role="treeitem"][aria-level="1"]:eq(0) [role="treeitem"][aria-level="2"]:eq(0)').click();

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 40 }));
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(1)').attr('id'));
});

test('it collapses the (expanded) currently-focused node when left arrow key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();
  this.$('[role="treeitem"][aria-level="1"]:eq(0)').dblclick();

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'true');
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('id'));

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 37 }));
  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'false');
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('id'));
});

test('it selects the parent of the (collapsed) currently-focused node when left arrow key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').dblclick();
  this.$('[role="treeitem"][aria-level="1"] [role="treeitem"][aria-level="2"]:eq(0)').click();

  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"] [role="treeitem"][aria-level="2"]:eq(0)').attr('id'));

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 37 }));
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('id'));
});

test('it expands a collapsed treeitem and selects its first child when right arrow key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'false');

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 39 }));
  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-expanded'), 'true');
  assert.equal(this.$('[role="tree"]').attr('aria-activedescendant'), this.$('[role="treeitem"][aria-level="1"] [role="treeitem"][aria-level="2"]:eq(0)').attr('id'));
});

test('it expands all treeitems when asterisk (shift+8) key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
    {{/ivy-tree}}
  `);

  assert.notEqual(this.$('[role="treeitem"][aria-expanded="false"]').length, 0);

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 56, shiftKey: true }));
  assert.equal(this.$('[role="treeitem"][aria-expanded="false"]').length, 0);
});

test('it selects the first, visible treeitem when home key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem}}
      {{tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(1)').click();

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-selected'), 'false');

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 36 }));

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(0)').attr('aria-selected'), 'true');
});

test('it selects the last, visible treeitem when end key is pressed', function(assert) {
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem}}
      {{tree.treeitem}}
      {{#tree.treeitem as |treeitem|}}
        {{#treeitem.group as |group|}}
          {{group.treeitem}}
        {{/treeitem.group}}
      {{/tree.treeitem}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(1)').click();

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(2)').attr('aria-selected'), 'false');

  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 35 }));

  assert.equal(this.$('[role="treeitem"][aria-level="1"]:eq(2)').attr('aria-selected'), 'true');

  // expand the last item to make deeper items visible.
  this.$('[role="treeitem"][aria-level="1"]:eq(2)').dblclick();
  this.$('[role="tree"]').trigger(Ember.$.Event('keydown', { keyCode: 35 }));

  assert.equal(this.$('[role="treeitem"][aria-level="2"]:eq(0)').attr('aria-selected'), 'true');
});

test('it sends an "onSelect" action when a treeitem is selected', function(assert) {
  assert.expect(1);

  this.on('onSelect', function() {
    assert.ok(true);
  });
  this.render(hbs`
    {{#ivy-tree as |tree|}}
      {{tree.treeitem onSelect=(action "onSelect")}}
    {{/ivy-tree}}
  `);

  this.$('[role="treeitem"][aria-level="1"]:eq(0)').click();
});
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Actor LLC. <https://actor.im>
 */

import React, { Component } from 'react';
import { Container } from 'flux/utils';
import classnames from 'classnames';

import Modal from 'react-modal';

import { KeyCodes } from 'constants/ActorAppConstants';

import ProfilePictureActionCreators from 'actions/ProfilePictureActionCreators';

import ProfilePictureStore from 'stores/ProfilePictureStore'

const ProfilePictureStep = {
  SELECT_SOURCE: 1,
  SELECT_FROM_DISK: 2,
  SELECT_FROM_WEBCAM: 3,
  CROP: 4
};

class ProfilePictureModal extends Component {
  static getStores = () => [ProfilePictureStore];

  static calculateState() {
    return {
      isOpen: ProfilePictureStore.isOpen(),
      currentStep: ProfilePictureStep.SELECT_SOURCE,
      cropPosition: {
        x: 0,
        y: 0
      },
      cropSize: {
        height: 200,
        width: 200
      }
    };
  }

  componentWillUpdate(nextProps, nextState) {
    const { isOpen } = nextState;

    if (isOpen) {
      document.addEventListener('keydown', this.onKeyDown, false);
    } else {
      document.removeEventListener('keydown', this.onKeyDown, false);
    }
  }

  onClose = () => {
    ProfilePictureActionCreators.hide();
    this.setState({profilePhoto: null});
  };

  onKeyDown = (event) => {
    if (event.keyCode === KeyCodes.ESC) {
      event.preventDefault();
      this.onClose();
    }
  };

  onStartMoving = (event) => {
    const { cropPosition } = this.state;

    event.preventDefault();

    const overlay = React.findDOMNode(this.refs.overlay);
    const overlayRect = overlay.getBoundingClientRect();

    const dragOffset = {
      x: (event.pageX - overlayRect.left) - cropPosition.x,
      y: (event.pageY - overlayRect.top) - cropPosition.y
    };
    this.setState({dragOffset});

    overlay.addEventListener('mousemove', this.onMoving);
    overlay.addEventListener('touchmove', this.onMoving);
  };

  onMoving = (event) => {
    const { dragOffset, cropSize } = this.state;
    const overlay = React.findDOMNode(this.refs.overlay);
    const overlayRect = overlay.getBoundingClientRect();

    event.preventDefault();
    event.stopPropagation();

    const cropPosition = {
      x: (event.pageX - overlayRect.left) - dragOffset.x,
      y: (event.pageY - overlayRect.top) - dragOffset.y
    };

    if (cropPosition.x < cropSize.width/2) {
      cropPosition.x = 0;
    //} else if (cropPosition.x > (overlayRect - cropSize.width)) {
    //  console.debug('x>or-w/2')
    }

    this.setState({cropPosition})
  };

  onEndMoving = (event) => {
    const overlay = React.findDOMNode(this.refs.overlay);

    event.preventDefault();

    overlay.removeEventListener('mousemove', this.onMoving);
    overlay.removeEventListener('touchmove', this.onMoving);
  };

  onProfilePhotoChange = () => {
    const imageInput = React.findDOMNode(this.refs.imageInput);
    const imageForm = React.findDOMNode(this.refs.imageForm);
    const file = imageInput.files[0];

    let reader = new FileReader();
    reader.onload = (event) => {
      this.setState({
        profilePhotoSource: event.target.result,
        currentStep: ProfilePictureStep.CROP
      });
      imageForm.reset();
    };
    reader.readAsDataURL(file);
  };

  onSelectFromDisk = () => this.setState({currentStep: ProfilePictureStep.SELECT_FROM_DISK});
  onSelectFromWebcam = () => this.setState({currentStep: ProfilePictureStep.SELECT_FROM_WEBCAM});

  render() {
    const { isOpen, profilePhotoSource, currentStep, cropPosition } = this.state;

    let modalBody;
    switch (currentStep) {
      case ProfilePictureStep.SELECT_SOURCE:
        modalBody = (

select source

            <button onClick={this.onSelectFromDisk}>Disk</button> | <button onClick={this.onSelectFromWebcam}>Webcam</button>

        );
        break;
      case ProfilePictureStep.SELECT_FROM_DISK:
        modalBody = (
          <form ref="imageForm" style={{padding: 20}}>

Chose image to set profile picture

            <input type="file" ref="imageInput" onChange={this.onProfilePhotoChange}/>
          </form>
        );
        break;
      case ProfilePictureStep.CROP:
        modalBody = (

        );
        break;
    }

    if (isOpen) {
      return (
        <Modal className="modal-new modal-new--profile-picture"
               closeTimeoutMS={150}
               isOpen={isOpen}>

            portrait

Profile picture

              <button className="button button--lightblue" onClick={this.onClose}>Done</button>

            {modalBody}

        </Modal>
      );
    } else {
      return null;
    }
  }
}

export default Container.create(ProfilePictureModal, {pure: false});
<commit_msg>wip(web): add touch event handling, check crop block position on move.<commit_after>/*
 * Copyright (C) 2015 Actor LLC. <https://actor.im>
 */

import React, { Component } from 'react';
import { Container } from 'flux/utils';
import classnames from 'classnames';

import Modal from 'react-modal';

import { KeyCodes } from 'constants/ActorAppConstants';

import ProfilePictureActionCreators from 'actions/ProfilePictureActionCreators';

import ProfilePictureStore from 'stores/ProfilePictureStore'

const ProfilePictureStep = {
  SELECT_SOURCE: 1,
  SELECT_FROM_DISK: 2,
  SELECT_FROM_WEBCAM: 3,
  CROP: 4
};

class ProfilePictureModal extends Component {
  static getStores = () => [ProfilePictureStore];

  static calculateState() {
    return {
      isOpen: ProfilePictureStore.isOpen(),
      currentStep: ProfilePictureStep.SELECT_SOURCE,
      cropPosition: {
        x: 0,
        y: 0
      },
      cropSize: {
        height: 200,
        width: 200
      }
    };
  }

  componentWillUpdate(nextProps, nextState) {
    const { isOpen } = nextState;

    if (isOpen) {
      document.addEventListener('keydown', this.onKeyDown, false);
    } else {
      document.removeEventListener('keydown', this.onKeyDown, false);
    }
  }

  onClose = () => {
    ProfilePictureActionCreators.hide();
    this.setState({
      profilePhoto: null,
      currentStep: ProfilePictureStep.SELECT_SOURCE
    });
  };

  onKeyDown = (event) => {
    if (event.keyCode === KeyCodes.ESC) {
      event.preventDefault();
      this.onClose();
    }
  };

  onStartMoving = (event) => {
    const { cropPosition } = this.state;

    event.preventDefault();

    const overlay = React.findDOMNode(this.refs.overlay);
    const overlayRect = overlay.getBoundingClientRect();

    const dragOffset = {
      x: (event.pageX - overlayRect.left) - cropPosition.x,
      y: (event.pageY - overlayRect.top) - cropPosition.y
    };
    this.setState({dragOffset});

    overlay.addEventListener('mousemove', this.onMoving);
    overlay.addEventListener('touchmove', this.onMoving);
  };

  onMoving = (event) => {
    const { dragOffset, cropSize } = this.state;
    const overlay = React.findDOMNode(this.refs.overlay);
    const overlayRect = overlay.getBoundingClientRect();

    event.preventDefault();
    event.stopPropagation();

    let cropPosition = {
      x: (event.pageX - overlayRect.left) - dragOffset.x,
      y: (event.pageY - overlayRect.top) - dragOffset.y
    };

    if (cropPosition.x < 0) {
      cropPosition.x = 0;
    } else if (cropPosition.x > overlayRect.width - cropSize.width) {
      cropPosition.x = overlayRect.width - cropSize.width;
    }

    if (cropPosition.y < 0) {
      cropPosition.y = 0;
    } else if (cropPosition.y > overlayRect.height - cropSize.height) {
      cropPosition.y = overlayRect.height - cropSize.height;
    }

    this.setState({cropPosition})
  };

  onEndMoving = (event) => {
    const overlay = React.findDOMNode(this.refs.overlay);

    event.preventDefault();

    overlay.removeEventListener('mousemove', this.onMoving);
    overlay.removeEventListener('touchmove', this.onMoving);
  };

  onProfilePhotoChange = () => {
    const imageInput = React.findDOMNode(this.refs.imageInput);
    const imageForm = React.findDOMNode(this.refs.imageForm);
    const file = imageInput.files[0];

    let reader = new FileReader();
    reader.onload = (event) => {
      this.setState({
        profilePhotoSource: event.target.result,
        currentStep: ProfilePictureStep.CROP
      });
      imageForm.reset();
    };
    reader.readAsDataURL(file);
  };

  onSelectFromDisk = () => this.setState({currentStep: ProfilePictureStep.SELECT_FROM_DISK});
  onSelectFromWebcam = () => this.setState({currentStep: ProfilePictureStep.SELECT_FROM_WEBCAM});

  render() {
    const { isOpen, profilePhotoSource, currentStep, cropPosition } = this.state;

    let modalBody;
    switch (currentStep) {
      case ProfilePictureStep.SELECT_SOURCE:
        modalBody = (

select source

            <button onClick={this.onSelectFromDisk}>Disk</button> | <button onClick={this.onSelectFromWebcam}>Webcam</button>

        );
        break;
      case ProfilePictureStep.SELECT_FROM_DISK:
        modalBody = (
          <form ref="imageForm" style={{padding: 20}}>

Chose image to set profile picture

            <input type="file" ref="imageInput" onChange={this.onProfilePhotoChange}/>
          </form>
        );
        break;
      case ProfilePictureStep.CROP:
        modalBody = (

        );
        break;
    }

    if (isOpen) {
      return (
        <Modal className="modal-new modal-new--profile-picture"
               closeTimeoutMS={150}
               isOpen={isOpen}>

            portrait

Profile picture

              <button className="button button--lightblue" onClick={this.onClose}>Done</button>

            {modalBody}

        </Modal>
      );
    } else {
      return null;
    }
  }
}

export default Container.create(ProfilePictureModal, {pure: false});
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     435
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v436 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     436
// ==/UserScript==
<|endoftext|>


<commit_before>// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
// IT'S ALL JUST JUNK FOR OUR DOCS!
// ++++++++++++++++++++++++++++++++++++++++++

/*!
 * JavaScript for Bootstrap's docs (http://getbootstrap.com)
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under the Creative Commons Attribution 3.0 Unported License. For
 * details, see http://creativecommons.org/licenses/by/3.0/.
 */

/* global ZeroClipboard, addAnchors */

!function ($) {
  'use strict';

  $(function () {

    // Scrollspy
    var $window = $(window)
    var $body   = $(document.body)

    // Kill links
    $('.bs-docs-container [href=#]').click(function (e) {
      e.preventDefault()
    })

    // Sidenav affixing
    setTimeout(function () {
      var $sideBar = $('.bs-docs-sidebar')

      $sideBar.affix({
        offset: {
          top: function () {
            var offsetTop      = $sideBar.offset().top
            var sideBarMargin  = parseInt($sideBar.children(0).css('margin-top'), 10)
            var navOuterHeight = $('.bs-docs-nav').height()

            return (this.top = offsetTop - navOuterHeight - sideBarMargin)
          },
          bottom: function () {
            return (this.bottom = $('.bs-docs-footer').outerHeight(true))
          }
        }
      })
    }, 100)

    setTimeout(function () {
      $('.bs-top').affix()
    }, 100)

    // theme toggler
    ;(function () {
      var stylesheetLink = $('#bs-theme-stylesheet')
      var themeBtn = $('.bs-docs-theme-toggle')

      var activateTheme = function () {
        stylesheetLink.attr('href', stylesheetLink.attr('data-href'))
        themeBtn.text('Disable theme preview')
        localStorage.setItem('previewTheme', true)
      }

      if (localStorage.getItem('previewTheme')) {
        activateTheme()
      }

      themeBtn.click(function () {
        var href = stylesheetLink.attr('href')
        if (!href || href.indexOf('data') === 0) {
          activateTheme()
        } else {
          stylesheetLink.attr('href', '')
          themeBtn.text('Preview theme')
          localStorage.removeItem('previewTheme')
        }
      })
    })();

    // Tooltip and popover demos
    $('.tooltip-demo').tooltip({
      selector: '[data-toggle="tooltip"]',
      container: 'body'
    })
    $('.popover-demo').popover({
      selector: '[data-toggle="popover"]',
      container: 'body'
    })

    // Demos within modals
    $('.tooltip-test').tooltip()
    $('.popover-test').popover()

    // Popover demos
    $('.bs-docs-popover').popover()

    // Button state demo
    $('#loading-example-btn').on('click', function () {
      var btn = $(this)
      btn.button('loading')
      setTimeout(function () {
        btn.button('reset')
      }, 3000)
    })

    // Modal relatedTarget demo
    $('#exampleModal').on('show.bs.modal', function (event) {
      var button = $(event.relatedTarget) // Button that triggered the modal
      var recipient = button.data('whatever') // Extract info from data-* attributes
      // If necessary, you could initiate an AJAX request here (and then do the updating in a callback).
      // Update the modal's content. We'll use jQuery here, but you could use a data binding library or other methods instead.
      var modal = $(this)
      modal.find('.modal-title').text('New message to ' + recipient)
      modal.find('.modal-body input').val(recipient)
    })

    // Config ZeroClipboard
    ZeroClipboard.config({
      moviePath: '/assets/flash/ZeroClipboard.swf',
      hoverClass: 'btn-clipboard-hover'
    })

    // Insert copy to clipboard button before .highlight
    $('.highlight').each(function () {
      var btnHtml = '
Copy
'
      $(this).before(btnHtml)
    })
    var zeroClipboard = new ZeroClipboard($('.btn-clipboard'))
    var htmlBridge = $('#global-zeroclipboard-html-bridge')

    // Handlers for ZeroClipboard
    zeroClipboard.on('load', function () {
      htmlBridge
        .data('placement', 'top')
        .attr('title', 'Copy to clipboard')
        .tooltip()
    })

    // Copy to clipboard
    zeroClipboard.on('dataRequested', function (client) {
      var highlight = $(this).parent().nextAll('.highlight').first()
      client.setText(highlight.text())
    })

    // Notify copy success and reset tooltip title
    zeroClipboard.on('complete', function () {
      htmlBridge
        .attr('title', 'Copied!')
        .tooltip('fixTitle')
        .tooltip('show')
        .attr('title', 'Copy to clipboard')
        .tooltip('fixTitle')
    })

    // Notify copy failure
    zeroClipboard.on('noflash wrongflash', function () {
      htmlBridge
        .attr('title', 'Flash required')
        .tooltip('fixTitle')
        .tooltip('show')
    })

  })

}(jQuery)

;(function () {
  'use strict';
  addAnchors('.bs-docs-container h1, .bs-docs-container h2, .bs-docs-container h3, .bs-docs-container h4, .bs-docs-container h5');
})();
<commit_msg><commit_after>// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
// IT'S ALL JUST JUNK FOR OUR DOCS!
// ++++++++++++++++++++++++++++++++++++++++++

/*!
 * JavaScript for Bootstrap's docs (http://getbootstrap.com)
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under the Creative Commons Attribution 3.0 Unported License. For
 * details, see http://creativecommons.org/licenses/by/3.0/.
 */

/* global ZeroClipboard, addAnchors */

!function ($) {
  'use strict';

  $(function () {

    // Scrollspy
    var $window = $(window)
    var $body   = $(document.body)

    // Kill links
    $('.bs-docs-container [href=#]').click(function (e) {
      e.preventDefault()
    })

    // Sidenav affixing
    setTimeout(function () {
      var $sideBar = $('.bs-docs-sidebar')

      $sideBar.affix({
        offset: {
          top: function () {
            var offsetTop      = $sideBar.offset().top
            var sideBarMargin  = parseInt($sideBar.children(0).css('margin-top'), 10)
            var navOuterHeight = $('.bs-docs-nav').height()

            return (this.top = offsetTop - navOuterHeight - sideBarMargin)
          },
          bottom: function () {
            return (this.bottom = $('.bs-docs-footer').outerHeight(true))
          }
        }
      })
    }, 100)

    setTimeout(function () {
      $('.bs-top').affix()
    }, 100)

    // theme toggler
    ;(function () {
      var stylesheetLink = $('#bs-theme-stylesheet')
      var themeBtn = $('.bs-docs-theme-toggle')

      var activateTheme = function () {
        stylesheetLink.attr('href', stylesheetLink.attr('data-href'))
        themeBtn.text('Disable theme preview')
        localStorage.setItem('previewTheme', true)
      }

      if (localStorage.getItem('previewTheme')) {
        activateTheme()
      }

      themeBtn.click(function () {
        var href = stylesheetLink.attr('href')
        if (!href || href.indexOf('data') === 0) {
          activateTheme()
        } else {
          stylesheetLink.attr('href', '')
          themeBtn.text('Preview theme')
          localStorage.removeItem('previewTheme')
        }
      })
    })();

    // Tooltip and popover demos
    $('.tooltip-demo').tooltip({
      selector: '[data-toggle="tooltip"]',
      container: 'body'
    })
    $('.popover-demo').popover({
      selector: '[data-toggle="popover"]',
      container: 'body'
    })

    // Demos within modals
    $('.tooltip-test').tooltip()
    $('.popover-test').popover()

    // Popover demos
    $('.bs-docs-popover').popover()

    // Button state demo
    $('#loading-example-btn').on('click', function () {
      var btn = $(this)
      btn.button('loading')
      setTimeout(function () {
        btn.button('reset')
      }, 3000)
    })

    // Modal relatedTarget demo
    $('#exampleModal').on('show.bs.modal', function (event) {
      var button = $(event.relatedTarget) // Button that triggered the modal
      var recipient = button.data('whatever') // Extract info from data-* attributes
      // If necessary, you could initiate an AJAX request here (and then do the updating in a callback).
      // Update the modal's content. We'll use jQuery here, but you could use a data binding library or other methods instead.
      var modal = $(this)
      modal.find('.modal-title').text('New message to ' + recipient)
      modal.find('.modal-body input').val(recipient)
    })

    // Config ZeroClipboard
    ZeroClipboard.config({
      moviePath: '/assets/flash/ZeroClipboard.swf',
      hoverClass: 'btn-clipboard-hover'
    })

    // Insert copy to clipboard button before .highlight
    $('.highlight').each(function () {
      var btnHtml = '
Copy
'
      $(this).before(btnHtml)
    })
    var zeroClipboard = new ZeroClipboard($('.btn-clipboard'))
    var htmlBridge = $('#global-zeroclipboard-html-bridge')

    // Handlers for ZeroClipboard
    zeroClipboard.on('load', function () {
      htmlBridge
        .data('placement', 'top')
        .attr('title', 'Copy to clipboard')
        .tooltip();

      htmlBridge.find('object').text("flash object for zero clipboard");
    })

    // Copy to clipboard
    zeroClipboard.on('dataRequested', function (client) {
      var highlight = $(this).parent().nextAll('.highlight').first()
      client.setText(highlight.text())
    })

    // Notify copy success and reset tooltip title
    zeroClipboard.on('complete', function () {
      htmlBridge
        .attr('title', 'Copied!')
        .tooltip('fixTitle')
        .tooltip('show')
        .attr('title', 'Copy to clipboard')
        .tooltip('fixTitle')
    })

    // Notify copy failure
    zeroClipboard.on('noflash wrongflash', function () {
      htmlBridge
        .attr('title', 'Flash required')
        .tooltip('fixTitle')
        .tooltip('show')
    })

  })

}(jQuery)

;(function () {
  'use strict';
  addAnchors('.bs-docs-container h1, .bs-docs-container h2, .bs-docs-container h3, .bs-docs-container h4, .bs-docs-container h5');
})();
<|endoftext|>


<commit_before>describe("Session", function() {
	beforeEach(function(){
		spyOn(Date, "now").and.returnValue(1404810612883);
	});

	describe("initialSet", function() {
		beforeEach(function() {
			spyOn(localStorage, "setItem").and.callThrough();
			localStorage.setItem.calls.reset()
			Session.initialSet()
		});

		it("sets an EntryTime", function() {
			expect(localStorage.setItem).toHaveBeenCalledWith("EntryTime", Date.now())
		});

		it("sets a Username", function() {
			expect(localStorage.setItem.calls.mostRecent().args[0]).toEqual("Username");
		});
	});

	describe("checkTime", function() {
		beforeEach(function() {
			spyOn(Window, "clearChat");
			spyOn(Window, "redirectTo");
			spyOn(Window, "appendSystemMessage");
		});

		describe("at 10mins", function() {
			beforeEach(function(){
				localStorage.setItem("EntryTime", Date.now() - 600001)
				Session.checkTime();
			});

			it("clears the chat", function() {
				expect(Window.clearChat).toHaveBeenCalled();
			});
		});

		describe("at 9 minutes", function() {
			beforeEach(function(){
				localStorage.setItem("EntryTime", Date.now() - 540001)
				Session.checkTime();
			})

			it("doesn't clear the chat", function() {
				expect(Window.clearChat).not.toHaveBeenCalled();
			});

			it("appends a warning message", function() {
				expect(Window.appendSystemMessage).toHaveBeenCalled();
			});
		});

		describe("before 9 minutes", function() {
			beforeEach(function() {
				localStorage.setItem("EntryTime", Date.now() - 539999)
				Session.checkTime();
			});
			it("doesn't clear the chat", function() {
				expect(Window.clearChat).not.toHaveBeenCalled();
			});

			it("doesn't append a warning message", function() {
				expect(Window.appendSystemMessage).not.toHaveBeenCalled();
			});
		});
	});
});
<commit_msg>fixed session tests<commit_after>describe("Session", function() {
	beforeEach(function(){
		spyOn(Date, "now").and.returnValue(1404810612883);

	});

	describe("initialSet", function() {
		beforeEach(function() {
			spyOn(localStorage, "setItem").and.callThrough();
			localStorage.setItem.calls.reset()
			Session.initialSet()
		});

		it("sets an EntryTime", function() {
			expect(localStorage.setItem).toHaveBeenCalledWith("EntryTime", Date.now())
		});

		it("sets a Username", function() {
			expect(localStorage.setItem.calls.mostRecent().args[0]).toEqual("Username");
		});
	});

	describe("checkTime", function() {
		beforeEach(function() {
			spyOn(Window, "clearChat");
			spyOn(Window, "redirectTo");
			spyOn(Window, "appendSystemMessage");
		});

		describe("at 10mins", function() {
			beforeEach(function(){
				localStorage.setItem("EntryTime", Date.now() - 600001)
				Session.checkTime();
			});

			it("redirects to splash page", function() {
				expect(Window.redirectTo).toHaveBeenCalled();
			});
		});

		describe("at 9 minutes", function() {
			beforeEach(function(){
				localStorage.setItem("EntryTime", Date.now() - 540001)
				Session.checkTime();
			})

			it("doesn't redirect to splash page", function() {
				expect(Window.redirectTo).not.toHaveBeenCalled();
			});

			it("appends a warning message", function() {
				expect(Window.appendSystemMessage).toHaveBeenCalled();
			});
		});

		describe("before 9 minutes", function() {
			beforeEach(function() {
				localStorage.setItem("EntryTime", Date.now() - 539999)
				Session.checkTime();
			});
			it("doesn't redirect to splash page", function() {
				expect(Window.redirectTo).not.toHaveBeenCalled();
			});

			it("doesn't append a warning message", function() {
				expect(Window.appendSystemMessage).not.toHaveBeenCalled();
			});
		});
	});
});
<|endoftext|>


<commit_before>//--------------------------------------------------------------------------
// Proyecto Icasus <https://gestionproyectos.us.es/projects/r2h2-icasus/>
// Archivo: public/js/index.js
// Desarrolladores: Juanan Ruiz (juanan@us.es), Jesus Martin Corredera (jjmc@us.es),
// Joaquín Valonero Zaera (tecnibus1@us.es)
//--------------------------------------------------------------------------
// Incluye el código JavaScript para el fichero index.tpl
//----------------------------------------------------------------------------

//Loads the correct sidebar on window load,
//collapses the sidebar on window resize.
// Sets the min-height of #page-wrapper to window size
$(function () {
    $(window).bind("load resize", function () {
        topOffset = 50;
        width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width;
        if (width < 768) {
            $('div.navbar-collapse').addClass('collapse');
            topOffset = 100; // 2-row-menu
        } else {
            $('div.navbar-collapse').removeClass('collapse');
        }

        height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1;
        height = height - topOffset;
        if (height < 1)
            height = 1;
        if (height > topOffset) {
            $("#page-wrapper").css("min-height", (height) + "px");
        }
    });

    var url = window.location;
    var element = $('ul.nav a').filter(function () {
        return this.href == url || url.href.indexOf(this.href) == 0;
    }).addClass('active').parent().parent().addClass('in').parent();
    if (element.is('li')) {
        element.addClass('active');
    }
});

//Collapsible panels
jQuery(function ($) {
    $('.panel-heading i.clickable').on("click", function (e) {
        if ($(this).hasClass('panel-collapsed')) {
            // expand the panel
            $(this).parents('.panel').find('.panel-body').slideDown();
            $(this).removeClass('panel-collapsed');
            $(this).removeClass('fa-chevron-down').addClass('fa-chevron-up');
        }
        else {
            // collapse the panel
            $(this).parents('.panel').find('.panel-body').slideUp();
            $(this).addClass('panel-collapsed');
            $(this).removeClass('fa-chevron-up').addClass('fa-chevron-down');
        }
    });
});

// Back to Top
// Only enable if the document has a long scroll bar
// Note the window height + offset
backToTop();
//Si cambia la longitud del documento
onElementHeightChange(document.body, function () {
    backToTop();
});
function backToTop() {
    if (($(window).height() + 100) < $(document).height()) {
        $('#top-link-block').removeClass('hidden').affix({
            // how far to scroll down before link "slides" into view
            offset: {top: 100}
        });
    }
}
//Detecta cambios de longitud del documento
function onElementHeightChange(elm, callback) {
    var lastHeight = elm.clientHeight, newHeight;
    (function run() {
        newHeight = elm.clientHeight;
        if (lastHeight != newHeight)
            callback();
        lastHeight = newHeight;

        if (elm.onElementHeightChangeTimer)
            clearTimeout(elm.onElementHeightChangeTimer);

        elm.onElementHeightChangeTimer = setTimeout(run, 200);
    })();
}

//Datatables
$(document).ready(function () {
    $('.datatable').DataTable({
        "pagingType": "full_numbers",
        "iDisplayLength": 25,
        dom: "<'row'<'col-sm-3'l><'col-sm-3'B><'col-sm-6'f>>" +
                "<'row'<'col-sm-12'tr>>" +
                "<'row'<'col-sm-5'i><'col-sm-7'p>>",
        buttons: [
            {extend: 'colvis', text: 'Ver columnas'},
            {
                extend: 'collection',
                text: 'Exportar',
                buttons: [
                    {extend: 'csv'},
                    {extend: 'excel'},
                    {extend: 'print', text: 'Imprimir/PDF'}
                ]
            }
        ]
    });
});

//Map Responsive
$(document).ready(function () {
    $('img[usemap]').rwdImageMaps();
});

//Chosen selects (añade búsqueda a los html select)
$(".chosen-select").chosen({
    disable_search_threshold: 10,
    no_results_text: "Oops, no se encuentran registros coincidentes"
});

// Javascript to enable link to tab
var url = document.location.toString();
if (url.match('#')) {
    $('.nav-tabs a[href=#' + url.split('#')[1] + ']').tab('show');
}

// Change hash for page-reload
$('.nav-tabs a').on('shown.bs.tab', function (e) {
    window.location.hash = e.target.hash;
});<commit_msg>Deshabilitada la actualización de página sobre una pestaña por problemas menores de estilo. Se mantienen los enlaces directos a pestañas.<commit_after>//--------------------------------------------------------------------------
// Proyecto Icasus <https://gestionproyectos.us.es/projects/r2h2-icasus/>
// Archivo: public/js/index.js
// Desarrolladores: Juanan Ruiz (juanan@us.es), Jesus Martin Corredera (jjmc@us.es),
// Joaquín Valonero Zaera (tecnibus1@us.es)
//--------------------------------------------------------------------------
// Incluye el código JavaScript para el fichero index.tpl
//----------------------------------------------------------------------------

//Loads the correct sidebar on window load,
//collapses the sidebar on window resize.
// Sets the min-height of #page-wrapper to window size
$(function () {
    $(window).bind("load resize", function () {
        topOffset = 50;
        width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width;
        if (width < 768) {
            $('div.navbar-collapse').addClass('collapse');
            topOffset = 100; // 2-row-menu
        } else {
            $('div.navbar-collapse').removeClass('collapse');
        }

        height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1;
        height = height - topOffset;
        if (height < 1)
            height = 1;
        if (height > topOffset) {
            $("#page-wrapper").css("min-height", (height) + "px");
        }
    });

    var url = window.location;
    var element = $('ul.nav a').filter(function () {
        return this.href == url || url.href.indexOf(this.href) == 0;
    }).addClass('active').parent().parent().addClass('in').parent();
    if (element.is('li')) {
        element.addClass('active');
    }
});

//Collapsible panels
jQuery(function ($) {
    $('.panel-heading i.clickable').on("click", function (e) {
        if ($(this).hasClass('panel-collapsed')) {
            // expand the panel
            $(this).parents('.panel').find('.panel-body').slideDown();
            $(this).removeClass('panel-collapsed');
            $(this).removeClass('fa-chevron-down').addClass('fa-chevron-up');
        }
        else {
            // collapse the panel
            $(this).parents('.panel').find('.panel-body').slideUp();
            $(this).addClass('panel-collapsed');
            $(this).removeClass('fa-chevron-up').addClass('fa-chevron-down');
        }
    });
});

// Back to Top
// Only enable if the document has a long scroll bar
// Note the window height + offset
backToTop();
//Si cambia la longitud del documento
onElementHeightChange(document.body, function () {
    backToTop();
});
function backToTop() {
    if (($(window).height() + 100) < $(document).height()) {
        $('#top-link-block').removeClass('hidden').affix({
            // how far to scroll down before link "slides" into view
            offset: {top: 100}
        });
    }
}
//Detecta cambios de longitud del documento
function onElementHeightChange(elm, callback) {
    var lastHeight = elm.clientHeight, newHeight;
    (function run() {
        newHeight = elm.clientHeight;
        if (lastHeight != newHeight)
            callback();
        lastHeight = newHeight;

        if (elm.onElementHeightChangeTimer)
            clearTimeout(elm.onElementHeightChangeTimer);

        elm.onElementHeightChangeTimer = setTimeout(run, 200);
    })();
}

//Datatables
$(document).ready(function () {
    $('.datatable').DataTable({
        "pagingType": "full_numbers",
        "iDisplayLength": 25,
        dom: "<'row'<'col-sm-3'l><'col-sm-3'B><'col-sm-6'f>>" +
                "<'row'<'col-sm-12'tr>>" +
                "<'row'<'col-sm-5'i><'col-sm-7'p>>",
        buttons: [
            {extend: 'colvis', text: 'Ver columnas'},
            {
                extend: 'collection',
                text: 'Exportar',
                buttons: [
                    {extend: 'csv'},
                    {extend: 'excel'},
                    {extend: 'print', text: 'Imprimir/PDF'}
                ]
            }
        ]
    });
});

//Map Responsive
$(document).ready(function () {
    $('img[usemap]').rwdImageMaps();
});

//Chosen selects (añade búsqueda a los html select)
$(".chosen-select").chosen({
    disable_search_threshold: 10,
    no_results_text: "Oops, no se encuentran registros coincidentes"
});

//Habilita enlaces directos a pestañas dentro de una página
var url = document.location.toString();
if (url.match('#')) {
    $('.nav-tabs a[href=#' + url.split('#')[1] + ']').tab('show');
}<|endoftext|>