<commit_before>/**
 * @file TriggerPop component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component, Fragment, createRef} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import eventsOn from 'dom-helpers/events/on';
import eventsOff from 'dom-helpers/events/off';

import Pop from '../_Pop';
import Paper from '../Paper';
import Theme from '../Theme';

import Position from '../_statics/Position';

import Util from '../_vendors/Util';
import TriggerPopCalculation from '../_vendors/TriggerPopCalculation';
import Dom from '../_vendors/Dom';

class TriggerPop extends Component {

    static Position = Position;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        // closest scrollable element of trigger
        this.scrollEl = null;

        this.pop = createRef();

    }

    /**
     * public
     */
    getEl = () => {
        return this.pop && this.pop.current && this.pop.current.getEl();
    };

    /**
     * reset pop position
     * @param transitionEl
     */
    resetPosition = (transitionEl = this.getEl()) => {

        const {parentEl, triggerEl, position} = this.props;

        TriggerPopCalculation.setStyle(parentEl, triggerEl, transitionEl, this.getScrollEl(), position);

    };

    /**
     * find the closest scrollable parent element
     * @param triggerEl
     * @returns {*}
     */
    getScrollEl = (triggerEl = this.props.triggerEl) => {

        if (this.props.scrollEl) {
            return this.props.scrollEl;
        }

        if (this.scrollEl) {
            return this.scrollEl;
        }

        const scrollEl = Dom.getClosestScrollable(triggerEl);
        return this.scrollEl = scrollEl || document.body;

    };

    /**
     * scroll event callback
     */
    handleScroll = () => {
        this.resetPosition();
    };

    /**
     * add scroll event
     * @param scrollEl
     */
    addWatchScroll = (scrollEl = this.getScrollEl()) => {

        const {triggerEl} = this.props;

        if (!triggerEl) {
            return;
        }

        scrollEl && eventsOn(scrollEl, 'scroll', this.handleScroll);

    };

    /**
     * remove scroll event
     * @param scrollEl
     */
    removeWatchScroll = (scrollEl = this.getScrollEl()) => {
        scrollEl && eventsOff(scrollEl, 'scroll', this.handleScroll);
    };

    componentDidUpdate(prevProps) {
        if (this.props.shouldFollowScroll && !prevProps.visible && this.props.visible) {
            this.addWatchScroll();
        } else if (prevProps.shouldFollowScroll && prevProps.visible && !this.props.visible) {
            this.removeWatchScroll();
        }
    }

    render() {

        const {

            children,

            className, contentClassName, theme,
            hasTriangle, triangle, position, isAnimated,

            // not passing down these props
            isEscClose, isBlurClose, shouldFollowScroll,

            ...restProps

        } = this.props;

        return (
            <Pop {...restProps}
                 ref={this.pop}
                 className={classNames('trigger-pop', {
                     'trigger-pop-has-triangle': hasTriangle,
                     [`theme-${theme}`]: theme,
                     [`trigger-pop-${position}`]: position,
                     'trigger-pop-animated': isAnimated,
                     [className]: className
                 })}
                 container={<Paper></Paper>}
                 isAnimated={isAnimated}
                 resetPosition={this.resetPosition}>
                {
                    popEl => (
                        <Fragment>

                            {
                                hasTriangle ?

                                        {triangle}

                                    :
                                    null
                            }

                                {typeof children === 'function' ? children(popEl) : children}

                        </Fragment>
                    )
                }
            </Pop>
        );

    }

}

TriggerPop.propTypes = {

    children: PropTypes.any,

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * The CSS class name of the content element.
     */
    contentClassName: PropTypes.string,

    modalClassName: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The trigger pop theme.Can be primary,highlight,success,warning,error.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    parentEl: PropTypes.object,

    /**
     * This is the DOM element that will be used to set the position of the trigger pop.
     */
    triggerEl: PropTypes.object,

    /**
     * If true,the trigger pop is visible.
     */
    visible: PropTypes.bool,

    /**
     * If true,the trigger pop will have a triangle on the top of the DOM element.
     */
    hasTriangle: PropTypes.bool,

    triangle: PropTypes.element,

    showModal: PropTypes.bool,

    /**
     * The trigger pop alignment.The value can be Popup.Position.LEFT or Popup.Position.RIGHT.
     */
    position: PropTypes.oneOf(Util.enumerateValue(Position)),

    /**
     * If true,popup will have animation effects.
     */
    isAnimated: PropTypes.bool,

    /**
     * The depth of Paper component.
     */
    depth: PropTypes.number,

    isBlurClose: PropTypes.bool,
    isEscClose: PropTypes.bool,

    shouldFollowScroll: PropTypes.bool,
    scrollEl: PropTypes.object,

    resetPositionWait: PropTypes.number,

    /**
     * The function of popup render.
     */
    onRender: PropTypes.func,

    /**
     * The function of popup rendered.
     */
    onRendered: PropTypes.func,

    /**
     * The function of popup destroy.
     */
    onDestroy: PropTypes.func,

    /**
     * The function of popup destroyed.
     */
    onDestroyed: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func

};

TriggerPop.defaultProps = {

    theme: Theme.DEFAULT,
    parentEl: document.body,
    depth: 3,

    visible: false,
    hasTriangle: true,
    triangle:

,
    showModal: false,
    position: Position.BOTTOM,
    isAnimated: true,

    isBlurClose: true,
    isEscClose: true,
    shouldFollowScroll: false,
    resetPositionWait: 250

};

export default TriggerPop;
<commit_msg>update TriggerPop<commit_after>/**
 * @file TriggerPop component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component, Fragment, createRef} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import eventsOn from 'dom-helpers/events/on';
import eventsOff from 'dom-helpers/events/off';

import Pop from '../_Pop';
import Paper from '../Paper';
import Theme from '../Theme';

import Position from '../_statics/Position';

import Util from '../_vendors/Util';
import TriggerPopCalculation from '../_vendors/TriggerPopCalculation';
import Dom from '../_vendors/Dom';

class TriggerPop extends Component {

    static Position = Position;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        // closest scrollable element of trigger
        this.scrollEl = null;

        this.pop = createRef();

    }

    /**
     * public
     */
    getEl = () => {
        return this.pop && this.pop.current && this.pop.current.getEl();
    };

    /**
     * reset pop position
     * @param transitionEl
     */
    resetPosition = (transitionEl = this.getEl()) => {

        const {parentEl, triggerEl, position} = this.props;

        TriggerPopCalculation.setStyle(parentEl, triggerEl, transitionEl, this.getScrollEl(), position);

    };

    /**
     * find the closest scrollable parent element
     * @param triggerEl
     * @returns {*}
     */
    getScrollEl = (triggerEl = this.props.triggerEl) => {

        if (this.props.scrollEl) {
            return this.props.scrollEl;
        }

        if (this.scrollEl) {
            return this.scrollEl;
        }

        const scrollEl = Dom.getClosestScrollable(triggerEl);
        return this.scrollEl = scrollEl || document.body;

    };

    /**
     * scroll event callback
     */
    handleScroll = () => {
        this.resetPosition();
    };

    /**
     * add scroll event
     * @param scrollEl
     */
    addWatchScroll = (scrollEl = this.getScrollEl()) => {

        const {triggerEl} = this.props;

        if (!triggerEl) {
            return;
        }

        scrollEl && eventsOn(scrollEl, 'scroll', this.handleScroll);

    };

    /**
     * remove scroll event
     * @param scrollEl
     */
    removeWatchScroll = (scrollEl = this.getScrollEl()) => {
        scrollEl && eventsOff(scrollEl, 'scroll', this.handleScroll);
    };

    componentDidUpdate(prevProps) {
        if (this.props.shouldFollowScroll && !prevProps.visible && this.props.visible) {
            this.addWatchScroll();
        } else if (prevProps.shouldFollowScroll && prevProps.visible && !this.props.visible) {
            this.removeWatchScroll();
        }
    }

    render() {

        const {

            children,

            className, contentClassName, theme,
            hasTriangle, triangle, position, isAnimated,

            // not passing down these props
            isEscClose, isBlurClose, shouldFollowScroll,

            ...restProps

        } = this.props;

        return (
            <Pop {...restProps}
                 ref={this.pop}
                 className={classNames('trigger-pop', {
                     'trigger-pop-has-triangle': hasTriangle,
                     [`theme-${theme}`]: theme,
                     [`trigger-pop-${position}`]: position,
                     'trigger-pop-animated': isAnimated,
                     [className]: className
                 })}
                 container={<Paper></Paper>}
                 isAnimated={isAnimated}
                 resetPosition={this.resetPosition}>
                {
                    popEl => (
                        <Fragment>

                            {
                                hasTriangle ?

                                        {triangle}

                                    :
                                    null
                            }

                                {typeof children === 'function' ? children(popEl) : children}

                        </Fragment>
                    )
                }
            </Pop>
        );

    }

}

TriggerPop.propTypes = {

    children: PropTypes.any,

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * The CSS class name of the content element.
     */
    contentClassName: PropTypes.string,

    modalClassName: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The trigger pop theme.Can be primary,highlight,success,warning,error.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    parentEl: PropTypes.object,

    /**
     * This is the DOM element that will be used to set the position of the trigger pop.
     */
    triggerEl: PropTypes.object,

    /**
     * If true,the trigger pop is visible.
     */
    visible: PropTypes.bool,

    /**
     * If true,the trigger pop will have a triangle on the top of the DOM element.
     */
    hasTriangle: PropTypes.bool,

    triangle: PropTypes.element,

    showModal: PropTypes.bool,

    /**
     * The trigger pop alignment.The value can be Popup.Position.LEFT or Popup.Position.RIGHT.
     */
    position: PropTypes.oneOf(Util.enumerateValue(Position)),

    /**
     * If true,popup will have animation effects.
     */
    isAnimated: PropTypes.bool,

    /**
     * The depth of Paper component.
     */
    depth: PropTypes.number,

    isBlurClose: PropTypes.bool,
    isEscClose: PropTypes.bool,

    shouldFollowScroll: PropTypes.bool,
    scrollEl: PropTypes.object,

    resetPositionWait: PropTypes.number,

    /**
     * The function of popup render.
     */
    onRender: PropTypes.func,

    /**
     * The function of popup rendered.
     */
    onRendered: PropTypes.func,

    /**
     * The function of popup destroy.
     */
    onDestroy: PropTypes.func,

    /**
     * The function of popup destroyed.
     */
    onDestroyed: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func

};

TriggerPop.defaultProps = {

    theme: Theme.DEFAULT,
    parentEl: document.body,
    depth: 2,

    visible: false,
    hasTriangle: true,
    triangle:

,
    showModal: false,
    position: Position.BOTTOM,
    isAnimated: true,

    isBlurClose: true,
    isEscClose: true,
    shouldFollowScroll: false,
    resetPositionWait: 250

};

export default TriggerPop;
<|endoftext|>


<commit_before>import 'isomorphic-fetch';

import { OpenSearchDescription } from './description';
import { OpenSearchPaginator } from './paginator';
import { search, createBaseRequest } from './search';
import { getSupportedTypes } from './formats/';
import { fetchAndCheck, createXHR } from './utils';
import { config } from './config';

