<commit_before>import BaseLfo from '../core/BaseLfo';
import parameters from 'parameters';

/**
 * Create a function that returns time in seconds according to the current
 * environnement (node or browser).
 * If running in node the time rely on `process.hrtime`, while if in the browser
 * it is provided by the `currentTime` of an `AudioContext`, this context can
 * optionnaly be provided to keep time consistency between several `EventIn`
 * nodes.
 *
 * @param {AudioContext} [audioContext=null] - Optionnal audio context.
 * @return {Function}
 * @private
 */
function getTimeFunction(audioContext = null) {
  if (typeof window === 'undefined') {
    return () => {
      const t = process.hrtime();
      return t[0] + t[1] * 1e-9;
    }
  } else {
    if (audioContext === null || (!audioContext instanceof AudioContext))
      audioContext = new AudioContext();

    return () => audioContext.currentTime;
  }
}

const definitions = {
  absoluteTime: {
    type: 'boolean',
    default: false,
    constant: true,
  },
  frameType: {
    type: 'enum',
    list: ['signal', 'vector', 'scalar'],
    default: 'signal',
    constant: true,
  },
  frameSize: {
    type: 'integer',
    default: 1,
    min: 1,
    max: +Infinity, // not recommended...
    metas: { kind: 'static' },
  },
  sampleRate: {
    type: 'float',
    default: 0,
    min: 0,
    max: +Infinity, // same here
    metas: { kind: 'static' },
  },
  description: {
    type: 'any',
    default: null,
    constant: true,
  }
};

/**
 * The `EventIn` operator allows to manually create a stream of data or to feed
 * a stream from another source (e.g. accelerometers) into a processing graph.
 *
 * @param {Object} options - Override parameters' default values.
 * @param {String} [options.frameType='signal'] - Type of the input - allowed
 * values: `signal`,  `vector` or `scalar`.
 * @param {Number} [options.frameSize=1] - Size of the output frame.
 * @param {Number} [options.sampleRate=0] - Rate of the source stream.
 * @param {Array|String} [options.description] - Optionnal description
 *  describing the dimensions of the output frame
 * @param {Boolean} [options.absoluteTime=false] - Define if time should be used
 *  as forwarded as given in the process method, or relatively to the time of
 *  the first `process` call after start.
 *
 * @memberof module:source
 *
 * @todo - Add a `logicalTime` parameter to tag frame acoording to frame rate.
 * @todo - Define if it makes sens to define a `frameRate` if time is defined
 *  on the fly.
 * @todo - For vector frame type use `options.frameRate` instead of
 *  `options.sampleRate`.
 *
 * @example
 * import * as lfo from 'waves-lfo';
 *
 * const eventIn = new lfo.source.EventIn({
 *   frameType: 'vector',
 *   frameSize: 3,
 *   frameRate: 1 / 50,
 *   description: ['alpha', 'beta', 'gamma']
 * });
 *
 * // connect source to operators and sink(s)
 *
 * // initialize the whole graph
 * eventIn.start();
 *
 * // feed `deviceorientation` data into the graph
 * window.addEventListener('deviceorientation', (e) => {
 *   const frame = {
 *     time: new Date().getTime(),
 *     data: [e.alpha, e.beta, e.gamma],
 *   };
 *   // by setting `time` to null, we let the node create
 *   // a relative time tag internally
 *   eventIn.processFrame(frame);
 * }, false);
 */
class EventIn extends BaseLfo {
  constructor(options = {}) {
    super();

    // audioContext is not a real parameter so cache it and remove from options
    const audioContext = options.audioContext;
    delete options.audioContext;

    this.params = parameters(definitions, options);
    this.params.addListener(this.onParamUpdate);

    this._getTime = getTimeFunction(audioContext);
    this._isStarted = false;
    this._startTime = null;
    this._systemTime = null;
    this._absoluteTime = this.params.get('absoluteTime');
  }

  /**
   * Start the whole graph, propagate the `streamParams` in the graph. Any call
   * to `process` or `processFrame` before `start` will be ignored.
   *
   * @see {@link module:core.BaseLfo#processStreamParams}
   * @see {@link module:core.BaseLfo#resetStream}
   * @see {@link module:source.EventIn#stop}
   */
  start(startTime = null) {
    this.processStreamParams();
    this.resetStream();

    this._startTime = startTime;
    this._isStarted = true;
    // values set in the first `process` call
    this._systemTime = null;
  }

  /**
   * Stop the whole graph, and finalize the strem. Any call to `process` after
   * `stop` will be ignored.
   *
   * @see {@link module:core.BaseLfo#finalizeStream}
   * @see {@link module:source.EventIn#start}
   */
  stop() {
    if (this._isStarted && this._startTime) {
      const currentTime = this._getTime();
      const endTime = this.frame.time + (currentTime - this._systemTime);

      this.finalizeStream(endTime);
      this._isStarted = false;
    }
  }

  /** @private */
  processStreamParams() {
    const frameSize = this.params.get('frameSize');
    const frameType = this.params.get('frameType');
    const sampleRate = this.params.get('sampleRate');
    const description = this.params.get('description');
    // init operator's stream params
    this.streamParams.frameSize = frameSize;
    this.streamParams.frameType = frameType;
    this.streamParams.sourceSampleRate = sampleRate;
    this.streamParams.description = description;

    if (frameType === 'signal')
      this.streamParams.frameRate = sampleRate / frameSize;
    else // `vector` or `scalar`
      this.streamParams.frameRate = sampleRate;

    this.propagateStreamParams();
  }

  /**
   * Propagate a frame object in the graph.
   *
   * @param {Object} frame - Input frame.
   * @param {Number} frame.time - Frame time.
   * @param {Float32Array|Array} frame.data - Frame data.
   * @param {Object} [frame.metadata=undefined] - Optionnal frame metadata.
   *
   * @example
   * eventIn.processFrame({ time: 1, data: [0, 1, 2] });
   */
  processFrame(frame) {
    if (!this._isStarted) return;

    this.prepareFrame();
    this.processFunction(frame);
    this.propagateFrame();
  }

  /**
   * Alternative interface to propagate a frame in the graph. Pack `time`,
   * `data` and `metadata` in a frame object.
   *
   * @param {Number} time - Frame time.
   * @param {Float32Array|Array} data - Frame data.
   * @param {Object} metadata - Optionnal frame metadata.
   *
   * @example
   * eventIn.process(1, [0, 1, 2]);
   * // is equivalent to
   * eventIn.processFrame({ time: 1, data: [0, 1, 2] });
   */
  process(time, data, metadata = null) {
    this.processFrame({ time, data, metadata });
  }

  /** @private */
  processFunction(frame) {
    const currentTime = this._getTime();
    const inData = frame.data.length ? frame.data : [frame.data];
    const outData = this.frame.data;
    // if no time provided, use system time
    let time = Number.isFinite(frame.time) ? frame.time : currentTime;

    if (this._startTime = null)
      this._startTime = time;

    if (this._absoluteTime === false)
      time = time - this._startTime;

    for (let i = 0, l = this.streamParams.frameSize; i < l; i++)
      outData[i] = inData[i];

    this.frame.time = time;
    this.frame.metadata = frame.metadata;
    // store current time to compute `endTime` on stop
    this._systemTime = currentTime;
  }
}

export default EventIn;
<commit_msg>updated<commit_after>import BaseLfo from '../core/BaseLfo';

/**
 * Create a function that returns time in seconds according to the current
 * environnement (node or browser).
 * If running in node the time rely on `process.hrtime`, while if in the browser
 * it is provided by the `currentTime` of an `AudioContext`, this context can
 * optionnaly be provided to keep time consistency between several `EventIn`
 * nodes.
 *
 * @param {AudioContext} [audioContext=null] - Optionnal audio context.
 * @return {Function}
 * @private
 */
function getTimeFunction(audioContext = null) {
  if (typeof window === 'undefined') {
    return () => {
      const t = process.hrtime();
      return t[0] + t[1] * 1e-9;
    }
  } else {
    if (audioContext === null || (!audioContext instanceof AudioContext))
      audioContext = new AudioContext();

    return () => audioContext.currentTime;
  }
}

const definitions = {
  absoluteTime: {
    type: 'boolean',
    default: false,
    constant: true,
  },
  audioContext: {
    type: 'any',
    default: null,
    constant: true,
    nullable: true,
  },
  frameType: {
    type: 'enum',
    list: ['signal', 'vector', 'scalar'],
    default: 'signal',
    constant: true,
  },
  frameSize: {
    type: 'integer',
    default: 1,
    min: 1,
    max: +Infinity, // not recommended...
    metas: { kind: 'static' },
  },
  sampleRate: {
    type: 'float',
    default: null,
    min: 0,
    max: +Infinity, // same here
    nullable: true,
    metas: { kind: 'static' },
  },
  frameRate: {
    type: 'float',
    default: null,
    min: 0,
    max: +Infinity, // same here
    nullable: true,
    metas: { kind: 'static' },
  },
  description: {
    type: 'any',
    default: null,
    constant: true,
  }
};

/**
 * The `EventIn` operator allows to manually create a stream of data or to feed
 * a stream from another source (e.g. sensors) into a processing graph.
 *
 * @param {Object} options - Override parameters' default values.
 * @param {String} [options.frameType='signal'] - Type of the input - allowed
 * values: `signal`,  `vector` or `scalar`.
 * @param {Number} [options.frameSize=1] - Size of the output frame.
 * @param {Number} [options.sampleRate=null] - Sample rate of the source stream,
 *  if of type `signal`.
 * @param {Number} [options.frameRate=null] - Rate of the source stream, if of
 *  type `vector`.
 * @param {Array|String} [options.description] - Optionnal description
 *  describing the dimensions of the output frame
 * @param {Boolean} [options.absoluteTime=false] - Define if time should be used
 *  as forwarded as given in the process method, or relatively to the time of
 *  the first `process` call after start.
 *
 * @memberof module:source
 *
 * @todo - Add a `logicalTime` parameter to tag frame according to frame rate.
 *
 * @example
 * import * as lfo from 'waves-lfo';
 *
 * const eventIn = new lfo.source.EventIn({
 *   frameType: 'vector',
 *   frameSize: 3,
 *   frameRate: 1 / 50,
 *   description: ['alpha', 'beta', 'gamma'],
 * });
 *
 * // connect source to operators and sink(s)
 *
 * // initialize and start the graph
 * eventIn.start();
 *
 * // feed `deviceorientation` data into the graph
 * window.addEventListener('deviceorientation', (e) => {
 *   const frame = {
 *     time: new Date().getTime(),
 *     data: [e.alpha, e.beta, e.gamma],
 *   };
 *
 *   eventIn.processFrame(frame);
 * }, false);
 */
class EventIn extends BaseLfo {
  constructor(options = {}) {
    super(definitions, options);

    const audioContext = this.params.get('audioContext');
    this._getTime = getTimeFunction(audioContext);
    this._isStarted = false;
    this._startTime = null;
    this._systemTime = null;
    this._absoluteTime = this.params.get('absoluteTime');

    this.initialize();
  }

