<commit_before>/** @license
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.provide('shakaDemo.Search');

/** @type {?shakaDemo.Search} */
let shakaDemoSearch;

/**
 * Shaka Player demo, feature discovery page layout.
 */
shakaDemo.Search = class {
  /**
   * Register the page configuration.
   */
  static init() {
    const elements = shakaDemoMain.addNavButton('search');
    shakaDemoSearch = new shakaDemo.Search(elements.container, elements.button);
  }

  /**
   * @param {!Element} container
   * @param {!Element} button
   */
  constructor(container, button) {
    /** @private {!Array.<!shakaAssets.Feature>} */
    this.desiredFeatures_ = [];

    /** @private {?shakaAssets.Source} */
    this.desiredSource_;

    /** @private {?shakaAssets.KeySystem} */
    this.desiredDRM_;

    /** @private {!Element} */
    this.button_ = button;

    /** @private {!Element} */
    this.resultsDiv_ = document.createElement('div');

    /** @private {!Array.<!shakaDemo.AssetCard>} */
    this.assetCards_ = [];

    document.addEventListener('shaka-main-selected-asset-changed', () => {
      this.updateSelected_();
    });
    document.addEventListener('shaka-main-offline-progress', () => {
      this.updateOfflineProgress_();
    });
    document.addEventListener('shaka-main-locale-changed', () => {
      this.remakeSearchDiv_(container);
      this.remakeResultsDiv_();
      // Update the componentHandler, to account for any new MDL elements added.
      componentHandler.upgradeDom();
    });
    document.addEventListener('shaka-main-page-changed', () => {
      if (!this.resultsDiv_.childNodes.length &&
          !container.classList.contains('hidden')) {
        // Now that the page is showing, create the contents that we deferred
        // until now.
        this.remakeResultsDiv_();
      }
    });

    this.readHashParameters_();
    this.updateHashParameters_();
    this.remakeSearchDiv_(container);
  }

  /** @private */
  readHashParameters_() {
    const hashValues = this.button_.getAttribute('tab-hash');
    if (hashValues) {
      for (const valueRaw of hashValues.split(',')) {
        if (valueRaw.startsWith('drm:')) {
          this.desiredDRM_ =
          /** @type {!shakaAssets.KeySystem} */ (valueRaw.split('drm:')[1]);
        } else if (valueRaw.startsWith('source:')) {
          this.desiredSource_ =
          /** @type {!shakaAssets.Source} */ (valueRaw.split('source:')[1]);
        } else {
          this.desiredFeatures_.push(
              /** @type {!shakaAssets.Feature} */ (valueRaw));
        }
      }
    }
  }

  /** @private */
  updateHashParameters_() {
    const hashValues = [];
    if (this.desiredSource_) {
      hashValues.push('source:' + this.desiredSource_);
    }
    if (this.desiredDRM_) {
      hashValues.push('drm:' + this.desiredDRM_);
    }
    for (const feature of this.desiredFeatures_) {
      hashValues.push(feature);
    }
    if (hashValues.length > 0) {
      this.button_.setAttribute('tab-hash', hashValues.join(','));
    } else {
      this.button_.removeAttribute('tab-hash');
    }
    shakaDemoMain.remakeHash();
  }

  /**
   * @param {!ShakaDemoAssetInfo} asset
   * @return {!shakaDemo.AssetCard}
   * @private
   */
  createAssetCardFor_(asset) {
    const resultsDiv = this.resultsDiv_;
    const isFeatured = false;
    return new shakaDemo.AssetCard(resultsDiv, asset, isFeatured, (c) => {
      const unsupportedReason = shakaDemoMain.getAssetUnsupportedReason(
          asset, /* needOffline= */ false);
      if (unsupportedReason) {
        c.markAsUnsupported(unsupportedReason);
      } else {
        c.addButton(shakaDemo.MessageIds.PLAY, () => {
          shakaDemoMain.loadAsset(asset);
          this.updateSelected_();
        });
        c.addStoreButton();
      }
    });
  }

  /**
   * Updates progress bars on asset cards.
   * @private
   */
  updateOfflineProgress_() {
    for (const card of this.assetCards_) {
      card.updateProgress();
    }
  }

  /**
   * Updates which asset card is selected.
   * @private
   */
  updateSelected_() {
    for (const card of this.assetCards_) {
      card.selectByAsset(shakaDemoMain.selectedAsset);
    }
  }

  /** @private */
  remakeResultsDiv_() {
    shaka.util.Dom.removeAllChildren(this.resultsDiv_);

    const assets = this.searchResults_();
    this.assetCards_ = assets.map((asset) => this.createAssetCardFor_(asset));
    this.updateSelected_();
  }

  /**
   * @param {!shakaDemo.Search.SearchTerm} term
   * @param {shakaDemo.Search.TermType} type
   * @return {boolean}
   * @private
   */
  checkDesiredTerm_(term, type) {
    switch (type) {
      case shakaDemo.Search.TermType.DRM:
        return this.desiredDRM_ == term;
      case shakaDemo.Search.TermType.SOURCE:
        return this.desiredSource_ == term;
      case shakaDemo.Search.TermType.FEATURE:
        return this.desiredFeatures_.includes(
            /** @type {!shakaAssets.Feature} */ (term));
      default:
        return false;
    }
  }

  /**
   * @param {!shakaDemo.Search.SearchTerm} term
   * @param {shakaDemo.Search.TermType} type
   * @param {!Array.<!shakaDemo.Search.SearchTerm>} others
   * @private
   */
  addDesiredTerm_(term, type, others) {
    switch (type) {
      case shakaDemo.Search.TermType.DRM:
        this.desiredDRM_ = /** @type {shakaAssets.KeySystem} */ (term);
        break;
      case shakaDemo.Search.TermType.SOURCE:
        this.desiredSource_ = /** @type {shakaAssets.Source} */ (term);
        break;
      case shakaDemo.Search.TermType.FEATURE:
        // Only this term should be in the desired features.
        for (const term of others) {
          const index = this.desiredFeatures_.indexOf(
              /** @type {shakaAssets.Feature} */ (term));
          if (index != -1) {
            this.desiredFeatures_.splice(index, 1);
          }
        }
        this.desiredFeatures_.push(/** @type {shakaAssets.Feature} */ (term));
        break;
    }
  }

  /**
   * @param {!shakaDemo.Search.SearchTerm} term
   * @param {shakaDemo.Search.TermType} type
   * @private
   */
  removeDesiredTerm_(term, type) {
    let index;
    switch (type) {
      case shakaDemo.Search.TermType.DRM:
        this.desiredDRM_ = null;
        break;
      case shakaDemo.Search.TermType.SOURCE:
        this.desiredSource_ = null;
        break;
      case shakaDemo.Search.TermType.FEATURE:
        index = this.desiredFeatures_.indexOf(
            /** @type {shakaAssets.Feature} */ (term));
        if (index != -1) {
          this.desiredFeatures_.splice(index, 1);
        }
        break;
    }
  }

  /**
   * Creates an input for a single search term.
   * @param {!shakaDemo.InputContainer} searchContainer
   * @param {!shakaDemo.Search.SearchTerm} choice
   * The term this represents.
   * @param {shakaDemo.Search.TermType} type
   * The type of term that this term is.
   * @param {?shakaDemo.MessageIds} tooltip
   * @private
   */
  makeBooleanInput_(searchContainer, choice, type, tooltip) {
    // Give the container a significant amount of right padding, to make
    // it clearer which toggle corresponds to which label.
    searchContainer.addRow(choice, tooltip, 'significant-right-padding');
    const onChange = (input) => {
      if (input.checked) {
        this.addDesiredTerm_(choice, type, [choice]);
      } else {
        this.removeDesiredTerm_(choice, type);
      }
      this.remakeResultsDiv_();
      // Update the componentHandler, to account for any new MDL elements
      // added. Notably, tooltips.
      componentHandler.upgradeDom();
      // Update the hash.
      this.updateHashParameters_();
    };
    const input = new shakaDemo.BoolInput(searchContainer, choice, onChange);
    input.input().checked = this.checkDesiredTerm_(choice, type);
  }

  /**
   * Creates an input for a group of related but mutually-exclusive search
   * terms.
   * @param {!shakaDemo.InputContainer} searchContainer
   * @param {!shakaDemo.MessageIds} name
   * @param {!Array.<!shakaDemo.Search.SearchTerm>} choices
   * An array of the terms in this term group.
   * @param {shakaDemo.Search.TermType} type
   * The type of term that this term group contains. All of the
   * terms in the "choices" array must be of this type.
   * @private
   */
  makeSelectInput_(searchContainer, name, choices, type) {
    searchContainer.addRow(null, null);
    const nullOption = '---';
    const valuesObject = {};
    for (const term of choices) {
      valuesObject[term] = shakaDemoMain.getLocalizedString(term);
    }
    valuesObject[nullOption] = nullOption;
    let lastValue = nullOption;
    const onChange = (input) => {
      if (input.value != nullOption) {
        this.addDesiredTerm_(input.value, type, choices);
      } else {
        this.removeDesiredTerm_(lastValue, type);
      }
      lastValue = input.value;
      this.remakeResultsDiv_();
      // Update the componentHandler, to account for any new MDL elements added.
      // Notably, tooltips.
      componentHandler.upgradeDom();
      // Update the hash.
      this.updateHashParameters_();
    };
    const input = new shakaDemo.SelectInput(
        searchContainer, name, onChange, valuesObject);
    input.input().value = nullOption;
    for (const choice of choices) {
      if (this.checkDesiredTerm_(choice, type)) {
        input.input().value = choice;
        lastValue = choice;
        break;
      }
    }
  }

  /**
   * @param {!Element} container
   * @private
   */
  remakeSearchDiv_(container) {
    shaka.util.Dom.removeAllChildren(container);

    const Feature = shakaAssets.Feature;
    const FEATURE = shakaDemo.Search.TermType.FEATURE;
    const DRM = shakaDemo.Search.TermType.DRM;
    const SOURCE = shakaDemo.Search.TermType.SOURCE;

    // Core term inputs.
    const coreContainer = new shakaDemo.InputContainer(
        container, /* headerText= */ null, shakaDemo.InputContainer.Style.FLEX,
        /* docLink= */ null);
    this.makeSelectInput_(coreContainer,
        shakaDemo.MessageIds.MANIFEST_SEARCH,
        [Feature.DASH, Feature.HLS], FEATURE);
    this.makeSelectInput_(coreContainer,
        shakaDemo.MessageIds.CONTAINER_SEARCH,
        [Feature.MP4, Feature.MP2TS, Feature.WEBM], FEATURE);
    this.makeSelectInput_(coreContainer,
        shakaDemo.MessageIds.DRM_SEARCH,
        Object.values(shakaAssets.KeySystem), DRM);
    this.makeSelectInput_(coreContainer,
        shakaDemo.MessageIds.SOURCE_SEARCH,
        Object.values(shakaAssets.Source).filter((term) => {
          return term != shakaAssets.Source.CUSTOM;
        }), SOURCE);
    this.makeSelectInput_(coreContainer,
        shakaDemo.MessageIds.LIVE_SEARCH,
        [Feature.LIVE, Feature.VOD], FEATURE);

    // Special terms.
    const containerStyle = shakaDemo.InputContainer.Style.FLEX;
    const specialContainer = new shakaDemo.InputContainer(
        container, /* headerText= */ null, containerStyle,
        /* docLink= */ null);
    this.makeBooleanInput_(specialContainer, Feature.HIGH_DEFINITION, FEATURE,
        shakaDemo.MessageIds.HIGH_DEFINITION_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.XLINK, FEATURE,
        shakaDemo.MessageIds.XLINK_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.SUBTITLES, FEATURE,
        shakaDemo.MessageIds.SUBTITLES_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.TRICK_MODE, FEATURE,
        shakaDemo.MessageIds.TRICK_MODE_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.SURROUND, FEATURE,
        shakaDemo.MessageIds.SURROUND_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.OFFLINE, FEATURE,
        shakaDemo.MessageIds.OFFLINE_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.STORED, FEATURE,
        shakaDemo.MessageIds.STORED_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.ADS, FEATURE,
        shakaDemo.MessageIds.AD_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.AUDIO_ONLY, FEATURE,
        shakaDemo.MessageIds.AUDIO_ONLY_SEARCH);

    container.appendChild(this.resultsDiv_);
  }

  /**
   * @return {!Array.<!ShakaDemoAssetInfo>}
   * @private
   */
  searchResults_() {
    return shakaAssets.testAssets.filter((asset) => {
      if (asset.disabled) {
        return false;
      }
      if (this.desiredDRM_ && !asset.drm.includes(this.desiredDRM_)) {
        return false;
      }
      if (this.desiredSource_ && asset.source != this.desiredSource_) {
        return false;
      }
      for (const feature of this.desiredFeatures_) {
        if (feature == shakaAssets.Feature.STORED) {
          if (!asset.isStored()) {
            return false;
          }
        } else if (!asset.features.includes(feature)) {
          return false;
        }
      }
      return true;
    });
  }
};