/**
 * Class to perform searches.
 */
export class OpenSearchService {
  /**
   * Create an OpenSearchDescription object
   * @param {OpenSearchDescription} descriptionDocument The parsed description document
   */
  constructor(descriptionDocument) {
    this.descriptionDocument = descriptionDocument;
  }

  /**
   * Get the underlying {@link OpenSearchDescription} object.
   * @returns {OpenSearchDescription}
   */
  getDescription() {
    return this.descriptionDocument;
  }

  /**
   * Get the URL for the given parameters.
   * @param {object} parameters An object mapping the name or type to the value
   * @param {string} [type=null] The preferred transfer type.
   * @param {string} [method=null] The preferred HTTP method type.
   * @returns {OpenSearchUrl} The resulting URL objec.
   */
  getUrl(parameters, type, method) {
    const url = this.descriptionDocument.getUrl(parameters, type, method);
    if (!url) {
      // try to give better feedback, when just one URL is possible
      const alternativeUrls = this.descriptionDocument.getUrls(null, type, method);
      if (alternativeUrls.length === 1) {
        const missingParamNames = alternativeUrls[0]
          .getMissingMandatoryParameters(parameters)
          .map(p => `"${p.type}"`);
        const unsupportedParameterKeys = alternativeUrls[0]
          .getUnsupportedParameterKeys(parameters)
          .map(k => `"${k}"`);

        const terms = [];
        if (missingParamNames.length) {
          terms.push(`missing parameters: ${missingParamNames.join(', ')}`);
        }
        if (unsupportedParameterKeys) {
          terms.push(`unsupported parameters keys: ${unsupportedParameterKeys.join(', ')}`);
        }
        throw new Error(`No matching URL found, ${terms.join(' and ')}`);
      }

      // standard error, when multiple/no URLs with that type/method are specified
      throw new Error(`No URL found for type '${type}' and the given parameters.`);
    }
    return url;
  }

  /**
   * Returns a base request object for the given parameters. This allows to
   * inspect the request values before sending them to the server.
   * @param {object} parameters An object mapping the name or type to the value
   * @param {string} [type=null] The preferred transfer type.
   * @param {string} [method=null] The preferred HTTP method type.
   * @returns {object} The search request
   */
  createSearchRequest(parameters, type = null, method = null) {
    let url = null;
    if (!type) {
      // try to find a suitable URL
      const supportedTypes = getSupportedTypes();
      for (let i = 0; i < supportedTypes.length; ++i) {
        url = this.descriptionDocument.getUrl(parameters, supportedTypes[i], method);
        if (url && url.isCompatible(parameters)) {
          break;
        }
      }
      if (!url) {
        throw new Error('No compatible URL found.');
      }
    } else {
      url = this.getUrl(parameters, type, method);
    }

    return createBaseRequest(url, parameters);
  }

  /**
   * Checks whether this URL is compatible with the given parameters
   * @param {object} parameters An object mapping the name or type to the value
   * @param {string} [type=null] The preferred transfer type.
   * @param {string} [method=null] The preferred HTTP method type.
   * @param {boolean} [raw=false] Whether the response shall be parsed or returned raw.
   * @param {number} [maxUrlLength=undefined] The maximum URL length. URLs longer than that
                                              will result in errors.
   * @returns {Promise<array>|Promise<Response>} The search result as a Promise
   */
  search(parameters, type = null, method = null, raw = false, maxUrlLength = undefined) {
    let url = null;
    if (!type) {
      // try to find a suitable URL
      const supportedTypes = getSupportedTypes();
      for (let i = 0; i < supportedTypes.length; ++i) {
        url = this.descriptionDocument.getUrl(parameters, supportedTypes[i], method);
        if (url && url.isCompatible(parameters)) {
          break;
        }
      }
      if (!url) {
        throw new Error('No compatible URL found.');
      }
    } else {
      url = this.getUrl(parameters, type, method);
    }

    return search(url, parameters, type, raw, maxUrlLength);
  }

  /**
   * Gets the suggestions for the current search parameters.
   * @param {object} parameters An object mapping the name or type to the value
   * @param {string} [method=null] The preferred HTTP method type.
   * @param {number} [maxUrlLength=undefined] The maximum URL length. URLs longer than that
   *                                          will result in errors.
   * @returns {Promise<Suggestion[]>} The fetched suggestions.
   */
  getSuggestions(parameters, method = null, maxUrlLength = undefined) {
    const type = 'application/x-suggestions+json';
    let url;
    try {
      url = this.getUrl(parameters, type, method);
    } catch (error) {
      return Promise.reject(new Error('No suggestion URL found.'));
    }
    return search(url, parameters, type, false, maxUrlLength);
  }

  /**
   * Creates a new Paginator object to enable a simpler search result handling
   * for multi-page results.
   * @param {object} parameters An object mapping the name or type to the value
   * @param {string} [type=null] The preferred transfer type.
   * @param {string} [method=null] The preferred HTTP method type.
   * @param {object} [options] Additional options for the paginator
   * @returns {OpenSearchPaginator} The created Paginator object.
   */
  getPaginator(parameters, type = null, method = null, options = undefined) {
    return new OpenSearchPaginator(
      this.getUrl(parameters, type, method), parameters, options
    );
  }

  /**
   * Accesses an OpenSearch service and discovers it.
   * @param {object} url The URL to find the OpenSearchDescription XML document
   * @returns {Promise<OpenSearchService>} The {@link OpenSearchService} as a Promise
   */
  static discover(url) {
    const { useXHR } = config();
    if (useXHR) {
      return new Promise((resolve, reject, onCancel) => {
        const xhr = createXHR(url);
        xhr.onload = () => {
          try {
            resolve(OpenSearchService.fromXml(xhr.responseText));
          } catch (error) {
            reject(error);
          }
        };
        xhr.onerror = () => {
          reject(new TypeError('Failed to fetch'));
        };
        if (onCancel && typeof onCancel === 'function') {
          onCancel(() => {
            xhr.abort();
          });
        }
      });
    }
    return fetchAndCheck(url)
      .then(response => response.text())
      .then(response => OpenSearchService.fromXml(response));
  }

  /**
   * Create a new {@link OpenSearchService} from an OSDD XML string.
   * @param {string} xml The XML string to parse the description from
   * @returns {OpenSearchService} The created service object
   */
  static fromXml(xml) {
    return new OpenSearchService(OpenSearchDescription.fromXml(xml));
  }

  /**
   * Serialize the service to a simple object.
   * @returns {object} The serialized service description
   */
  serialize() {
    return {
      description: this.descriptionDocument.serialize(),
    };
  }

  /**
   * Deserialize an OpenSearch description from an object.
   * @param {object} values The serialized service description
   * @returns {OpenSearchService} The deserialized service
   */
  static deserialize(values) {
    return new OpenSearchService(
      OpenSearchDescription.deserialize(values.description)
    );
  }
}
<commit_msg>Fixing error message when parameters are missing/unsupported<commit_after>import 'isomorphic-fetch';

import { OpenSearchDescription } from './description';
import { OpenSearchPaginator } from './paginator';
import { search, createBaseRequest } from './search';
import { getSupportedTypes } from './formats/';
import { fetchAndCheck, createXHR } from './utils';
import { config } from './config';

/**
 * Class to perform searches.
 */
export class OpenSearchService {
  /**
   * Create an OpenSearchDescription object
   * @param {OpenSearchDescription} descriptionDocument The parsed description document
   */
  constructor(descriptionDocument) {
    this.descriptionDocument = descriptionDocument;
  }

  /**
   * Get the underlying {@link OpenSearchDescription} object.
   * @returns {OpenSearchDescription}
   */
  getDescription() {
    return this.descriptionDocument;
  }

  /**
   * Get the URL for the given parameters.
   * @param {object} parameters An object mapping the name or type to the value
   * @param {string} [type=null] The preferred transfer type.
   * @param {string} [method=null] The preferred HTTP method type.
   * @returns {OpenSearchUrl} The resulting URL objec.
   */
  getUrl(parameters, type, method) {
    const url = this.descriptionDocument.getUrl(parameters, type, method);
    if (!url) {
      // try to give better feedback, when just one URL is possible
      const alternativeUrls = this.descriptionDocument.getUrls(null, type, method);
      if (alternativeUrls.length === 1) {
        const missingParamNames = alternativeUrls[0]
          .getMissingMandatoryParameters(parameters)
          .map(p => `"${p.type}"`);
        const unsupportedParameterKeys = alternativeUrls[0]
          .getUnsupportedParameterKeys(parameters)
          .map(k => `"${k}"`);

        const terms = [];
        if (missingParamNames.length) {
          terms.push(`missing parameters: ${missingParamNames.join(', ')}`);
        }
        if (unsupportedParameterKeys.length) {
          terms.push(`unsupported parameters keys: ${unsupportedParameterKeys.join(', ')}`);
        }
        throw new Error(`No matching URL found, ${terms.join(' and ')}`);
      }

      // standard error, when multiple/no URLs with that type/method are specified
      throw new Error(`No URL found for type '${type}' and the given parameters.`);
    }
    return url;
  }

  /**
   * Returns a base request object for the given parameters. This allows to
   * inspect the request values before sending them to the server.
   * @param {object} parameters An object mapping the name or type to the value
   * @param {string} [type=null] The preferred transfer type.
   * @param {string} [method=null] The preferred HTTP method type.
   * @returns {object} The search request
   */
  createSearchRequest(parameters, type = null, method = null) {
    let url = null;
    if (!type) {
      // try to find a suitable URL
      const supportedTypes = getSupportedTypes();
      for (let i = 0; i < supportedTypes.length; ++i) {
        url = this.descriptionDocument.getUrl(parameters, supportedTypes[i], method);
        if (url && url.isCompatible(parameters)) {
          break;
        }
      }
      if (!url) {
        throw new Error('No compatible URL found.');
      }
    } else {
      url = this.getUrl(parameters, type, method);
    }

    return createBaseRequest(url, parameters);
  }

  /**
   * Checks whether this URL is compatible with the given parameters
   * @param {object} parameters An object mapping the name or type to the value
   * @param {string} [type=null] The preferred transfer type.
   * @param {string} [method=null] The preferred HTTP method type.
   * @param {boolean} [raw=false] Whether the response shall be parsed or returned raw.
   * @param {number} [maxUrlLength=undefined] The maximum URL length. URLs longer than that
                                              will result in errors.
   * @returns {Promise<array>|Promise<Response>} The search result as a Promise
   */
  search(parameters, type = null, method = null, raw = false, maxUrlLength = undefined) {
    let url = null;
    if (!type) {
      // try to find a suitable URL
      const supportedTypes = getSupportedTypes();
      for (let i = 0; i < supportedTypes.length; ++i) {
        url = this.descriptionDocument.getUrl(parameters, supportedTypes[i], method);
        if (url && url.isCompatible(parameters)) {
          break;
        }
      }
      if (!url) {
        throw new Error('No compatible URL found.');
      }
    } else {
      url = this.getUrl(parameters, type, method);
    }

    return search(url, parameters, type, raw, maxUrlLength);
  }