  /**
   * Propagate the `streamParams` in the graph and allow to input frames into
   * the graph. Any call to `process` or `processFrame` before `start` will be
   * ignored.
   *
   * @see {@link module:core.BaseLfo#processStreamParams}
   * @see {@link module:core.BaseLfo#resetStream}
   * @see {@link module:source.EventIn#stop}
   */
  start(startTime = null) {
    this.processStreamParams();
    this.resetStream();

    this._startTime = startTime;
    this._isStarted = true;
    // values set in the first `process` call
    this._systemTime = null;
  }

  /**
   * Finalize the stream and stop the whole graph. Any call to `process` or
   * `processFrame` after `stop` will be ignored.
   *
   * @see {@link module:core.BaseLfo#finalizeStream}
   * @see {@link module:source.EventIn#start}
   */
  stop() {
    if (this._isStarted && this._startTime) {
      const currentTime = this._getTime();
      const endTime = this.frame.time + (currentTime - this._systemTime);

      this.finalizeStream(endTime);
      this._isStarted = false;
    }
  }

  /** @private */
  processStreamParams() {
    const frameSize = this.params.get('frameSize');
    const frameType = this.params.get('frameType');
    const sampleRate = this.params.get('sampleRate');
    const description = this.params.get('description');
    // init operator's stream params
    this.streamParams.frameSize = frameSize;
    this.streamParams.frameType = frameType;
    this.streamParams.description = description;

    if (frameType === 'signal') {
      if (sampleRate === null)
        throw new Error('Undefined "sampleRate" for "signal" stream');

      this.streamParams.sourceSampleRate = sampleRate;
      this.streamParams.frameRate = sampleRate / frameSize;
    } else { // `vector` or `scalar`
      if (sampleRate === null)
        throw new Error('Undefined "frameRate" for "vector" stream');

      this.streamParams.frameRate = frameRate;
      this.streamParams.sampleRate = frameRate;
    }

    this.propagateStreamParams();
  }

  /** @private */
  processFunction(frame) {
    const currentTime = this._getTime();
    const inData = frame.data.length ? frame.data : [frame.data];
    const outData = this.frame.data;
    // if no time provided, use system time
    let time = Number.isFinite(frame.time) ? frame.time : currentTime;

    if (this._startTime === null)
      this._startTime = time;

    if (this._absoluteTime === false)
      time = time - this._startTime;

    for (let i = 0, l = this.streamParams.frameSize; i < l; i++)
      outData[i] = inData[i];

    this.frame.time = time;
    this.frame.metadata = frame.metadata;
    // store current time to compute `endTime` on stop
    this._systemTime = currentTime;
  }

  /**
   * Alternative interface to propagate a frame in the graph. Pack `time`,
   * `data` and `metadata` in a frame object.
   *
   * @param {Number} time - Frame time.
   * @param {Float32Array|Array} data - Frame data.
   * @param {Object} metadata - Optionnal frame metadata.
   *
   * @example
   * eventIn.process(1, [0, 1, 2]);
   * // is equivalent to
   * eventIn.processFrame({ time: 1, data: [0, 1, 2] });
   */
  process(time, data, metadata = null) {
    this.processFrame({ time, data, metadata });
  }

  /**
   * Propagate a frame object in the graph.
   *
   * @param {Object} frame - Input frame.
   * @param {Number} frame.time - Frame time.
   * @param {Float32Array|Array} frame.data - Frame data.
   * @param {Object} [frame.metadata=undefined] - Optionnal frame metadata.
   *
   * @example
   * eventIn.processFrame({ time: 1, data: [0, 1, 2] });
   */
  processFrame(frame) {
    if (!this._isStarted) return;

    this.prepareFrame();
    this.processFunction(frame);
    this.propagateFrame();
  }
}

export default EventIn;
<|endoftext|>


<commit_before>import DataSource, {NetworkTileSource} from './data_source';
import Tile from '../tile';
import Geo from '../geo';

export class RasterTileSource extends NetworkTileSource {

    constructor(source, sources) {
        super(source, sources);

        if (this.rasters.indexOf(this.name) === -1) {
            this.rasters.unshift(this.name); // add this raster as the first
        }
        this.filtering = source.filtering; // optional texture filtering (nearest, linear, mipmap)

        // save texture objects by tile key, so URL remains stable if tile is built multiple times,
        // e.g. avoid re-loading the same tile texture under a different subdomain when using tile hosts
        this.textures = {};
    }

    load(tile) {
        tile.source_data = {};
        tile.source_data.layers = {};
        tile.pad_scale = this.pad_scale;
        tile.rasters = [...this.rasters]; // copy list of rasters to load for tile

        // Generate a single quad that fills the entire tile
        let scale = Geo.tile_scale;
        tile.source_data.layers = {
            _default: {
                type: 'FeatureCollection',
                features: [{
                    geometry: {
                        type: 'Polygon',
                        coordinates: [[
                            [0, 0], [scale, 0],
                            [scale, -scale], [0, -scale], [0, 0]
                        ]]
                    },
                    properties: {}
                }]
            }
        };

        tile.default_winding = 'CW';
        return Promise.resolve(tile);
    }

    // Return texture info for a raster tile
    tileTexture (tile) {
        let coords = Tile.normalizedCoordinate(tile.coords, { max_zoom: this.max_zoom, zoom_bias: this.zoom_offset });
        let key = coords.key;
        if (!this.textures[key]) {
            let url = this.formatUrl(this.url, { coords });
            this.textures[key] = { url, filtering: this.filtering, coords };
        }
        return this.textures[key];
    }

}

DataSource.register(RasterTileSource, 'Raster');
<commit_msg>correctly downsample attached rasters that have *less* zoom detail than the attaching source add warning when attached rasters that support *more* zoom detail have to be downsampled (was working, but silently) fix bug where standalone raster sources were applying their zoom_offset twice<commit_after>import DataSource, {NetworkTileSource} from './data_source';
import Tile from '../tile';
import Geo from '../geo';
import log from '../utils/log';

export class RasterTileSource extends NetworkTileSource {

    constructor(source, sources) {
        super(source, sources);

        if (this.rasters.indexOf(this.name) === -1) {
            this.rasters.unshift(this.name); // add this raster as the first
        }
        this.filtering = source.filtering; // optional texture filtering (nearest, linear, mipmap)

        // save texture objects by tile key, so URL remains stable if tile is built multiple times,
        // e.g. avoid re-loading the same tile texture under a different subdomain when using tile hosts
        this.textures = {};
    }

    load(tile) {
        tile.source_data = {};
        tile.source_data.layers = {};
        tile.pad_scale = this.pad_scale;
        tile.rasters = [...this.rasters]; // copy list of rasters to load for tile

        // Generate a single quad that fills the entire tile
        let scale = Geo.tile_scale;
        tile.source_data.layers = {
            _default: {
                type: 'FeatureCollection',
                features: [{
                    geometry: {
                        type: 'Polygon',
                        coordinates: [[
                            [0, 0], [scale, 0],
                            [scale, -scale], [0, -scale], [0, 0]
                        ]]
                    },
                    properties: {}
                }]
            }
        };

        tile.default_winding = 'CW';
        return Promise.resolve(tile);
    }

    // Return texture info for a raster tile
    tileTexture (tile) {
        let coords = this.adjustRasterTileZoom(tile);

        let key = coords.key;
        if (!this.textures[key]) {
            let url = this.formatUrl(this.url, { coords });
            this.textures[key] = { url, filtering: this.filtering, coords };
        }
        return this.textures[key];
    }

    // If the raster is attached to another source, we need to compare their levels of zoom detail
    // to see if any adjustments are needed. Both the `tile_size` and `zoom_offset` data source params
    // cause the zoom level to be downsampled relative to the "base" zoom level of the map view.
    // The attaching source has already applied its own zoom downsampling. If this source has a lower
    // level of detail, we apply the remaining differential here.
    adjustRasterTileZoom (tile) {
        let coords = tile.coords;
        const tile_source = this.sources[tile.source];
        if (tile_source !== this) { // no-op if the raster source isn't being rendered as an attachment
            let zdiff = this.zoom_bias - tile_source.zoom_bias; // difference in zoom detail between the sources
            if (zdiff > 0) { // raster source is less detailed
                // do extra zoom adjustment and apply this raster source's max zoom
                coords = Tile.normalizedCoordinate(tile.coords, {
                    zoom_bias: zdiff,
                    max_zoom: this.max_zoom
                });
            }
            else {
                // raster source supports higher detail, but was downsampled to match (the downsampling already
                // happened upstream, when the attaching source calculated its own tile coordinate)
                if (zdiff < 0) {
                    log({ level: 'warn', once: true},
                        `Raster source '${this.name}' supports higher zoom detail than source '${tile_source.name}' ` +
                        `it's attached to. Downsampling this source ${-zdiff} extra zoom levels to match.`
                    );
                }

                // no extra zoom adjustment needed, but still need to apply this raster source's max zoom
                coords = Tile.coordinateWithMaxZoom(coords, this.max_zoom);
            }
        }
        return coords;
    }

}

DataSource.register(RasterTileSource, 'Raster');
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @providesModule Button
 * @flow
 */
'use strict';

const ColorPropType = require('ColorPropType');
const Platform = require('Platform');
const React = require('React');
const PropTypes = require('prop-types');
const StyleSheet = require('StyleSheet');
const Text = require('Text');
const TouchableNativeFeedback = require('TouchableNativeFeedback');
const TouchableOpacity = require('TouchableOpacity');
const View = require('View');

const invariant = require('fbjs/lib/invariant');

/**
 * A basic button component that should render nicely on any platform. Supports
 * a minimal level of customization.
 *
 *
 *
 * If this button doesn't look right for your app, you can build your own
 * button using [TouchableOpacity](docs/touchableopacity.html)
 * or [TouchableNativeFeedback](docs/touchablenativefeedback.html).
 * For inspiration, look at the [source code for this button component](https://github.com/facebook/react-native/blob/master/Libraries/Components/Button.js).
 * Or, take a look at the [wide variety of button components built by the community](https://js.coach/react-native?search=button).
 *
 * Example usage:
 *
 * ```
 * import { Button } from 'react-native';
 * ...
 *
 * <Button
 *   onPress={onPressLearnMore}
 *   title="Learn More"
 *   color="#841584"
 *   accessibilityLabel="Learn more about this purple button"
 * />
 * ```
 *
 */