/** @typedef {shakaAssets.Feature|shakaAssets.Source} */
shakaDemo.Search.SearchTerm;

/** @enum {string} */
shakaDemo.Search.TermType = {
  FEATURE: 'Feature',
  DRM: 'DRM',
  SOURCE: 'Source',
};

document.addEventListener('shaka-main-loaded', shakaDemo.Search.init);
document.addEventListener('shaka-main-cleanup', () => {
  shakaDemoSearch = null;
});
<commit_msg>v2.5.x search hash parameters compatibility<commit_after>/** @license
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.provide('shakaDemo.Search');

/** @type {?shakaDemo.Search} */
let shakaDemoSearch;

/**
 * Shaka Player demo, feature discovery page layout.
 */
shakaDemo.Search = class {
  /**
   * Register the page configuration.
   */
  static init() {
    const elements = shakaDemoMain.addNavButton('search');
    shakaDemoSearch = new shakaDemo.Search(elements.container, elements.button);
  }

  /**
   * @param {!Element} container
   * @param {!Element} button
   */
  constructor(container, button) {
    /** @private {!Array.<!shakaAssets.Feature>} */
    this.desiredFeatures_ = [];

    /** @private {?shakaAssets.Source} */
    this.desiredSource_;

    /** @private {?shakaAssets.KeySystem} */
    this.desiredDRM_;

    /** @private {!Element} */
    this.button_ = button;

    /** @private {!Element} */
    this.resultsDiv_ = document.createElement('div');

    /** @private {!Array.<!shakaDemo.AssetCard>} */
    this.assetCards_ = [];

    document.addEventListener('shaka-main-selected-asset-changed', () => {
      this.updateSelected_();
    });
    document.addEventListener('shaka-main-offline-progress', () => {
      this.updateOfflineProgress_();
    });
    document.addEventListener('shaka-main-locale-changed', () => {
      this.remakeSearchDiv_(container);
      this.remakeResultsDiv_();
      // Update the componentHandler, to account for any new MDL elements added.
      componentHandler.upgradeDom();
    });
    document.addEventListener('shaka-main-page-changed', () => {
      if (!this.resultsDiv_.childNodes.length &&
          !container.classList.contains('hidden')) {
        // Now that the page is showing, create the contents that we deferred
        // until now.
        this.remakeResultsDiv_();
      }
    });

    this.readHashParameters_();
    this.updateHashParameters_();
    this.remakeSearchDiv_(container);
  }

  /** @private */
  readHashParameters_() {
    const hashValues = this.button_.getAttribute('tab-hash');
    if (hashValues) {
      for (const valueRaw of hashValues.split(',')) {
        if (valueRaw.startsWith('drm:')) {
          const key = valueRaw.split('drm:')[1];
          const value = shakaAssets.KeySystem[key];
          if (value) {
            this.desiredDRM_ = value;
          }
        } else if (valueRaw.startsWith('source:')) {
          const key = valueRaw.split('source:')[1];
          const value = shakaAssets.Source[key];
          if (value) {
            this.desiredSource_ = value;
          }
        } else {
          const value = shakaAssets.Feature[valueRaw];
          if (value) {
            this.desiredFeatures_.push(value);
          }
        }
      }
    }
  }

  /** @private */
  updateHashParameters_() {
    const hashValues = [];
    if (this.desiredSource_) {
      for (const key in shakaAssets.Source) {
        if (shakaAssets.Source[key] == this.desiredSource_) {
          hashValues.push('source:' + key);
        }
      }
    }
    if (this.desiredDRM_) {
      for (const key in shakaAssets.KeySystem) {
        if (shakaAssets.KeySystem[key] == this.desiredDRM_) {
          hashValues.push('drm:' + key);
        }
      }
    }
    for (const feature of this.desiredFeatures_) {
      for (const key in shakaAssets.Feature) {
        if (shakaAssets.Feature[key] == feature) {
          hashValues.push(key);
        }
      }
    }
    if (hashValues.length > 0) {
      this.button_.setAttribute('tab-hash', hashValues.join(','));
    } else {
      this.button_.removeAttribute('tab-hash');
    }
    shakaDemoMain.remakeHash();
  }

  /**
   * @param {!ShakaDemoAssetInfo} asset
   * @return {!shakaDemo.AssetCard}
   * @private
   */
  createAssetCardFor_(asset) {
    const resultsDiv = this.resultsDiv_;
    const isFeatured = false;
    return new shakaDemo.AssetCard(resultsDiv, asset, isFeatured, (c) => {
      const unsupportedReason = shakaDemoMain.getAssetUnsupportedReason(
          asset, /* needOffline= */ false);
      if (unsupportedReason) {
        c.markAsUnsupported(unsupportedReason);
      } else {
        c.addButton(shakaDemo.MessageIds.PLAY, () => {
          shakaDemoMain.loadAsset(asset);
          this.updateSelected_();
        });
        c.addStoreButton();
      }
    });
  }

  /**
   * Updates progress bars on asset cards.
   * @private
   */
  updateOfflineProgress_() {
    for (const card of this.assetCards_) {
      card.updateProgress();
    }
  }

  /**
   * Updates which asset card is selected.
   * @private
   */
  updateSelected_() {
    for (const card of this.assetCards_) {
      card.selectByAsset(shakaDemoMain.selectedAsset);
    }
  }

  /** @private */
  remakeResultsDiv_() {
    shaka.util.Dom.removeAllChildren(this.resultsDiv_);

    const assets = this.searchResults_();
    this.assetCards_ = assets.map((asset) => this.createAssetCardFor_(asset));
    this.updateSelected_();
  }

  /**
   * @param {!shakaDemo.Search.SearchTerm} term
   * @param {shakaDemo.Search.TermType} type
   * @return {boolean}
   * @private
   */
  checkDesiredTerm_(term, type) {
    switch (type) {
      case shakaDemo.Search.TermType.DRM:
        return this.desiredDRM_ == term;
      case shakaDemo.Search.TermType.SOURCE:
        return this.desiredSource_ == term;
      case shakaDemo.Search.TermType.FEATURE:
        return this.desiredFeatures_.includes(
            /** @type {!shakaAssets.Feature} */ (term));
      default:
        return false;
    }
  }

  /**
   * @param {!shakaDemo.Search.SearchTerm} term
   * @param {shakaDemo.Search.TermType} type
   * @param {!Array.<!shakaDemo.Search.SearchTerm>} others
   * @private
   */
  addDesiredTerm_(term, type, others) {
    switch (type) {
      case shakaDemo.Search.TermType.DRM:
        this.desiredDRM_ = /** @type {shakaAssets.KeySystem} */ (term);
        break;
      case shakaDemo.Search.TermType.SOURCE:
        this.desiredSource_ = /** @type {shakaAssets.Source} */ (term);
        break;
      case shakaDemo.Search.TermType.FEATURE:
        // Only this term should be in the desired features.
        for (const term of others) {
          const index = this.desiredFeatures_.indexOf(
              /** @type {shakaAssets.Feature} */ (term));
          if (index != -1) {
            this.desiredFeatures_.splice(index, 1);
          }
        }
        this.desiredFeatures_.push(/** @type {shakaAssets.Feature} */ (term));
        break;
    }
  }

  /**
   * @param {!shakaDemo.Search.SearchTerm} term
   * @param {shakaDemo.Search.TermType} type
   * @private
   */
  removeDesiredTerm_(term, type) {
    let index;
    switch (type) {
      case shakaDemo.Search.TermType.DRM:
        this.desiredDRM_ = null;
        break;
      case shakaDemo.Search.TermType.SOURCE:
        this.desiredSource_ = null;
        break;
      case shakaDemo.Search.TermType.FEATURE:
        index = this.desiredFeatures_.indexOf(
            /** @type {shakaAssets.Feature} */ (term));
        if (index != -1) {
          this.desiredFeatures_.splice(index, 1);
        }
        break;
    }
  }

  /**
   * Creates an input for a single search term.
   * @param {!shakaDemo.InputContainer} searchContainer
   * @param {!shakaDemo.Search.SearchTerm} choice
   * The term this represents.
   * @param {shakaDemo.Search.TermType} type
   * The type of term that this term is.
   * @param {?shakaDemo.MessageIds} tooltip
   * @private
   */
  makeBooleanInput_(searchContainer, choice, type, tooltip) {
    // Give the container a significant amount of right padding, to make
    // it clearer which toggle corresponds to which label.
    searchContainer.addRow(choice, tooltip, 'significant-right-padding');
    const onChange = (input) => {
      if (input.checked) {
        this.addDesiredTerm_(choice, type, [choice]);
      } else {
        this.removeDesiredTerm_(choice, type);
      }
      this.remakeResultsDiv_();
      // Update the componentHandler, to account for any new MDL elements
      // added. Notably, tooltips.
      componentHandler.upgradeDom();
      // Update the hash.
      this.updateHashParameters_();
    };
    const input = new shakaDemo.BoolInput(searchContainer, choice, onChange);
    input.input().checked = this.checkDesiredTerm_(choice, type);
  }

  /**
   * Creates an input for a group of related but mutually-exclusive search
   * terms.
   * @param {!shakaDemo.InputContainer} searchContainer
   * @param {!shakaDemo.MessageIds} name
   * @param {!Array.<!shakaDemo.Search.SearchTerm>} choices
   * An array of the terms in this term group.
   * @param {shakaDemo.Search.TermType} type
   * The type of term that this term group contains. All of the
   * terms in the "choices" array must be of this type.
   * @private
   */
  makeSelectInput_(searchContainer, name, choices, type) {
    searchContainer.addRow(null, null);
    const nullOption = '---';
    const valuesObject = {};
    for (const term of choices) {
      valuesObject[term] = shakaDemoMain.getLocalizedString(term);
    }
    valuesObject[nullOption] = nullOption;
    let lastValue = nullOption;
    const onChange = (input) => {
      if (input.value != nullOption) {
        this.addDesiredTerm_(input.value, type, choices);
      } else {
        this.removeDesiredTerm_(lastValue, type);
      }
      lastValue = input.value;
      this.remakeResultsDiv_();
      // Update the componentHandler, to account for any new MDL elements added.
      // Notably, tooltips.
      componentHandler.upgradeDom();
      // Update the hash.
      this.updateHashParameters_();
    };
    const input = new shakaDemo.SelectInput(
        searchContainer, name, onChange, valuesObject);
    input.input().value = nullOption;
    for (const choice of choices) {
      if (this.checkDesiredTerm_(choice, type)) {
        input.input().value = choice;
        lastValue = choice;
        break;
      }
    }
  }

  /**
   * @param {!Element} container
   * @private
   */
  remakeSearchDiv_(container) {
    shaka.util.Dom.removeAllChildren(container);

    const Feature = shakaAssets.Feature;
    const FEATURE = shakaDemo.Search.TermType.FEATURE;
    const DRM = shakaDemo.Search.TermType.DRM;
    const SOURCE = shakaDemo.Search.TermType.SOURCE;

    // Core term inputs.
    const coreContainer = new shakaDemo.InputContainer(
        container, /* headerText= */ null, shakaDemo.InputContainer.Style.FLEX,
        /* docLink= */ null);
    this.makeSelectInput_(coreContainer,
        shakaDemo.MessageIds.MANIFEST_SEARCH,
        [Feature.DASH, Feature.HLS], FEATURE);
    this.makeSelectInput_(coreContainer,
        shakaDemo.MessageIds.CONTAINER_SEARCH,
        [Feature.MP4, Feature.MP2TS, Feature.WEBM], FEATURE);
    this.makeSelectInput_(coreContainer,
        shakaDemo.MessageIds.DRM_SEARCH,
        Object.values(shakaAssets.KeySystem), DRM);
    this.makeSelectInput_(coreContainer,
        shakaDemo.MessageIds.SOURCE_SEARCH,
        Object.values(shakaAssets.Source).filter((term) => {
          return term != shakaAssets.Source.CUSTOM;
        }), SOURCE);
    this.makeSelectInput_(coreContainer,
        shakaDemo.MessageIds.LIVE_SEARCH,
        [Feature.LIVE, Feature.VOD], FEATURE);

    // Special terms.
    const containerStyle = shakaDemo.InputContainer.Style.FLEX;
    const specialContainer = new shakaDemo.InputContainer(
        container, /* headerText= */ null, containerStyle,
        /* docLink= */ null);
    this.makeBooleanInput_(specialContainer, Feature.HIGH_DEFINITION, FEATURE,
        shakaDemo.MessageIds.HIGH_DEFINITION_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.XLINK, FEATURE,
        shakaDemo.MessageIds.XLINK_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.SUBTITLES, FEATURE,
        shakaDemo.MessageIds.SUBTITLES_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.TRICK_MODE, FEATURE,
        shakaDemo.MessageIds.TRICK_MODE_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.SURROUND, FEATURE,
        shakaDemo.MessageIds.SURROUND_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.OFFLINE, FEATURE,
        shakaDemo.MessageIds.OFFLINE_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.STORED, FEATURE,
        shakaDemo.MessageIds.STORED_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.ADS, FEATURE,
        shakaDemo.MessageIds.AD_SEARCH);
    this.makeBooleanInput_(specialContainer, Feature.AUDIO_ONLY, FEATURE,
        shakaDemo.MessageIds.AUDIO_ONLY_SEARCH);

    container.appendChild(this.resultsDiv_);
  }

  /**
   * @return {!Array.<!ShakaDemoAssetInfo>}
   * @private
   */
  searchResults_() {
    return shakaAssets.testAssets.filter((asset) => {
      if (asset.disabled) {
        return false;
      }
      if (this.desiredDRM_ && !asset.drm.includes(this.desiredDRM_)) {
        return false;
      }
      if (this.desiredSource_ && asset.source != this.desiredSource_) {
        return false;
      }
      for (const feature of this.desiredFeatures_) {
        if (feature == shakaAssets.Feature.STORED) {
          if (!asset.isStored()) {
            return false;
          }
        } else if (!asset.features.includes(feature)) {
          return false;
        }
      }
      return true;
    });
  }
};