  /**
   * Gets the suggestions for the current search parameters.
   * @param {object} parameters An object mapping the name or type to the value
   * @param {string} [method=null] The preferred HTTP method type.
   * @param {number} [maxUrlLength=undefined] The maximum URL length. URLs longer than that
   *                                          will result in errors.
   * @returns {Promise<Suggestion[]>} The fetched suggestions.
   */
  getSuggestions(parameters, method = null, maxUrlLength = undefined) {
    const type = 'application/x-suggestions+json';
    let url;
    try {
      url = this.getUrl(parameters, type, method);
    } catch (error) {
      return Promise.reject(new Error('No suggestion URL found.'));
    }
    return search(url, parameters, type, false, maxUrlLength);
  }

  /**
   * Creates a new Paginator object to enable a simpler search result handling
   * for multi-page results.
   * @param {object} parameters An object mapping the name or type to the value
   * @param {string} [type=null] The preferred transfer type.
   * @param {string} [method=null] The preferred HTTP method type.
   * @param {object} [options] Additional options for the paginator
   * @returns {OpenSearchPaginator} The created Paginator object.
   */
  getPaginator(parameters, type = null, method = null, options = undefined) {
    return new OpenSearchPaginator(
      this.getUrl(parameters, type, method), parameters, options
    );
  }

  /**
   * Accesses an OpenSearch service and discovers it.
   * @param {object} url The URL to find the OpenSearchDescription XML document
   * @returns {Promise<OpenSearchService>} The {@link OpenSearchService} as a Promise
   */
  static discover(url) {
    const { useXHR } = config();
    if (useXHR) {
      return new Promise((resolve, reject, onCancel) => {
        const xhr = createXHR(url);
        xhr.onload = () => {
          try {
            resolve(OpenSearchService.fromXml(xhr.responseText));
          } catch (error) {
            reject(error);
          }
        };
        xhr.onerror = () => {
          reject(new TypeError('Failed to fetch'));
        };
        if (onCancel && typeof onCancel === 'function') {
          onCancel(() => {
            xhr.abort();
          });
        }
      });
    }
    return fetchAndCheck(url)
      .then(response => response.text())
      .then(response => OpenSearchService.fromXml(response));
  }

  /**
   * Create a new {@link OpenSearchService} from an OSDD XML string.
   * @param {string} xml The XML string to parse the description from
   * @returns {OpenSearchService} The created service object
   */
  static fromXml(xml) {
    return new OpenSearchService(OpenSearchDescription.fromXml(xml));
  }

  /**
   * Serialize the service to a simple object.
   * @returns {object} The serialized service description
   */
  serialize() {
    return {
      description: this.descriptionDocument.serialize(),
    };
  }

  /**
   * Deserialize an OpenSearch description from an object.
   * @param {object} values The serialized service description
   * @returns {OpenSearchService} The deserialized service
   */
  static deserialize(values) {
    return new OpenSearchService(
      OpenSearchDescription.deserialize(values.description)
    );
  }
}
<|endoftext|>


<commit_before>function vsaverMain() {
    document.querySelector('.playScreen').click()

    let interval = null;
    let findVideo = () => {
        let video = document.querySelector('video');
        if (video && video.src && video.src.indexOf('http') === 0) {
            let title = window.title
            let titleElement = document.querySelector('.playScreen .title')
            if (titleElement && titleElement.innerText) {
                title = titleElement.innerText
            }
            vsaverCompletion(video.src, title)
            clearInterval(interval)
        }
    }
    interval = setInterval(findVideo, 500)
    findVideo()
}
<commit_msg>Updated script for Ustream<commit_after>function vsaverMain() {
    const playButton = document.querySelector('.playScreen') || document.querySelector('#play')
    if (playButton) {
        playButton.click()
    }

    let interval = null;
    let findVideo = () => {
        let video = document.querySelector('video');
        if (video && video.src && video.src.indexOf('http') === 0) {
            let title = window.title
            let titleElement = document.querySelector('.title')
            if (titleElement && titleElement.innerText) {
                title = titleElement.innerText
            }
            if (vsaverCompletion) {
                vsaverCompletion(video.src, title)
                clearInterval(interval)
            }
        }
    }
    interval = setInterval(findVideo, 500)
    findVideo()
}
<|endoftext|>


<commit_before>'use strict';

const ChainJobQueue = require('./chain-job-queue'),
  path = require('path'),
  bluebird = require('bluebird'),
  winston = require('winston'),
  fs = bluebird.promisifyAll(require('fs')),
  redis = require('redis'),
  clientRedis = redis.createClient(),
  cluster = require('cluster'),
  numberFork = require('os').cpus().length / 2;

const logger = new (winston.Logger)({
  transports: [
    new (winston.transports.File)({
      filename: 'logs/sisyphe.log',
      level: 'info'
    })
  ]
});
bluebird.promisifyAll(redis.RedisClient.prototype);
bluebird.promisifyAll(redis.Multi.prototype);

class Sisyphe {
  constructor(starter, workers) {
    const defaultStarter = {
      module: "walker-fs",
      options: {
        path: path.resolve(__dirname + "/../test/dataset")
      }
    };
    this.starter = starter || defaultStarter;
    const defaultWorkers = [{
      name: "Alpha",
      module: "alpha-worker"
    }, {
      name: "Beta",
      module: "beta-worker"
    }];
    this.workers = workers || defaultWorkers;
  }

  startToGenerateTask() {
    this.starterModule.start();
  }

  start() {
    this.initializeWorker().then(() => {
      if (cluster.isMaster) {
        for (var i = 0; i < numberFork; i++) {
          const fork = cluster.fork();
          fork.on('online', () => {
            logger.info('fork created');
          });
          fork.on('exit', () => {
            logger.info('fork exit');
          });
        }
        this.initializeStarter().then(() => this.startToGenerateTask());
      }
      this.activateWorker();
    });
  }

  initializeWorker() {
    const workerDirectory = path.resolve(__dirname + "/../worker");
    this.workflow = new ChainJobQueue();

    return bluebird.map(this.workers, (worker) => {
      return fs.accessAsync(workerDirectory + "/" + worker.module)
    }).then(() => {
      return this.workers.map((worker) => {
        const pathToWorkerModule = workerDirectory + "/" + worker.module;
        const workerModule = require(pathToWorkerModule);
        const packageWorkerModule = require(pathToWorkerModule + '/package.json');
        return {
          name: packageWorkerModule.name,
          doTheJob: workerModule.doTheJob,
          finalJob: workerModule.finalJob
        };
      })
    }).then((arrayWorkerModule) => {
      arrayWorkerModule.map((workerModule) => {
        this.workflow.addWorker(workerModule.name, workerModule.doTheJob, workerModule.finalJob);
      });
      this.workflow.createQueueForWorkers();
      return this;
    });
  }

  activateWorker() {
    const callFinishers = () => {
      return bluebird.filter(this.workflow.listWorker, (worker) => {
        return worker.finalFunction !== undefined
      }).map((worker) => {
        return bluebird.promisify(worker.finalFunction)();
      })
    };

    // Heartbeat
    if (cluster.isMaster) {
      setInterval(function () {
        clientRedis.hgetallAsync('sisyphe').then((values) => {
          const totalJobs = +[values.totalPerformedTask] + +[values.totalFailedTask];
          if (+values.totalGeneratedTask && totalJobs >= +values.totalGeneratedTask) {
            logger.info("Total jobs created = " + values.totalGeneratedTask);
            logger.info("Total jobs completed = " + values.totalPerformedTask);
            logger.info("Total jobs failed = " + values.totalFailedTask);
            logger.info("Total jobs = " + totalJobs);
            logger.info('release finishers !');
            callFinishers().then(() => {
              logger.info('All finalJob executed !');
              clientRedis.del('sisyphe');
              clearInterval(this);
              console.log('');
              console.log('This is the end !');
            }).catch((error) => {
              // TODO : rajouter une gestion des erreur pour les logs
              logger.info(error);
            });
          }
        });
      }, 1000);
    }

    this.workflow.addJobProcessToWorkers();
    return this;
  }

  initializeStarter() {
    const starterDirectory = path.resolve(__dirname + "/../starter");
    const pathToStarterModule = starterDirectory + "/" + this.starter.module;
    return fs.accessAsync(pathToStarterModule).then(() => {
      const StarterModule = require(starterDirectory + "/" + this.starter.module);
      this.starterModule = new StarterModule(this.starter.options);

      this.starterModule.setFunctionEventOnData((data) => {
        this.starterModule.totalFile++;
        this.workflow.addTask(data);
      });

      this.starterModule.setFunctionEventOnEnd(() => {
        this.workflow.totalGeneratedTask = this.starterModule.totalFile;
        clientRedis.hset('sisyphe', 'totalGeneratedTask', this.starterModule.totalFile);
        logger.info('Total jobs generated by starter module = ' + this.starterModule.totalFile);
      });

      return this;
    });
  }
}

module.exports = Sisyphe;<commit_msg>Kill earlier the heartbeat<commit_after>'use strict';

const ChainJobQueue = require('./chain-job-queue'),
  path = require('path'),
  bluebird = require('bluebird'),
  winston = require('winston'),
  fs = bluebird.promisifyAll(require('fs')),
  redis = require('redis'),
  clientRedis = redis.createClient(),
  cluster = require('cluster'),
  numberFork = require('os').cpus().length / 2;

const logger = new (winston.Logger)({
  transports: [
    new (winston.transports.File)({
      filename: 'logs/sisyphe.log',
      level: 'info'
    })
  ]
});
bluebird.promisifyAll(redis.RedisClient.prototype);
bluebird.promisifyAll(redis.Multi.prototype);

class Sisyphe {
  constructor(starter, workers) {
    const defaultStarter = {
      module: "walker-fs",
      options: {
        path: path.resolve(__dirname + "/../test/dataset")
      }
    };
    this.starter = starter || defaultStarter;
    const defaultWorkers = [{
      name: "Alpha",
      module: "alpha-worker"
    }, {
      name: "Beta",
      module: "beta-worker"
    }];
    this.workers = workers || defaultWorkers;
  }

  startToGenerateTask() {
    this.starterModule.start();
  }

  start() {
    this.initializeWorker().then(() => {
      if (cluster.isMaster) {
        for (var i = 0; i < numberFork; i++) {
          const fork = cluster.fork();
          fork.on('online', () => {
            logger.info('fork created');
          });
          fork.on('exit', () => {
            logger.info('fork exit');
          });
        }
        this.initializeStarter().then(() => this.startToGenerateTask());
      }
      this.activateWorker();
    });
  }

  initializeWorker() {
    const workerDirectory = path.resolve(__dirname + "/../worker");
    this.workflow = new ChainJobQueue();

    return bluebird.map(this.workers, (worker) => {
      return fs.accessAsync(workerDirectory + "/" + worker.module)
    }).then(() => {
      return this.workers.map((worker) => {
        const pathToWorkerModule = workerDirectory + "/" + worker.module;
        const workerModule = require(pathToWorkerModule);
        const packageWorkerModule = require(pathToWorkerModule + '/package.json');
        return {
          name: packageWorkerModule.name,
          doTheJob: workerModule.doTheJob,
          finalJob: workerModule.finalJob
        };
      })
    }).then((arrayWorkerModule) => {
      arrayWorkerModule.map((workerModule) => {
        this.workflow.addWorker(workerModule.name, workerModule.doTheJob, workerModule.finalJob);
      });
      this.workflow.createQueueForWorkers();
      return this;
    });
  }