class Button extends React.Component<{
  title: string,
  onPress: () => any,
  color?: ?string,
  hasTVPreferredFocus?: ?boolean,
  accessibilityLabel?: ?string,
  disabled?: ?boolean,
  testID?: ?string,
  hasTVPreferredFocus?: ?boolean,
}> {
  static propTypes = {
    /**
     * Text to display inside the button
     */
    title: PropTypes.string.isRequired,
    /**
     * Text to display for blindness accessibility features
     */
    accessibilityLabel: PropTypes.string,
    /**
     * Color of the text (iOS), or background color of the button (Android)
     */
    color: ColorPropType,
    /**
     * If true, disable all interactions for this component.
     */
    disabled: PropTypes.bool,
    /**
     * TV preferred focus (see documentation for the View component).
     */
    hasTVPreferredFocus: PropTypes.bool,
    /**
     * Handler to be called when the user taps the button
     */
    onPress: PropTypes.func.isRequired,
    /**
     * Used to locate this view in end-to-end tests.
     */
    testID: PropTypes.string,
  };

  render() {
    const {
      accessibilityLabel,
      color,
      onPress,
      title,
      hasTVPreferredFocus,
      disabled,
      testID,
    } = this.props;
    const buttonStyles = [styles.button];
    const textStyles = [styles.text];
    if (color) {
      if (Platform.OS === 'ios') {
        textStyles.push({color: color});
      } else {
        buttonStyles.push({backgroundColor: color});
      }
    }
    const accessibilityTraits = ['button'];
    if (disabled) {
      buttonStyles.push(styles.buttonDisabled);
      textStyles.push(styles.textDisabled);
      accessibilityTraits.push('disabled');
    }
    invariant(
      typeof title === 'string',
      'The title prop of a Button must be a string',
    );
    const formattedTitle = Platform.OS === 'android' ? title.toUpperCase() : title;
    const Touchable = Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity;
    return (
      <Touchable
        accessibilityComponentType="button"
        accessibilityLabel={accessibilityLabel}
        accessibilityTraits={accessibilityTraits}
        hasTVPreferredFocus={hasTVPreferredFocus}
        testID={testID}
        disabled={disabled}
        onPress={onPress}>
        <View style={buttonStyles}>
          <Text style={textStyles} disabled={disabled}>{formattedTitle}</Text>
        </View>
      </Touchable>
    );
  }
}

const styles = StyleSheet.create({
  button: Platform.select({
    ios: {},
    android: {
      elevation: 4,
      // Material design blue from https://material.google.com/style/color.html#color-color-palette
      backgroundColor: '#2196F3',
      borderRadius: 2,
    },
  }),
  text: Platform.select({
    ios: {
      // iOS blue from https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/
      color: '#007AFF',
      textAlign: 'center',
      padding: 8,
      fontSize: 18,
    },
    android: {
      color: 'white',
      textAlign: 'center',
      padding: 8,
      fontWeight: '500',
    },
  }),
  buttonDisabled: Platform.select({
    ios: {},
    android: {
      elevation: 0,
      backgroundColor: '#dfdfdf',
    }
  }),
  textDisabled: Platform.select({
    ios: {
      color: '#cdcdcd',
    },
    android: {
      color: '#a1a1a1',
    }
  }),
});

module.exports = Button;
<commit_msg>Remove duplicate prop definition<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @providesModule Button
 * @flow
 */
'use strict';

const ColorPropType = require('ColorPropType');
const Platform = require('Platform');
const React = require('React');
const PropTypes = require('prop-types');
const StyleSheet = require('StyleSheet');
const Text = require('Text');
const TouchableNativeFeedback = require('TouchableNativeFeedback');
const TouchableOpacity = require('TouchableOpacity');
const View = require('View');

const invariant = require('fbjs/lib/invariant');

/**
 * A basic button component that should render nicely on any platform. Supports
 * a minimal level of customization.
 *
 *
 *
 * If this button doesn't look right for your app, you can build your own
 * button using [TouchableOpacity](docs/touchableopacity.html)
 * or [TouchableNativeFeedback](docs/touchablenativefeedback.html).
 * For inspiration, look at the [source code for this button component](https://github.com/facebook/react-native/blob/master/Libraries/Components/Button.js).
 * Or, take a look at the [wide variety of button components built by the community](https://js.coach/react-native?search=button).
 *
 * Example usage:
 *
 * ```
 * import { Button } from 'react-native';
 * ...
 *
 * <Button
 *   onPress={onPressLearnMore}
 *   title="Learn More"
 *   color="#841584"
 *   accessibilityLabel="Learn more about this purple button"
 * />
 * ```
 *
 */

class Button extends React.Component<{
  title: string,
  onPress: () => any,
  color?: ?string,
  hasTVPreferredFocus?: ?boolean,
  accessibilityLabel?: ?string,
  disabled?: ?boolean,
  testID?: ?string,
}> {
  static propTypes = {
    /**
     * Text to display inside the button
     */
    title: PropTypes.string.isRequired,
    /**
     * Text to display for blindness accessibility features
     */
    accessibilityLabel: PropTypes.string,
    /**
     * Color of the text (iOS), or background color of the button (Android)
     */
    color: ColorPropType,
    /**
     * If true, disable all interactions for this component.
     */
    disabled: PropTypes.bool,
    /**
     * TV preferred focus (see documentation for the View component).
     */
    hasTVPreferredFocus: PropTypes.bool,
    /**
     * Handler to be called when the user taps the button
     */
    onPress: PropTypes.func.isRequired,
    /**
     * Used to locate this view in end-to-end tests.
     */
    testID: PropTypes.string,
  };

  render() {
    const {
      accessibilityLabel,
      color,
      onPress,
      title,
      hasTVPreferredFocus,
      disabled,
      testID,
    } = this.props;
    const buttonStyles = [styles.button];
    const textStyles = [styles.text];
    if (color) {
      if (Platform.OS === 'ios') {
        textStyles.push({color: color});
      } else {
        buttonStyles.push({backgroundColor: color});
      }
    }
    const accessibilityTraits = ['button'];
    if (disabled) {
      buttonStyles.push(styles.buttonDisabled);
      textStyles.push(styles.textDisabled);
      accessibilityTraits.push('disabled');
    }
    invariant(
      typeof title === 'string',
      'The title prop of a Button must be a string',
    );
    const formattedTitle = Platform.OS === 'android' ? title.toUpperCase() : title;
    const Touchable = Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity;
    return (
      <Touchable
        accessibilityComponentType="button"
        accessibilityLabel={accessibilityLabel}
        accessibilityTraits={accessibilityTraits}
        hasTVPreferredFocus={hasTVPreferredFocus}
        testID={testID}
        disabled={disabled}
        onPress={onPress}>
        <View style={buttonStyles}>
          <Text style={textStyles} disabled={disabled}>{formattedTitle}</Text>
        </View>
      </Touchable>
    );
  }
}

const styles = StyleSheet.create({
  button: Platform.select({
    ios: {},
    android: {
      elevation: 4,
      // Material design blue from https://material.google.com/style/color.html#color-color-palette
      backgroundColor: '#2196F3',
      borderRadius: 2,
    },
  }),
  text: Platform.select({
    ios: {
      // iOS blue from https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/
      color: '#007AFF',
      textAlign: 'center',
      padding: 8,
      fontSize: 18,
    },
    android: {
      color: 'white',
      textAlign: 'center',
      padding: 8,
      fontWeight: '500',
    },
  }),
  buttonDisabled: Platform.select({
    ios: {},
    android: {
      elevation: 0,
      backgroundColor: '#dfdfdf',
    }
  }),
  textDisabled: Platform.select({
    ios: {
      color: '#cdcdcd',
    },
    android: {
      color: '#a1a1a1',
    }
  }),
});

module.exports = Button;
<|endoftext|>


<commit_before>"use strict";

if (typeof (JSIL) === "undefined")
  throw new Error("JSIL.Core required");

if (typeof ($jsilstorage) === "undefined")
  throw new Error("JSIL.Storage required");

var $jsillocalstorage = JSIL.DeclareAssembly("JSIL.LocalStorage");

JSIL.MakeClass($jsilstorage.TypeRef("VirtualVolume"), "LocalStorageVolume", true, [], function ($) {
  $.RawMethod(false, ".ctor", function (name, rootPath) {
    var existingInodes = this.readInodes(name);

    VirtualVolume.prototype._ctor.call(
      this, name, rootPath, existingInodes
    );
  });

  var getKey = function (name) {
    return "storage_" + name;
  };

  $.RawMethod(false, "readInodes", function (name) {
    var key = getKey(name) + "_inodes";

    var json = localStorage.getItem(key);
    if (json)
      return JSON.parse(json);
    else
      return null;
  });

  $.RawMethod(false, "writeInodes", function (name) {
    var key = getKey(name) + "_inodes";

    localStorage.setItem(key, JSON.stringify(this.inodes));
  });

  $.RawMethod(false, "flush", function () {
    this.writeInodes(this.name);
  });

  $.RawMethod(false, "deleteFileBytes", function (name) {
    var key = getKey(this.name) + "_blobs_" + name;

    localStorage.removeItem(key);

    JSIL.Host.logWriteLine("Deleted '" + name + "' from local storage.");
  });

  $.RawMethod(false, "getFileBytes", function (name) {
    var key = getKey(this.name) + "_blobs_" + name;

    var json = localStorage.getItem(key);

    if (json) {
      JSIL.Host.logWriteLine("Loaded '" + name + "' from local storage.");

      var array = JSON.parse(json);
      if (window.Uint8Array) {
        return new Uint8Array(array);
      } else {
        return array;
      }
    } else {
      JSIL.Host.logWriteLine("Could not find '" + name + "' in local storage.");
      return null;
    }
  });

  $.RawMethod(false, "setFileBytes", function (name, value) {
    var key = getKey(this.name) + "_blobs_" + name;

    // JSON.stringify turns typed arrays into dictionaries :|

    var json = "[";
    for (var i = 0; i < value.length; i++) {
      if (i !== 0)
        json += ",";

      json += value[i];
    }
    json += "]";

    localStorage.setItem(key, json);

    JSIL.Host.logWriteLine("Saved '" + name + "' to local storage.");
  });

  $.RawMethod(false, "toString", function () {
    return "<LocalStorage Volume '" + this.name + "'>";
  });
});

JSIL.LocalStorage = {};
JSIL.LocalStorage.Initialize = function (appName) {
  var volume = null;

  JSIL.RegisterStorageProvider({
    getVolumes: function () {
      if (!volume)
        volume = new LocalStorageVolume(appName, "/");

      return [volume];
    }
  });
};<commit_msg>When a localStorage write fails, log diagnostic information.<commit_after>"use strict";

if (typeof (JSIL) === "undefined")
  throw new Error("JSIL.Core required");

if (typeof ($jsilstorage) === "undefined")
  throw new Error("JSIL.Storage required");

var $jsillocalstorage = JSIL.DeclareAssembly("JSIL.LocalStorage");