/** @typedef {shakaAssets.Feature|shakaAssets.Source} */
shakaDemo.Search.SearchTerm;

/** @enum {string} */
shakaDemo.Search.TermType = {
  FEATURE: 'Feature',
  DRM: 'DRM',
  SOURCE: 'Source',
};

document.addEventListener('shaka-main-loaded', shakaDemo.Search.init);
document.addEventListener('shaka-main-cleanup', () => {
  shakaDemoSearch = null;
});
<|endoftext|>


<commit_before>asynctest(
  'EscapingKeyingTest',

  [

  ],

  function () {
    var success = arguments[arguments.length - 2];
    var failure = arguments[arguments.length - 1];

    success();

  }
);<commit_msg>VAN-44: escaping test<commit_after>asynctest(
  'EscapingKeyingTest',

  [
    'ephox.agar.api.Keyboard',
    'ephox.agar.api.Keys',
    'ephox.agar.api.Step',
    'ephox.alloy.api.GuiFactory',
    'ephox.alloy.test.GuiSetup'
  ],

  function (Keyboard, Keys, Step, GuiFactory, GuiSetup) {
    var success = arguments[arguments.length - 2];
    var failure = arguments[arguments.length - 1];

    GuiSetup.setup(function (store, doc, body) {
      return GuiFactory.build({
        uiType: 'custom',
        dom: {
          tag: 'div',
          classes: [ 'escape-keying-test'],
          styles: {

          }
        },
        uid: 'custom-uid',
        keying: {
          mode: 'escaping',
          onEscape: store.adder('detected.escape')
        },
        components: [

        ],
        focusing: true
      });

    }, function (doc, body, gui, component, store) {
      return [
        Step.sync(function () {
          component.apis().focus();
        }),
        store.sAssertEq('Initially empty', [ ]),
        Keyboard.sKeydown(doc, Keys.escape(), { }),
        store.sAssertEq('Post escape', [ 'detected.escape' ])
      ];
    }, function () { success(); }, failure);

  }
);<|endoftext|>


<commit_before>/* eslint no-unused-vars:0 */
import _debug from 'debug'
import mapValues from 'lodash.mapvalues'
const debug = _debug('sutro:errors')

const getError = (err) => {
  if (err.error) return getError(err.error)
  if (err.stack) return err.stack
  if (err.message) return err.message
  return err
}

const getErrorFields = (err) => {
  if (!err.errors) return
  return mapValues(err.errors, getError)
}

const sendError = (err, res) => {
  const error = {
    error: getError(err),
    fields: getErrorFields(err)
  }
  res.status(err.status || 500)
  res.json(error)
  res.end()
  return error
}

export default (err, req, res, next) => {
  const error = sendError(err, res)
  debug(error)
}
<commit_msg>fixing stupidity<commit_after>/* eslint no-unused-vars:0 */
import _debug from 'debug'
import mapValues from 'lodash.mapvalues'
const debug = _debug('sutro:errors')

const getError = (err) => {
  if (err.error) return getError(err.error)
  if (err.stack) return err.stack
  if (err.message) return err.message
  return err
}

const getErrorFields = (err) => {
  if (!err.errors) return
  return mapValues(err.errors, getError)
}

const sendError = (err, res) => {
  res.status(err.status || 500)
  res.json({
    error: getError(err),
    fields: getErrorFields(err)
  })
  res.end()
}

export default (err, req, res, next) => {
  sendError(err, res)
  debug(getError(err))
}
<|endoftext|>


<commit_before>import Handsontable from './browser';
import {polymerWrap, closest} from './helpers/dom/element';
import {isWebComponentSupportedNatively} from './helpers/feature';
import {stopImmediatePropagation as _stopImmediatePropagation} from './helpers/dom/event';

/**
 * Event DOM manager for internal use in Handsontable.
 *
 * @class EventManager
 * @util
 */
class EventManager {
  /**
   * @param {Object} [context=null]
   * @private
   */
  constructor(context = null) {
    this.context = context || this;

    if (!this.context.eventListeners) {
      this.context.eventListeners = [];
    }
  }

  /**
   * Register specified listener (`eventName`) to the element.
   *
   * @param {Element} element Target element.
   * @param {String} eventName Event name.
   * @param {Function} callback Function which will be called after event occur.
   * @returns {Function} Returns function which you can easily call to remove that event
   */
  addEventListener(element, eventName, callback) {
    let context = this.context;

    function callbackProxy(event) {
      if (event.target == void 0 && event.srcElement != void 0) {
        if (event.definePoperty) {
          event.definePoperty('target', {
            value: event.srcElement
          });
        } else {
          event.target = event.srcElement;
        }
      }
      if (event.preventDefault == void 0) {
        if (event.definePoperty) {
          event.definePoperty('preventDefault', {
            value: function() {
              this.returnValue = false;
            }
          });
        } else {
          event.preventDefault = function() {
            this.returnValue = false;
          };
        }
      }
      event = extendEvent(context, event);

      /* jshint validthis:true */
      callback.call(this, event);
    }
    this.context.eventListeners.push({
      element: element,
      event: eventName,
      callback: callback,
      callbackProxy: callbackProxy,
    });

    if (window.addEventListener) {
      element.addEventListener(eventName, callbackProxy, false);
    } else {
      element.attachEvent('on' + eventName, callbackProxy);
    }
    Handsontable.countEventManagerListeners++;

    return () => {
      this.removeEventListener(element, eventName, callback);
    };
  }

  /**
   * Remove the event listener previously registered.
   *
   * @param {Element} element Target element.
   * @param {String} eventName Event name.
   * @param {Function} callback Function to remove from the event target. It must be the same as during registration listener.
   */
  removeEventListener(element, eventName, callback) {
    let len = this.context.eventListeners.length;
    let tmpEvent;

    while (len--) {
      tmpEvent = this.context.eventListeners[len];

      if (tmpEvent.event == eventName && tmpEvent.element == element) {
        if (callback && callback != tmpEvent.callback) {
          continue;
        }
        this.context.eventListeners.splice(len, 1);

        if (tmpEvent.element.removeEventListener) {
          tmpEvent.element.removeEventListener(tmpEvent.event, tmpEvent.callbackProxy, false);
        } else {
          tmpEvent.element.detachEvent('on' + tmpEvent.event, tmpEvent.callbackProxy);
        }
        Handsontable.countEventManagerListeners--;
      }
    }
  }

  /**
   * Clear all previously registered events.
   *
   * @private
   * @since 0.15.0-beta3
   */
  clearEvents() {
    if (!this.context) {
      return;
    }
    let len = this.context.eventListeners.length;

    while (len--) {
      let event = this.context.eventListeners[len];

      if (event) {
        this.removeEventListener(event.element, event.event, event.callback);
      }
    }
  }

  /**
   * Clear all previously registered events.
   */
  clear() {
    this.clearEvents();
  }

  /**
   * Destroy instance of EventManager.
   */
  destroy() {
    this.clearEvents();
    this.context = null;
  }

  /**
   * Trigger event at the specified target element.
   *
   * @param {Element} element Target element.
   * @param {String} eventName Event name.
   */
  fireEvent(element, eventName) {
    let options = {
      bubbles: true,
      cancelable: (eventName !== 'mousemove'),
      view: window,
      detail: 0,
      screenX: 0,
      screenY: 0,
      clientX: 1,
      clientY: 1,
      ctrlKey: false,
      altKey: false,
      shiftKey: false,
      metaKey: false,
      button: 0,
      relatedTarget: undefined,
    };
    var event;

    if (document.createEvent) {
      event = document.createEvent('MouseEvents');
      event.initMouseEvent(eventName, options.bubbles, options.cancelable,
        options.view, options.detail,
        options.screenX, options.screenY, options.clientX, options.clientY,
        options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
        options.button, options.relatedTarget || document.body.parentNode);

    } else {
      event = document.createEventObject();
    }

    if (element.dispatchEvent) {
      element.dispatchEvent(event);
    } else {
      element.fireEvent('on' + eventName, event);
    }
  }
}