  activateWorker() {
    const callFinishers = () => {
      return bluebird.filter(this.workflow.listWorker, (worker) => {
        return worker.finalFunction !== undefined
      }).map((worker) => {
        return bluebird.promisify(worker.finalFunction)();
      })
    };

    // Heartbeat
    if (cluster.isMaster) {
      setInterval(function () {
        clientRedis.hgetallAsync('sisyphe').then((values) => {
          const totalJobs = +[values.totalPerformedTask] + +[values.totalFailedTask];
          if (+values.totalGeneratedTask && totalJobs >= +values.totalGeneratedTask) {
            clearInterval(this);
            logger.info("Total jobs created = " + values.totalGeneratedTask);
            logger.info("Total jobs completed = " + values.totalPerformedTask);
            logger.info("Total jobs failed = " + values.totalFailedTask);
            logger.info("Total jobs = " + totalJobs);
            logger.info('release finishers !');
            callFinishers().then(() => {
              logger.info('All finalJob executed !');
              clientRedis.del('sisyphe');
              console.log('');
              console.log('This is the end !');
            }).catch((error) => {
              // TODO : rajouter une gestion des erreur pour les logs
              logger.info(error);
            });
          }
        });
      }, 1000);
    }

    this.workflow.addJobProcessToWorkers();
    return this;
  }

  initializeStarter() {
    const starterDirectory = path.resolve(__dirname + "/../starter");
    const pathToStarterModule = starterDirectory + "/" + this.starter.module;
    return fs.accessAsync(pathToStarterModule).then(() => {
      const StarterModule = require(starterDirectory + "/" + this.starter.module);
      this.starterModule = new StarterModule(this.starter.options);

      this.starterModule.setFunctionEventOnData((data) => {
        this.starterModule.totalFile++;
        this.workflow.addTask(data);
      });

      this.starterModule.setFunctionEventOnEnd(() => {
        this.workflow.totalGeneratedTask = this.starterModule.totalFile;
        clientRedis.hset('sisyphe', 'totalGeneratedTask', this.starterModule.totalFile);
        logger.info('Total jobs generated by starter module = ' + this.starterModule.totalFile);
      });

      return this;
    });
  }
}

module.exports = Sisyphe;<|endoftext|>