JSIL.MakeClass($jsilstorage.TypeRef("VirtualVolume"), "LocalStorageVolume", true, [], function ($) {
  $.RawMethod(false, ".ctor", function (name, rootPath) {
    var existingInodes = this.readInodes(name);

    VirtualVolume.prototype._ctor.call(
      this, name, rootPath, existingInodes
    );
  });

  var handleLocalStorageFailure = function (exc, fn) {
    JSIL.Host.logWriteLine("Failed to write '" + fn + "' to local storage. Local storage currently contains " + localStorage.length + " key(s):");
    for (var i = 0, l = localStorage.length; i < l; i++) {
      var key = localStorage.key(i);
      JSIL.Host.logWriteLine(System.String.Format("{0:0000.0}kb '{1}'", (localStorage[key] || "").length / 1024, key));
    }

    throw exc;
  };

  var getKey = function (name) {
    return "storage_" + name;
  };

  $.RawMethod(false, "readInodes", function (name) {
    var key = getKey(name) + "_inodes";

    var json = localStorage.getItem(key);
    if (json)
      return JSON.parse(json);
    else
      return null;
  });

  $.RawMethod(false, "writeInodes", function (name) {
    var key = getKey(name) + "_inodes";

    try {
      localStorage.setItem(key, JSON.stringify(this.inodes));
    } catch (exc) {
      handleLocalStorageFailure(exc, "inodes");
    }
  });

  $.RawMethod(false, "flush", function () {
    this.writeInodes(this.name);
  });

  $.RawMethod(false, "deleteFileBytes", function (name) {
    var key = getKey(this.name) + "_blobs_" + name;

    localStorage.removeItem(key);

    JSIL.Host.logWriteLine("Deleted '" + name + "' from local storage.");
  });

  $.RawMethod(false, "getFileBytes", function (name) {
    var key = getKey(this.name) + "_blobs_" + name;

    var json = localStorage.getItem(key);

    if (json) {
      JSIL.Host.logWriteLine("Loaded '" + name + "' from local storage.");

      var array = JSON.parse(json);
      if (window.Uint8Array) {
        return new Uint8Array(array);
      } else {
        return array;
      }
    } else {
      JSIL.Host.logWriteLine("Could not find '" + name + "' in local storage.");
      return null;
    }
  });

  $.RawMethod(false, "setFileBytes", function (name, value) {
    var key = getKey(this.name) + "_blobs_" + name;

    // JSON.stringify turns typed arrays into dictionaries :|

    var json = "[";
    for (var i = 0; i < value.length; i++) {
      if (i !== 0)
        json += ",";

      json += value[i];
    }
    json += "]";

    try {
      localStorage.setItem(key, json);
    } catch (exc) {
      handleLocalStorageFailure(exc, name);
    }

    JSIL.Host.logWriteLine("Saved '" + name + "' to local storage.");
  });

  $.RawMethod(false, "toString", function () {
    return "<LocalStorage Volume '" + this.name + "'>";
  });
});

JSIL.LocalStorage = {};
JSIL.LocalStorage.Initialize = function (appName) {
  var volume = null;

  JSIL.RegisterStorageProvider({
    getVolumes: function () {
      if (!volume)
        volume = new LocalStorageVolume(appName, "/");

      return [volume];
    }
  });
};<|endoftext|>