/**
 * @param {Object} context
 * @param {Event} event
 * @private
 * @returns {*}
 */
function extendEvent(context, event) {
  let componentName = 'HOT-TABLE';
  let isHotTableSpotted;
  let fromElement;
  let realTarget;
  let target;
  let len;
  let nativeStopImmediatePropagation;

  event.isTargetWebComponent = false;
  event.realTarget = event.target;

  nativeStopImmediatePropagation = event.stopImmediatePropagation;
  event.stopImmediatePropagation = function() {
    nativeStopImmediatePropagation.apply(this);
    _stopImmediatePropagation(this);
  };

  if (!Handsontable.eventManager.isHotTableEnv) {
    return event;
  }
  event = polymerWrap(event);
  len = event.path ? event.path.length : 0;

  while (len--) {
    if (event.path[len].nodeName === componentName) {
      isHotTableSpotted = true;

    } else if (isHotTableSpotted && event.path[len].shadowRoot) {
      target = event.path[len];

      break;
    }
    if (len === 0 && !target) {
      target = event.path[len];
    }
  }
  if (!target) {
    target = event.target;
  }
  event.isTargetWebComponent = true;

  if (isWebComponentSupportedNatively()) {
    event.realTarget = event.srcElement || event.toElement;

  } else if (context instanceof Handsontable.Core || context instanceof Walkontable) {
    // Polymer doesn't support `event.target` property properly we must emulate it ourselves
    if (context instanceof Handsontable.Core) {
      fromElement = context.view ? context.view.wt.wtTable.TABLE : null;

    } else if (context instanceof Walkontable) {
      // .wtHider
      fromElement = context.wtTable.TABLE.parentNode.parentNode;
    }
    realTarget = closest(event.target, [componentName], fromElement);

    if (realTarget) {
      event.realTarget = fromElement.querySelector(componentName) || event.target;
    } else {
      event.realTarget = event.target;
    }
  }

  Object.defineProperty(event, 'target', {
    get: function() {
      return polymerWrap(target);
    },
    enumerable: true,
    configurable: true,
  });

  return event;
}

export {EventManager, eventManager};

// used to debug memory leaks
Handsontable.countEventManagerListeners = 0;
// support for older versions of Handsontable, deprecated
Handsontable.eventManager = eventManager;

function eventManager(context) {
  return new EventManager(context);
}
<commit_msg>Remove addEventListener support for <=IE8. #3277<commit_after>import Handsontable from './browser';
import {polymerWrap, closest} from './helpers/dom/element';
import {isWebComponentSupportedNatively} from './helpers/feature';
import {stopImmediatePropagation as _stopImmediatePropagation} from './helpers/dom/event';

/**
 * Event DOM manager for internal use in Handsontable.
 *
 * @class EventManager
 * @util
 */
class EventManager {
  /**
   * @param {Object} [context=null]
   * @private
   */
  constructor(context = null) {
    this.context = context || this;

    if (!this.context.eventListeners) {
      this.context.eventListeners = [];
    }
  }

  /**
   * Register specified listener (`eventName`) to the element.
   *
   * @param {Element} element Target element.
   * @param {String} eventName Event name.
   * @param {Function} callback Function which will be called after event occur.
   * @returns {Function} Returns function which you can easily call to remove that event
   */
  addEventListener(element, eventName, callback) {
    let context = this.context;

    function callbackProxy(event) {
      event = extendEvent(context, event);

      /* jshint validthis:true */
      callback.call(this, event);
    }
    this.context.eventListeners.push({
      element: element,
      event: eventName,
      callback: callback,
      callbackProxy: callbackProxy,
    });

    if (window.addEventListener) {
      element.addEventListener(eventName, callbackProxy, false);
    } else {
      element.attachEvent('on' + eventName, callbackProxy);
    }
    Handsontable.countEventManagerListeners++;

    return () => {
      this.removeEventListener(element, eventName, callback);
    };
  }

  /**
   * Remove the event listener previously registered.
   *
   * @param {Element} element Target element.
   * @param {String} eventName Event name.
   * @param {Function} callback Function to remove from the event target. It must be the same as during registration listener.
   */
  removeEventListener(element, eventName, callback) {
    let len = this.context.eventListeners.length;
    let tmpEvent;

    while (len--) {
      tmpEvent = this.context.eventListeners[len];

      if (tmpEvent.event == eventName && tmpEvent.element == element) {
        if (callback && callback != tmpEvent.callback) {
          continue;
        }
        this.context.eventListeners.splice(len, 1);

        if (tmpEvent.element.removeEventListener) {
          tmpEvent.element.removeEventListener(tmpEvent.event, tmpEvent.callbackProxy, false);
        } else {
          tmpEvent.element.detachEvent('on' + tmpEvent.event, tmpEvent.callbackProxy);
        }
        Handsontable.countEventManagerListeners--;
      }
    }
  }

  /**
   * Clear all previously registered events.
   *
   * @private
   * @since 0.15.0-beta3
   */
  clearEvents() {
    if (!this.context) {
      return;
    }
    let len = this.context.eventListeners.length;

    while (len--) {
      let event = this.context.eventListeners[len];

      if (event) {
        this.removeEventListener(event.element, event.event, event.callback);
      }
    }
  }

  /**
   * Clear all previously registered events.
   */
  clear() {
    this.clearEvents();
  }

  /**
   * Destroy instance of EventManager.
   */
  destroy() {
    this.clearEvents();
    this.context = null;
  }

  /**
   * Trigger event at the specified target element.
   *
   * @param {Element} element Target element.
   * @param {String} eventName Event name.
   */
  fireEvent(element, eventName) {
    let options = {
      bubbles: true,
      cancelable: (eventName !== 'mousemove'),
      view: window,
      detail: 0,
      screenX: 0,
      screenY: 0,
      clientX: 1,
      clientY: 1,
      ctrlKey: false,
      altKey: false,
      shiftKey: false,
      metaKey: false,
      button: 0,
      relatedTarget: undefined,
    };
    var event;

    if (document.createEvent) {
      event = document.createEvent('MouseEvents');
      event.initMouseEvent(eventName, options.bubbles, options.cancelable,
        options.view, options.detail,
        options.screenX, options.screenY, options.clientX, options.clientY,
        options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
        options.button, options.relatedTarget || document.body.parentNode);

    } else {
      event = document.createEventObject();
    }

    if (element.dispatchEvent) {
      element.dispatchEvent(event);
    } else {
      element.fireEvent('on' + eventName, event);
    }
  }
}

/**
 * @param {Object} context
 * @param {Event} event
 * @private
 * @returns {*}
 */
function extendEvent(context, event) {
  let componentName = 'HOT-TABLE';
  let isHotTableSpotted;
  let fromElement;
  let realTarget;
  let target;
  let len;
  let nativeStopImmediatePropagation;

  event.isTargetWebComponent = false;
  event.realTarget = event.target;

  nativeStopImmediatePropagation = event.stopImmediatePropagation;
  event.stopImmediatePropagation = function() {
    nativeStopImmediatePropagation.apply(this);
    _stopImmediatePropagation(this);
  };

  if (!Handsontable.eventManager.isHotTableEnv) {
    return event;
  }
  event = polymerWrap(event);
  len = event.path ? event.path.length : 0;

  while (len--) {
    if (event.path[len].nodeName === componentName) {
      isHotTableSpotted = true;

    } else if (isHotTableSpotted && event.path[len].shadowRoot) {
      target = event.path[len];

      break;
    }
    if (len === 0 && !target) {
      target = event.path[len];
    }
  }
  if (!target) {
    target = event.target;
  }
  event.isTargetWebComponent = true;

  if (isWebComponentSupportedNatively()) {
    event.realTarget = event.srcElement || event.toElement;

  } else if (context instanceof Handsontable.Core || context instanceof Walkontable) {
    // Polymer doesn't support `event.target` property properly we must emulate it ourselves
    if (context instanceof Handsontable.Core) {
      fromElement = context.view ? context.view.wt.wtTable.TABLE : null;

    } else if (context instanceof Walkontable) {
      // .wtHider
      fromElement = context.wtTable.TABLE.parentNode.parentNode;
    }
    realTarget = closest(event.target, [componentName], fromElement);

    if (realTarget) {
      event.realTarget = fromElement.querySelector(componentName) || event.target;
    } else {
      event.realTarget = event.target;
    }
  }

  Object.defineProperty(event, 'target', {
    get: function() {
      return polymerWrap(target);
    },
    enumerable: true,
    configurable: true,
  });

  return event;
}

export {EventManager, eventManager};

// used to debug memory leaks
Handsontable.countEventManagerListeners = 0;
// support for older versions of Handsontable, deprecated
Handsontable.eventManager = eventManager;

function eventManager(context) {
  return new EventManager(context);
}
<|endoftext|>


<commit_before>var DEBUG = 1;

// AJAX setup
var AJAX_SETUP = {
	timeout: 9000,
	cache: false, // this little sentence killed a lot of little bugs that was actually one big big
}

// Loops & intervals
var BACKGROUND_LOOP = 30000; // 30s
var BACKGROUND_LOOP_OFFLINE = 3000; // 3s, respond quickly when we get back online
var PAGE_LOOP = 10000; // 10s

// Update stuff at every X intervals
var UPDATE_OFFICE_INTERVAL = 1; // recommended: 1
var UPDATE_SERVANT_INTERVAL = 20; // recommended: 20
var UPDATE_MEETINGS_INTERVAL = 20; // recommended: 20
var UPDATE_COFFEE_INTERVAL = 1; // recommended: 1
var UPDATE_HOURS_INTERVAL = 60; // recommended: 60
var UPDATE_CANTINAS_INTERVAL = 60; // recommended: 60
var UPDATE_BUS_INTERVAL = 2; // recommended: 1
var UPDATE_NEWS_INTERVAL = 20; // recommended: 20

// Meme detection
var urlExists = function(url) {
	try {
	    var http = new XMLHttpRequest();
	    http.open('HEAD', url, false);
	    http.send();
	    return http.status!=404;
	}
	catch (e) {
		return false;
	}
}
var MEME_AMOUNT = 0;
var __counter__ = 1;
while (urlExists('meme/'+__counter__+'.jpg')) {
	MEME_AMOUNT++;
	__counter__++;
}

// OS detection
var OPERATING_SYSTEM = "Unknown";
if (navigator.appVersion.indexOf("Win")!==-1) OPERATING_SYSTEM="Windows";
else if (navigator.appVersion.indexOf("Linux")!==-1) OPERATING_SYSTEM="Linux";
else if (navigator.appVersion.indexOf("X11")!==-1) OPERATING_SYSTEM="UNIX";
else if (navigator.appVersion.indexOf("Mac")!==-1) {
	OPERATING_SYSTEM = "Old Mac";
	if (navigator.appVersion.indexOf("10_7")!==-1||navigator.appVersion.indexOf("10_8")!==-1) {
		OPERATING_SYSTEM = "Mac";
	}
}
else {
	console.log('WARNING: Potentially unsupported operating system');
}

// Browser detection
var BROWSER = "Unknown";
if (typeof chrome != "undefined" && typeof opr == "undefined")
	BROWSER = "Chrome";
else if (typeof chrome != "undefined" && typeof opr != "undefined")
	BROWSER = "Opera";
else
	console.log('WARNING: Potentially unsupported browser');

// Production detection
if (BROWSER == 'Chrome')
	if (typeof chrome.i18n != 'undefined')
		if (chrome.i18n.getMessage('@@extension_id') === 'hfgffimlnajpbenfpaofmmffcdmgkllf')
			DEBUG = 0;
// else if (BROWSER == 'Opera')
// 	if (typeof chrome.i18n != 'undefined')
// 		if (chrome.i18n.getMessage('@@extension_id') === 'TODO: OPERA EXTENSION ID HER')
// 			DEBUG = 0;
<commit_msg>constants: added production detection for Opera<commit_after>var DEBUG = 1;