<commit_before>/* snapper css snap points carousel */
;(function( w, $ ){
	var pluginName = "snapper";
	$.fn[ pluginName ] = function(){
		// css snap points feature test.
		// even if this test passes, several behaviors will still be polyfilled, such as snapping after resize, and animated advancing of slides with anchor links or next/prev links
		var testProp = "scroll-snap-type";
		var snapSupported = w.CSS && w.CSS.supports && ( w.CSS.supports( testProp, "mandatory") || w.CSS.supports("-webkit-" + testProp, "mandatory")  || w.CSS.supports("-ms-" + testProp, "mandatory") );

		// get the snapper_item elements whose left offsets fall within the scroll pane. Returns a wrapped array.
		function itemsAtOffset( elem, offset ){
			var $childNodes = $( elem ).find( "." + pluginName + "_item" );
			var containWidth = $( elem ).width();
			var activeItems = [];

			$childNodes.each(function( i ){
				if( this.offsetLeft >= offset - 5 && this.offsetLeft < offset + containWidth - 5 ){
					activeItems.push( this );
				}
			});
			return $( activeItems );
		}

		function outerWidth( $elem ){
			return $elem.width() + parseFloat( $elem.css( "margin-left" ) ) + parseFloat( $elem.css( "margin-right" ) );
		}

		// snapEvent dispatches the "snapper.snap" event.
		// The snapper_item elements with left offsets that are inside the scroll viewport are listed in an array in the second callback argument's activeSlides property.
		// use like this: $( ".snapper" ).bind( "snapper.snap", function( event, data ){ console.log( data.activeSlides );  } );
		function snapEvent( elem, x ){
			var activeSlides = itemsAtOffset( elem, x );
			$( elem ).trigger( pluginName + ".snap", { activeSlides: activeSlides } );
		}

		// optional: include overthrow.toss() in your page to get a smooth scroll, otherwise it'll just jump to the slide
		function goto( elem, x, nothrow ){
			snapEvent( elem, x );
			if( typeof w.overthrow !== "undefined" && !nothrow ){
				w.overthrow.toss( elem, { left: x } );
			}
			else {
				elem.scrollLeft = x;
			}
		}

		// Loop through snapper elements and enhance/bind events
		return this.each(function(){
			var self = this;
			var $self = $( self );
			var addNextPrev = $self.is( "[data-" + pluginName + "-nextprev]" );
			var autoTiming = $self.attr( "data-autoplay" );
			var autoInterval;
			var $slider = $( "." + pluginName + "_pane", self );
			var enhancedClass = pluginName + "-enhanced";
			var $itemsContain = $slider.find( "." + pluginName + "_items" );
			var $items = $itemsContain.children();
			$items.addClass( pluginName + "_item" );
			var numItems = $items.length;
			var $nav = $( "." + pluginName + "_nav", self );
			var navSelectedClass = pluginName + "_nav_item-selected";

			// this function updates the widths of the items within the slider, and their container.
			// It factors in margins and converts those to values that make sense when all items are placed in a long row
			function updateWidths(){
				var itemsContainStyle = $itemsContain.attr( "style" );
				$itemsContain.attr( "style", "" );
				var itemStyle = $items.eq(0).attr( "style" );
				$items.eq(0).attr( "style", "" );
				var sliderWidth = $slider.width();
				var itemWidth = $items.eq(0).width();
				var computed = w.getComputedStyle( $items[ 0 ], null );
				var itemLeftMargin = parseFloat( computed.getPropertyValue( "margin-left" ) );
				var itemRightMargin = parseFloat( computed.getPropertyValue( "margin-right" ) );
				var outerItemWidth = itemWidth + itemLeftMargin + itemRightMargin;
				$items.eq(0).attr( "style", itemStyle );
				$itemsContain.attr( "style", itemsContainStyle );
				var parentWidth = numItems / Math.round(sliderWidth / outerItemWidth) * 100;
				var iPercentWidth = itemWidth / sliderWidth * 100;
				var iPercentRightMargin = itemRightMargin / sliderWidth * 100;
				var iPercentLeftMargin = itemLeftMargin / sliderWidth * 100;
				var outerPercentWidth = iPercentWidth + iPercentLeftMargin + iPercentRightMargin;
				var percentAsWidth = iPercentWidth / outerPercentWidth;
				var percentAsRightMargin = iPercentRightMargin / outerPercentWidth;
				var percentAsLeftMargin = iPercentLeftMargin / outerPercentWidth;
				$itemsContain.css( "width", parentWidth + "%");
				$items.css( "width", 100 / numItems * percentAsWidth + "%" );
				$items.css( "margin-left", 100 / numItems * percentAsLeftMargin + "%" );
				$items.css( "margin-right", 100 / numItems * percentAsRightMargin + "%" );
			}

			updateWidths();
			$( self ).addClass( enhancedClass );

			// if the nextprev option is set, add the nextprev nav
			if( addNextPrev ){
				var	$nextprev = $( '

Prev

Next

' );
				var $nextprevContain = $( ".snapper_nextprev_contain", self );
				if( !$nextprevContain.length ){
					$nextprevContain = $( self );
				}
				$nextprev.appendTo( $nextprevContain );
			}

			// This click binding will allow deep-linking to slides without causing the page to scroll to the carousel container
			// this also supports click handling for generated next/prev links
			$( "a", this ).bind( "click", function( e ){
				clearInterval(autoInterval);

				var slideID = $( this ).attr( "href" );
				if( slideID.indexOf( "#" ) === -1 ){
					// only local anchor links
					return;
				}
				e.preventDefault();
				if( $( this ).is( ".snapper_nextprev_next" ) ){
					return arrowNavigate( true );
				}
				else if( $( this ).is( ".snapper_nextprev_prev" ) ){
					return arrowNavigate( false );
				}
				else {
					var $slide = $( slideID, self );
					goto( $slider[ 0 ], $slide[ 0 ].offsetLeft );
					if( "replaceState" in w.history ){
						w.history.replaceState( {}, document.title, slideID );
					}
				}
			});

			// arrow key bindings for next/prev
			$( this )
				.attr( "tabindex", "0" )
				.bind( "keyup", function( e ){
					if( e.keyCode === 37 || e.keyCode === 38 ){
						clearInterval(autoInterval);
						e.preventDefault();
						arrowNavigate( false );
					}
					if( e.keyCode === 39 || e.keyCode === 40 ){
						clearInterval(autoInterval);
						e.preventDefault();
						arrowNavigate( true );
					}
				} );

			// snap to nearest slide. Useful after a scroll stops, for polyfilling snap points
			function snapScroll(){
				var currScroll = $slider[ 0 ].scrollLeft;
				var width = $itemsContain.width();
				var itemWidth = $items[ 1 ] ? $items[ 1 ].offsetLeft : outerWidth( $items.eq( 0 ) );
				var roundedScroll = Math.round(currScroll/itemWidth)*itemWidth;
				var maxScroll = width - $slider.width();
				if( roundedScroll > maxScroll ){
					roundedScroll = maxScroll;
				}
				if( currScroll !== roundedScroll ){
					if( snapSupported ){
						snapEvent( $slider[ 0 ], roundedScroll );
					}
					else {
						goto( $slider[ 0 ], roundedScroll );
					}
				}
			}

			// retain snapping on resize (necessary even in scroll-snap supporting browsers currently, unfortunately)
			var startSlide;
			var afterResize;
			function snapStay(){
				var currScroll = $slider[ 0 ].scrollLeft;
				var numItems = $items.length;
				var width = $itemsContain.width();
				if( startSlide === undefined ){
					startSlide = Math.round( currScroll / width * numItems );
				}
				if( afterResize ){
					clearTimeout( afterResize );
				}
				afterResize = setTimeout( function(){
					updateWidths();
					goto( $slider[ 0 ], $items[ startSlide ].offsetLeft, true );
					startSlide = afterResize = undefined;
				}, 50 );
			}
			$( w ).bind( "resize", snapStay );

			// next/prev links or arrows should loop back to the other end when an extreme is reached
			function arrowNavigate( forward ){
				var currScroll = $slider[ 0 ].scrollLeft;
				var width = $itemsContain.width();
				var itemWidth = outerWidth( $slider );
				var maxScroll = width - itemWidth - 5;
				if( forward ){
					if( currScroll >= maxScroll ){
						return first();
					}
					else {
						return next();
					}
				}
				else {
					if( currScroll === 0 ){
						return last();
					}
					else {
						return prev();
					}
				}
			}

			// advance slide one full scrollpane's width forward
			function next(){
				goto( $slider[ 0 ], $slider[ 0 ].scrollLeft + ( $itemsContain.width() / numItems ) );
			}

			// advance slide one full scrollpane's width backwards
			function prev(){
				goto( $slider[ 0 ], $slider[ 0 ].scrollLeft - ( $itemsContain.width() / numItems ) );
			}

			// go to first slide
			function first(){
				goto( $slider[ 0 ], 0 );
			}

			// go to last slide
			function last(){
				goto( $slider[ 0 ], $itemsContain.width() - $slider.width() );
			}

			// update thumbnail state on pane scroll
			if( $nav.length ){
				function activeItem(){
					var currScroll = $slider[ 0 ].scrollLeft;
					var width = outerWidth( $itemsContain );
					var activeIndex = Math.round( currScroll / width * numItems );
					$nav
						.children().removeClass( navSelectedClass )
						.eq( activeIndex )
						.addClass( navSelectedClass );
				}
				// set active item on scroll
				$slider.bind( "scroll", activeItem );
				// set active item on init
				activeItem();
			}

			// apply snapping after scroll, in browsers that don't support CSS scroll-snap
			var scrollStop;
			$slider.bind( "scroll", function(e){
				if( scrollStop ){
					clearTimeout( scrollStop );
				}
				scrollStop = setTimeout( snapScroll, 50 );
			});

			// if the `data-autotplay` attribute is assigned a natural number value
			// use it to make the slides cycle until there is a user interaction
			if(autoTiming){
				var parseError = false;

				try {
					autoTiming = parseInt(autoTiming, 10);
				} catch(e) {
					parseError = true;
				}

				// if NaN or there was an error throw an exception
				if( !autoTiming || parseError ) {
					var msg = "Snapper: `data-autoplay` must have an natural number value.";
					throw new Error(msg);
				}

				// autoInterval is cleared in each user interaction binding
				autoInterval = setInterval(function(){
					arrowNavigate(true);
				}, autoTiming);
			}
		});
	};

	// auto-init on enhance
	$( document ).bind( "enhance", function( e ){
		$( "." + pluginName, e.target ).add( e.target ).filter( "." + pluginName )[ pluginName ]();
	});
}( this, jQuery ));
<commit_msg>disable on touchstart<commit_after>/* snapper css snap points carousel */
;(function( w, $ ){
	var pluginName = "snapper";
	$.fn[ pluginName ] = function(){
		// css snap points feature test.
		// even if this test passes, several behaviors will still be polyfilled, such as snapping after resize, and animated advancing of slides with anchor links or next/prev links
		var testProp = "scroll-snap-type";
		var snapSupported = w.CSS && w.CSS.supports && ( w.CSS.supports( testProp, "mandatory") || w.CSS.supports("-webkit-" + testProp, "mandatory")  || w.CSS.supports("-ms-" + testProp, "mandatory") );

		// get the snapper_item elements whose left offsets fall within the scroll pane. Returns a wrapped array.
		function itemsAtOffset( elem, offset ){
			var $childNodes = $( elem ).find( "." + pluginName + "_item" );
			var containWidth = $( elem ).width();
			var activeItems = [];

			$childNodes.each(function( i ){
				if( this.offsetLeft >= offset - 5 && this.offsetLeft < offset + containWidth - 5 ){
					activeItems.push( this );
				}
			});
			return $( activeItems );
		}

		function outerWidth( $elem ){
			return $elem.width() + parseFloat( $elem.css( "margin-left" ) ) + parseFloat( $elem.css( "margin-right" ) );
		}

		// snapEvent dispatches the "snapper.snap" event.
		// The snapper_item elements with left offsets that are inside the scroll viewport are listed in an array in the second callback argument's activeSlides property.
		// use like this: $( ".snapper" ).bind( "snapper.snap", function( event, data ){ console.log( data.activeSlides );  } );
		function snapEvent( elem, x ){
			var activeSlides = itemsAtOffset( elem, x );
			$( elem ).trigger( pluginName + ".snap", { activeSlides: activeSlides } );
		}

		// optional: include overthrow.toss() in your page to get a smooth scroll, otherwise it'll just jump to the slide
		function goto( elem, x, nothrow ){
			snapEvent( elem, x );
			if( typeof w.overthrow !== "undefined" && !nothrow ){
				w.overthrow.toss( elem, { left: x } );
			}
			else {
				elem.scrollLeft = x;
			}
		}

		// Loop through snapper elements and enhance/bind events
		return this.each(function(){
			var self = this;
			var $self = $( self );
			var addNextPrev = $self.is( "[data-" + pluginName + "-nextprev]" );
			var autoTiming = $self.attr( "data-autoplay" );
			var autoInterval;
			var $slider = $( "." + pluginName + "_pane", self );
			var enhancedClass = pluginName + "-enhanced";
			var $itemsContain = $slider.find( "." + pluginName + "_items" );
			var $items = $itemsContain.children();
			$items.addClass( pluginName + "_item" );
			var numItems = $items.length;
			var $nav = $( "." + pluginName + "_nav", self );
			var navSelectedClass = pluginName + "_nav_item-selected";

			// this function updates the widths of the items within the slider, and their container.
			// It factors in margins and converts those to values that make sense when all items are placed in a long row
			function updateWidths(){
				var itemsContainStyle = $itemsContain.attr( "style" );
				$itemsContain.attr( "style", "" );
				var itemStyle = $items.eq(0).attr( "style" );
				$items.eq(0).attr( "style", "" );
				var sliderWidth = $slider.width();
				var itemWidth = $items.eq(0).width();
				var computed = w.getComputedStyle( $items[ 0 ], null );
				var itemLeftMargin = parseFloat( computed.getPropertyValue( "margin-left" ) );
				var itemRightMargin = parseFloat( computed.getPropertyValue( "margin-right" ) );
				var outerItemWidth = itemWidth + itemLeftMargin + itemRightMargin;
				$items.eq(0).attr( "style", itemStyle );
				$itemsContain.attr( "style", itemsContainStyle );
				var parentWidth = numItems / Math.round(sliderWidth / outerItemWidth) * 100;
				var iPercentWidth = itemWidth / sliderWidth * 100;
				var iPercentRightMargin = itemRightMargin / sliderWidth * 100;
				var iPercentLeftMargin = itemLeftMargin / sliderWidth * 100;
				var outerPercentWidth = iPercentWidth + iPercentLeftMargin + iPercentRightMargin;
				var percentAsWidth = iPercentWidth / outerPercentWidth;
				var percentAsRightMargin = iPercentRightMargin / outerPercentWidth;
				var percentAsLeftMargin = iPercentLeftMargin / outerPercentWidth;
				$itemsContain.css( "width", parentWidth + "%");
				$items.css( "width", 100 / numItems * percentAsWidth + "%" );
				$items.css( "margin-left", 100 / numItems * percentAsLeftMargin + "%" );
				$items.css( "margin-right", 100 / numItems * percentAsRightMargin + "%" );
			}

			updateWidths();
			$( self ).addClass( enhancedClass );

			// if the nextprev option is set, add the nextprev nav
			if( addNextPrev ){
				var	$nextprev = $( '

Prev

Next

' );
				var $nextprevContain = $( ".snapper_nextprev_contain", self );
				if( !$nextprevContain.length ){
					$nextprevContain = $( self );
				}
				$nextprev.appendTo( $nextprevContain );
			}

			// This click binding will allow deep-linking to slides without causing the page to scroll to the carousel container
			// this also supports click handling for generated next/prev links
			$( "a", this ).bind( "click", function( e ){
				clearInterval(autoInterval);

				var slideID = $( this ).attr( "href" );
				if( slideID.indexOf( "#" ) === -1 ){
					// only local anchor links
					return;
				}
				e.preventDefault();
				if( $( this ).is( ".snapper_nextprev_next" ) ){
					return arrowNavigate( true );
				}
				else if( $( this ).is( ".snapper_nextprev_prev" ) ){
					return arrowNavigate( false );
				}
				else {
					var $slide = $( slideID, self );
					goto( $slider[ 0 ], $slide[ 0 ].offsetLeft );
					if( "replaceState" in w.history ){
						w.history.replaceState( {}, document.title, slideID );
					}
				}
			});

			// arrow key bindings for next/prev
			$( this )
				.attr( "tabindex", "0" )
				.bind( "keyup", function( e ){
					if( e.keyCode === 37 || e.keyCode === 38 ){
						clearInterval(autoInterval);
						e.preventDefault();
						arrowNavigate( false );
					}
					if( e.keyCode === 39 || e.keyCode === 40 ){
						clearInterval(autoInterval);
						e.preventDefault();
						arrowNavigate( true );
					}
				} );

			// snap to nearest slide. Useful after a scroll stops, for polyfilling snap points
			function snapScroll(){
				var currScroll = $slider[ 0 ].scrollLeft;
				var width = $itemsContain.width();
				var itemWidth = $items[ 1 ] ? $items[ 1 ].offsetLeft : outerWidth( $items.eq( 0 ) );
				var roundedScroll = Math.round(currScroll/itemWidth)*itemWidth;
				var maxScroll = width - $slider.width();
				if( roundedScroll > maxScroll ){
					roundedScroll = maxScroll;
				}
				if( currScroll !== roundedScroll ){
					if( snapSupported ){
						snapEvent( $slider[ 0 ], roundedScroll );
					}
					else {
						goto( $slider[ 0 ], roundedScroll );
					}
				}
			}

			// retain snapping on resize (necessary even in scroll-snap supporting browsers currently, unfortunately)
			var startSlide;
			var afterResize;
			function snapStay(){
				var currScroll = $slider[ 0 ].scrollLeft;
				var numItems = $items.length;
				var width = $itemsContain.width();
				if( startSlide === undefined ){
					startSlide = Math.round( currScroll / width * numItems );
				}
				if( afterResize ){
					clearTimeout( afterResize );
				}
				afterResize = setTimeout( function(){
					updateWidths();
					goto( $slider[ 0 ], $items[ startSlide ].offsetLeft, true );
					startSlide = afterResize = undefined;
				}, 50 );
			}
			$( w ).bind( "resize", snapStay );

			// next/prev links or arrows should loop back to the other end when an extreme is reached
			function arrowNavigate( forward ){
				var currScroll = $slider[ 0 ].scrollLeft;
				var width = $itemsContain.width();
				var itemWidth = outerWidth( $slider );
				var maxScroll = width - itemWidth - 5;
				if( forward ){
					if( currScroll >= maxScroll ){
						return first();
					}
					else {
						return next();
					}
				}
				else {
					if( currScroll === 0 ){
						return last();
					}
					else {
						return prev();
					}
				}
			}

			// advance slide one full scrollpane's width forward
			function next(){
				goto( $slider[ 0 ], $slider[ 0 ].scrollLeft + ( $itemsContain.width() / numItems ) );
			}

			// advance slide one full scrollpane's width backwards
			function prev(){
				goto( $slider[ 0 ], $slider[ 0 ].scrollLeft - ( $itemsContain.width() / numItems ) );
			}

			// go to first slide
			function first(){
				goto( $slider[ 0 ], 0 );
			}

			// go to last slide
			function last(){
				goto( $slider[ 0 ], $itemsContain.width() - $slider.width() );
			}

			// update thumbnail state on pane scroll
			if( $nav.length ){
				function activeItem(){
					var currScroll = $slider[ 0 ].scrollLeft;
					var width = outerWidth( $itemsContain );
					var activeIndex = Math.round( currScroll / width * numItems );
					$nav
						.children().removeClass( navSelectedClass )
						.eq( activeIndex )
						.addClass( navSelectedClass );
				}
				// set active item on scroll
				$slider.bind( "scroll", activeItem );
				// set active item on init
				activeItem();
			}

			// apply snapping after scroll, in browsers that don't support CSS scroll-snap
			var scrollStop;
			$slider.bind( "scroll", function(e){
				if( scrollStop ){
					clearTimeout( scrollStop );
				}
				scrollStop = setTimeout( snapScroll, 50 );
			});

			// if a touch event is fired on the snapper we know the user is trying to
			// interact with it and we should disable the auto play
			$self.bind("touchstart", function(){
				clearTimeout(autoInterval);
			});

			// if the `data-autotplay` attribute is assigned a natural number value
			// use it to make the slides cycle until there is a user interaction
			if(autoTiming){
				var parseError = false;

				try {
					autoTiming = parseInt(autoTiming, 10);
				} catch(e) {
					parseError = true;
				}

				// if NaN or there was an error throw an exception
				if( !autoTiming || parseError ) {
					var msg = "Snapper: `data-autoplay` must have an natural number value.";
					throw new Error(msg);
				}

				// autoInterval is cleared in each user interaction binding
				autoInterval = setInterval(function(){
					arrowNavigate(true);
				}, autoTiming);
			}
		});
	};

	// auto-init on enhance
	$( document ).bind( "enhance", function( e ){
		$( "." + pluginName, e.target ).add( e.target ).filter( "." + pluginName )[ pluginName ]();
	});
}( this, jQuery ));
<|endoftext|>


<commit_before>(function(window) {
    var re = {
        not_string: /[^s]/,
        number: /[diefg]/,
        json: /[j]/,
        not_json: /[^j]/,
        text: /^[^\x25]+/,
        modulo: /^\x25{2}/,
        placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])/,
        key: /^([a-z_][a-z_\d]*)/i,
        key_access: /^\.([a-z_][a-z_\d]*)/i,
        index_access: /^\[(\d+)\]/,
        sign: /^[\+\-]/
    }

    function sprintf() {
        var key = arguments[0], cache = sprintf.cache
        if (!(cache[key] && cache.hasOwnProperty(key))) {
            cache[key] = sprintf.parse(key)
        }
        return sprintf.format.call(null, cache[key], arguments)
    }

    sprintf.format = function(parse_tree, argv) {
        var cursor = 1, tree_length = parse_tree.length, node_type = "", arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = ""
        for (i = 0; i < tree_length; i++) {
            node_type = get_type(parse_tree[i])
            if (node_type === "string") {
                output[output.length] = parse_tree[i]
            }
            else if (node_type === "array") {
                match = parse_tree[i] // convenience purposes only
                if (match[2]) { // keyword argument
                    arg = argv[cursor]
                    for (k = 0; k < match[2].length; k++) {
                        if (!arg.hasOwnProperty(match[2][k])) {
                            throw new Error(sprintf("[sprintf] property '%s' does not exist", match[2][k]))
                        }
                        arg = arg[match[2][k]]
                    }
                }
                else if (match[1]) { // positional argument (explicit)
                    arg = argv[match[1]]
                }
                else { // positional argument (implicit)
                    arg = argv[cursor++]
                }

                if (get_type(arg) == "function") {
                    arg = arg()
                }

                if (re.not_string.test(match[8]) && re.not_json.test(match[8]) && (get_type(arg) != "number" && isNaN(arg))) {
                    throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg)))
                }

                if (re.number.test(match[8])) {
                    is_positive = arg >= 0
                }

                switch (match[8]) {
                    case "b":
                        arg = arg.toString(2)
                    break
                    case "c":
                        arg = String.fromCharCode(arg)
                    break
                    case "d":
                    case "i":
                        arg = parseInt(arg, 10)
                    break
                    case "j":
                        arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0)
                    break
                    case "e":
                        arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential()
                    break
                    case "f":
                        arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg)
                    break
                    case "g":
                        arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg)
                    break
                    case "o":
                        arg = arg.toString(8)
                    break
                    case "s":
                        arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)
                    break
                    case "u":
                        arg = arg >>> 0
                    break
                    case "x":
                        arg = arg.toString(16)
                    break
                    case "X":
                        arg = arg.toString(16).toUpperCase()
                    break
                }
                if (re.json.test(match[8])) {
                    output[output.length] = arg
                }
                else {
                    if (re.number.test(match[8]) && (!is_positive || match[3])) {
                        sign = is_positive ? "+" : "-"
                        arg = arg.toString().replace(re.sign, "")
                    }
                    else {
                        sign = ""
                    }
                    pad_character = match[4] ? match[4] === "0" ? "0" : match[4].charAt(1) : " "
                    pad_length = match[6] - (sign + arg).length
                    pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : "") : ""
                    output[output.length] = match[5] ? sign + arg + pad : (pad_character === "0" ? sign + pad + arg : pad + sign + arg)
                }
            }
        }
        return output.join("")
    }

    sprintf.cache = {}

    sprintf.parse = function(fmt) {
        var _fmt = fmt, match = [], parse_tree = [], arg_names = 0
        while (_fmt) {
            if ((match = re.text.exec(_fmt)) !== null) {
                parse_tree[parse_tree.length] = match[0]
            }
            else if ((match = re.modulo.exec(_fmt)) !== null) {
                parse_tree[parse_tree.length] = "%"
            }
            else if ((match = re.placeholder.exec(_fmt)) !== null) {
                if (match[2]) {
                    arg_names |= 1
                    var field_list = [], replacement_field = match[2], field_match = []
                    if ((field_match = re.key.exec(replacement_field)) !== null) {
                        field_list[field_list.length] = field_match[1]
                        while ((replacement_field = replacement_field.substring(field_match[0].length)) !== "") {
                            if ((field_match = re.key_access.exec(replacement_field)) !== null) {
                                field_list[field_list.length] = field_match[1]
                            }
                            else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
                                field_list[field_list.length] = field_match[1]
                            }
                            else {
                                throw new SyntaxError("[sprintf] failed to parse named argument key")
                            }
                        }
                    }
                    else {
                        throw new SyntaxError("[sprintf] failed to parse named argument key")
                    }
                    match[2] = field_list
                }
                else {
                    arg_names |= 2
                }
                if (arg_names === 3) {
                    throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported")
                }
                parse_tree[parse_tree.length] = match
            }
            else {
                throw new SyntaxError("[sprintf] unexpected placeholder")
            }
            _fmt = _fmt.substring(match[0].length)
        }
        return parse_tree
    }

    var vsprintf = function(fmt, argv, _argv) {
        _argv = (argv || []).slice(0)
        _argv.splice(0, 0, fmt)
        return sprintf.apply(null, _argv)
    }

    /**
     * helpers
     */
    function get_type(variable) {
        return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase()
    }

    function str_repeat(input, multiplier) {
        return Array(multiplier + 1).join(input)
    }

    /**
     * export to either browser or node.js
     */
    if (typeof exports !== "undefined") {
        exports.sprintf = sprintf
        exports.vsprintf = vsprintf
    }
    else {
        window.sprintf = sprintf
        window.vsprintf = vsprintf

        if (typeof define === "function" && define.amd) {
            define(function() {
                return {
                    sprintf: sprintf,
                    vsprintf: vsprintf
                }
            })
        }
    }
})(typeof window === "undefined" ? this : window);
<commit_msg>- JS Lint.<commit_after>(function(window) {
    var re = {
        not_string: /[^s]/,
        number: /[diefg]/,
        json: /[j]/,
        not_json: /[^j]/,
        text: /^[^\x25]+/,
        modulo: /^\x25{2}/,
        placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])/,
        key: /^([a-z_][a-z_\d]*)/i,
        key_access: /^\.([a-z_][a-z_\d]*)/i,
        index_access: /^\[(\d+)\]/,
        sign: /^[\+\-]/
    };

    function sprintf() {
        var key = arguments[0], cache = sprintf.cache;
        if (!(cache[key] && cache.hasOwnProperty(key))) {
            cache[key] = sprintf.parse(key);
        }
        return sprintf.format.call(null, cache[key], arguments);
    }

    sprintf.format = function(parse_tree, argv) {
        var cursor = 1, tree_length = parse_tree.length, node_type = "", arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = "";
        for (i = 0; i < tree_length; i++) {
            node_type = get_type(parse_tree[i]);
            if (node_type === "string") {
                output[output.length] = parse_tree[i];
            }
            else if (node_type === "array") {
                match = parse_tree[i]; // convenience purposes only
                if (match[2]) { // keyword argument
                    arg = argv[cursor];
                    for (k = 0; k < match[2].length; k++) {
                        if (!arg.hasOwnProperty(match[2][k])) {
                            throw new Error(sprintf("[sprintf] property '%s' does not exist", match[2][k]));
                        }
                        arg = arg[match[2][k]];
                    }
                }
                else if (match[1]) { // positional argument (explicit)
                    arg = argv[match[1]];
                }
                else { // positional argument (implicit)
                    arg = argv[cursor++];
                }

                if (get_type(arg) == "function") {
                    arg = arg();
                }

                if (re.not_string.test(match[8]) && re.not_json.test(match[8]) && (get_type(arg) != "number" && isNaN(arg))) {
                    throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg)));
                }

                if (re.number.test(match[8])) {
                    is_positive = arg >= 0;
                }

                switch (match[8]) {
                    case "b":
                        arg = arg.toString(2);
                    break;
                    case "c":
                        arg = String.fromCharCode(arg);
                    break;
                    case "d":
                    case "i":
                        arg = parseInt(arg, 10);
                    break;
                    case "j":
                        arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0);
                    break;
                    case "e":
                        arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential();
                    break;
                    case "f":
                        arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg);
                    break;
                    case "g":
                        arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg);
                    break;
                    case "o":
                        arg = arg.toString(8);
                    break;
                    case "s":
                        arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg);
                    break;
                    case "u":
                        arg = arg >>> 0;
                    break;
                    case "x":
                        arg = arg.toString(16);
                    break;
                    case "X":
                        arg = arg.toString(16).toUpperCase();
                    break;
                }
                if (re.json.test(match[8])) {
                    output[output.length] = arg;
                }
                else {
                    if (re.number.test(match[8]) && (!is_positive || match[3])) {
                        sign = is_positive ? "+" : "-";
                        arg = arg.toString().replace(re.sign, "");
                    }
                    else {
                        sign = "";
                    }
                    pad_character = match[4] ? match[4] === "0" ? "0" : match[4].charAt(1) : " ";
                    pad_length = match[6] - (sign + arg).length;
                    pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : "") : "";
                    output[output.length] = match[5] ? sign + arg + pad : (pad_character === "0" ? sign + pad + arg : pad + sign + arg);
                }
            }
        }
        return output.join("");
    };

    sprintf.cache = {};

    sprintf.parse = function(fmt) {
        var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
        while (_fmt) {
            if ((match = re.text.exec(_fmt)) !== null) {
                parse_tree[parse_tree.length] = match[0];
            }
            else if ((match = re.modulo.exec(_fmt)) !== null) {
                parse_tree[parse_tree.length] = "%";
            }
            else if ((match = re.placeholder.exec(_fmt)) !== null) {
                if (match[2]) {
                    arg_names |= 1;
                    var field_list = [], replacement_field = match[2], field_match = [];
                    if ((field_match = re.key.exec(replacement_field)) !== null) {
                        field_list[field_list.length] = field_match[1];
                        while ((replacement_field = replacement_field.substring(field_match[0].length)) !== "") {
                            if ((field_match = re.key_access.exec(replacement_field)) !== null) {
                                field_list[field_list.length] = field_match[1];
                            }
                            else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
                                field_list[field_list.length] = field_match[1];
                            }
                            else {
                                throw new SyntaxError("[sprintf] failed to parse named argument key");
                            }
                        }
                    }
                    else {
                        throw new SyntaxError("[sprintf] failed to parse named argument key");
                    }
                    match[2] = field_list;
                }
                else {
                    arg_names |= 2;
                }
                if (arg_names === 3) {
                    throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");
                }
                parse_tree[parse_tree.length] = match;
            }
            else {
                throw new SyntaxError("[sprintf] unexpected placeholder");
            }
            _fmt = _fmt.substring(match[0].length);
        }
        return parse_tree;
    };

    var vsprintf = function(fmt, argv, _argv) {
        _argv = (argv || []).slice(0);
        _argv.splice(0, 0, fmt);
        return sprintf.apply(null, _argv);
    };

    /**
     * helpers
     */
    function get_type(variable) {
        return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
    }

    function str_repeat(input, multiplier) {
        return Array(multiplier + 1).join(input);
    }

    /**
     * export to either browser or node.js
     */
    if (typeof exports !== "undefined") {
        exports.sprintf = sprintf;
        exports.vsprintf = vsprintf;
    }
    else {
        window.sprintf = sprintf;
        window.vsprintf = vsprintf;

        if (typeof define === "function" && define.amd) {
            define(function() {
                return {
                    sprintf: sprintf,
                    vsprintf: vsprintf
                };
            });
        }
    }
})(typeof window === "undefined" ? this : window);
<|endoftext|>