<commit_before>if("undefined"==typeof OGX)var OGX=function(){};OGX.prototype.Roulette=function(t){"use strict";function e(t){t?p.on("mousedown",function(t){console.log("down"),t.preventDefault(),t.stopImmediatePropagation(),_=(new Date).getTime(),h=t.pageY,v=t.pageY,o(!0),e(!1),n(!0),a(!0)}):p.off("mousedown")}function o(t){if(t){var i,r,s,u,c;$(document).on("mouseup",function(t){console.log("up"),a(!1),n(!1),o(!1),e(!0),t.preventDefault(),t.stopImmediatePropagation(),x=(new Date).getTime(),m=t.pageY,i=Math.abs(m-h),i>g?i=g:null,w=m>h?1:-1,r=i/(x-_)*O.force_coef,s=l(f),console.log("trans",s),c=Math.round(s/g)*w,i=c*g-s*w,u=Math.round(r*O.time_coef*100)/100,console.log("on tile",c,"dist",i,"trans",s,"time",u,"force",r),f.css({transition:"all 0.2s ease-out",transform:"translate3D(0px, "+(s+i*w)+"px, 0px)"})})}else $(document).off("mouseup")}function n(t){if(t){var e,o;$(document).on("mousemove",function(t){console.log("move"),t.preventDefault(),t.stopImmediatePropagation(),o=t.pageY>v?1:-1,o!==w?(e=0,v=t.pageY):(e=t.pageY-v,v=t.pageY),w=o,f.css({transition:"none",transform:"translate3D(0px, "+(l(f)+e)+"px, 0px)"})})}else $(document).off("mousemove")}function a(t){t?y=setInterval(i,10):clearInterval(y)}function i(){var t,e,o;t=Math.floor((f.position().top+g*w)/(g*O.data.length)*-1),t!==I&&(0===f.children('.ogx_roulette_stripe[data-id="'+t+'"]').length&&(e=Y.clone(),e.css("top",t*(g*O.data.length)+"px"),e.attr("data-id",t),f.append(e)),f.children(".ogx_roulette_stripe").each(function(t,e){if(o=f.position().top+$(e).position().top,Math.ceil(o+g*O.data.length)<-g||o>g)return void $(e).remove()}),I=t)}function r(){for(var t='
',e=0;e<2;e++){t+='
';for(var o=0;o<O.data.length;o++)t+='
'+O.data[o].label+"
";t+="
"}t+="
",p.html(t),f=p.children(".ogx_roulette_stripes:first"),D=f.children(".ogx_roulette_stripe:first"),Y=f.children(".ogx_roulette_stripe:last"),Y=Y.detach();var n=s(O.value);f.css({transition:"none",transform:"translate3D(0px, "+-n*g+"px, 0px)"})}function s(t){for(var e=0;e<O.data.length;e++)if(O.data[e].value===t)return e}function l(t){var e=0;return"none"!==t.css("transform")&&(e=Number(t.css("transform").split(",")[5].split(")")[0])),e}function u(){for(var t in G)O.hasOwnProperty(t)||(O[t]=G[t]);p=b.el=$(O.container),p.addClass("ogx_roulette"),d=p.width(),g=p.height()}function c(){u(),r(),e(!0)}var p,f,d,g,h,m,v,_,x,w,y,D,Y,b=this,O=t,G={force_coef:10,time_coef:.1,min_force_pitch:2,value:0},I=0;this.el=null,this.val=function(t,e){if("undefined"==typeof e)var e=!0;O.val=t},this.destroy=function(){o(!1),e(!1),n(!1),a(!1)},c()},OGX.Roulette=function(t){"use strict";return new OGX.prototype.Roulette(t)};<commit_msg>Stable build<commit_after>if("undefined"==typeof OGX)var OGX=function(){};OGX.prototype.Roulette=function(t){"use strict";function e(t){t?v.on(w,function(t){t.preventDefault(),t.stopImmediatePropagation(),t=c(t),D=(new Date).getTime(),O=t.pageY,M=t.pageY,o(!0),e(!1),a(!0),i(!0)}):v.off(w)}function o(t){if(t){var n,r,l,u,f,p,d;$(document).on(P,function(t){t.preventDefault(),t.stopImmediatePropagation(),t=c(t),i(!1),a(!1),o(!1),e(!0),E=(new Date).getTime(),x=t.pageY,n=Math.abs(x-O),n>_?n=_:null,G=x>O?1:-1,r=n/(E-D)*k.force_coef,l=s(g),f=Math.round(l/_)*G,n=f*_-l*G,u=Math.round(r*k.time_coef*100)/100,d=Math.abs(Math.ceil((l+n*G)/(_*k.data.length))),p=Math.abs((Math.abs(l+n*G)-d*_*k.data.length)/_),k.value!==p&&(k.value=p,T.el.trigger(OGX.Roulette.CHANGE,k.data[p].value)),g.css({transition:"all 0.2s ease-out",transform:"translate3D(0px, "+(l+n*G)+"px, 0px)"})})}else $(document).off(P)}function a(t){n(t),t?$(document).on(Y,function(t){t.preventDefault(),t.stopImmediatePropagation(),t=c(t),y=t}):$(document).off(Y)}function n(t){t?b=setInterval(r,50):(clearInterval(b),y=0)}function r(){if(y){var t,e;e=y.pageY>M?1:-1,e!==G?(t=0,M=y.pageY):(t=y.pageY-M,M=y.pageY),G=e,g.css({transition:"none",transform:"translate3D(0px, "+(s(g)+t)+"px, 0px)"})}}function i(t){t?X=setInterval(l,10):clearInterval(X)}function l(){var t,e,o;t=Math.floor((g.position().top+_*G)/(_*k.data.length)*-1),t!==B&&(0===g.children('.ogx_roulette_stripe[data-id="'+t+'"]').length&&(e=I.clone(),e.css("top",t*(_*k.data.length)+"px"),e.attr("data-id",t),g.append(e)),g.children(".ogx_roulette_stripe").each(function(t,e){if(o=g.position().top+$(e).position().top,Math.ceil(o+_*k.data.length)<-_||o>_)return void $(e).remove()}),B=t)}function u(){for(var t='
',e=0;e<2;e++){t+='
';for(var o=0;o<k.data.length;o++)t+='
'+k.data[o].label+"
";t+="
"}t+="
",v.html(t),g=v.children(".ogx_roulette_stripes:first"),R=g.children(".ogx_roulette_stripe:first"),I=g.children(".ogx_roulette_stripe:last"),I=I.detach();var a=k.value*_*-1;g.css({transition:"none",transform:"translate3D(0px, "+a+"px, 0px)"})}function s(t){var e=0;return"none"!==t.css("transform")&&(e=Number(t.css("transform").split(",")[5].split(")")[0])),e}function c(t){return k.mode===OGX.Roulette.MODE_MOBILE?t.originalEvent.changedTouches[0]:t}function f(t){for(var e=0;e<k.data.length;e++)if(k.data[e].value===t)return e;return-1}function p(){switch(k.mode){case OGX.Roulette.MODE_MOBILE:w="touchstart",P="touchend",Y="touchmove";break;case OGX.Roulette.MODE_DESKTOP:w="mousedown",P="mouseup",Y="mousemove"}}function d(){for(var t in C)k.hasOwnProperty(t)||(k[t]=C[t]);v=T.el=$(k.container),v.addClass("ogx_roulette"),m=v.width(),_=v.height();k.value;k.value=f(k.value)}function h(){d(),p(),u(),e(!0)}var v,g,m,_,O,x,M,D,E,G,X,b,R,I,y,w,P,Y,T=this,k=t,C={force_coef:10,time_coef:.1,min_force_pitch:2,value:0,mode:OGX.Roulette.MODE_DESKTOP},B=0;this.el=null,this.val=function(t,e){if("undefined"==typeof t)return k.data[k.value].value;var o=f(t);if(o>-1&&(k.value=o),"undefined"==typeof e)var e=!0},this.destroy=function(){o(!1),e(!1),a(!1),i(!1)},h()},OGX.Roulette=function(t){"use strict";return new OGX.prototype.Roulette(t)},OGX.Roulette.MODE_DESKTOP="desktop",OGX.Roulette.MODE_MOBILE="mobile",OGX.Roulette.CHANGE="rouletteChange";<|endoftext|>


<commit_before>/* eslint-disable no-unused-vars, no-var, max-len */
/* eslint sort-keys: ["error", "asc", {"caseSensitive": false}] */

/**
 * !!!IMPORTANT!!!
 *
 * This file is considered deprecated. All options will eventually be moved to
 * config.js, and no new options should be added here.
 */

var interfaceConfig = {
    APP_NAME: 'Jitsi Meet',
    AUDIO_LEVEL_PRIMARY_COLOR: 'rgba(255,255,255,0.4)',
    AUDIO_LEVEL_SECONDARY_COLOR: 'rgba(255,255,255,0.2)',

    /**
     * A UX mode where the last screen share participant is automatically
     * pinned. Valid values are the string "remote-only" so remote participants
     * get pinned but not local, otherwise any truthy value for all participants,
     * and any falsy value to disable the feature.
     *
     * Note: this mode is experimental and subject to breakage.
     */
    AUTO_PIN_LATEST_SCREEN_SHARE: 'remote-only',
    BRAND_WATERMARK_LINK: '',

    CLOSE_PAGE_GUEST_HINT: false, // A html text to be shown to guests on the close page, false disables it

    DEFAULT_BACKGROUND: '#040404',
    DEFAULT_LOGO_URL: 'images/watermark.svg',
    DEFAULT_WELCOME_PAGE_LOGO_URL: 'images/watermark.svg',

    DISABLE_DOMINANT_SPEAKER_INDICATOR: false,

    /**
     * If true, notifications regarding joining/leaving are no longer displayed.
     */
    DISABLE_JOIN_LEAVE_NOTIFICATIONS: false,

    /**
     * If true, presence status: busy, calling, connected etc. is not displayed.
     */
    DISABLE_PRESENCE_STATUS: false,

    /**
     * Whether the ringing sound in the call/ring overlay is disabled. If
     * {@code undefined}, defaults to {@code false}.
     *
     * @type {boolean}
     */
    DISABLE_RINGING: false,

    /**
     * Whether the speech to text transcription subtitles panel is disabled.
     * If {@code undefined}, defaults to {@code false}.
     *
     * @type {boolean}
     */
    DISABLE_TRANSCRIPTION_SUBTITLES: false,

    /**
     * Whether or not the blurred video background for large video should be
     * displayed on browsers that can support it.
     */
    DISABLE_VIDEO_BACKGROUND: false,

    DISPLAY_WELCOME_FOOTER: true,
    DISPLAY_WELCOME_PAGE_ADDITIONAL_CARD: false,
    DISPLAY_WELCOME_PAGE_CONTENT: false,
    DISPLAY_WELCOME_PAGE_TOOLBAR_ADDITIONAL_CONTENT: false,

    ENABLE_DIAL_OUT: true,

    ENABLE_FEEDBACK_ANIMATION: false, // Enables feedback star animation.

    FILM_STRIP_MAX_HEIGHT: 120,

    GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,

    /**
     * Hide the logo on the deep linking pages.
     */
    HIDE_DEEP_LINKING_LOGO: false,

    /**
     * Hide the invite prompt in the header when alone in the meeting.
     */
    HIDE_INVITE_MORE_HEADER: false,

    JITSI_WATERMARK_LINK: 'https://jitsi.org',

    LANG_DETECTION: true, // Allow i18n to detect the system language
    LIVE_STREAMING_HELP_LINK: 'https://jitsi.org/live', // Documentation reference for the live streaming feature.
    LOCAL_THUMBNAIL_RATIO: 16 / 9, // 16:9

    /**
     * Maximum coefficient of the ratio of the large video to the visible area
     * after the large video is scaled to fit the window.
     *
     * @type {number}
     */
    MAXIMUM_ZOOMING_COEFFICIENT: 1.3,

    /**
     * Whether the mobile app Jitsi Meet is to be promoted to participants
     * attempting to join a conference in a mobile Web browser. If
     * {@code undefined}, defaults to {@code true}.
     *
     * @type {boolean}
     */
    MOBILE_APP_PROMO: true,

    /**
     * Specify custom URL for downloading android mobile app.
     */
    MOBILE_DOWNLOAD_LINK_ANDROID: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',

    /**
     * Specify custom URL for downloading f droid app.
     */
    MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/en/packages/org.jitsi.meet/',

    /**
     * Specify URL for downloading ios mobile app.
     */
    MOBILE_DOWNLOAD_LINK_IOS: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',

    NATIVE_APP_NAME: 'Jitsi Meet',

    // Names of browsers which should show a warning stating the current browser
    // has a suboptimal experience. Browsers which are not listed as optimal or
    // unsupported are considered suboptimal. Valid values are:
    // chrome, chromium, edge, electron, firefox, nwjs, opera, safari
    OPTIMAL_BROWSERS: [ 'chrome', 'chromium', 'firefox', 'nwjs', 'electron', 'safari' ],

    POLICY_LOGO: null,
    PROVIDER_NAME: 'Jitsi',

    /**
     * If true, will display recent list
     *
     * @type {boolean}
     */
    RECENT_LIST_ENABLED: true,
    REMOTE_THUMBNAIL_RATIO: 1, // 1:1

    SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar', 'sounds' ],

    /**
     * Specify which sharing features should be displayed. If the value is not set
     * all sharing features will be shown. You can set [] to disable all.
     */
    // SHARING_FEATURES: ['email', 'url', 'dial-in', 'embed'],

    SHOW_BRAND_WATERMARK: false,

    /**
     * Decides whether the chrome extension banner should be rendered on the landing page and during the meeting.
     * If this is set to false, the banner will not be rendered at all. If set to true, the check for extension(s)
     * being already installed is done before rendering.
     */
    SHOW_CHROME_EXTENSION_BANNER: false,

    SHOW_DEEP_LINKING_IMAGE: false,
    SHOW_JITSI_WATERMARK: true,
    SHOW_POWERED_BY: false,
    SHOW_PROMOTIONAL_CLOSE_PAGE: false,

    /*
     * If indicated some of the error dialogs may point to the support URL for
     * help.
     */
    SUPPORT_URL: 'https://community.jitsi.org/',

    // Browsers, in addition to those which do not fully support WebRTC, that
    // are not supported and should show the unsupported browser page.
    UNSUPPORTED_BROWSERS: [],

    /**
     * Whether to show thumbnails in filmstrip as a column instead of as a row.
     */
    VERTICAL_FILMSTRIP: true,

    // Determines how the video would fit the screen. 'both' would fit the whole
    // screen, 'height' would fit the original video height to the height of the
    // screen, 'width' would fit the original video width to the width of the
    // screen respecting ratio, 'nocrop' would make the video as large as
    // possible and preserve aspect ratio without cropping.
    VIDEO_LAYOUT_FIT: 'both',

    /**
     * If true, hides the video quality label indicating the resolution status
     * of the current large video.
     *
     * @type {boolean}
     */
    VIDEO_QUALITY_LABEL_DISABLED: false,

    /**
     * How many columns the tile view can expand to. The respected range is
     * between 1 and 5.
     */
    // TILE_VIEW_MAX_COLUMNS: 5,

    /**
     * Specify Firebase dynamic link properties for the mobile apps.
     */
    // MOBILE_DYNAMIC_LINK: {
    //    APN: 'org.jitsi.meet',
    //    APP_CODE: 'w2atb',
    //    CUSTOM_DOMAIN: undefined,
    //    IBI: 'com.atlassian.JitsiMeet.ios',
    //    ISI: '1165103905'
    // },

    /**
     * Specify mobile app scheme for opening the app from the mobile browser.
     */
    // APP_SCHEME: 'org.jitsi.meet',

    /**
     * Specify the Android app package name.
     */
    // ANDROID_APP_PACKAGE: 'org.jitsi.meet',

    // List of undocumented settings
    /**
     INDICATOR_FONT_SIZES
     PHONE_NUMBER_REGEX
    */

    // -----------------DEPRECATED CONFIGS BELOW THIS LINE-----------------------------

    // Connection indicators (
    // CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
    // CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT,
    // CONNECTION_INDICATOR_DISABLED) got moved to config.js.

    // Please use disableModeratorIndicator from config.js
    // DISABLE_FOCUS_INDICATOR: false,

    // Please use defaultLocalDisplayName from config.js
    // DEFAULT_LOCAL_DISPLAY_NAME: 'me',

    // Please use defaultRemoteDisplayName from config.js
    // DEFAULT_REMOTE_DISPLAY_NAME: 'Fellow Jitster',

    // Moved to config.js as `toolbarConfig.initialTimeout`.
    // INITIAL_TOOLBAR_TIMEOUT: 20000,

    // Moved to config.js as `toolbarConfig.alwaysVisible`.
    // TOOLBAR_ALWAYS_VISIBLE: false,

    // This config was moved to config.js as `toolbarButtons`.
    // TOOLBAR_BUTTONS: [],

    // Moved to config.js as `toolbarConfig.timeout`.
    // TOOLBAR_TIMEOUT: 4000,

    // Allow all above example options to include a trailing comma and
    // prevent fear when commenting out the last value.
    // eslint-disable-next-line sort-keys
    makeJsonParserHappy: 'even if last key had a trailing comma'

    // No configuration value should follow this line.
};

/* eslint-enable no-unused-vars, no-var, max-len */
<commit_msg>fix(thumbnails) Change local thumbnail aspect ratio (#10861)<commit_after>/* eslint-disable no-unused-vars, no-var, max-len */
/* eslint sort-keys: ["error", "asc", {"caseSensitive": false}] */

/**
 * !!!IMPORTANT!!!
 *
 * This file is considered deprecated. All options will eventually be moved to
 * config.js, and no new options should be added here.
 */

var interfaceConfig = {
    APP_NAME: 'Jitsi Meet',
    AUDIO_LEVEL_PRIMARY_COLOR: 'rgba(255,255,255,0.4)',
    AUDIO_LEVEL_SECONDARY_COLOR: 'rgba(255,255,255,0.2)',

    /**
     * A UX mode where the last screen share participant is automatically
     * pinned. Valid values are the string "remote-only" so remote participants
     * get pinned but not local, otherwise any truthy value for all participants,
     * and any falsy value to disable the feature.
     *
     * Note: this mode is experimental and subject to breakage.
     */
    AUTO_PIN_LATEST_SCREEN_SHARE: 'remote-only',
    BRAND_WATERMARK_LINK: '',

    CLOSE_PAGE_GUEST_HINT: false, // A html text to be shown to guests on the close page, false disables it

    DEFAULT_BACKGROUND: '#040404',
    DEFAULT_LOGO_URL: 'images/watermark.svg',
    DEFAULT_WELCOME_PAGE_LOGO_URL: 'images/watermark.svg',

    DISABLE_DOMINANT_SPEAKER_INDICATOR: false,

    /**
     * If true, notifications regarding joining/leaving are no longer displayed.
     */
    DISABLE_JOIN_LEAVE_NOTIFICATIONS: false,

    /**
     * If true, presence status: busy, calling, connected etc. is not displayed.
     */
    DISABLE_PRESENCE_STATUS: false,

    /**
     * Whether the ringing sound in the call/ring overlay is disabled. If
     * {@code undefined}, defaults to {@code false}.
     *
     * @type {boolean}
     */
    DISABLE_RINGING: false,

    /**
     * Whether the speech to text transcription subtitles panel is disabled.
     * If {@code undefined}, defaults to {@code false}.
     *
     * @type {boolean}
     */
    DISABLE_TRANSCRIPTION_SUBTITLES: false,

    /**
     * Whether or not the blurred video background for large video should be
     * displayed on browsers that can support it.
     */
    DISABLE_VIDEO_BACKGROUND: false,

    DISPLAY_WELCOME_FOOTER: true,
    DISPLAY_WELCOME_PAGE_ADDITIONAL_CARD: false,
    DISPLAY_WELCOME_PAGE_CONTENT: false,
    DISPLAY_WELCOME_PAGE_TOOLBAR_ADDITIONAL_CONTENT: false,

    ENABLE_DIAL_OUT: true,

    ENABLE_FEEDBACK_ANIMATION: false, // Enables feedback star animation.

    FILM_STRIP_MAX_HEIGHT: 120,

    GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,

    /**
     * Hide the logo on the deep linking pages.
     */
    HIDE_DEEP_LINKING_LOGO: false,

    /**
     * Hide the invite prompt in the header when alone in the meeting.
     */
    HIDE_INVITE_MORE_HEADER: false,

    JITSI_WATERMARK_LINK: 'https://jitsi.org',

    LANG_DETECTION: true, // Allow i18n to detect the system language
    LIVE_STREAMING_HELP_LINK: 'https://jitsi.org/live', // Documentation reference for the live streaming feature.
    LOCAL_THUMBNAIL_RATIO: 1, // 1:1

    /**
     * Maximum coefficient of the ratio of the large video to the visible area
     * after the large video is scaled to fit the window.
     *
     * @type {number}
     */
    MAXIMUM_ZOOMING_COEFFICIENT: 1.3,

    /**
     * Whether the mobile app Jitsi Meet is to be promoted to participants
     * attempting to join a conference in a mobile Web browser. If
     * {@code undefined}, defaults to {@code true}.
     *
     * @type {boolean}
     */
    MOBILE_APP_PROMO: true,

    /**
     * Specify custom URL for downloading android mobile app.
     */
    MOBILE_DOWNLOAD_LINK_ANDROID: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',

    /**
     * Specify custom URL for downloading f droid app.
     */
    MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/en/packages/org.jitsi.meet/',

    /**
     * Specify URL for downloading ios mobile app.
     */
    MOBILE_DOWNLOAD_LINK_IOS: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',

    NATIVE_APP_NAME: 'Jitsi Meet',

    // Names of browsers which should show a warning stating the current browser
    // has a suboptimal experience. Browsers which are not listed as optimal or
    // unsupported are considered suboptimal. Valid values are:
    // chrome, chromium, edge, electron, firefox, nwjs, opera, safari
    OPTIMAL_BROWSERS: [ 'chrome', 'chromium', 'firefox', 'nwjs', 'electron', 'safari' ],

    POLICY_LOGO: null,
    PROVIDER_NAME: 'Jitsi',

    /**
     * If true, will display recent list
     *
     * @type {boolean}
     */
    RECENT_LIST_ENABLED: true,
    REMOTE_THUMBNAIL_RATIO: 1, // 1:1

    SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar', 'sounds' ],

    /**
     * Specify which sharing features should be displayed. If the value is not set
     * all sharing features will be shown. You can set [] to disable all.
     */
    // SHARING_FEATURES: ['email', 'url', 'dial-in', 'embed'],

    SHOW_BRAND_WATERMARK: false,

    /**
     * Decides whether the chrome extension banner should be rendered on the landing page and during the meeting.
     * If this is set to false, the banner will not be rendered at all. If set to true, the check for extension(s)
     * being already installed is done before rendering.
     */
    SHOW_CHROME_EXTENSION_BANNER: false,

    SHOW_DEEP_LINKING_IMAGE: false,
    SHOW_JITSI_WATERMARK: true,
    SHOW_POWERED_BY: false,
    SHOW_PROMOTIONAL_CLOSE_PAGE: false,

    /*
     * If indicated some of the error dialogs may point to the support URL for
     * help.
     */
    SUPPORT_URL: 'https://community.jitsi.org/',

    // Browsers, in addition to those which do not fully support WebRTC, that
    // are not supported and should show the unsupported browser page.
    UNSUPPORTED_BROWSERS: [],

    /**
     * Whether to show thumbnails in filmstrip as a column instead of as a row.
     */
    VERTICAL_FILMSTRIP: true,

    // Determines how the video would fit the screen. 'both' would fit the whole
    // screen, 'height' would fit the original video height to the height of the
    // screen, 'width' would fit the original video width to the width of the
    // screen respecting ratio, 'nocrop' would make the video as large as
    // possible and preserve aspect ratio without cropping.
    VIDEO_LAYOUT_FIT: 'both',

    /**
     * If true, hides the video quality label indicating the resolution status
     * of the current large video.
     *
     * @type {boolean}
     */
    VIDEO_QUALITY_LABEL_DISABLED: false,

    /**
     * How many columns the tile view can expand to. The respected range is
     * between 1 and 5.
     */
    // TILE_VIEW_MAX_COLUMNS: 5,

    /**
     * Specify Firebase dynamic link properties for the mobile apps.
     */
    // MOBILE_DYNAMIC_LINK: {
    //    APN: 'org.jitsi.meet',
    //    APP_CODE: 'w2atb',
    //    CUSTOM_DOMAIN: undefined,
    //    IBI: 'com.atlassian.JitsiMeet.ios',
    //    ISI: '1165103905'
    // },

    /**
     * Specify mobile app scheme for opening the app from the mobile browser.
     */
    // APP_SCHEME: 'org.jitsi.meet',

    /**
     * Specify the Android app package name.
     */
    // ANDROID_APP_PACKAGE: 'org.jitsi.meet',

    // List of undocumented settings
    /**
     INDICATOR_FONT_SIZES
     PHONE_NUMBER_REGEX
    */

    // -----------------DEPRECATED CONFIGS BELOW THIS LINE-----------------------------

    // Connection indicators (
    // CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
    // CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT,
    // CONNECTION_INDICATOR_DISABLED) got moved to config.js.

    // Please use disableModeratorIndicator from config.js
    // DISABLE_FOCUS_INDICATOR: false,

    // Please use defaultLocalDisplayName from config.js
    // DEFAULT_LOCAL_DISPLAY_NAME: 'me',

    // Please use defaultRemoteDisplayName from config.js
    // DEFAULT_REMOTE_DISPLAY_NAME: 'Fellow Jitster',

    // Moved to config.js as `toolbarConfig.initialTimeout`.
    // INITIAL_TOOLBAR_TIMEOUT: 20000,

    // Moved to config.js as `toolbarConfig.alwaysVisible`.
    // TOOLBAR_ALWAYS_VISIBLE: false,

    // This config was moved to config.js as `toolbarButtons`.
    // TOOLBAR_BUTTONS: [],

    // Moved to config.js as `toolbarConfig.timeout`.
    // TOOLBAR_TIMEOUT: 4000,

    // Allow all above example options to include a trailing comma and
    // prevent fear when commenting out the last value.
    // eslint-disable-next-line sort-keys
    makeJsonParserHappy: 'even if last key had a trailing comma'

    // No configuration value should follow this line.
};

/* eslint-enable no-unused-vars, no-var, max-len */
<|endoftext|>


<commit_before>/**
 * Describes the play state.
 */

var PlayState = function() {
	var mSelf = this;
	var mHud; 				// Game hud
	var mBackground;		// group with all the cards
	var mCards; 			// group with all the cards
	var mMonster;
	var mTrash;
	var mQuestionTime;		// Time, in seconds, the player has to answer the current question
	var mQuestionTimer;		// Counts from mQuestionTime until zero.
	var mQuestion;			// Info about the current question
	var mQuestionCounter;	// How many questions so far.
	var mMatchTime;			// Remaining time for the match
	var mIsThinking;		// Informs if the player is in a thinking part of the game (e.g. card analysis)
	var mHealth;			// Available health points.
	var mDifficultyIndex;	// Regulates the game difficulty.
	var mScore = {			// Info regarding global score (throughout the game session)
		right: 0,
		wrong: 0,
		miss: 0
	};
	var mTurnBasedScore = {		// Info regarding score for each new question
		right: 0,
		wrong: 0,
		miss: 0
	};
	var mSfxNewQuestion;
	var mTutorial;

	this.create = function() {
		var i,
			j = 0,
			aCard;

		mMonster = Game.add.sprite(Game.world.width * 0.85, Game.world.height * 0.42, 'monster');
		mMonster.animations.add('idle', [0,1], 2, true);
		mMonster.animations.add('sick', [2], 1, false);
		mMonster.animations.play('idle');

		mTrash = Game.add.sprite(Game.world.width * 0.85, mMonster.y + mMonster.height + 70, 'trash');
		mTrash.addChild(new Phaser.Text(Game, -40, 70, 'Trash', {fontSize: 26, fill: '#000', align: 'center'}));

		mMonster.anchor.setTo(0.5);
		mTrash.anchor.setTo(0.5);

		mBackground = Game.add.sprite(Game.world.width * 0.05, Game.world.height * 0.15, 'deck-background');
		mCards = Game.add.group();

		for(i = 0; i < Constants.CARDS_MAX; i++) {
			if(i != 0 && (i % Constants.CARDS_PER_ROW) == 0) {
				j++;
			}

			aCard = new Card(mBackground.x + 70 + 120 * (i % Constants.CARDS_PER_ROW), mBackground.y + 60 + 120 * j);
			mCards.add(aCard);
		}

		mDifficultyIndex = 0;

		mQuestionTime = getDifficulty().QUESTION_DURATION;
		mQuestionTimer = 0;
		mMatchTime = Constants.GAME_MATCH_DURATION;
		mHealth = Constants.GAME_HEALTH_MAX;
		mSfxNewQuestion = Game.add.audio('sfx-new-question', Constants.SFX_VOLUME);
		mIsThinking = false;

		mQuestion = 1;
		mQuestionCounter = 0;

		mHud = new Hud();
		Game.add.existing(mHud);

		mTutorial = new Tutorial();
		Game.add.existing(mTutorial);
	};

	this.update = function() {
		var aElapsed = Game.time.elapsedMS,
			aOldDifficultyIndex;

		aOldDifficultyIndex = mDifficultyIndex;
		updateDifficultiIndex();

		if(aOldDifficultyIndex != mDifficultyIndex) {
			console.log('Difficulty has changed to ' + mDifficultyIndex);
		}

		// Update everything related to turn control (time)
		// and generation of new questions.
		updateTurnAndNewQuestionControl(aElapsed);

		if(GlobalInfo) {
			if(GlobalInfo.expression) {
				var aEmotions = GlobalInfo.expression.getEmotions();
				var aPoints = GlobalInfo.expression.getPoints();

				// Emotions are available for reading?
				if(aEmotions.length > 0) {
					// Yeah, they are, collect them
					GlobalInfo.data.log({e: aEmotions, p: aPoints});
				}
			}

			if(Constants.GAME_ENABLE_DATA_LOG) {
				GlobalInfo.data.log({s: mTurnBasedScore});
				GlobalInfo.data.send(GlobalInfo.user, GlobalInfo.game);
			}
		}

		// Update match time
		mMatchTime -= aElapsed;

		if(mMatchTime <= 0 || mHealth <= 0) {
			// Match is over!
			if(GlobalInfo) {
				GlobalInfo.score = mScore;
			}
			// TODO: disable face tracking here
			Game.state.start('over');
		}
	};

	// theType can be 'right', 'wrong' or 'miss'
	this.countMove = function(theType) {
		if(theType == 'right') {
			mScore.right++;
			mTurnBasedScore.right++;
			mHealth += getDifficulty().GAME_CORRECT_HEALTH;

		} else if(theType == 'wrong') {
			mScore.wrong++;
			mTurnBasedScore.wrong++;
			mHealth -= getDifficulty().GAME_MISTAKE_HEALTH;

		} else {
			mScore.miss++;
			mTurnBasedScore.miss++;
			mHealth -= getDifficulty().GAME_MISTAKE_HEALTH;
		}

		// Prevent overfeeding the monster.
		if(mHealth >= Constants.GAME_HEALTH_MAX) {
			mHealth = Constants.GAME_HEALTH_MAX;
		}

		mHud.refresh();
	}

	var updateDifficultiIndex = function() {
		mDifficultyIndex = (1 - mMatchTime / Constants.GAME_MATCH_DURATION) * Constants.DIFFICULTY.length;
		mDifficultyIndex |= 0; // cast to int
	};

	var updateTurnAndNewQuestionControl = function(theElapsed) {
		// Update counters regarding question
		mQuestionTimer -= theElapsed;

		// Check if it is time to ask a new question
		if(mQuestionTimer <= 0) {
			generateNewQuestion();
			mQuestionTime = mQuestionCounter == 1 ? Constants.QUESTION_DURATION_1ST : getDifficulty().QUESTION_DURATION;
			mQuestionTimer = mQuestionTime;
		}

		// If all cards have been analyzed already,
		// speed up the turn count down
		if(mIsThinking && countFlippingCards() == 0) {
			mQuestionTimer -= mQuestionCounter == 1 ? Constants.QUESTION_DOWN_PACE_1ST : getDifficulty().QUESTION_DOWN_PACE;
		}
	};

	var flipRandomCardsUp = function() {
		var aCard,
		 	i,
			aTotal = getDifficulty().CARDS_FLIPS_TURN;

		for(i = 0; i < aTotal; i++) {
			aCard = mCards.getRandom();

			if(aCard) {
				aCard.flip();
			}
		}
	};

	var penalizeStillFlippedUpCards = function() {
		var aCard,
			i,
			aTotal = mCards.length;

		for(i = 0; i < aTotal; i++) {
			aCard = mCards.getChildAt(i);

			if(aCard && aCard.isFlippedUp()) {
				mSelf.countMove('miss');
				mHud.showRightWrongSign(aCard, false);
				aCard.flipDown();
			}
		}
	};

	var countFlippingCards = function() {
		var aCard,
			i,
			aTotal = mCards.length,
			aRet = 0;

		for(i = 0; i < aTotal; i++) {
			aCard = mCards.getChildAt(i);

			if(aCard && (aCard.isFlippedUp() || aCard.isFlipping())) {
				aRet++;
			}
		}

		return aRet;
	};

	var clearTurnBasedScore = function() {
		mTurnBasedScore.right = 0;
		mTurnBasedScore.wrong = 0;
		mTurnBasedScore.miss = 0;
	};

	var flipCardsForNewRound = function() {
		// Is it the very first question the user sees?
		if(mQuestionCounter == 1) {
			// Yes! Let's flip some carefully selected cards
			// and show the tutorial info.
			mTutorial.activate();

		} else {
			// No, the player already saw the tutorial.
			// Let's go on with the show then.
			flipRandomCardsUp();

			// Is the tutorial active?
			if(mTutorial != null) {
				// Yeah, it is. Remove it because it's game time!
				mTutorial.destroy();
				mTutorial = null;

				// Reset match timer to ignore time spent during tutorial
				mMatchTime = Constants.GAME_MATCH_DURATION;

				// Log start of the game
				GlobalInfo.data.logMilestone(GlobalInfo.user, GlobalInfo.game, 'game_start');
			}
		}

		mIsThinking = true;
	};

	var generateNewQuestion = function() {
		mIsThinking = false;
		mQuestion = Game.rnd.integerInRange(1, Constants.CARDS_COLORS - 1);

		mHud.refresh();
		mHud.highlightNewQuestion();

		penalizeStillFlippedUpCards();

		// Before clearing the turn based score, save information
		// regarding the current turn.
		if(GlobalInfo && GlobalInfo.data) {
			GlobalInfo.data.log({turn: mQuestionCounter, s: mTurnBasedScore}, true);
		}

		clearTurnBasedScore();

		// Schedule the cards to flip up and the
		// thinking phase to begin.
		Game.time.events.add(Phaser.Timer.SECOND * 0.5, flipCardsForNewRound, this);

		mQuestionCounter++;

		mSfxNewQuestion.play();
	};

	var getDifficulty = function() {
		return Constants.DIFFICULTY[mDifficultyIndex];
	};

	this.terminateCurrentTurn = function() {
		mQuestionTimer = 0;
	}

	// Getters
	this.getQuestion = function() {
		return mQuestion;
	};

	this.getCards = function() {
		return mCards;
	};

	this.getScore = function() {
		return mScore;
	};

	this.getMatchRemainingTime = function() {
		return mMatchTime;
	};

	this.getHud = function() {
		return mHud;
	};

	this.getMonster = function() {
		return mMonster;
	};

	this.getPercentageTimeRemainingAnswerQuestion = function() {
		return mQuestionTimer / mQuestionTime;
	};

	this.getTrash = function() {
		return mTrash;
	};

	this.getHealthPercentage = function() {
		return mHealth/Constants.GAME_HEALTH_MAX;
	};
};
<commit_msg>More data logs for card game<commit_after>/**
 * Describes the play state.
 */

var PlayState = function() {
	var mSelf = this;
	var mHud; 				// Game hud
	var mBackground;		// group with all the cards
	var mCards; 			// group with all the cards
	var mMonster;
	var mTrash;
	var mQuestionTime;		// Time, in seconds, the player has to answer the current question
	var mQuestionTimer;		// Counts from mQuestionTime until zero.
	var mQuestion;			// Info about the current question
	var mQuestionCounter;	// How many questions so far.
	var mMatchTime;			// Remaining time for the match
	var mIsThinking;		// Informs if the player is in a thinking part of the game (e.g. card analysis)
	var mHealth;			// Available health points.
	var mDifficultyIndex;	// Regulates the game difficulty.
	var mScore = {			// Info regarding global score (throughout the game session)
		right: 0,
		wrong: 0,
		miss: 0
	};
	var mTurnBasedScore = {		// Info regarding score for each new question
		right: 0,
		wrong: 0,
		miss: 0
	};
	var mSfxNewQuestion;
	var mTutorial;

	this.create = function() {
		var i,
			j = 0,
			aCard;

		mMonster = Game.add.sprite(Game.world.width * 0.85, Game.world.height * 0.42, 'monster');
		mMonster.animations.add('idle', [0,1], 2, true);
		mMonster.animations.add('sick', [2], 1, false);
		mMonster.animations.play('idle');

		mTrash = Game.add.sprite(Game.world.width * 0.85, mMonster.y + mMonster.height + 70, 'trash');
		mTrash.addChild(new Phaser.Text(Game, -40, 70, 'Trash', {fontSize: 26, fill: '#000', align: 'center'}));

		mMonster.anchor.setTo(0.5);
		mTrash.anchor.setTo(0.5);

		mBackground = Game.add.sprite(Game.world.width * 0.05, Game.world.height * 0.15, 'deck-background');
		mCards = Game.add.group();

		for(i = 0; i < Constants.CARDS_MAX; i++) {
			if(i != 0 && (i % Constants.CARDS_PER_ROW) == 0) {
				j++;
			}

			aCard = new Card(mBackground.x + 70 + 120 * (i % Constants.CARDS_PER_ROW), mBackground.y + 60 + 120 * j);
			mCards.add(aCard);
		}

		mDifficultyIndex = 0;

		mQuestionTime = getDifficulty().QUESTION_DURATION;
		mQuestionTimer = 0;
		mMatchTime = Constants.GAME_MATCH_DURATION;
		mHealth = Constants.GAME_HEALTH_MAX;
		mSfxNewQuestion = Game.add.audio('sfx-new-question', Constants.SFX_VOLUME);
		mIsThinking = false;

		mQuestion = 1;
		mQuestionCounter = 0;

		mHud = new Hud();
		Game.add.existing(mHud);

		mTutorial = new Tutorial();
		Game.add.existing(mTutorial);
	};

	this.update = function() {
		var aElapsed = Game.time.elapsedMS,
			aOldDifficultyIndex;

		aOldDifficultyIndex = mDifficultyIndex;
		updateDifficultiIndex();

		if(aOldDifficultyIndex != mDifficultyIndex) {
			console.log('Difficulty has changed to ' + mDifficultyIndex);

			if(GlobalInfo && GlobalInfo.data) {
				GlobalInfo.data.log({a: 'difficulty', v: mDifficultyIndex}, true);
			}
		}

		// Update everything related to turn control (time)
		// and generation of new questions.
		updateTurnAndNewQuestionControl(aElapsed);

		if(GlobalInfo) {
			if(GlobalInfo.expression) {
				var aEmotions = GlobalInfo.expression.getEmotions();
				var aPoints = GlobalInfo.expression.getPoints();

				// Emotions are available for reading?
				if(aEmotions.length > 0) {
					// Yeah, they are, collect them
					GlobalInfo.data.log({e: aEmotions, p: aPoints});
				}
			}

			if(Constants.GAME_ENABLE_DATA_LOG) {
				GlobalInfo.data.log({s: mTurnBasedScore});
				GlobalInfo.data.send(GlobalInfo.user, GlobalInfo.game);
			}
		}

		// Update match time
		mMatchTime -= aElapsed;

		if(mMatchTime <= 0 || mHealth <= 0) {
			// Match is over!
			if(GlobalInfo) {
				GlobalInfo.score = mScore;
			}
			// TODO: disable face tracking here
			Game.state.start('over');
		}
	};

	// theType can be 'right', 'wrong' or 'miss'
	this.countMove = function(theType) {
		if(theType == 'right') {
			mScore.right++;
			mTurnBasedScore.right++;
			mHealth += getDifficulty().GAME_CORRECT_HEALTH;

			if(GlobalInfo && GlobalInfo.data) {
				GlobalInfo.data.log({a: 'right'}, true);
			}

		} else if(theType == 'wrong') {
			mScore.wrong++;
			mTurnBasedScore.wrong++;
			mHealth -= getDifficulty().GAME_MISTAKE_HEALTH;

			if(GlobalInfo && GlobalInfo.data) {
				GlobalInfo.data.log({a: 'wrong'}, true);
			}

		} else {
			mScore.miss++;
			mTurnBasedScore.miss++;
			mHealth -= getDifficulty().GAME_MISTAKE_HEALTH;

			if(GlobalInfo && GlobalInfo.data) {
				GlobalInfo.data.log({a: 'miss'}, true);
			}
		}

		// Prevent overfeeding the monster.
		if(mHealth >= Constants.GAME_HEALTH_MAX) {
			mHealth = Constants.GAME_HEALTH_MAX;
		}

		mHud.refresh();
	}

	var updateDifficultiIndex = function() {
		mDifficultyIndex = (1 - mMatchTime / Constants.GAME_MATCH_DURATION) * Constants.DIFFICULTY.length;
		mDifficultyIndex |= 0; // cast to int
	};

	var updateTurnAndNewQuestionControl = function(theElapsed) {
		// Update counters regarding question
		mQuestionTimer -= theElapsed;

		// Check if it is time to ask a new question
		if(mQuestionTimer <= 0) {
			generateNewQuestion();
			mQuestionTime = mQuestionCounter == 1 ? Constants.QUESTION_DURATION_1ST : getDifficulty().QUESTION_DURATION;
			mQuestionTimer = mQuestionTime;

			if(GlobalInfo && GlobalInfo.data) {
				GlobalInfo.data.log({a: 'question'}, true);
			}
		}

		// If all cards have been analyzed already,
		// speed up the turn count down
		if(mIsThinking && countFlippingCards() == 0) {
			mQuestionTimer -= mQuestionCounter == 1 ? Constants.QUESTION_DOWN_PACE_1ST : getDifficulty().QUESTION_DOWN_PACE;
		}
	};

	var flipRandomCardsUp = function() {
		var aCard,
		 	i,
			aTotal = getDifficulty().CARDS_FLIPS_TURN;

		for(i = 0; i < aTotal; i++) {
			aCard = mCards.getRandom();

			if(aCard) {
				aCard.flip();
			}
		}
	};

	var penalizeStillFlippedUpCards = function() {
		var aCard,
			i,
			aTotal = mCards.length;

		for(i = 0; i < aTotal; i++) {
			aCard = mCards.getChildAt(i);

			if(aCard && aCard.isFlippedUp()) {
				mSelf.countMove('miss');
				mHud.showRightWrongSign(aCard, false);
				aCard.flipDown();
			}
		}
	};

	var countFlippingCards = function() {
		var aCard,
			i,
			aTotal = mCards.length,
			aRet = 0;

		for(i = 0; i < aTotal; i++) {
			aCard = mCards.getChildAt(i);

			if(aCard && (aCard.isFlippedUp() || aCard.isFlipping())) {
				aRet++;
			}
		}

		return aRet;
	};

	var clearTurnBasedScore = function() {
		mTurnBasedScore.right = 0;
		mTurnBasedScore.wrong = 0;
		mTurnBasedScore.miss = 0;
	};

	var flipCardsForNewRound = function() {
		// Is it the very first question the user sees?
		if(mQuestionCounter == 1) {
			// Yes! Let's flip some carefully selected cards
			// and show the tutorial info.
			mTutorial.activate();

		} else {
			// No, the player already saw the tutorial.
			// Let's go on with the show then.
			flipRandomCardsUp();

			// Is the tutorial active?
			if(mTutorial != null) {
				// Yeah, it is. Remove it because it's game time!
				mTutorial.destroy();
				mTutorial = null;

				// Reset match timer to ignore time spent during tutorial
				mMatchTime = Constants.GAME_MATCH_DURATION;

				// Log start of the game
				GlobalInfo.data.logMilestone(GlobalInfo.user, GlobalInfo.game, 'game_start');
			}
		}

		mIsThinking = true;
	};

	var generateNewQuestion = function() {
		mIsThinking = false;
		mQuestion = Game.rnd.integerInRange(1, Constants.CARDS_COLORS - 1);

		mHud.refresh();
		mHud.highlightNewQuestion();

		penalizeStillFlippedUpCards();

		// Before clearing the turn based score, save information
		// regarding the current turn.
		if(GlobalInfo && GlobalInfo.data) {
			GlobalInfo.data.log({turn: mQuestionCounter, s: mTurnBasedScore}, true);
		}

		clearTurnBasedScore();

		// Schedule the cards to flip up and the
		// thinking phase to begin.
		Game.time.events.add(Phaser.Timer.SECOND * 0.5, flipCardsForNewRound, this);

		mQuestionCounter++;

		mSfxNewQuestion.play();
	};

	var getDifficulty = function() {
		return Constants.DIFFICULTY[mDifficultyIndex];
	};

	this.terminateCurrentTurn = function() {
		mQuestionTimer = 0;
	}

	// Getters
	this.getQuestion = function() {
		return mQuestion;
	};

	this.getCards = function() {
		return mCards;
	};

	this.getScore = function() {
		return mScore;
	};

	this.getMatchRemainingTime = function() {
		return mMatchTime;
	};

	this.getHud = function() {
		return mHud;
	};

	this.getMonster = function() {
		return mMonster;
	};

	this.getPercentageTimeRemainingAnswerQuestion = function() {
		return mQuestionTimer / mQuestionTime;
	};

	this.getTrash = function() {
		return mTrash;
	};

	this.getHealthPercentage = function() {
		return mHealth/Constants.GAME_HEALTH_MAX;
	};
};
<|endoftext|>


<commit_before>angular.module('ion-google-place', [])
    .directive('ionGooglePlace', [
        '$ionicTemplateLoader',
        '$ionicBackdrop',
        '$q',
        '$timeout',
        '$rootScope',
        '$document',
        function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $document) {
            return {
                require: '?ngModel',
                restrict: 'E',
                template: '<input type="text" readonly="readonly" class="ion-google-place" autocomplete="off">',
                replace: true,
                link: function(scope, element, attrs, ngModel) {
                    scope.locations = [];
                    var geocoder = new google.maps.Geocoder();
                    var searchEventTimeout = undefined;

                    var POPUP_TPL = [
                        '
',
                            '
',
                                '',
                                    '',
                                    '<input class="google-place-search" type="search" ng-model="searchQuery" placeholder="' + attrs.searchPlaceholder || 'Enter an address, place or ZIP code' + '">',
                                '',
                                '<button class="button button-clear">',
                                    attrs.labelCancel || 'Cancel',
                                '</button>',
                            '
',
                            '<ion-content class="has-header has-header">',
                                '<ion-list>',
                                    '<ion-item ng-repeat="location in locations" type="item-text-wrap" ng-click="selectLocation(location)">',
                                        '{{location.formatted_address}}',
                                    '</ion-item>',
                                '</ion-list>',
                            '</ion-content>',
                        '
'
                    ].join('');

                    var popupPromise = $ionicTemplateLoader.compile({
                        template: POPUP_TPL,
                        scope: scope,
                        appendTo: $document[0].body
                    });

                    popupPromise.then(function(el){
                        var searchInputElement = angular.element(el.element.find('input'));

                        scope.selectLocation = function(location){
                            ngModel.$setViewValue(location);
                            ngModel.$render();
                            el.element.css('display', 'none');
                            $ionicBackdrop.release();
                        };

                        scope.$watch('searchQuery', function(query){
                            if (searchEventTimeout) $timeout.cancel(searchEventTimeout);
                            searchEventTimeout = $timeout(function() {
                                if(!query) return;
                                if(query.length < 3);

                                var req = scope.geocodeOptions || {};
                                req.address = query;
                                geocoder.geocode(req, function(results, status) {
                                    if (status == google.maps.GeocoderStatus.OK) {
                                        scope.$apply(function(){
                                            scope.locations = results;
                                        });
                                    } else {
                                        // @TODO: Figure out what to do when the geocoding fails
                                    }
                                });
                            }, 350); // we're throttling the input by 350ms to be nice to google's API
                        });

                        var onClick = function(e){
                            e.preventDefault();
                            e.stopPropagation();
                            $ionicBackdrop.retain();
                            el.element.css('display', 'block');
                            searchInputElement[0].focus();
                            setTimeout(function(){
                                searchInputElement[0].focus();
                            },0);
                        };

                        var onCancel = function(e){
                            scope.searchQuery = '';
                            $ionicBackdrop.release();
                            el.element.css('display', 'none');
                        };

                        element.bind('click', onClick);
                        element.bind('touchend', onClick);

                        el.element.find('button').bind('click', onCancel);
                    });

                    if(attrs.placeholder){
                        element.attr('placeholder', attrs.placeholder);
                    }

                    ngModel.$formatters.unshift(function (modelValue) {
                        if (!modelValue) return '';
                        return modelValue;
                    });

                    ngModel.$parsers.unshift(function (viewValue) {
                        return viewValue;
                    });

                    ngModel.$render = function(){
                        if(!ngModel.$viewValue){
                            element.val('');
                        } else {
                            element.val(ngModel.$viewValue.formatted_address || '');
                        }
                    };
                }
            };
        }
    ]);
<commit_msg>Fix placeholder if attribute not defined<commit_after>angular.module('ion-google-place', [])
    .directive('ionGooglePlace', [
        '$ionicTemplateLoader',
        '$ionicBackdrop',
        '$q',
        '$timeout',
        '$rootScope',
        '$document',
        function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $document) {
            return {
                require: '?ngModel',
                restrict: 'E',
                template: '<input type="text" readonly="readonly" class="ion-google-place" autocomplete="off">',
                replace: true,
                link: function(scope, element, attrs, ngModel) {
                    scope.locations = [];
                    var geocoder = new google.maps.Geocoder();
                    var searchEventTimeout = undefined;

                    var POPUP_TPL = [
                        '
',
                            '
',
                                '',
                                    '',
                                    '<input class="google-place-search" type="search" ng-model="searchQuery" placeholder="' + (attrs.searchPlaceholder || 'Enter an address, place or ZIP code') + '">',
                                '',
                                '<button class="button button-clear">',
                                    attrs.labelCancel || 'Cancel',
                                '</button>',
                            '
',
                            '<ion-content class="has-header has-header">',
                                '<ion-list>',
                                    '<ion-item ng-repeat="location in locations" type="item-text-wrap" ng-click="selectLocation(location)">',
                                        '{{location.formatted_address}}',
                                    '</ion-item>',
                                '</ion-list>',
                            '</ion-content>',
                        '
'
                    ].join('');

                    var popupPromise = $ionicTemplateLoader.compile({
                        template: POPUP_TPL,
                        scope: scope,
                        appendTo: $document[0].body
                    });

                    popupPromise.then(function(el){
                        var searchInputElement = angular.element(el.element.find('input'));

                        scope.selectLocation = function(location){
                            ngModel.$setViewValue(location);
                            ngModel.$render();
                            el.element.css('display', 'none');
                            $ionicBackdrop.release();
                        };

                        scope.$watch('searchQuery', function(query){
                            if (searchEventTimeout) $timeout.cancel(searchEventTimeout);
                            searchEventTimeout = $timeout(function() {
                                if(!query) return;
                                if(query.length < 3);

                                var req = scope.geocodeOptions || {};
                                req.address = query;
                                geocoder.geocode(req, function(results, status) {
                                    if (status == google.maps.GeocoderStatus.OK) {
                                        scope.$apply(function(){
                                            scope.locations = results;
                                        });
                                    } else {
                                        // @TODO: Figure out what to do when the geocoding fails
                                    }
                                });
                            }, 350); // we're throttling the input by 350ms to be nice to google's API
                        });

                        var onClick = function(e){
                            e.preventDefault();
                            e.stopPropagation();
                            $ionicBackdrop.retain();
                            el.element.css('display', 'block');
                            searchInputElement[0].focus();
                            setTimeout(function(){
                                searchInputElement[0].focus();
                            },0);
                        };

                        var onCancel = function(e){
                            scope.searchQuery = '';
                            $ionicBackdrop.release();
                            el.element.css('display', 'none');
                        };

                        element.bind('click', onClick);
                        element.bind('touchend', onClick);

                        el.element.find('button').bind('click', onCancel);
                    });

                    if(attrs.placeholder){
                        element.attr('placeholder', attrs.placeholder);
                    }

                    ngModel.$formatters.unshift(function (modelValue) {
                        if (!modelValue) return '';
                        return modelValue;
                    });

                    ngModel.$parsers.unshift(function (viewValue) {
                        return viewValue;
                    });

                    ngModel.$render = function(){
                        if(!ngModel.$viewValue){
                            element.val('');
                        } else {
                            element.val(ngModel.$viewValue.formatted_address || '');
                        }
                    };
                }
            };
        }
    ]);
<|endoftext|>