// AJAX setup
var AJAX_SETUP = {
	timeout: 9000,
	cache: false, // this little sentence killed a lot of little bugs that was actually one big big
}

// Loops & intervals
var BACKGROUND_LOOP = 30000; // 30s
var BACKGROUND_LOOP_OFFLINE = 3000; // 3s, respond quickly when we get back online
var PAGE_LOOP = 10000; // 10s

// Update stuff at every X intervals
var UPDATE_OFFICE_INTERVAL = 1; // recommended: 1
var UPDATE_SERVANT_INTERVAL = 20; // recommended: 20
var UPDATE_MEETINGS_INTERVAL = 20; // recommended: 20
var UPDATE_COFFEE_INTERVAL = 1; // recommended: 1
var UPDATE_HOURS_INTERVAL = 60; // recommended: 60
var UPDATE_CANTINAS_INTERVAL = 60; // recommended: 60
var UPDATE_BUS_INTERVAL = 2; // recommended: 1
var UPDATE_NEWS_INTERVAL = 20; // recommended: 20

// Meme detection
var urlExists = function(url) {
	try {
	    var http = new XMLHttpRequest();
	    http.open('HEAD', url, false);
	    http.send();
	    return http.status!=404;
	}
	catch (e) {
		return false;
	}
}
var MEME_AMOUNT = 0;
var __counter__ = 1;
while (urlExists('meme/'+__counter__+'.jpg')) {
	MEME_AMOUNT++;
	__counter__++;
}

// OS detection
var OPERATING_SYSTEM = "Unknown";
if (navigator.appVersion.indexOf("Win")!==-1) OPERATING_SYSTEM="Windows";
else if (navigator.appVersion.indexOf("Linux")!==-1) OPERATING_SYSTEM="Linux";
else if (navigator.appVersion.indexOf("X11")!==-1) OPERATING_SYSTEM="UNIX";
else if (navigator.appVersion.indexOf("Mac")!==-1) {
	OPERATING_SYSTEM = "Old Mac";
	if (navigator.appVersion.indexOf("10_7")!==-1||navigator.appVersion.indexOf("10_8")!==-1) {
		OPERATING_SYSTEM = "Mac";
	}
}
else {
	console.log('WARNING: Potentially unsupported operating system');
}

// Browser detection
var BROWSER = "Unknown";
if (typeof chrome != "undefined" && typeof opr == "undefined")
	BROWSER = "Chrome";
else if (typeof chrome != "undefined" && typeof opr != "undefined")
	BROWSER = "Opera";
else
	console.log('WARNING: Potentially unsupported browser');

// Production detection
if (BROWSER == 'Chrome')
	if (typeof chrome.i18n != 'undefined')
		if (chrome.i18n.getMessage('@@extension_id') === 'hfgffimlnajpbenfpaofmmffcdmgkllf')
			DEBUG = 0;
else if (BROWSER == 'Opera')
	if (typeof chrome.i18n != 'undefined')
		if (chrome.i18n.getMessage('@@extension_id') === 'npnpbddfcaibgnegafofkmffmbmflelj')
			DEBUG = 0;
<|endoftext|>


<commit_before>/* eslint-env mocha */

import bel from 'bel'
import expect from 'must'
import { component } from './component'

function simpleElement () {
  return bel`

`
}

function simpleComponent (el, opts) {
  return { el, opts, factory: simpleComponent }
}
simpleComponent.NAME = 'heinrich'
simpleComponent.DEFAULTS = { lang: 'es' }

describe('component', () => {
  const components = component('foo', simpleComponent, simpleElement())

  it('accepts a name, looks for [data-component="$name"] elements and returns them initialized', () => {
    expect(components).to.be.an.array()
    expect(components).to.have.length(3)

    expect(components[0].factory).to.equal(simpleComponent)
  })

  it('passes an element and options object to the factory', () => {
    let callArgs
    component('foo', (...args) => { callArgs = args }, simpleElement())
    expect(callArgs).to.have.length(2)
    expect(callArgs[0]).to.be.an.instanceof(window.HTMLElement)
    expect(callArgs[1]).to.be.an.object()
  })

  it('adds the component name to the componentReady dataset of the element', () => {
    expect(components[0].el.dataset.componentReady).to.contain('foo')
  })

  it('stores the index of the processed element inside the options', () => {
    expect(components[0].opts.index).to.be(0)
    expect(components[1].opts.index).to.be(1)
    expect(components[2].opts.index).to.be(2)
  })

  describe('the passed options.conf object', () => {
    it('merges the factory defaults', () => {
      expect(components[0].opts.conf.lang).to.be(simpleComponent.DEFAULTS.lang)
    })

    it('merges options provided on component call', () => {
      const components = component('foo', simpleComponent, { root: simpleElement(), conf: { lang: 'fr' } })
      expect(components[0].opts.conf.lang).to.be('fr')
      expect(components[1].opts.conf.lang).to.be('de')
    })

    it('merges options provided via data-component-* attributes', () => {
      expect(components[1].opts.conf.lang).to.be('de')
      expect(components[1].opts.conf.langFallback).to.be('en')
    })

    it('merges options provided via data-$name-* attributes', () => {
      expect(components[2].opts.conf.lang).to.be('en')
      expect(components[2].opts.conf.langFallback).to.be('es')
    })
  })

  describe('NodeList/Array instead of a name', () => {
    function create (...args) {
      return component(simpleElement().querySelectorAll('div'), ...args)
    }

    it('uses the NAME attribute from the init function', () => {
      expect(create(simpleComponent)[0].el.dataset.componentReady)
        .to.contain(simpleComponent.NAME)
    })

    it('or the name attribute from the provided options', () => {
      expect(create(el => ({ el }), { name: 'herold' })[0].el.dataset.componentReady)
        .to.contain('herold')
    })

    it('throws an exception if neither is provided', () => {
      expect(() => create(() => {})).to.throw(Error, /provide component name/)
    })
  })
})
<commit_msg>initialize variable to get rid of editor warnings :/<commit_after>/* eslint-env mocha */

import bel from 'bel'
import expect from 'must'
import { component } from './component'

function simpleElement () {
  return bel`

`
}

function simpleComponent (el, opts) {
  return { el, opts, factory: simpleComponent }
}
simpleComponent.NAME = 'heinrich'
simpleComponent.DEFAULTS = { lang: 'es' }

describe('component', () => {
  const components = component('foo', simpleComponent, simpleElement())

  it('accepts a name, looks for [data-component="$name"] elements and returns them initialized', () => {
    expect(components).to.be.an.array()
    expect(components).to.have.length(3)

    expect(components[0].factory).to.equal(simpleComponent)
  })

  it('passes an element and options object to the factory', () => {
    let callArgs = null
    component('foo', (...args) => { callArgs = args }, simpleElement())
    expect(callArgs).to.have.length(2)
    expect(callArgs[0]).to.be.an.instanceof(window.HTMLElement)
    expect(callArgs[1]).to.be.an.object()
  })

  it('adds the component name to the componentReady dataset of the element', () => {
    expect(components[0].el.dataset.componentReady).to.contain('foo')
  })

  it('stores the index of the processed element inside the options', () => {
    expect(components[0].opts.index).to.be(0)
    expect(components[1].opts.index).to.be(1)
    expect(components[2].opts.index).to.be(2)
  })

  describe('the passed options.conf object', () => {
    it('merges the factory defaults', () => {
      expect(components[0].opts.conf.lang).to.be(simpleComponent.DEFAULTS.lang)
    })

    it('merges options provided on component call', () => {
      const components = component('foo', simpleComponent, { root: simpleElement(), conf: { lang: 'fr' } })
      expect(components[0].opts.conf.lang).to.be('fr')
      expect(components[1].opts.conf.lang).to.be('de')
    })

    it('merges options provided via data-component-* attributes', () => {
      expect(components[1].opts.conf.lang).to.be('de')
      expect(components[1].opts.conf.langFallback).to.be('en')
    })

    it('merges options provided via data-$name-* attributes', () => {
      expect(components[2].opts.conf.lang).to.be('en')
      expect(components[2].opts.conf.langFallback).to.be('es')
    })
  })

  describe('NodeList/Array instead of a name', () => {
    function create (...args) {
      return component(simpleElement().querySelectorAll('div'), ...args)
    }

    it('uses the NAME attribute from the init function', () => {
      expect(create(simpleComponent)[0].el.dataset.componentReady)
        .to.contain(simpleComponent.NAME)
    })

    it('or the name attribute from the provided options', () => {
      expect(create(el => ({ el }), { name: 'herold' })[0].el.dataset.componentReady)
        .to.contain('herold')
    })

    it('throws an exception if neither is provided', () => {
      expect(() => create(() => {})).to.throw(Error, /provide component name/)
    })
  })
})
<|endoftext|>


<commit_before>import m from 'mithril'

// Registry of controllers and corresponding root nodes
let roots = new Map()

// Register views to bind their roots to the above
const register = view =>
  function registeredView( ctrl ){
    const output     = view( ...arguments )
    const { attrs }  = output
    const { config } = attrs

    if( ctrl.exit )
      attrs.config = function superConfig( el ){
        roots.set( ctrl, el )

        if( config )
          return config.call( this, ...arguments )
      }

    return output
  }

// Root components (those mounted or routed) are the source of redraws.
// Before they draw, there is no stateful virtual DOM.
// Therefore their view execution is the source of all truth in what is currently rendered.
const root = ( { view, ...component } ) =>
  Object.assign( component, {
    view : function rootView(){
      // All previously registered exitable components are saved here
      const previous = Array.from( roots )

      // Then we reset
      roots.clear()

      // Execute the view, registering all exitables
      let output   = register( view ).call( this, ...arguments )

      // Now, set up a list of confirmed exits
      const exits    = []

      // For every previous exitable instance...
      for( let [ ctrl, el ] of previous )
        // ...if it hasn't re-registered...
        if( !roots.has( ctrl ) )
          // It's gone! Call the exit method and keep its output.
          exits.push( ctrl.exit( el ) )

      // If we have exits...
      if( exits.length ){
        // Noop this draw
        output = { subtree : 'retain' }

        // ...and all subsequent ones...
        m.startComputation()

        // ...until all exits have resolved
        Promise.all( exits ).then( m.endComputation )
      }

      return output
    }
  } )

// Helper: transform each value in an object. Even in ES7, this is painfully contrived.
const reduce = ( object, transformer ) =>
  Object.keys( object ).reduce(
    ( output, key ) =>
      Object.assign( output, {
        [ key ] : transformer( object[ key ] )
      } ),
    {}
  )

// Export a patched Mithril API
export default Object.assign(
  // Core m function needs to sniff out components...
  function m(){
    const output = m( ...arguments )

    output.children.forEach( child => {
      const { view } = child

      if( view )
        // ...and get their views to register controllers and root nodes
        Object.assign( child, {
          view : register( view )
        } )
    } )

    return output
  },

  // Then we have all the m methods
  m,

  // Mount and Route need to register root components for snapshot logic
  {
    mount : ( el, component ) =>
      m.mount( el, root( component ) ),

    route( el, path, map ){
      if( map ){
        return m.route( el, path, reduce( root ) )
      }
      else {
        return m.route( ...arguments )
      }
    }
  }
)
<commit_msg>Fix #1<commit_after>import mithril from 'mithril'

// Registry of controllers and corresponding root nodes
const roots     = new Map()
// A record of recent view outputs for every root-level component
const histories = new WeakMap()
// Whether the current draw is being used to revert to its previous state
let   reverting = false