<commit_before>/**@
 * #Storage
 * @category Utilities
 * Utility to allow data to be saved to a permanent storage solution: IndexedDB, WebSQL, localstorage or cookies
 */
	/**@
	 * #.open
	 * @sign .open(String gameName)
	 * @param gameName - a machine-readable string to uniquely identify your game
	 * Opens a connection to the database. If the best they have is localstorage or lower, it does nothing
	 *
	 * @example
	 * Open a database
	 * ---------------
	 * Crafty.storage.open('MyGame');
	 */

	/**@
	 * #.save
	 * @sign .save(String key, String type, Mixed data)
	 * @param key - A unique key for identifying this piece of data
	 * @param type - 'save' or 'cache'
	 * @param data - Some kind of data.
	 * Saves a piece of data to the database. Can be anything, although entities are preferred.
	 * For all storage methods but IndexedDB, the data will be serialized as a string
	 * During serialization, an entity's SaveData event will be triggered.
	 * Components should implement a SaveData handler and attach the necessary information to the passed object
	 *
	 * @example
	 * Saves an entity to the database
	 * ----------------
	 * var ent = Crafty.e("2D, DOM")
	 *		 .attr({x: 20, y: 20, w: 100, h:100});
	 * Crafty.storage.open('MyGame');
	 * Crafty.storage.save('MyEntity', 'save', ent);
	 */

	/**@
	 * #.load
	 * @sign .load(String key, String type)
	 * @param key - A unique key to search for
	 * @param type - 'save' or 'cache'
	 * Loads a piece of data from the database.
	 * Entities will be reconstructed from the serialized string

	 * @example
	 * Loads an entity from the database
	 * ------------------
	 * Crafty.storage.open('MyGame');
	 * var ent = Crafty.storage.load('MyEntity', 'save');
	 */

	/**@
	 * SaveData event
	 * @param data - An object containing all of the data to be serialized
	 * Any data a component wants to save should be added to this object.
	 * Straight attribute should be set in data.attr.
	 * Anything that requires a special handler should be set in a unique property.
	 *
	 * @example
	 * Saves the innerHTML of an entity
	 * ---------
	 * Crafty.e("2D DOM").bind("SaveData", function (data) {
	 * 		data.attr.x = this.x;
	 *		data.attr.y = this.y;
	 *		data.dom = this.element.innerHTML;
	 * });
	 */

	/**@
	 * LoadData event
	 * @param data - An object containing all the data that been saved
	 * Handlers for processing any data that needs more than straight assignment
	 *
	 * Note that data stord in the .attr object is automatically added to the entity.
	 * It does not need to be handled here
	 *
	 * @example
	 * ---------
	 * Sets the innerHTML from a saved entity
	 * Crafty.e("2D DOM").bind("LoadData", function (data) {
	 * 		this.element.innerHTML = data.dom;
	 * });
	 */

Crafty.storage = (function () {
	var db = null, external = '', gameName;

	function process(obj) {
		if (obj.c) {
			var d = Crafty.e(obj.c)
						.attr(obj.attr)
						.trigger('LoadData', obj);
			return d;
		}
		else if (typeof obj == 'object') {
			for (var prop in obj) {
				obj[prop] = process(obj[prop]);
			}
		}
		return obj;
	}

	function unserialize(str) {
		var data = (JSON?JSON.parse(str):eval('('+str+')'));
		return process(data);
	}

	/* recursive function
	 * searches for entities in an object and processes them for serialization
	 */
	function prep(obj) {
		if (obj.__c) {
			// object is entity
			var data = {c: [], attr: {}};
			obj.trigger("SaveData", data);
			for (var i in obj.__c) {
				data.c.push(i);
			}
			data.c = data.c.join(', ');
			obj = data;
		}
		else if (typeof obj == 'object') {
			// recurse and look for entities
			for (var prop in obj) {
				obj[prop] = prep(obj[prop]);
			}
		}
		return obj;
	}

	function serialize(e) {
		if (JSON) {
			var data = prep(e);
			return JSON.stringify(data);
		}
		else {
			alert("Crafty does not support saving on your browser. Please upgrade to a newer browser.");
			return false;
		}
	}

	if (0) {
	}
	else {
		// default fallback to cookies
		return {
			open: function (gameName_n) {
				gameName = gameName_n;
			},

			save: function (key, type, data) {
				// cookies are very limited in space. we can only keep saves there
				if (type != 'save') return;
				var str = serialize(data);
				document.cookie = gameName+'_'+key+'='+str+'; expires=Thur, 31 Dec 2099 23:59:59 UTC; path=/';
			},

			load: function (key, type) {
				if (type != 'save') return;
				var reg = new RegExp(gameName+'_'+key+'=[^;]*'),
					result = reg.exec(document.cookie);
					data = unserialize(result[0].replace(gameName+'_'+key+'=', ''));

				return data;
			},
		};
	}
	/* template
	return {
		open: function (gameName) {
		},
		save: function (key, type, data) {
		},
		load: function (key, type) {
		},
	}*/
})();<commit_msg>localStorage implementation.<commit_after>/**@
 * #Storage
 * @category Utilities
 * Utility to allow data to be saved to a permanent storage solution: IndexedDB, WebSQL, localstorage or cookies
 */
	/**@
	 * #.open
	 * @sign .open(String gameName)
	 * @param gameName - a machine-readable string to uniquely identify your game
	 * Opens a connection to the database. If the best they have is localstorage or lower, it does nothing
	 *
	 * @example
	 * Open a database
	 * ---------------
	 * Crafty.storage.open('MyGame');
	 */

	/**@
	 * #.save
	 * @sign .save(String key, String type, Mixed data)
	 * @param key - A unique key for identifying this piece of data
	 * @param type - 'save' or 'cache'
	 * @param data - Some kind of data.
	 * Saves a piece of data to the database. Can be anything, although entities are preferred.
	 * For all storage methods but IndexedDB, the data will be serialized as a string
	 * During serialization, an entity's SaveData event will be triggered.
	 * Components should implement a SaveData handler and attach the necessary information to the passed object
	 *
	 * @example
	 * Saves an entity to the database
	 * ----------------
	 * var ent = Crafty.e("2D, DOM")
	 *		 .attr({x: 20, y: 20, w: 100, h:100});
	 * Crafty.storage.open('MyGame');
	 * Crafty.storage.save('MyEntity', 'save', ent);
	 */

	/**@
	 * #.load
	 * @sign .load(String key, String type)
	 * @param key - A unique key to search for
	 * @param type - 'save' or 'cache'
	 * Loads a piece of data from the database.
	 * Entities will be reconstructed from the serialized string

	 * @example
	 * Loads an entity from the database
	 * ------------------
	 * Crafty.storage.open('MyGame');
	 * var ent = Crafty.storage.load('MyEntity', 'save');
	 */

	/**@
	 * SaveData event
	 * @param data - An object containing all of the data to be serialized
	 * Any data a component wants to save should be added to this object.
	 * Straight attribute should be set in data.attr.
	 * Anything that requires a special handler should be set in a unique property.
	 *
	 * @example
	 * Saves the innerHTML of an entity
	 * ---------
	 * Crafty.e("2D DOM").bind("SaveData", function (data) {
	 * 		data.attr.x = this.x;
	 *		data.attr.y = this.y;
	 *		data.dom = this.element.innerHTML;
	 * });
	 */

	/**@
	 * LoadData event
	 * @param data - An object containing all the data that been saved
	 * Handlers for processing any data that needs more than straight assignment
	 *
	 * Note that data stord in the .attr object is automatically added to the entity.
	 * It does not need to be handled here
	 *
	 * @example
	 * ---------
	 * Sets the innerHTML from a saved entity
	 * Crafty.e("2D DOM").bind("LoadData", function (data) {
	 * 		this.element.innerHTML = data.dom;
	 * });
	 */