// Register views to bind their roots to the above
const register = view =>
  function registeredView( ctrl ){
    const output     = view( ...arguments )

    // Don't register exits during a reversion
    if( reverting )
      return output

    const { attrs }  = output
    const { config } = attrs

    if( ctrl.exit )
      attrs.config = function superConfig( el ){
        roots.set( ctrl, el )

        if( config )
          return config.call( this, ...arguments )
      }

    return output
  }

// Root components (those mounted or routed) are the source of redraws.
// Before they draw, there is no stateful virtual DOM.
// Therefore their view execution is the source of all truth in what is currently rendered.
const root = ( { view, ...component } ) =>
  Object.assign( component, {
    view : function rootView( ctrl ){
      const history = histories.get( ctrl )

      // If we are in the middle of a reversion, we just want to patch
      // Mithril's internal virtual DOM HEAD to what it was before the
      // last output
      if( reverting )
        return history

      // All previously registered exitable components are saved here
      const previous = Array.from( roots )

      // Then we reset
      roots.clear()

      // Execute the view, registering all exitables
      let output     = register( view ).call( this, ...arguments )

      // Otherwise we need to revord the new output
      histories.set( ctrl, output )

      // Now, set up a list of confirmed exits
      const exits    = []

      // For every previous exitable instance...
      for( let [ ctrl, el ] of previous )
        // ...if it hasn't re-registered...
        if( !roots.has( ctrl ) )
          // It's gone! Call the exit method and keep its output.
          exits.push( ctrl.exit( el ) )

      // If we have exits...
      if( exits.length ){
        // Noop this draw
        output = { subtree : 'retain' }

        // Freeze the draw process
        m.startComputation()

        // ...until all exits have resolved
        Promise.all( exits ).then( () => {
          // We now need to revert Mithril's internal virtual DOM head so that
          // it will correctly patch the live DOM to match the state in which
          // components are removed: it currently believes that already happend
          // Because it ran the diff before we told it to retain the subtree at
          // the last minute
          reverting = true

          // Next draw should not patch, only diff
          m.redraw.strategy( 'none' )

          // Force a synchronous draw despite being frozen
          m.redraw( true )

          // Now it's as if we never here to begin with
					reverting = false

          // Resume business as usual
          m.endComputation()
        } )
      }

      return output
    }
  } )

// Helper: transform each value in an object. Even in ES7, this is painfully contrived.
const reduce = ( object, transformer ) =>
  Object.keys( object ).reduce(
    ( output, key ) =>
      Object.assign( output, {
        [ key ] : transformer( object[ key ] )
      } ),
    {}
  )

// Export a patched Mithril API
export default Object.assign(
  // Core m function needs to sniff out components...
  function m(){
    const output = mithril( ...arguments )

    output.children.forEach( child => {
      const { view } = child

      if( view )
        // ...and get their views to register controllers and root nodes
        Object.assign( child, {
          view : register( view )
        } )
    } )

    return output
  },

  // Then we have all the m methods
  mithril,

  // Mount and Route need to register root components for snapshot logic
  {
    mount : ( el, component ) =>
      mithril.mount( el, root( component ) ),

    route( el, path, map ){
      if( map ){
        return mithril.route( el, path, reduce( root ) )
      }
      else {
        return mithril.route( ...arguments )
      }
    }
  }
)
<|endoftext|>


<commit_before>class Page {

    constructor(siteGen, pageName) {

        this.siteGen = siteGen
        this.pageName = pageName
        this.posts = []
        this.onInsert = null
    }

    loadXMLDoc(filename, onDone)
    {
    	this.xhttp = new XMLHttpRequest();
    	this.xhttp.open("GET", filename, true);
    	this.xhttp.onload = function (e)
    	{
    		if (this.xhttp.readyState === 4)
    		{
    			if (this.xhttp.status === 200)
    			{
    				onDone(false, this.xhttp.responseText);
    			}
    			else
    			{
    				onDone(true, this.xhttp.responseText);
    			}
    		}
    	};
    	this.xhttp.onerror = function (e)
    	{
    		onDone(true, this.xhttp.responseText);
    	};
    	this.xhttp.send(null);
    }

    makePostBase() {

        let post = new PostBase(this)
        this.posts.push(post)
        return post
    }

    makePost() {

        let post = new Post(this)
        this.posts.push(post)
        return post
    }

    makePostSimple(title = 'Title', marginTop = 20) {

        let post = new PostSimple(this, title, marginTop)
        this.posts.push(post)
        return post
    }

    makePostFancyCode(title = 'Title', formTitle = '') {

        let post = new PostFancyCode(this, title, formTitle)
        this.posts.push(post)
        return post
    }

    makePostThreeColums(    titleOne = 'Title One',
                            titleTwo = 'Title Two',
                            titleThree = 'Title Three') {

        let post = new PostThreeColums(this, titleOne, titleTwo, titleThree)
        this.posts.push(post)
        return post
    }

    makeTitle(titleText = 'Title', backgroundColor = 'rgba(0, 63, 63, 0.8)') {
        let title = new TitleBig(this.siteGen, titleText, backgroundColor)
        this.posts.push(title)
        return title
    }

    insert() {

        for (let i = 0; i < this.posts.length; i++)
        {
            this.posts[i].insert()
        }
    }

    onPageInsert ( ) {

        if (this.onInsert != null) this.onInsert ( )
    }
}

class PageContent {

    constructor(siteGen) {

        this.siteGen = siteGen
        this.pageNumber = siteGen.pages.length
        this.pageName = siteGen.pageNames[siteGen.pages.length]
        this.page = siteGen.makePage(this.pageName)
        this.page.pageNumber = this.pageNumber
    }

    disqus ( disqus_identifier, newTitle ) {

        if ( this.siteGen.disqus.enabled == false )  return

        const onDivInserted = ( disqus_identifier, newTitle ) => {

            let newUrl = this.siteGen.siteURL + 'index.html?page=' + this.pageNumber

            let newTitle = newTitle

            this.siteGen.disqus.insert ( disqus_identifier, newUrl, newTitle  )
        }
        this.page.onInsert = ( disqus_identifier, newTitle ) => onDivInserted ( disqus_identifier, newTitle )
    }
}
<commit_msg>disqus improvement<commit_after>class Page {

    constructor(siteGen, pageName) {

        this.siteGen = siteGen
        this.pageName = pageName
        this.posts = []
        this.onInsert = null
    }

    loadXMLDoc(filename, onDone)
    {
    	this.xhttp = new XMLHttpRequest();
    	this.xhttp.open("GET", filename, true);
    	this.xhttp.onload = function (e)
    	{
    		if (this.xhttp.readyState === 4)
    		{
    			if (this.xhttp.status === 200)
    			{
    				onDone(false, this.xhttp.responseText);
    			}
    			else
    			{
    				onDone(true, this.xhttp.responseText);
    			}
    		}
    	};
    	this.xhttp.onerror = function (e)
    	{
    		onDone(true, this.xhttp.responseText);
    	};
    	this.xhttp.send(null);
    }

    makePostBase() {

        let post = new PostBase(this)
        this.posts.push(post)
        return post
    }

    makePost() {

        let post = new Post(this)
        this.posts.push(post)
        return post
    }

    makePostSimple(title = 'Title', marginTop = 20) {

        let post = new PostSimple(this, title, marginTop)
        this.posts.push(post)
        return post
    }

    makePostFancyCode(title = 'Title', formTitle = '') {

        let post = new PostFancyCode(this, title, formTitle)
        this.posts.push(post)
        return post
    }

    makePostThreeColums(    titleOne = 'Title One',
                            titleTwo = 'Title Two',
                            titleThree = 'Title Three') {

        let post = new PostThreeColums(this, titleOne, titleTwo, titleThree)
        this.posts.push(post)
        return post
    }

    makeTitle(titleText = 'Title', backgroundColor = 'rgba(0, 63, 63, 0.8)') {
        let title = new TitleBig(this.siteGen, titleText, backgroundColor)
        this.posts.push(title)
        return title
    }

    insert() {

        for (let i = 0; i < this.posts.length; i++)
        {
            this.posts[i].insert()
        }
    }

    onPageInsert ( ) {

        if (this.onInsert != null) this.onInsert ( )
    }
}

class PageContent {

    constructor(siteGen) {

        this.siteGen = siteGen
        this.pageNumber = siteGen.pages.length
        this.pageName = siteGen.pageNames[siteGen.pages.length]
        this.page = siteGen.makePage(this.pageName)
        this.page.pageNumber = this.pageNumber
    }

    disqus ( disqus_identifier, newTitle ) {

        if ( this.siteGen.disqus.enabled == false )  return

        const onDivInserted = ( disqus_identifier, newTitle ) => {

            let newUrl = this.siteGen.siteURL + 'index.html?page=' + this.pageNumber

            this.siteGen.disqus.insert ( disqus_identifier, newUrl, newTitle  )
        }
        this.page.onInsert = ( disqus_identifier, newTitle ) => onDivInserted ( disqus_identifier, newTitle )
    }
}
<|endoftext|>


<commit_before>/**
 * @fileOverview Cross
 */
import { Component } from 'react';

class Cell extends Component {

  static displayName = 'Cell';

  render() {
    return null;
  }
}

export default Cell;
<commit_msg>Adding PRESENTATION_ATTRIBUTES to Cell propTypes<commit_after>/**
 * @fileOverview Cross
 */
import { Component } from 'react';

import { PRESENTATION_ATTRIBUTES } from '../util/ReactUtils';

class Cell extends Component {

  static displayName = 'Cell';

  static propTypes = {
    ...PRESENTATION_ATTRIBUTES,
  };

  render() {
    return null;
  }
}

export default Cell;
<|endoftext|>


<commit_before>import React from 'react'
import Tabs from './Tabs'
import { View, StatusBar, AppState } from 'react-native'
import ReturningLogin from './login/ReturningLogin'
import Onboarding from './login/Onboarding'
import { mainComponent } from '../store/reducer/navigation'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import Login from './login/Login'
import LoginOverlay from './login/LoginOverlay'
import StatusMessage from './StatusMessage'
import Colors from '@Colors/colors'
import Config from '@Config/config'
import SendMoney from './sendMoney/SendMoney'
import AppCover from './lockedState/AppCover'
import md5 from 'md5'
import { logout } from '../store/reducer/login'
import { closeConfirmation, setCoverApp, navigateToTab, hideModal, setOverlayOpen } from '../store/reducer/navigation'
import { updatePage, resetPayment, resetForm } from '../store/reducer/sendMoney'
import UnlockAppAlert, {maxAttempts} from './lockedState/UnlockAppAlert'

class Root extends React.Component {

  constructor () {
    super()
    this.state = {
      appState: AppState.currentState,
      unlockError: false,
      failedAttempts: 0
    }
  }

  componentDidMount () {
    AppState.addEventListener('change', this._handleAppStateChange)
  }

  componentWillUnmount () {
    AppState.removeEventListener('change', this._handleAppStateChange)
  }