Crafty.storage = (function () {
	var db = null, external = '', gameName;

	function process(obj) {
		if (obj.c) {
			var d = Crafty.e(obj.c)
						.attr(obj.attr)
						.trigger('LoadData', obj);
			return d;
		}
		else if (typeof obj == 'object') {
			for (var prop in obj) {
				obj[prop] = process(obj[prop]);
			}
		}
		return obj;
	}

	function unserialize(str) {
		var data = (JSON?JSON.parse(str):eval('('+str+')'));
		return process(data);
	}

	/* recursive function
	 * searches for entities in an object and processes them for serialization
	 */
	function prep(obj) {
		if (obj.__c) {
			// object is entity
			var data = {c: [], attr: {}};
			obj.trigger("SaveData", data);
			for (var i in obj.__c) {
				data.c.push(i);
			}
			data.c = data.c.join(', ');
			obj = data;
		}
		else if (typeof obj == 'object') {
			// recurse and look for entities
			for (var prop in obj) {
				obj[prop] = prep(obj[prop]);
			}
		}
		return obj;
	}

	function serialize(e) {
		if (JSON) {
			if (typeof e != 'string') return null;
			var data = prep(e);
			return JSON.stringify(data);
		}
		else {
			alert("Crafty does not support saving on your browser. Please upgrade to a newer browser.");
			return false;
		}
	}

	if (0) {
	}
	else if (typeof window.localStorage == 'object') {
		return {
			open: function (gameName_n) {
				gameName = gameName_n;
			},

			save: function (key, type, data) {
				var k = gameName+'.'+type+'.'+key,
					str = serialize(data);
				window.localStorage[k] = str;
			},

			load: function (key, type) {
				var k = gameName+'.'+type+'.'+key,
					str = window.localStorage[k];

				return unserialize(str);
			},
		};
	}
	else {
		// default fallback to cookies
		return {
			open: function (gameName_n) {
				gameName = gameName_n;
			},

			save: function (key, type, data) {
				// cookies are very limited in space. we can only keep saves there
				if (type != 'save') return;
				var str = serialize(data);
				document.cookie = gameName+'_'+key+'='+str+'; expires=Thur, 31 Dec 2099 23:59:59 UTC; path=/';
			},

			load: function (key, type) {
				if (type != 'save') return;
				var reg = new RegExp(gameName+'_'+key+'=[^;]*'),
					result = reg.exec(document.cookie);
					data = unserialize(result[0].replace(gameName+'_'+key+'=', ''));

				return data;
			},
		};
	}
	/* template
	return {
		open: function (gameName) {
		},
		save: function (key, type, data) {
		},
		load: function (key, type) {
		},
	}*/
})();<|endoftext|>


<commit_before>import normalizeValue from './normalizeValue';

// { scale: 2 } => 'scale(2)'
// { translateX: 20 } => 'translateX(20px)'
const mapTransform = (transform) => {
  const type = Object.keys(transform)[0];
  const value = normalizeValue(type, transform[type]);
  return `${type}(${value})`;
};

// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)'
const convertTransformMatrix = (transformMatrix) => {
  const matrix = transformMatrix.join(',');
  return `matrix3d(${matrix})`;
};

const processTransform = (style) => {
  if (style) {
    if (style.transform) {
      style.transform = style.transform.map(mapTransform).join(' ');
    } else if (style.transformMatrix) {
      style.transform = convertTransformMatrix(style.transformMatrix);
      style.transformMatrix = null;
    }
  }
  return style;
};

module.exports = processTransform;
<commit_msg>[fix] StyleSheet check 'transform' is an array<commit_after>import normalizeValue from './normalizeValue';

// { scale: 2 } => 'scale(2)'
// { translateX: 20 } => 'translateX(20px)'
const mapTransform = (transform) => {
  const type = Object.keys(transform)[0];
  const value = normalizeValue(type, transform[type]);
  return `${type}(${value})`;
};

// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)'
const convertTransformMatrix = (transformMatrix) => {
  const matrix = transformMatrix.join(',');
  return `matrix3d(${matrix})`;
};

const processTransform = (style) => {
  if (style) {
    if (style.transform && Array.isArray(style.transform)) {
      style.transform = style.transform.map(mapTransform).join(' ');
    } else if (style.transformMatrix) {
      style.transform = convertTransformMatrix(style.transformMatrix);
      style.transformMatrix = null;
    }
  }
  return style;
};

module.exports = processTransform;
<|endoftext|>


<commit_before>define([
	"Ember",
	"text!templates/components/modallog.html.hbs"
], function(
	Ember,
	layout
) {

	var scheduleOnce = Ember.run.scheduleOnce;

	return Ember.Component.extend({
		layout: Ember.HTMLBars.compile( layout ),

		tagName: "section",
		classNames: [ "modal-log" ],

		log: function() {
			return [];
		}.property(),

		_logObserver: function() {
			scheduleOnce( "afterRender", this, "scrollToBottom" );
		}.observes( "log.[]" ),

		scrollToBottom: function() {
			var elem = this.element;
			elem.scrollTop = Math.max( 0, elem.scrollHeight - elem.clientHeight );
		}.on( "didInsertElement" )
	});

});
<commit_msg>Fix scrollToBottom in ModalLogComponent<commit_after>define([
	"Ember",
	"text!templates/components/modallog.html.hbs"
], function(
	Ember,
	layout
) {

	var scheduleOnce = Ember.run.scheduleOnce;

	return Ember.Component.extend({
		layout: Ember.HTMLBars.compile( layout ),

		tagName: "section",
		classNames: [ "modal-log" ],

		log: function() {
			return [];
		}.property(),

		_logObserver: function() {
			scheduleOnce( "afterRender", this, "scrollToBottom" );
		}.observes( "log.[]" ),

		scrollToBottom: function() {
			var elem = this.element;
			if ( !elem ) { return; }
			elem.scrollTop = Math.max( 0, elem.scrollHeight - elem.clientHeight );
		}.on( "didInsertElement" )
	});

});
<|endoftext|>


<commit_before>var os = require('os');
var path = require('path');
var async = require('async');
var deepExtend = require('deep-extend');
var methods = require('./lib');
var Cache = require('./lib/util/Cache');

function RegistryClient(config, logger) {
    var bowerRegistry = 'https://bower.herokuapp.com';

    config = config || {};
    this._config = config;
    this._logger = logger;

    // Parse config
    // Registry
    config.registry = config.registry || bowerRegistry;
    if (typeof config.registry === 'string') {
        config.registry = {
            search: [config.registry],
            register: config.registry,
            publish: config.registry
        };
    } else {
        config.registry = deepExtend({
            search: bowerRegistry,
            register: bowerRegistry,
            publish: bowerRegistry
        }, config.registry);

        if (!Array.isArray(config.registry.search)) {
            config.registry.search = [config.registry.search];
        }
    }

    // Ensure that every registry endpoint does not end with /
    config.registry.search = config.registry.search.map(function (url) {
        return url.replace(/\/+$/, '');
    });
    config.registry.register = config.registry.register.replace(/\/+$/, '');
    config.registry.publish = config.registry.publish.replace(/\/+$/, '');

    // CA
    if (typeof config.ca === 'string') {
        config.ca = {
            search: [config.ca],
            register: config.ca,
            publish: config.ca
        };
    } else {
        config.ca = config.ca || {};

        if (config.ca.search && !Array.isArray(config.ca.search)) {
            config.ca.search = [config.ca.search];
        }
    }

    // Cache
    if (!config.cache) {
        config.cache = path.join(os.tmpdir ? os.tmpdir() : os.tmpDir(), 'bower-registry');
    }

    // Timeout
    if (typeof config.timeout !== 'number') {
        config.timeout = 60000;
    }

    // Strict ssl
    config.strictSsl = config.strictSsl == null ? true : !!config.strictSsl;

    // Init the cache
    this._initCache();
}

// Add every method to the prototype
RegistryClient.prototype.lookup = methods.lookup;
RegistryClient.prototype.search = methods.search;
RegistryClient.prototype.list = methods.list;
RegistryClient.prototype.register = methods.register;

RegistryClient.prototype.clearCache = function (name, callback) {
    if (typeof name === 'function') {
        callback = name;
        name = null;
    }

    async.parallel([
        this.lookup.clearCache.bind(this, name),
        this.search.clearCache.bind(this, name),
        this.list.clearCache.bind(this)
    ], callback);
};

RegistryClient.prototype.resetCache = function (name) {
    this.lookup.resetCache.call(this, name);
    this.search.resetCache.call(this, name);
    this.list.resetCache.call(this);

    return this;
};

RegistryClient.clearRuntimeCache = function () {
    Cache.clearRuntimeCache();
};

// -----------------------------

RegistryClient.prototype._initCache = function () {
    var cache;
    var dir = this._config.cache;

    // Cache is stored/retrieved statically to ensure singularity
    // among instances
    cache = this.constructor._cache = this.constructor._cache || {};
    this._cache = cache[dir] = cache[dir] || {};

    this.lookup.initCache.call(this);
    this.search.initCache.call(this);
    this.list.initCache.call(this);
};

module.exports = RegistryClient;
<commit_msg>Fix tests.<commit_after>var os = require('os');
var path = require('path');
var async = require('async');
var deepExtend = require('deep-extend');
var methods = require('./lib');
var Cache = require('./lib/util/Cache');

function RegistryClient(config, logger) {
    var bowerRegistry = 'https://bower.herokuapp.com';

    config = config || {};
    this._config = config;
    this._logger = logger;

    // Parse config
    // Registry
    config.registry = config.registry || bowerRegistry;
    if (typeof config.registry === 'string') {
        config.registry = {
            search: [config.registry],
            register: config.registry,
            publish: config.registry
        };
    } else {
        config.registry = deepExtend({
            search: bowerRegistry,
            register: bowerRegistry,
            publish: bowerRegistry
        }, config.registry);

        if (!Array.isArray(config.registry.search)) {
            config.registry.search = [config.registry.search];
        }
    }

    // Ensure that every registry endpoint does not end with /
    config.registry.search = config.registry.search.map(function (url) {
        return url.replace(/\/+$/, '');
    });
    config.registry.register = config.registry.register.replace(/\/+$/, '');
    config.registry.publish = config.registry.publish.replace(/\/+$/, '');

    // CA
    if (typeof config.ca === 'string') {
        config.ca = {
            search: [config.ca],
            register: config.ca,
            publish: config.ca
        };
    } else {
        config.ca = deepExtend({
            search: []
        }, config.ca);

        if (!Array.isArray(config.ca.search)) {
            config.ca.search = [config.ca.search];
        }
    }

    // Cache
    if (!config.cache) {
        config.cache = path.join(os.tmpdir ? os.tmpdir() : os.tmpDir(), 'bower-registry');
    }

    // Timeout
    if (typeof config.timeout !== 'number') {
        config.timeout = 60000;
    }

    // Strict ssl
    config.strictSsl = config.strictSsl == null ? true : !!config.strictSsl;

    // Init the cache
    this._initCache();
}

// Add every method to the prototype
RegistryClient.prototype.lookup = methods.lookup;
RegistryClient.prototype.search = methods.search;
RegistryClient.prototype.list = methods.list;
RegistryClient.prototype.register = methods.register;

RegistryClient.prototype.clearCache = function (name, callback) {
    if (typeof name === 'function') {
        callback = name;
        name = null;
    }

    async.parallel([
        this.lookup.clearCache.bind(this, name),
        this.search.clearCache.bind(this, name),
        this.list.clearCache.bind(this)
    ], callback);
};

RegistryClient.prototype.resetCache = function (name) {
    this.lookup.resetCache.call(this, name);
    this.search.resetCache.call(this, name);
    this.list.resetCache.call(this);

    return this;
};

RegistryClient.clearRuntimeCache = function () {
    Cache.clearRuntimeCache();
};

// -----------------------------

RegistryClient.prototype._initCache = function () {
    var cache;
    var dir = this._config.cache;

    // Cache is stored/retrieved statically to ensure singularity
    // among instances
    cache = this.constructor._cache = this.constructor._cache || {};
    this._cache = cache[dir] = cache[dir] || {};

    this.lookup.initCache.call(this);
    this.search.initCache.call(this);
    this.list.initCache.call(this);
};

module.exports = RegistryClient;
<|endoftext|>