  _handleAppStateChange = (nextAppState) => {
    if (nextAppState.match(/inactive|background/) && this.state.appState === 'active') {
      this.props.setCoverApp(true)
      this.setState({ appState: nextAppState})
    } else if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
      if (this.props.passToUnlock!=='') {
        this.setState({askToUnlock: true, appState: nextAppState})
      } else {
        this.props.setCoverApp(false)
        this.setState({appState: nextAppState})
      }
    } else {
      this.setState({appState: nextAppState})
    }
  }

  checkPass (pass) {
    var encodedPass = md5(pass)
    if (encodedPass === this.props.passToUnlock) {
      this.props.setCoverApp(false)
      this.setState({askToUnlock: false, unlockError: false, failedAttempts: 0})
    } else {
      var failedAttempts = this.state.failedAttempts + 1
      if(failedAttempts < maxAttempts) {
        this.setState({unlockError: true, failedAttempts})
      } else {
        this.props.resetPayment()
        this.logout()
      }
    }
  }

  logout () {
    this.props.logout()
    this.props.closeConfirmation()
    this.setState({askToUnlock: false, unlockError: false, failedAttempts: 0})
    this.props.setCoverApp(false)
  }

  logoutPress () {
    this.props.navigateToTab(0)
    this.props.resetForm()
    this.props.setOverlayOpen(false)
    this.props.hideModal()
    this.logout()
  }

  render () {
      // The app is rendered before the state has been loaded via redux-persist. This state property allows
      // the main 'app' UI to be hidden until initialised.
      if (!this.props.stateInitialised) {
        return (
          <View style={{flex: 1}}/>
        )
      }
      let bodyComponent
      if (this.props.mainComponent === mainComponent.returningLogin) {
        bodyComponent = <ReturningLogin />
      } else if (this.props.mainComponent === mainComponent.onboarding) {
        bodyComponent = <Onboarding />
      } else if (this.props.mainComponent === mainComponent.tabs) {
        bodyComponent = <Tabs />
      } else {
        throw new Error('Invalid navigation state')
      }

      return (
        <View style={{flex: 1}}>
          <StatusBar animated={true}
              backgroundColor={Colors.transparent}
              translucent={true}
              barStyle={this.props.mainComponent === mainComponent.returningLogin ? 'light-content' : 'dark-content'}/>
          {bodyComponent}
          <LoginOverlay/>
          {(this.props.modalOpen && !this.props.loginFormOpen) && Config.ALLOW_LOGIN ? <SendMoney /> : undefined}
          <Login/>
          <StatusMessage/>
          {this.props.coverApp
              && <AppCover unlockOpened={this.props.passToUnlock!=='' && this.state.askToUnlock}/> }
          {this.props.passToUnlock!=='' && this.state.askToUnlock
              && <UnlockAppAlert
                  checkPass={(pass) => this.checkPass(pass)}
                  error={this.state.unlockError}
                  failedAttempts={this.state.failedAttempts}
                  logout={() => this.logoutPress()} />
          }
        </View>
      )
  }

}

const mapDispatchToProps = (dispatch) =>
  bindActionCreators({
    logout,
    hideModal,
    closeConfirmation,
    updatePage,
    resetPayment,
    setCoverApp,
    navigateToTab,
    setOverlayOpen,
    resetForm
  }, dispatch)

const mapStateToProps = (state) => ({
    ...state.navigation,
    loginFormOpen: state.login.loginFormOpen,
    passToUnlock: state.login.passToUnlock
})

export default connect(mapStateToProps, mapDispatchToProps)(Root)
<commit_msg>do not display send payment button to self (#42)<commit_after>import React from 'react'
import Tabs from './Tabs'
import { View, StatusBar, AppState } from 'react-native'
import ReturningLogin from './login/ReturningLogin'
import Onboarding from './login/Onboarding'
import { mainComponent } from '../store/reducer/navigation'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import Login from './login/Login'
import LoginOverlay from './login/LoginOverlay'
import StatusMessage from './StatusMessage'
import Colors from '@Colors/colors'
import Config from '@Config/config'
import SendMoney from './sendMoney/SendMoney'
import AppCover from './lockedState/AppCover'
import md5 from 'md5'
import { logout } from '../store/reducer/login'
import { closeConfirmation, setCoverApp, navigateToTab, hideModal, setOverlayOpen } from '../store/reducer/navigation'
import { updatePage, resetPayment, resetForm } from '../store/reducer/sendMoney'
import UnlockAppAlert, {maxAttempts} from './lockedState/UnlockAppAlert'

class Root extends React.Component {

  constructor () {
    super()
    this.state = {
      appState: AppState.currentState,
      unlockError: false,
      failedAttempts: 0
    }
  }

  componentDidMount () {
    AppState.addEventListener('change', this._handleAppStateChange)
  }

  componentWillUnmount () {
    AppState.removeEventListener('change', this._handleAppStateChange)
  }

  _handleAppStateChange = (nextAppState) => {
    if (nextAppState.match(/inactive|background/) && this.state.appState === 'active') {
      this.props.setCoverApp(true)
      this.setState({ appState: nextAppState})
    } else if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
      if (this.props.passToUnlock!=='') {
        this.setState({askToUnlock: true, appState: nextAppState})
      } else {
        this.props.setCoverApp(false)
        this.setState({appState: nextAppState})
      }
    } else {
      this.setState({appState: nextAppState})
    }
  }

  checkPass (pass) {
    var encodedPass = md5(pass)
    if (encodedPass === this.props.passToUnlock) {
      this.props.setCoverApp(false)
      this.setState({askToUnlock: false, unlockError: false, failedAttempts: 0})
    } else {
      var failedAttempts = this.state.failedAttempts + 1
      if(failedAttempts < maxAttempts) {
        this.setState({unlockError: true, failedAttempts})
      } else {
        this.props.resetPayment()
        this.logout()
      }
    }
  }

  logout () {
    this.props.logout()
    this.props.closeConfirmation()
    this.setState({askToUnlock: false, unlockError: false, failedAttempts: 0})
    this.props.setCoverApp(false)
  }

  logoutPress () {
    this.props.navigateToTab(0)
    this.props.resetForm()
    this.props.setOverlayOpen(false)
    this.props.hideModal()
    this.logout()
  }

  render () {
      // The app is rendered before the state has been loaded via redux-persist. This state property allows
      // the main 'app' UI to be hidden until initialised.
      if (!this.props.stateInitialised) {
        return (
          <View style={{flex: 1}}/>
        )
      }
      let bodyComponent
      if (this.props.mainComponent === mainComponent.returningLogin) {
        bodyComponent = <ReturningLogin />
      } else if (this.props.mainComponent === mainComponent.onboarding) {
        bodyComponent = <Onboarding />
      } else if (this.props.mainComponent === mainComponent.tabs) {
        bodyComponent = <Tabs />
      } else {
        throw new Error('Invalid navigation state')
      }

      return (
        <View style={{flex: 1}}>
          <StatusBar animated={true}
              backgroundColor={Colors.transparent}
              translucent={true}
              barStyle={this.props.mainComponent === mainComponent.returningLogin ? 'light-content' : 'dark-content'}/>
          {bodyComponent}
          <LoginOverlay/>
          {(this.props.modalOpen && !this.props.loginFormOpen) && Config.ALLOW_LOGIN && (this.props.userShortDisplay !== this.props.payeeShortDisplay) ? <SendMoney /> : undefined}
          <Login/>
          <StatusMessage/>
          {this.props.coverApp
              && <AppCover unlockOpened={this.props.passToUnlock!=='' && this.state.askToUnlock}/> }
          {this.props.passToUnlock!=='' && this.state.askToUnlock
              && <UnlockAppAlert
                  checkPass={(pass) => this.checkPass(pass)}
                  error={this.state.unlockError}
                  failedAttempts={this.state.failedAttempts}
                  logout={() => this.logoutPress()} />
          }
        </View>
      )
  }

}

const mapDispatchToProps = (dispatch) =>
  bindActionCreators({
    logout,
    hideModal,
    closeConfirmation,
    updatePage,
    resetPayment,
    setCoverApp,
    navigateToTab,
    setOverlayOpen,
    resetForm
  }, dispatch)

const mapStateToProps = (state) => ({
    ...state.navigation,
    userShortDisplay: state.account.details.shortDisplay,
    payeeShortDisplay: state.business.traderScreenBusinessId ? state.business.businessList[state.business.traderScreenBusinessId].fields.username : '',
    loginFormOpen: state.login.loginFormOpen,
    passToUnlock: state.login.passToUnlock
})

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


<commit_before>define(function(require) {
    'use strict';

    var graphic = require('../util/graphic');
    var zrUtil = require('zrender/core/util');
    var echarts = require('../echarts');

    require('../coord/cartesian/Grid');

    require('./axis');

    // Grid view
    echarts.extendComponentView({

        type: 'grid',

        render: function (gridModel, ecModel) {
            this.group.removeAll();
            if (gridModel.get('show')) {
                this.group.add(new graphic.Rect({
                    shape:gridModel.coordinateSystem.getRect(),
                    style: zrUtil.defaults({
                        fill: gridModel.get('backgroundColor')
                    }, gridModel.getItemStyle()),
                    silent: true
                }));
            }
        }
    });

    echarts.registerPreprocessor(function (option) {
        // Only create grid when need
        if (option.xAxis && option.yAxis && !option.grid) {
            option.grid = {};
        }
    });
});<commit_msg>[grid] Fix #4189 (background should be below split line by default)<commit_after>define(function(require) {
    'use strict';

    var graphic = require('../util/graphic');
    var zrUtil = require('zrender/core/util');
    var echarts = require('../echarts');

    require('../coord/cartesian/Grid');

    require('./axis');

    // Grid view
    echarts.extendComponentView({

        type: 'grid',

        render: function (gridModel, ecModel) {
            this.group.removeAll();
            if (gridModel.get('show')) {
                this.group.add(new graphic.Rect({
                    shape:gridModel.coordinateSystem.getRect(),
                    style: zrUtil.defaults({
                        fill: gridModel.get('backgroundColor')
                    }, gridModel.getItemStyle()),
                    silent: true,
                    z2: -1
                }));
            }
        }
    });

    echarts.registerPreprocessor(function (option) {
        // Only create grid when need
        if (option.xAxis && option.yAxis && !option.grid) {
            option.grid = {};
        }
    });
});<|endoftext|>


<commit_before>import React from 'react'

import '../styles.less'

function getCellColor (cell) {
  let density = cell.density
  return [
    (() => {
      switch (density) {
        case 1: return 127
        case 2: /* falls through */
        case 3: return 255
        default: return 0
      }
    })(),
    (() => {
      switch (density) {
        case 1: return 0
        case 2: return 127
        case 3: return 255
        default: return 0
      }
    })(),
    0,
    255
  ]
}

export default class App extends React.Component {

  componentDidMount () {

    setInterval(() => this.props.dispatch({
      type: 'STEP'
    }), 50)

    this.canvas = React.findDOMNode(this.refs.canvas)
    this.ctx = this.canvas.getContext('2d')
    this.componentDidUpdate()
  }

  componentDidUpdate (prevProps = {cam: {}}) {
    let {width, height, grid} = this.props
    let imageData = this.ctx.createImageData(width, height)

    grid.forEach((row, y) => {
      row.forEach((cell, x) => {
        let index = (x + y * imageData.width) * 4
        let [r, g, b, a] = getCellColor(cell)
        imageData.data[index + 0] = r
        imageData.data[index + 1] = g
        imageData.data[index + 2] = b
        imageData.data[index + 3] = a
      })
    })
    this.ctx.putImageData(imageData, 0, 0)
  }

  render () {
    let {width, height} = this.props
    return (
      <canvas {...{width, height}} style={{position: 'absolute', width: '100%', height: '100%'}} ref='canvas' />
    )
  }

}
<commit_msg>use raf<commit_after>import React from 'react'

import '../styles.less'

function getCellColor (cell) {
  let density = cell.density
  return [
    (() => {
      switch (density) {
        case 1: return 127
        case 2: /* falls through */
        case 3: return 255
        default: return 0
      }
    })(),
    (() => {
      switch (density) {
        case 1: return 0
        case 2: return 127
        case 3: return 255
        default: return 0
      }
    })(),
    0,
    255
  ]
}

export default class App extends React.Component {

  componentDidMount () {

    let update = () => {
      this.props.dispatch({
        type: 'STEP'
      })
      requestAnimationFrame(update)
    }
    update()

    // setInterval(update, 500)

    this.canvas = React.findDOMNode(this.refs.canvas)
    this.ctx = this.canvas.getContext('2d')
    this.componentDidUpdate()
  }

  componentDidUpdate (prevProps = {cam: {}}) {
    let {width, height, grid} = this.props
    let imageData = this.ctx.createImageData(width, height)

    grid.forEach((row, y) => {
      row.forEach((cell, x) => {
        let index = (x + y * imageData.width) * 4
        let [r, g, b, a] = getCellColor(cell)
        imageData.data[index + 0] = r
        imageData.data[index + 1] = g
        imageData.data[index + 2] = b
        imageData.data[index + 3] = a
      })
    })
    this.ctx.putImageData(imageData, 0, 0)
  }

  render () {
    let {width, height} = this.props
    return (
      <canvas {...{width, height}} style={{position: 'absolute', width: '100%', height: '100%'}} ref='canvas' />
    )
  }

}
<|endoftext|>


<commit_before>var eecQaPlugin = {};

eecQaPlugin.getControlValue = function(controlId) {
  return $('#' + cw.LayoutManagers.WOGeneral.Controls.get(controlId)).val();
};

eecQaPlugin.callApi = function(service, method, parameters, callback) {
  var url = eecQaPlugin.application + '/services/AMS/' + service + '/' + method;
  var data = { data: JSON.stringify(parameters) };
  if (!(service == 'Authentication' && method == 'Authenticate')) {
    data.token = eecQaPlugin.token['Token'];
  }
  $.post(url, data, function(response) {
    if (response['Status'] == 0) {
      if (typeof callback === 'function') { callback(response); }
    } else if (response['Status'] == 1) {
      alert('Error calling Cityworks API.  ' + response['Message']);
      if (typeof callback === 'function') { callback(); }
    } else if (response['Status'] == 2) {
      //TODO: Re-Authenticate?  Throw unauthorized error?  Does this happen when they just don't have permissions too?
    }
  });
};

eecQaPlugin.tests = {
  tasks: {
    status: true,
    complete: 2,
    total: 4,
    description: 'Tasks Complete',
    update: function() {
      eecQaPlugin.callApi('Tasks', 'ByWorkOrder', {WorkOrderIds: this.workOrderId}, function(data) {
        console.log(data);
      })
    }
  },
  inspections: {

  },
  customFields: {

  },
  requiredFields: {

  },
  labor: {

  },
  equipment: {

  },
  materials: {

  }
};

eecQaPlugin.update = function(tests) {

  if (typeof tests === 'string') { tests = [tests]; }

  var updateTestByName = function(test) {
    eecQaPlugin.tests[test].update();
  };

  if (tests == undefined || tests == null) {
    // Nothing specified.  Update all.
    for (var test in eecQaPlugin.tests) {
      if (eecQaPlugin.tests.hasOwnProperty(test)) {
        updateTestByName(test);
      }
    }
  } else {
    // Tests specified.  Update by name.
    for (var i=0; i<tests.length; i++) {
      if (eecQaPlugin.tests.hasOwnProperty(tests[i])) {
        updateTestByName(tests[i]);
      }
    }
  }
  //TODO: Callback?
};

eecQaPlugin.init = function(params) {

  var authenticate = function(callback) {

    var authWithServer = function(callback) {
      eecQaPlugin.callApi('Authentication', 'Authenticate', eecQaPlugin['credentials'], function(data) {
        eecQaPlugin.token = data['Value'];
        localStorage.setItem('eec-qa-token', eecQaPlugin.token);
        if (typeof callback === 'function') { callback(); }
      });
    };

    var token = localStorage.getItem('eec-qa-token');  //TODO: Check token with Authentication/Validate service
    if (token == null) {
      authWithServer(callback);
    } else if (token['Expires'] < new Date()) {  //TODO: Timezone check
      authWithServer(callback);
    } else {
      eecQaPlugin.token = token;
      if (typeof callback === 'function') { callback(); }
    }
  };

  var build = function() {

    for (var test in eecQaPlugin.tests) {
      if (eecQaPlugin.tests.hasOwnProperty(test)) {
        $(eecQaPlugin.selector)
          .append($('
')
            .append($('')
              .text(eecQaPlugin.tests[test].complete + '/' + eecQaPlugin.tests[test].total)
            )
            .append($('
')
              .text(eecQaPlugin.tests[test].description)
            )
          )
        ;
      }
    }

    eecQaPlugin.update('tasks'); //TODO: Update all
  };

  eecQaPlugin.application = params['application'];
  eecQaPlugin.credentials = params['credentials'];
  eecQaPlugin.selector = params['selector'];
  eecQaPlugin.workOrderId = eecQaPlugin.getControlValue('cboWorkOrderId');

  authenticate(build);

};
<commit_msg>Developing...<commit_after>var eecQaPlugin = {};

eecQaPlugin.getControlValue = function(controlId) {
  return $('#' + cw.LayoutManagers.WOGeneral.Controls.get(controlId)).val();
};

eecQaPlugin.callApi = function(service, method, parameters, callback) {
  var url = eecQaPlugin.application + '/services/AMS/' + service + '/' + method;
  var data = { data: JSON.stringify(parameters) };
  if (!(service == 'Authentication' && method == 'Authenticate')) {
    data.token = eecQaPlugin.token['Token'];
  }
  $.post(url, data, function(response) {
    if (response['Status'] == 0) {
      if (typeof callback === 'function') { callback(response); }
    } else if (response['Status'] == 1) {
      alert('Error calling Cityworks API.  ' + response['Message']);
      if (typeof callback === 'function') { callback(); }
    } else if (response['Status'] == 2) {
      //TODO: Re-Authenticate?  Throw unauthorized error?  Does this happen when they just don't have permissions too?
    }
  });
};

eecQaPlugin.tests = {
  tasks: {
    status: true,
    complete: 2,
    total: 4,
    description: 'Tasks Complete',
    update: function() {
      eecQaPlugin.callApi('Tasks', 'ByWorkOrder', {WorkOrderIds: eecQaPlugin.workOrderId}, function(data) {
        console.log(data);
      })
    }
  },
  inspections: {

  },
  customFields: {

  },
  requiredFields: {

  },
  labor: {

  },
  equipment: {

  },
  materials: {

  }
};

eecQaPlugin.update = function(tests) {

  if (typeof tests === 'string') { tests = [tests]; }

  var updateTestByName = function(test) {
    eecQaPlugin.tests[test].update();
  };

  if (tests == undefined || tests == null) {
    // Nothing specified.  Update all.
    for (var test in eecQaPlugin.tests) {
      if (eecQaPlugin.tests.hasOwnProperty(test)) {
        updateTestByName(test);
      }
    }
  } else {
    // Tests specified.  Update by name.
    for (var i=0; i<tests.length; i++) {
      if (eecQaPlugin.tests.hasOwnProperty(tests[i])) {
        updateTestByName(tests[i]);
      }
    }
  }
  //TODO: Callback?
};

eecQaPlugin.init = function(params) {

  var authenticate = function(callback) {

    var authWithServer = function(callback) {
      eecQaPlugin.callApi('Authentication', 'Authenticate', eecQaPlugin['credentials'], function(data) {
        eecQaPlugin.token = data['Value'];
        localStorage.setItem('eec-qa-token', eecQaPlugin.token);
        if (typeof callback === 'function') { callback(); }
      });
    };

    var token = localStorage.getItem('eec-qa-token');  //TODO: Check token with Authentication/Validate service
    if (token == null) {
      authWithServer(callback);
    } else if (token['Expires'] < new Date()) {  //TODO: Timezone check
      authWithServer(callback);
    } else {
      eecQaPlugin.token = token;
      if (typeof callback === 'function') { callback(); }
    }
  };

  var build = function() {

    for (var test in eecQaPlugin.tests) {
      if (eecQaPlugin.tests.hasOwnProperty(test)) {
        $(eecQaPlugin.selector)
          .append($('
')
            .append($('')
              .text(eecQaPlugin.tests[test].complete + '/' + eecQaPlugin.tests[test].total)
            )
            .append($('
')
              .text(eecQaPlugin.tests[test].description)
            )
          )
        ;
      }
    }

    eecQaPlugin.update('tasks'); //TODO: Update all
  };

  eecQaPlugin.application = params['application'];
  eecQaPlugin.credentials = params['credentials'];
  eecQaPlugin.selector = params['selector'];
  eecQaPlugin.workOrderId = eecQaPlugin.getControlValue('cboWorkOrderId');

  authenticate(build);

};
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import Search from './Search'
import List from './List'

import '../styles/App.sass'

const DEFAULT_QUERY = 'ruby'

const PATH_BASE = 'https://hn.algolia.com/api/v1'
const PATH_SEARCH = '/search'
const PARAM_SEARCH = 'query='

class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      result: null,
      searchTerm: DEFAULT_QUERY
    }

    this.setSearchTopStories = this.setSearchTopStories.bind(this)
    this.fetchSearchTopStories = this.fetchSearchTopStories.bind(this)
    this.onDismiss = this.onDismiss.bind(this)
    this.onSearchChange = this.onSearchChange.bind(this)
  }

  setSearchTopStories(result) {
    this.setState({ result })
  }

  fetchSearchTopStories(searchTerm) {
    fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}`)
      .then(response => response.json())
      .then(result => this.setSearchTopStories(result))
  }

  componentDidMount() {
    const { searchTerm } = this.state
    this.fetchSearchTopStories(searchTerm)
  }

  onDismiss(id) {
    const isNotId = item => item.objectID !== id
    const updatedList = this.state.list.filter(isNotId)
    this.setState({ list: updatedList })
  }

  onSearchChange(event) {
    this.setState({ searchTerm: event.target.value })
  }

  render() {
    const { searchTerm, result } = this.state

    if (!result) return null

    return (

              <Search
                value={searchTerm}
                onChange={this.onSearchChange}
                className="search"
              >
                Search
              </Search>

          <List
            list={result.hits}
            pattern={searchTerm}
            onDismiss={this.onDismiss}
          />

    )
  }
}

export default App
<commit_msg>dismiss button<commit_after>import React, { Component } from 'react'
import Search from './Search'
import List from './List'

import '../styles/App.sass'

const DEFAULT_QUERY = 'ruby'

const PATH_BASE = 'https://hn.algolia.com/api/v1'
const PATH_SEARCH = '/search'
const PARAM_SEARCH = 'query='

class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      result: null,
      searchTerm: DEFAULT_QUERY
    }

    this.setSearchTopStories = this.setSearchTopStories.bind(this)
    this.fetchSearchTopStories = this.fetchSearchTopStories.bind(this)
    this.onDismiss = this.onDismiss.bind(this)
    this.onSearchChange = this.onSearchChange.bind(this)
  }

  setSearchTopStories(result) {
    this.setState({ result })
  }

  fetchSearchTopStories(searchTerm) {
    fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}`)
      .then(response => response.json())
      .then(result => this.setSearchTopStories(result))
  }

  componentDidMount() {
    const { searchTerm } = this.state
    this.fetchSearchTopStories(searchTerm)
  }

  onDismiss(id) {
    const isNotId = item => item.objectID !== id
    const updateHits = this.state.result.hits.filter(isNotId)
    this.setState({
      result: { ...this.state.result, hits: updateHits }
    })
  }

  onSearchChange(event) {
    this.setState({ searchTerm: event.target.value })
  }

  render() {
    const { searchTerm, result } = this.state

    if (!result) return null

    return (

              <Search
                value={searchTerm}
                onChange={this.onSearchChange}
                className="search"
              >
                Search
              </Search>

          <List
            list={result.hits}
            pattern={searchTerm}
            onDismiss={this.onDismiss}
          />

    )
  }
}

export default App
<|endoftext|>