<commit_before>import {
  Color,
  combine,
  ComponentDatatype,
  GltfLoader,
  PrimitiveOutlinePipelineStage,
  ShaderBuilder,
  _shadersPrimitiveOutlineStageVS,
  _shadersPrimitiveOutlineStageFS,
  Resource,
  ResourceCache,
} from "../../../Source/Cesium.js";
import createScene from "../../createScene.js";
import ShaderBuilderTester from "../../ShaderBuilderTester.js";
import waitForLoaderProcess from "../../waitForLoaderProcess.js";

describe("Scene/ModelExperimental/PrimitiveOutlinePipelineStage", function () {
  const boxWithPrimitiveOutline =
    "./Data/Models/GltfLoader/BoxWithPrimitiveOutline/glTF/BoxWithPrimitiveOutline.gltf";

  let scene;
  const gltfLoaders = [];
  const resources = [];

  beforeAll(function () {
    scene = createScene();
  });

  afterAll(function () {
    scene.destroyForSpecs();
  });

  function cleanup(resourcesArray) {
    for (let i = 0; i < resourcesArray.length; i++) {
      const resource = resourcesArray[i];
      if (!resource.isDestroyed()) {
        resource.destroy();
      }
    }
    resourcesArray.length = 0;
  }

  afterEach(function () {
    cleanup(resources);
    cleanup(gltfLoaders);
    ResourceCache.clearForSpecs();
  });

  function getOptions(gltfPath, options) {
    const resource = new Resource({
      url: gltfPath,
    });

    return combine(options, {
      gltfResource: resource,
      incrementallyLoadTextures: false, // Default to false if not supplied
    });
  }

  function loadGltf(gltfPath, options) {
    const gltfLoader = new GltfLoader(getOptions(gltfPath, options));
    gltfLoaders.push(gltfLoader);
    gltfLoader.load();

    return waitForLoaderProcess(gltfLoader, scene);
  }

  function mockRenderResources() {
    return {
      model: {
        outlineColor: Color.RED,
        showOutline: false,
      },
      shaderBuilder: new ShaderBuilder(),
      uniformMap: {},
      attributes: [],
      attributeIndex: 1,
    };
  }

  it("Processes model with CESIUM_primitive_outline extension", function () {
    return loadGltf(boxWithPrimitiveOutline).then(function (gltfLoader) {
      const components = gltfLoader.components;
      const [node] = components.nodes;
      const [primitive] = node.primitives;
      const frameState = scene.frameState;
      const renderResources = mockRenderResources();

      PrimitiveOutlinePipelineStage.process(
        renderResources,
        primitive,
        frameState
      );

      const context = frameState.context;
      const outlineTexture = context.cache.modelOutliningCache.outlineTexture;

      const uniformMap = renderResources.uniformMap;
      expect(uniformMap.model_outlineTexture()).toBe(outlineTexture);
      expect(uniformMap.model_outlineColor()).toBe(Color.RED);
      expect(uniformMap.model_showOutline()).toBe(false);

      const shaderBuilder = renderResources.shaderBuilder;

      ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [
        "HAS_PRIMITIVE_OUTLINE",
      ]);
      ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [
        "HAS_PRIMITIVE_OUTLINE",
      ]);
      ShaderBuilderTester.expectHasAttributes(shaderBuilder, undefined, [
        "attribute vec3 a_outlineCoordinates;",
      ]);
      ShaderBuilderTester.expectHasVaryings(shaderBuilder, [
        "varying vec3 v_outlineCoordinates;",
      ]);
      ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []);
      ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [
        "uniform bool model_showOutline;",
        "uniform sampler2D model_outlineTexture;",
        "uniform vec4 modelOutlineColor;",
      ]);
      ShaderBuilderTester.expectVertexLinesEqual(shaderBuilder, [
        _shadersPrimitiveOutlineStageVS,
      ]);
      ShaderBuilderTester.expectFragmentLinesEqual(shaderBuilder, [
        _shadersPrimitiveOutlineStageFS,
      ]);

      const attributes = renderResources.attributes;
      expect(attributes.length).toBe(1);
      const [outlineCoordinates] = attributes;

      expect(outlineCoordinates.index).toBe(1);
      expect(outlineCoordinates.vertexBuffer).toBe(
        primitive.outlineCoordinates.buffer
      );
      expect(outlineCoordinates.componentsPerAttribute).toBe(3);
      expect(outlineCoordinates.componentDatatype).toBe(
        ComponentDatatype.FLOAT
      );
      expect(outlineCoordinates.offsetInBytes).toBe(0);
      expect(outlineCoordinates.strideInBytes).not.toBeDefined();
      expect(outlineCoordinates.normalize).toBe(false);
    });
  });
});
<commit_msg>Fix typo<commit_after>import {
  Color,
  combine,
  ComponentDatatype,
  GltfLoader,
  PrimitiveOutlinePipelineStage,
  ShaderBuilder,
  _shadersPrimitiveOutlineStageVS,
  _shadersPrimitiveOutlineStageFS,
  Resource,
  ResourceCache,
} from "../../../Source/Cesium.js";
import createScene from "../../createScene.js";
import ShaderBuilderTester from "../../ShaderBuilderTester.js";
import waitForLoaderProcess from "../../waitForLoaderProcess.js";

describe("Scene/ModelExperimental/PrimitiveOutlinePipelineStage", function () {
  const boxWithPrimitiveOutline =
    "./Data/Models/GltfLoader/BoxWithPrimitiveOutline/glTF/BoxWithPrimitiveOutline.gltf";

  let scene;
  const gltfLoaders = [];
  const resources = [];

  beforeAll(function () {
    scene = createScene();
  });

  afterAll(function () {
    scene.destroyForSpecs();
  });

  function cleanup(resourcesArray) {
    for (let i = 0; i < resourcesArray.length; i++) {
      const resource = resourcesArray[i];
      if (!resource.isDestroyed()) {
        resource.destroy();
      }
    }
    resourcesArray.length = 0;
  }

  afterEach(function () {
    cleanup(resources);
    cleanup(gltfLoaders);
    ResourceCache.clearForSpecs();
  });

  function getOptions(gltfPath, options) {
    const resource = new Resource({
      url: gltfPath,
    });

    return combine(options, {
      gltfResource: resource,
      incrementallyLoadTextures: false, // Default to false if not supplied
    });
  }

  function loadGltf(gltfPath, options) {
    const gltfLoader = new GltfLoader(getOptions(gltfPath, options));
    gltfLoaders.push(gltfLoader);
    gltfLoader.load();

    return waitForLoaderProcess(gltfLoader, scene);
  }

  function mockRenderResources() {
    return {
      model: {
        outlineColor: Color.RED,
        showOutline: false,
      },
      shaderBuilder: new ShaderBuilder(),
      uniformMap: {},
      attributes: [],
      attributeIndex: 1,
    };
  }

  it("Processes model with CESIUM_primitive_outline extension", function () {
    return loadGltf(boxWithPrimitiveOutline).then(function (gltfLoader) {
      const components = gltfLoader.components;
      const [node] = components.nodes;
      const [primitive] = node.primitives;
      const frameState = scene.frameState;
      const renderResources = mockRenderResources();

      PrimitiveOutlinePipelineStage.process(
        renderResources,
        primitive,
        frameState
      );

      const context = frameState.context;
      const outlineTexture = context.cache.modelOutliningCache.outlineTexture;

      const uniformMap = renderResources.uniformMap;
      expect(uniformMap.model_outlineTexture()).toBe(outlineTexture);
      expect(uniformMap.model_outlineColor()).toBe(Color.RED);
      expect(uniformMap.model_showOutline()).toBe(false);

      const shaderBuilder = renderResources.shaderBuilder;

      ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [
        "HAS_PRIMITIVE_OUTLINE",
      ]);
      ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [
        "HAS_PRIMITIVE_OUTLINE",
      ]);
      ShaderBuilderTester.expectHasAttributes(shaderBuilder, undefined, [
        "attribute vec3 a_outlineCoordinates;",
      ]);
      ShaderBuilderTester.expectHasVaryings(shaderBuilder, [
        "varying vec3 v_outlineCoordinates;",
      ]);
      ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []);
      ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [
        "uniform bool model_showOutline;",
        "uniform sampler2D model_outlineTexture;",
        "uniform vec4 model_outlineColor;",
      ]);
      ShaderBuilderTester.expectVertexLinesEqual(shaderBuilder, [
        _shadersPrimitiveOutlineStageVS,
      ]);
      ShaderBuilderTester.expectFragmentLinesEqual(shaderBuilder, [
        _shadersPrimitiveOutlineStageFS,
      ]);

      const attributes = renderResources.attributes;
      expect(attributes.length).toBe(1);
      const [outlineCoordinates] = attributes;

      expect(outlineCoordinates.index).toBe(1);
      expect(outlineCoordinates.vertexBuffer).toBe(
        primitive.outlineCoordinates.buffer
      );
      expect(outlineCoordinates.componentsPerAttribute).toBe(3);
      expect(outlineCoordinates.componentDatatype).toBe(
        ComponentDatatype.FLOAT
      );
      expect(outlineCoordinates.offsetInBytes).toBe(0);
      expect(outlineCoordinates.strideInBytes).not.toBeDefined();
      expect(outlineCoordinates.normalize).toBe(false);
    });
  });
});
<|endoftext|>


<commit_before>// dependencies
//
import React from 'react';
import uniqueId from 'lodash/uniqueId';
import classnames from 'classnames';

import Label from './Label';

/**
 * Clean a value of any non-numeric characters
 * @param  {String|Number} value The value to clean
 * @return {String}              The cleaned value
 */
const clean = (value) => `${value}`.replace(/[^0-9.-]/g, '');

/**
 * Convert a value to a fixed-decimal number
 * @param  {Number} value The number to convert
 * @param  {Number} count The number of decimal places
 * @return {String}       The formatted number
 */
const decimals = (value, count) => `${value.toFixed(count)}`;

/**
 * Convert a value to a currency string (2 decimal places, prefixed by "$")
 * @param  {Number} value The value to convert
 * @return {String}       The formatted value
 */
const currency = (value) => `$ ${decimals(value, 2)}`;

class NumericInput extends React.Component {
    constructor(props) {
        super(props);

        // generate a unique ID for the component
        //
        this.id = uniqueId('NumericInput-');

        // set the validation message from the properties or from a default
        //
        this.validationMessage = this.props.validationMessage
            || `${props.description} is required`;

        // initialize the component state
        //
        this.state = this.validate(props.value, true);

        // bind `this` to the event handlers
        //
        this.onBlur = this.onBlur.bind(this);
        this.onChange = this.onChange.bind(this);
        this.onFocus = this.onFocus.bind(this);
    }

    componentWillMount() {
        if (this.props.onValidation) {
            this.props.onValidation(
                this.state.hasValidated,
                this.state.isValid,
                this.state.validationMessage
            );
        }
    }

    componentWillReceiveProps(newProps) {
        const newState = this.validate(newProps.value);
        newState.hasValidated = this.state.hasValidated;

        if (newState.value !== this.state.value) {
            this.setState(newState);

            if (this.props.onValidation) {
                this.props.onValidation(
                    newState.hasValidated,
                    newState.isValid,
                    newState.validationMessage
                );
            }

            if (this.props.onChange) {
                this.props.onChange(newState.value);
            }
        }

    }

    onBlur(event) {

        const newState = this.validate(event.target.value, true);
        newState.hasValidated = true;

        this.setState(newState);

        if (this.props.onValidation) {
            this.props.onValidation(
                newState.hasValidated,
                newState.isValid,
                newState.validationMessage
            );
        }

        if (this.props.onChange && newState.value !== this.state.value) {
            this.props.onChange(newState.value);
        }
    }

    onChange(event) {
        const newState = this.validate(event.target.value);
        newState.hasValidated = this.state.hasValidated;

        this.setState(newState);

        if (this.props.onValidation && newState.hasValidated) {
            this.props.onValidation(
                newState.hasValidated,
                newState.isValid,
                newState.validationMessage
            );
        }

        if (this.props.onChange && newState.value !== this.state.value) {
            this.props.onChange(newState.value);
        }
    }

    onFocus(event) {
        const newState = this.validate(event.target.value);
        newState.hasValidated = this.state.hasValidated;

        newState.value = clean(newState.value);

        this.setState(newState);
    }

    validate(newValue, format = false) {
        let value = parseFloat(clean(newValue));

        const notANumber = Number.isNaN(value);

        if (notANumber) {
            value = '';
        } else if (format && this.props.isCurrency) {
            value = currency(value);
        } else if (format && (this.props.decimals || this.props.decimals === 0)) {
            value = decimals(value, this.props.decimals);
        } else {
            value = `${value}`;
        }

        const isValid = !this.props.required || !!value;

        return {
            value,
            isValid,
            validationMessage: isValid ? null : this.validationMessage,
            hasValidated:      false,
        };
    }

    render() {
        const divClasses = classnames('form-group', {
            'has-error': this.state.hasValidated && !this.state.isValid,
        });

        return (

                {this.props.label
                    ?
                    : ''
                }
                <input
                    type="text"
                    id={this.id}
                    className="form-control"
                    placeholder={this.props.placeholder || ''}
                    value={this.state.value}
                    onBlur={this.onBlur}
                    onChange={this.onChange}
                    onFocus={this.onFocus}
                />
                {this.state.hasValidated && !this.state.isValid
                    ? {this.validationMessage}
                    : ''
                }

        );
    }
}

// set the property types for the NumericInput component
//
NumericInput.propTypes = {
    label:             React.PropTypes.string,
    description:       React.PropTypes.string,
    required:          React.PropTypes.bool,
    validationMessage: React.PropTypes.string,
    placeholder:       React.PropTypes.string,
    isCurrency:        React.PropTypes.bool,
    decimals:          React.PropTypes.number,
    value:             React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number,
    ]),
    onChange:          React.PropTypes.func,
    onValidation:      React.PropTypes.func,
};

export default NumericInput;
<commit_msg>comment cleanup<commit_after>// dependencies
//
import React from 'react';
import uniqueId from 'lodash/uniqueId';
import classnames from 'classnames';

import Label from './Label';

/**
 * Clean a value of any non-numeric characters
 * @param  {String|Number} value The value to clean
 * @return {String}              The cleaned value
 */
const clean = (value) => `${value}`.replace(/[^0-9.-]/g, '');

/**
 * Convert a value to a fixed-decimal number
 * @param  {Number} value The number to convert
 * @param  {Number} count The number of decimal places
 * @return {String}       The formatted number
 */
const decimals = (value, count) => `${value.toFixed(count)}`;

/**
 * Convert a value to a currency string (2 decimal places, prefixed by "$")
 * @param  {Number} value The value to convert
 * @return {String}       The formatted value
 */
const currency = (value) => `$ ${decimals(value, 2)}`;

/**
 * The NumericInput component
 */
class NumericInput extends React.Component {
    constructor(props) {
        super(props);

        // generate a unique ID for the component
        //
        this.id = uniqueId('NumericInput-');

        // set the validation message from the properties or from a default
        //
        this.validationMessage = this.props.validationMessage
            || `${props.description} is required`;

        // initialize the component state
        //
        this.state = this.validate(props.value, true);

        // bind `this` to the event handlers
        //
        this.onBlur = this.onBlur.bind(this);
        this.onChange = this.onChange.bind(this);
        this.onFocus = this.onFocus.bind(this);
    }

    /**
     * Handle the initial mounting of the component
     */
    componentWillMount() {
        // do we have an onValidation handler? if so, call it with the current
        // validation state
        //
        if (this.props.onValidation) {
            this.props.onValidation(
                this.state.hasValidated,
                this.state.isValid,
                this.state.validationMessage
            );
        }
    }

    componentWillReceiveProps(newProps) {
        const newState = this.validate(newProps.value);
        newState.hasValidated = this.state.hasValidated;

        if (newState.value !== this.state.value) {
            this.setState(newState);

            if (this.props.onValidation) {
                this.props.onValidation(
                    newState.hasValidated,
                    newState.isValid,
                    newState.validationMessage
                );
            }

            if (this.props.onChange) {
                this.props.onChange(newState.value);
            }
        }

    }

    onBlur(event) {

        const newState = this.validate(event.target.value, true);
        newState.hasValidated = true;

        this.setState(newState);

        if (this.props.onValidation) {
            this.props.onValidation(
                newState.hasValidated,
                newState.isValid,
                newState.validationMessage
            );
        }

        if (this.props.onChange && newState.value !== this.state.value) {
            this.props.onChange(newState.value);
        }
    }

    onChange(event) {
        const newState = this.validate(event.target.value);
        newState.hasValidated = this.state.hasValidated;

        this.setState(newState);

        if (this.props.onValidation && newState.hasValidated) {
            this.props.onValidation(
                newState.hasValidated,
                newState.isValid,
                newState.validationMessage
            );
        }

        if (this.props.onChange && newState.value !== this.state.value) {
            this.props.onChange(newState.value);
        }
    }

    onFocus(event) {
        const newState = this.validate(event.target.value);
        newState.hasValidated = this.state.hasValidated;

        newState.value = clean(newState.value);

        this.setState(newState);
    }

    validate(newValue, format = false) {
        let value = parseFloat(clean(newValue));

        const notANumber = Number.isNaN(value);

        if (notANumber) {
            value = '';
        } else if (format && this.props.isCurrency) {
            value = currency(value);
        } else if (format && (this.props.decimals || this.props.decimals === 0)) {
            value = decimals(value, this.props.decimals);
        } else {
            value = `${value}`;
        }

        const isValid = !this.props.required || !!value;

        return {
            value,
            isValid,
            validationMessage: isValid ? null : this.validationMessage,
            hasValidated:      false,
        };
    }

    render() {
        const divClasses = classnames('form-group', {
            'has-error': this.state.hasValidated && !this.state.isValid,
        });

        return (

                {this.props.label
                    ?
                    : ''
                }
                <input
                    type="text"
                    id={this.id}
                    className="form-control"
                    placeholder={this.props.placeholder || ''}
                    value={this.state.value}
                    onBlur={this.onBlur}
                    onChange={this.onChange}
                    onFocus={this.onFocus}
                />
                {this.state.hasValidated && !this.state.isValid
                    ? {this.validationMessage}
                    : ''
                }

        );
    }
}

// set the property types for the NumericInput component
//
NumericInput.propTypes = {
    label:             React.PropTypes.string,
    description:       React.PropTypes.string,
    required:          React.PropTypes.bool,
    validationMessage: React.PropTypes.string,
    placeholder:       React.PropTypes.string,
    isCurrency:        React.PropTypes.bool,
    decimals:          React.PropTypes.number,
    value:             React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number,
    ]),
    onChange:          React.PropTypes.func,
    onValidation:      React.PropTypes.func,
};

export default NumericInput;
<|endoftext|>


<commit_before>// dependencies
//
import React from 'react';
import uniqueId from 'lodash/uniqueId';
import classnames from 'classnames';

import Label from './Label';
import columns from '../helpers/columns';

/**
 * Clean a value of any non-numeric characters
 * @param  {String|Number} value The value to clean
 * @return {String}              The cleaned value
 */
const clean = (value) => `${value}`.replace(/[^0-9.-]/g, '');

/**
 * Convert a value to a fixed-decimal number
 * @param  {Number} value The number to convert
 * @param  {Number} count The number of decimal places
 * @return {String}       The formatted number
 */
const decimals = (value, count) => (value !== '' ? `${value.toFixed(count)}` : '');

/**
 * Convert a value to a currency string (2 decimal places, prefixed by "$")
 * @param  {Number} value The value to convert
 * @return {String}       The formatted value
 */
const currency = (value) => (value !== '' ? `$ ${decimals(value, 2)}` : '');

/**
 * The NumericInput component
 */
class NumericInput extends React.Component {
    constructor(props) {
        super(props);

        // generate a unique ID for the component
        //
        this.id = uniqueId('NumericInput-');

        // set the validation message from the properties or from a default
        //
        this.validationMessage = this.props.validationMessage
            || `${props.description} is required`;

        // initialize the component state
        //
        this.state = this.validate(props.value);

        // bind `this` to the event handlers
        //
        this.onBlur = this.onBlur.bind(this);
        this.onChange = this.onChange.bind(this);
        this.onFocus = this.onFocus.bind(this);
    }

    /**
     * Handle the initial mounting of the component
     */
    componentWillMount() {
        // do we have an onValidation handler? if so, call it with the current
        // validation state
        //
        if (this.props.onValidation) {
            this.props.onValidation(
                this.state.hasValidated,
                this.state.isValid,
                this.state.validationMessage
            );
        }
    }

    /**
     * Handle new props passed from the parent component
     * @param  {Object} newProps The new props
     */
    componentWillReceiveProps(newProps) {
        // validate the new value
        //
        const newState = this.validate(newProps.value);

        // ensure that we preserve the `hasValidated` & `isEditing` state of the
        // component
        //
        newState.hasValidated = this.state.hasValidated;
        newState.isEditing = this.state.isEditing;

        // do we have a new value? if not, then don't bother changing anything
        //
        if (newState.value !== this.state.value) {
            // update the component state
            //
            this.setState(newState);

            // do we have an onValidation handler? if so, call it with the new
            // validation state
            //
            if (this.props.onValidation) {
                this.props.onValidation(
                    newState.hasValidated,
                    newState.isValid,
                    newState.validationMessage
                );
            }

            // do we have an onChange handler? if so, call it with the new value
            //
            if (this.props.onChange) {
                this.props.onChange(newState.value);
            }
        }
    }

    /**
     * Handle the blur event for the input element
     * @param  {Object} event The event object
     */
    onBlur(event) {
        // validate the new value WITH FORMATTING
        //
        const newState = this.validate(event.target.value);

        // set `hasValidated` to true
        //
        newState.hasValidated = true;

        // set `isEditing` to false (since the user has just tabbed away)
        //
        newState.isEditing = false;

        // handle the updated state
        //
        this.onUpdatedState(newState);
    }

    /**
     * Handle the change event for the input element
     * @param  {Object} event The event object
     */
    onChange(event) {
        // validate the new value without formatting it
        //
        const newState = this.validate(event.target.value);

        // preserve the `hasValidated` state
        //
        newState.hasValidated = this.state.hasValidated;

        // set `isEditing` to true, since the user is currently editing
        //
        newState.isEditing = true;

        // handle the updated state
        //
        this.onUpdatedState(newState);
    }

    /**
     * Handle the "update state" event
     * @param  {Object} newState The new state for the component
     */
    onUpdatedState(newState) {
        // set the new component state
        //
        this.setState(newState);

        // do we have an onValidation handler? has the component previously
        // validated? if so, call the onValidation handler with the new validation state
        //
        if (this.props.onValidation && newState.hasValidated) {
            this.props.onValidation(
                newState.hasValidated,
                newState.isValid,
                newState.validationMessage
            );
        }

        // do we have an onChange handler? do we have a new value? if so, call
        // the onChange handler with the new value
        //
        if (this.props.onChange && newState.value !== this.state.value) {
            this.props.onChange(newState.value);
        }
    }

    /**
     * Handle the focus event for the input element
     * @param  {Object} event The event object
     */
    onFocus(event) {
        // validate the new value
        //
        const newState = this.validate(event.target.value);

        // preserve the `hasValidated` state
        //
        newState.hasValidated = this.state.hasValidated;

        // set `isEditing` to true, since the user has just focused the input
        // element
        //
        newState.isEditing = true;

        // set the new state
        //
        this.setState(newState);
    }

    /**
     * Validate a value, optionally formatting it
     * @param  {String|Number} newValue The new value
     * @param  {Boolean} format         If true, then format the value according
     *                                  to the component props; otherwise, just
     *                                  clean it
     * @return {Object}                 The new state for the component,
     *                                  including value & validation state
     */
    validate(newValue) {
        // clean the value & convert it to a floating point number
        //
        let value = parseFloat(clean(newValue));

        // determine if the value is not a number (NaN)
        //
        const notANumber = Number.isNaN(value);

        // examine the value & ensure it's a number
        //
        if (notANumber) {
            // it's not a number - change to an empty string
            //
            value = '';
        }

        // check to see if the value is valid
        //
        const isValid = !this.props.required || (!!value || value === 0);

        // return the new component state
        //
        return {
            value,
            isValid,
            validationMessage: isValid ? null : this.validationMessage,
            hasValidated:      false,
            isEditing:         false,
            hasTrailingDecimal: false,
        };
    }

    /**
     * Render the component
     * @return {React.Element} The React Element representing this component
     */
    render() {
        let value = `${this.state.value}`;

        if (!this.state.isEditing) {
            if (this.props.isCurrency) {
                value = currency(this.state.value);
            } else if (this.props.decimals || this.props.decimals === 0) {
                value = decimals(this.state.value, this.props.decimals);
            }
        }

        // generate the classes for the outermost div element
        //
        const divClasses = classnames('form-group', {
            'has-error': this.state.hasValidated && !this.state.isValid,
        });

        // instantiate the input element
        //
        const input = (
            <input
                type="text"
                id={this.id}
                className="form-control"
                placeholder={this.props.placeholder || ''}
                value={value}
                readOnly={this.props.readOnly}
                onBlur={this.onBlur}
                onChange={this.onChange}
                onFocus={this.onFocus}
            />
        );

        // instantiate the help block if there's a validation error
        //
        const helpBlock = this.state.hasValidated && !this.state.isValid
            ? {this.validationMessage}
            : '';

        // render the component & return it
        //
        return (

                {this.props.label
                    ?
                    : ''
                }

{input}{helpBlock}

        );
    }
}

// set the property types for the NumericInput component
//
NumericInput.propTypes = {
    label:             React.PropTypes.string,
    description:       React.PropTypes.string,
    required:          React.PropTypes.bool,
    readOnly:          React.PropTypes.bool,
    validationMessage: React.PropTypes.string,
    placeholder:       React.PropTypes.string,
    isCurrency:        React.PropTypes.bool,
    decimals:          React.PropTypes.number,
    labelColumns:      React.PropTypes.object,
    inputColumns:      React.PropTypes.object,
    value:             React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number,
    ]),
    onChange:          React.PropTypes.func,
    onValidation:      React.PropTypes.func,
};

// export the component
//
export default NumericInput;
<commit_msg>code cleanup<commit_after>// dependencies
//
import React from 'react';
import uniqueId from 'lodash/uniqueId';
import classnames from 'classnames';

import Label from './Label';
import columns from '../helpers/columns';

/**
 * Clean a value of any non-numeric characters
 * @param  {String|Number} value The value to clean
 * @return {String}              The cleaned value
 */
const clean = (value) => `${value}`.replace(/[^0-9.-]/g, '');

/**
 * Convert a value to a fixed-decimal number
 * @param  {Number} value The number to convert
 * @param  {Number} count The number of decimal places
 * @return {String}       The formatted number
 */
const decimals = (value, count) => (value !== '' ? `${value.toFixed(count)}` : '');

/**
 * Convert a value to a currency string (2 decimal places, prefixed by "$")
 * @param  {Number} value The value to convert
 * @return {String}       The formatted value
 */
const currency = (value) => (value !== '' ? `$ ${decimals(value, 2)}` : '');

/**
 * The NumericInput component
 */
class NumericInput extends React.Component {
    constructor(props) {
        super(props);

        // generate a unique ID for the component
        //
        this.id = uniqueId('NumericInput-');

        // set the validation message from the properties or from a default
        //
        this.validationMessage = this.props.validationMessage
            || `${props.description} is required`;

        // initialize the component state
        //
        this.state = this.validate(props.value);

        // bind `this` to the event handlers
        //
        this.onBlur = this.onBlur.bind(this);
        this.onChange = this.onChange.bind(this);
        this.onFocus = this.onFocus.bind(this);
    }

    /**
     * Handle the initial mounting of the component
     */
    componentWillMount() {
        // do we have an onValidation handler? if so, call it with the current
        // validation state
        //
        if (this.props.onValidation) {
            this.props.onValidation(
                this.state.hasValidated,
                this.state.isValid,
                this.state.validationMessage
            );
        }
    }

    /**
     * Handle new props passed from the parent component
     * @param  {Object} newProps The new props
     */
    componentWillReceiveProps(newProps) {
        // validate the new value
        //
        const newState = this.validate(newProps.value);

        // ensure that we preserve the `hasValidated` & `isEditing` state of the
        // component
        //
        newState.hasValidated = this.state.hasValidated;
        newState.isEditing = this.state.isEditing;

        // do we have a new value? if not, then don't bother changing anything
        //
        if (newState.value !== this.state.value) {
            // update the component state
            //
            this.setState(newState);

            // do we have an onValidation handler? if so, call it with the new
            // validation state
            //
            if (this.props.onValidation) {
                this.props.onValidation(
                    newState.hasValidated,
                    newState.isValid,
                    newState.validationMessage
                );
            }

            // do we have an onChange handler? if so, call it with the new value
            //
            if (this.props.onChange) {
                this.props.onChange(newState.value);
            }
        }
    }

    /**
     * Handle the blur event for the input element
     * @param  {Object} event The event object
     */
    onBlur(event) {
        // validate the new value WITH FORMATTING
        //
        const newState = this.validate(event.target.value);

        // set `hasValidated` to true
        //
        newState.hasValidated = true;

        // set `isEditing` to false (since the user has just tabbed away)
        //
        newState.isEditing = false;

        // handle the updated state
        //
        this.onUpdatedState(newState);
    }

    /**
     * Handle the change event for the input element
     * @param  {Object} event The event object
     */
    onChange(event) {
        // validate the new value without formatting it
        //
        const newState = this.validate(event.target.value);

        // preserve the `hasValidated` state
        //
        newState.hasValidated = this.state.hasValidated;

        // set `isEditing` to true, since the user is currently editing
        //
        newState.isEditing = true;

        // handle the updated state
        //
        this.onUpdatedState(newState);
    }

    /**
     * Handle the "update state" event
     * @param  {Object} newState The new state for the component
     */
    onUpdatedState(newState) {
        // set the new component state
        //
        this.setState(newState);

        // do we have an onValidation handler? has the component previously
        // validated? if so, call the onValidation handler with the new validation state
        //
        if (this.props.onValidation && newState.hasValidated) {
            this.props.onValidation(
                newState.hasValidated,
                newState.isValid,
                newState.validationMessage
            );
        }

        // do we have an onChange handler? do we have a new value? if so, call
        // the onChange handler with the new value
        //
        if (this.props.onChange && newState.value !== this.state.value) {
            this.props.onChange(newState.value);
        }
    }

    /**
     * Handle the focus event for the input element
     * @param  {Object} event The event object
     */
    onFocus(event) {
        // validate the new value
        //
        const newState = this.validate(event.target.value);

        // preserve the `hasValidated` state
        //
        newState.hasValidated = this.state.hasValidated;

        // set `isEditing` to true, since the user has just focused the input
        // element
        //
        newState.isEditing = true;

        // set the new state
        //
        this.setState(newState);
    }

    /**
     * Validate a value, optionally formatting it
     * @param  {String|Number} newValue The new value
     * @param  {Boolean} format         If true, then format the value according
     *                                  to the component props; otherwise, just
     *                                  clean it
     * @return {Object}                 The new state for the component,
     *                                  including value & validation state
     */
    validate(newValue) {
        // clean the value & convert it to a floating point number
        //
        let value = parseFloat(clean(newValue));

        // determine if the value is not a number (NaN)
        //
        const notANumber = Number.isNaN(value);

        // examine the value & ensure it's a number
        //
        if (notANumber) {
            // it's not a number - change to an empty string
            //
            value = '';
        }

        // check to see if the value is valid
        //
        const isValid = !this.props.required || (!!value || value === 0);

        // return the new component state
        //
        return {
            value,
            isValid,
            validationMessage:  isValid ? null : this.validationMessage,
            hasValidated:       false,
            isEditing:          false,
            hasTrailingDecimal: false,
        };
    }

    /**
     * Render the component
     * @return {React.Element} The React Element representing this component
     */
    render() {
        let value = `${this.state.value}`;

        if (!this.state.isEditing) {
            if (this.props.isCurrency) {
                value = currency(this.state.value);
            } else if (this.props.decimals || this.props.decimals === 0) {
                value = decimals(this.state.value, this.props.decimals);
            }
        }

        // generate the classes for the outermost div element
        //
        const divClasses = classnames('form-group', {
            'has-error': this.state.hasValidated && !this.state.isValid,
        });

        // instantiate the input element
        //
        const input = (
            <input
                type="text"
                id={this.id}
                className="form-control"
                placeholder={this.props.placeholder || ''}
                value={value}
                readOnly={this.props.readOnly}
                onBlur={this.onBlur}
                onChange={this.onChange}
                onFocus={this.onFocus}
            />
        );

        // instantiate the help block if there's a validation error
        //
        const helpBlock = this.state.hasValidated && !this.state.isValid
            ? {this.validationMessage}
            : '';

        // render the component & return it
        //
        return (

                {this.props.label
                    ?
                    : ''
                }

{input}{helpBlock}

        );
    }
}

// set the property types for the NumericInput component
//
NumericInput.propTypes = {
    label:             React.PropTypes.string,
    description:       React.PropTypes.string,
    required:          React.PropTypes.bool,
    readOnly:          React.PropTypes.bool,
    validationMessage: React.PropTypes.string,
    placeholder:       React.PropTypes.string,
    isCurrency:        React.PropTypes.bool,
    decimals:          React.PropTypes.number,
    labelColumns:      React.PropTypes.object,
    inputColumns:      React.PropTypes.object,
    value:             React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number,
    ]),
    onChange:          React.PropTypes.func,
    onValidation:      React.PropTypes.func,
};

// export the component
//
export default NumericInput;
<|endoftext|>


<commit_before>import React from 'react';
import ReactHighcharts from 'react-highcharts';
import ReactHighmaps from 'react-highcharts/dist/ReactHighmaps.js';
import { injectIntl } from 'react-intl';
import Highlight from 'react-highlight';
let ReactDOM = require('react-dom');
var maps = require('./world-eckert3-highres');

class GeoChart extends React.Component {

  getConfig() {
    const { data } = this.props;
    let config = {
      // Initiate the chart
      title: {
        text: 'Geographic Attention via Highmaps',
      },

      subtitle: {
        text: 'Source map: World',
      },

      mapNavigation: {
        enabled: true,
        buttonOptions: {
          verticalAlign: 'bottom',
        },
      },

      colorAxis: {
        min: 0,
      },

      series : [{
        data: data,
        mapData: maps,
        joinBy: 'iso-a2',
        name: 'Random data',
        states: {
          hover: {
              color: '#BADA55',
          },
        },
        dataLabels: {
          enabled: false,
          format: '{point.count}',
        },
      }],
    };
    return config;
  }

  render() {
    const { data } = this.props;
    const config = this.getConfig();

    return (
       React.createElement(ReactHighmaps, { config })
    );
  }
}

GeoChart.propTypes = {
  data: React.PropTypes.array.isRequired,
};

export default GeoChart;
<commit_msg>minor edit<commit_after>import React from 'react';
import ReactHighcharts from 'react-highcharts';
import ReactHighmaps from 'react-highcharts/dist/ReactHighmaps.js';
import { injectIntl } from 'react-intl';
import Highlight from 'react-highlight';
let ReactDOM = require('react-dom');
var maps = require('./world-eckert3-highres');

class GeoChart extends React.Component {

  getConfig() {
    const { data } = this.props;
    let config = {
      // Initiate the chart
      title: {
        text: 'Geographic Attention via Highmaps',
      },

      subtitle: {
        text: 'Source map: World',
      },

      mapNavigation: {
        enabled: true,
        buttonOptions: {
          verticalAlign: 'bottom',
        },
      },

      colorAxis: {
        min: 0,
      },

      series : [{
        data: data,
        mapData: maps,
        joinBy: 'iso-a2',
        name: 'Sentence percentage',
        states: {
          hover: {
              color: '#BADA55',
          },
        },
        dataLabels: {
          enabled: false,
          format: '{point.count}',
        },
      }],
    };
    return config;
  }

  render() {
    const { data } = this.props;
    const config = this.getConfig();

    return (
       React.createElement(ReactHighmaps, { config })
    );
  }
}

GeoChart.propTypes = {
  data: React.PropTypes.array.isRequired,
};

export default GeoChart;
<|endoftext|>


<commit_before>/**
 * [Miscellaneous description]
 */

function Miscellaneous($, _content) {

    /**
     * [createFixedToolbar description]
     * @return {[type]}
     */
    this.createFixedToolbar = function() {
        $("div#header").css({
            "position": "fixed",
            "zIndex": 10,
            "top": 0
        });
        $("div#monkey").css("margin-top", "88px");
    };

    /**
     * Vergrößert den Content-Bereich um 200px.
     * Sorgt leider für ein "Flackern" wenn man die Seite lädt, da das CSS relativ spät geladen wird.
     * Lässt sich glaube ich nicht ändern im Userscript.
     */
    this.makeContentWider = function() {
        var maxWidth = 1200,
            leftSidebarWidth = $("#c_left").outerWidth(),
            rightSidebarWidth = $("#c_right").outerWidth(),
            sidebarsWidth = !leftSidebarWidth ? rightSidebarWidth + 1 : !rightSidebarWidth ? leftSidebarWidth + 1 : (leftSidebarWidth && rightSidebarWidth) ? leftSidebarWidth + rightSidebarWidth : 1200,
            padding = 4,
            siteLocation = new SiteLocation($);

        $("#header > div > div").css({
            "width": maxWidth + "px"
        });

        $("#monkey").css({
            "maxWidth": maxWidth + "px"
        });

        $("#container").css({
            "width": maxWidth + "px"
        });

        $("#c_content").css({
            "width": (maxWidth - sidebarsWidth - padding) + "px"
        });

        // fixt den Livestream Bereich
        if(siteLocation.getLocation("livestreams")) {
            // vergroessert die Livestream Thumbnails im Highlight Bereich um 30%
            $("#c_content > .livestream_highlights").find(".livestream_cap > img").each(function() {
                $(this).css("height", $(this).height() * 1.3 + "px");
                $(this).css("width", $(this).width() * 1.3 + "px");
            });
        }

        // fixt den VOD Bereich
        if(siteLocation.getLocation("vods")) {
            //vergroessert die VOD Thumbnails im Highlight Bereich um 30%
            $("#c_content > .vod_highlights").find(".vod_thumb > img").each(function() {
                $(this).css("height", $(this).height() * 1.3 + "px");
                $(this).css("width", $(this).width() * 1.3 + "px");
            });

            // veraendert die Margins der VOD Thumbnails, damit sie sauber in einer Reihe angezeigt werden
            $("#c_content > .vod_videos_list > li").each(function() {
                $(this).css("margin", "0px 10px 10px 10px");
            });
        }

        // fixt den Gallery Bereich
        if(siteLocation.getLocation("gallery")) {
            // verandert die Margins der Thumbnails, damit sie saber in einer Reihe angezeigt werden
            $("#c_content > .gallery_albums > li").each(function() {
                $(this).css("margin", "0px 5px 5px 10px");
            });
        }

        // fixt den verzerrten YouTube Player im Forum
        if(siteLocation.getLocation("forums")) {
            $(".forum_ed_youtube").each(function() {
                $(this).css("width", "516px");
                $(this).css("display", "block");
            });
        }

        // fixt den verzerrten YouTube Player in den News
        if (siteLocation.getLocation("news")) {
            $("iframe").each(function () {
                if ($(this).attr("src").match(/^http:\/\/(?:www\.)?youtube.com\/embed\/\w+(&\S*)?$/)) {
                    $(this).css("width", "516px");
                    $(this).css("display", "block");
                    $(this).css("margin", "0 auto");
                }
            })
        }

        // fixt den verzerrten YouTube Player in den Schlagzeilen
        if (siteLocation.getLocation("headlines")) {
            $("iframe").each(function () {
                if ($(this).attr("src").match(/^http:\/\/(?:www\.)?youtube.com\/embed\/\w+(&\S*)?$/)) {
                    $(this).css("width", "516px");
                    $(this).css("display", "block");
                    $(this).css("margin", "0 auto");
                }
            })
        }
    };

    /**
     * Ändert den Pfeil im Forum so, dass er zum letzten Post springt, nicht zum letzten gelesenen.
     */
    this.changeForumArrowBehavior = function() {
        $("img[src$='arrow_last_item.gif']").each(function() {
            var e = $(this).parent("a"),
                url = e.attr("href"),
                newLink = url.substring(0, url.lastIndexOf("/")) + "&page=last#plast";

            e.attr("href", newLink);
        });
    };

    /**
     * Sortiert den Thread-Titel um, sodass dieser an erster Stelle steht.
     */
    this.resortTitle = function () {
        var title = $('head').find('title').text();
        var pieces = title.split(' \u00BB Thread: ');

        document.title = pieces.reverse().join(' \u00BB ');
    };

    this.buttonScrollUp = function(){
        $('#c_content h2:last').append('')
            .find('#icon_miscellaneous_buttonScrollUp')
            .on('click', function(){
                window.scrollTo(0, 0);
            });
    };

    this.buttonScrollDown = function(){
        $('#c_content h1:first').append('')
            .find('#icon_miscellaneous_buttonScrollDown')
            .on('click', function(){
                window.scrollTo(0, _content.get('forumPosts').last().offset().top - 100);
            });
    }
}
<commit_msg>BUGFIX: verzerrter YouTube Embedded Player im breiten Design - Höhe statt Breite angepasst<commit_after>/**
 * [Miscellaneous description]
 */

function Miscellaneous($, _content) {

    /**
     * [createFixedToolbar description]
     * @return {[type]}
     */
    this.createFixedToolbar = function() {
        $("div#header").css({
            "position": "fixed",
            "zIndex": 10,
            "top": 0
        });
        $("div#monkey").css("margin-top", "88px");
    };

    /**
     * Vergrößert den Content-Bereich um 200px.
     * Sorgt leider für ein "Flackern" wenn man die Seite lädt, da das CSS relativ spät geladen wird.
     * Lässt sich glaube ich nicht ändern im Userscript.
     */
    this.makeContentWider = function() {
        var maxWidth = 1200,
            leftSidebarWidth = $("#c_left").outerWidth(),
            rightSidebarWidth = $("#c_right").outerWidth(),
            sidebarsWidth = !leftSidebarWidth ? rightSidebarWidth + 1 : !rightSidebarWidth ? leftSidebarWidth + 1 : (leftSidebarWidth && rightSidebarWidth) ? leftSidebarWidth + rightSidebarWidth : 1200,
            padding = 4,
            siteLocation = new SiteLocation($);

        $("#header > div > div").css({
            "width": maxWidth + "px"
        });

        $("#monkey").css({
            "maxWidth": maxWidth + "px"
        });

        $("#container").css({
            "width": maxWidth + "px"
        });

        $("#c_content").css({
            "width": (maxWidth - sidebarsWidth - padding) + "px"
        });

        // fixt den Livestream Bereich
        if(siteLocation.getLocation("livestreams")) {
            // vergroessert die Livestream Thumbnails im Highlight Bereich um 30%
            $("#c_content > .livestream_highlights").find(".livestream_cap > img").each(function() {
                $(this).css("height", $(this).height() * 1.3 + "px");
                $(this).css("width", $(this).width() * 1.3 + "px");
            });
        }

        // fixt den VOD Bereich
        if(siteLocation.getLocation("vods")) {
            //vergroessert die VOD Thumbnails im Highlight Bereich um 30%
            $("#c_content > .vod_highlights").find(".vod_thumb > img").each(function() {
                $(this).css("height", $(this).height() * 1.3 + "px");
                $(this).css("width", $(this).width() * 1.3 + "px");
            });

            // veraendert die Margins der VOD Thumbnails, damit sie sauber in einer Reihe angezeigt werden
            $("#c_content > .vod_videos_list > li").each(function() {
                $(this).css("margin", "0px 10px 10px 10px");
            });
        }

        // fixt den Gallery Bereich
        if(siteLocation.getLocation("gallery")) {
            // verandert die Margins der Thumbnails, damit sie saber in einer Reihe angezeigt werden
            $("#c_content > .gallery_albums > li").each(function() {
                $(this).css("margin", "0px 5px 5px 10px");
            });
        }

        // fixt den verzerrten YouTube Player im Forum
        if(siteLocation.getLocation("forums")) {
            $(".forum_ed_youtube embed").each(function() {
                $(this).css("height", $(this).width() * (9 / 16) + "px");
            });
        }

        // fixt den verzerrten YouTube Player in den News
        if (siteLocation.getLocation("news")) {
            $("iframe").each(function () {
                if ($(this).attr("src").match(/^http:\/\/(?:www\.)?youtube.com\/embed\/\w+(&\S*)?$/)) {
                    $(this).css("height", $(this).width() * (9 / 16) + "px");
                }
            })
        }

        // fixt den verzerrten YouTube Player in den Schlagzeilen
        if (siteLocation.getLocation("headlines")) {
            $("iframe").each(function () {
                if ($(this).attr("src").match(/^http:\/\/(?:www\.)?youtube.com\/embed\/\w+(&\S*)?$/)) {
                    $(this).css("height", $(this).width() * (9 / 16) + "px");
                }
            })
        }
    };

    /**
     * Ändert den Pfeil im Forum so, dass er zum letzten Post springt, nicht zum letzten gelesenen.
     */
    this.changeForumArrowBehavior = function() {
        $("img[src$='arrow_last_item.gif']").each(function() {
            var e = $(this).parent("a"),
                url = e.attr("href"),
                newLink = url.substring(0, url.lastIndexOf("/")) + "&page=last#plast";

            e.attr("href", newLink);
        });
    };

    /**
     * Sortiert den Thread-Titel um, sodass dieser an erster Stelle steht.
     */
    this.resortTitle = function () {
        var title = $('head').find('title').text();
        var pieces = title.split(' \u00BB Thread: ');

        document.title = pieces.reverse().join(' \u00BB ');
    };

    this.buttonScrollUp = function(){
        $('#c_content h2:last').append('')
            .find('#icon_miscellaneous_buttonScrollUp')
            .on('click', function(){
                window.scrollTo(0, 0);
            });
    };

    this.buttonScrollDown = function(){
        $('#c_content h1:first').append('')
            .find('#icon_miscellaneous_buttonScrollDown')
            .on('click', function(){
                window.scrollTo(0, _content.get('forumPosts').last().offset().top - 100);
            });
    }
}
<|endoftext|>


<commit_before>//All the questoin in a survey
function ResponsesNewView(surveyID) {
	var _ = require('lib/underscore')._;
	var Question = require('models/question');
	var Response = require('models/response');

	self = Ti.UI.createView({
		layout : 'vertical'
	});

	var answerFields = [];

	var generateLabelTextForQuestion = function(question) {
		text = '';
		text += question['content'];
		text += question.mandatory ? ' *' : '';
		if (question.max_length) {
			text += ' [' + question.max_length + ']';
		}
		return text;
	}
	var questions = Question.findBy('survey_id', surveyID);
	_(questions).each(function(question) {
		var label = Ti.UI.createLabel({
			color : '#000000',
			text : generateLabelTextForQuestion(question),
			height : 'auto',
			width : 'auto',
			left : 5
		});
		self.add(label);

		var textField = Ti.UI.createTextField({
			borderStyle : Ti.UI.INPUT_BORDERSTYLE_ROUNDED,
			color : '#336699',
			right : 5,
			left : 5,
			editable : true
		});
		self.add(textField);
		answerFields.push({
			'id' : question.id,
			'tf' : textField,
			'label' : label
		});
	});

	var findAnswerFieldByQuestionID = function(questionID) {
		var targetField;
		_(answerFields).each(function(answerField) {
			if (questionID == answerField.id) {
				Ti.API.info("question id:" + questionID);
				targetField = answerField;
			}
		});
		return targetField;
	}

	var displayErrors = function(responseErrors) {
		Ti.API.info("All the errors:" + responseErrors);
		for (var answerErrors in responseErrors) {
			Ti.API.info("Answer errors for:" + answerErrors);
			for (var field in responseErrors[answerErrors]) {
				var label = findAnswerFieldByQuestionID(answerErrors).label;
				label.setText(label.getText() + " " + responseErrors[answerErrors][field]);
				label.setColor("red");
				Ti.API.info(responseErrors[answerErrors][field]);
			}
		}
	}
	var saveButton = Ti.UI.createButton({
		title : 'Save',
		height : 30,
		top : 10,
		width : '100%'
	});
	self.add(saveButton);

	saveButton.addEventListener('click', function(e) {
		var answersData = _(answerFields).map(function(field) {
			return {
				'question_id' : field.id,
				'content' : field.tf.getValue()
			}
		});
		responseErrors = Response.validate(answersData);
		if (responseErrors.hasOwnProperty('errors')) {
			displayErrors(responseErrors['errors']);
			alert("There were some errors in the response.");
		} else {
			Response.createRecord(surveyID, answersData);
			_(answerFields).each(function(field) {
				field.tf.setValue(null);
			});
			Ti.App.fireEvent('ResponsesNewView:savedResponse');
		}
	});

	return self;
}

module.exports = ResponsesNewView;
<commit_msg>Set error text using the generator<commit_after>//All the questoin in a survey
function ResponsesNewView(surveyID) {
	var _ = require('lib/underscore')._;
	var Question = require('models/question');
	var Response = require('models/response');

	self = Ti.UI.createView({
		layout : 'vertical'
	});

	var answerFields = [];

	var generateLabelTextForQuestion = function(question, errorText) {
		text = '';
		text += question['content'];
		text += question.mandatory ? ' *' : '';
		if (question.max_length) {
			text += ' [' + question.max_length + ']';
		}
		if (errorText) {
			text += '\n' + errorText;
		}
		return text;
	}
	var questions = Question.findBy('survey_id', surveyID);
	_(questions).each(function(question) {
		var label = Ti.UI.createLabel({
			color : '#000000',
			text : generateLabelTextForQuestion(question, ""),
			height : 'auto',
			width : 'auto',
			left : 5
		});
		self.add(label);

		var textField = Ti.UI.createTextField({
			borderStyle : Ti.UI.INPUT_BORDERSTYLE_ROUNDED,
			color : '#336699',
			right : 5,
			left : 5,
			editable : true
		});
		self.add(textField);
		answerFields.push({
			'id' : question.id,
			'tf' : textField,
			'label' : label
		});
	});

	var findAnswerFieldByQuestionID = function(questionID) {
		var targetField;
		_(answerFields).each(function(answerField) {
			if (questionID == answerField.id) {
				Ti.API.info("question id:" + questionID);
				targetField = answerField;
			}
		});
		return targetField;
	}
	var displayErrors = function(responseErrors) {
		Ti.API.info("All the errors:" + responseErrors);
		for (var answerErrors in responseErrors) {
			Ti.API.info("Answer errors for:" + answerErrors);
			for (var field in responseErrors[answerErrors]) {
				var question_id = answerErrors;
				var question = Question.findOneById(question_id);
				var label = findAnswerFieldByQuestionID(question_id).label;
				var labelText = generateLabelTextForQuestion(question, responseErrors[question_id][field]);
				label.setText(labelText);
				label.setColor("red");
				Ti.API.info(responseErrors[question_id][field]);
			}
		}
	}
	var saveButton = Ti.UI.createButton({
		title : 'Save',
		height : 30,
		top : 10,
		width : '100%'
	});
	self.add(saveButton);

	saveButton.addEventListener('click', function(e) {
		var answersData = _(answerFields).map(function(field) {
			return {
				'question_id' : field.id,
				'content' : field.tf.getValue()
			}
		});
		responseErrors = Response.validate(answersData);
		if (responseErrors.hasOwnProperty('errors')) {
			displayErrors(responseErrors['errors']);
			alert("There were some errors in the response.");
		} else {
			Response.createRecord(surveyID, answersData);
			_(answerFields).each(function(field) {
				field.tf.setValue(null);
			});
			Ti.App.fireEvent('ResponsesNewView:savedResponse');
		}
	});

	return self;
}

module.exports = ResponsesNewView;
<|endoftext|>


<commit_before>//All the questoin in a survey
function ResponsesNewView(surveyID) {
  var _ = require('lib/underscore')._;
  var Question = require('models/question');
  var Survey = require('models/survey');
  var Response = require('models/response');
  var QuestionView = require('ui/common/questions/QuestionView');
  var ResponseViewHelper = require('ui/common/responses/ResponseViewHelper');
  var TopLevelView = require('ui/common/components/TopLevelView');
  var responseViewHelper = new ResponseViewHelper();
  var Toast = require('ui/common/components/Toast');

  var self = new TopLevelView('New Response');

  var scrollableView = Ti.UI.createScrollableView({
    top : self.headerHeight,
    showPagingControl : true
  });
  self.add(scrollableView);

  var survey = Survey.findOneById(surveyID);
  var questions = survey.firstLevelQuestionsAndCategories();

  var validateAndSaveAnswers = function(e, status) {
    activityIndicator.show();
    var questionViews = responseViewHelper.getQuestionViews(scrollableView.getViews());
    var answersData = _(questionViews).map(function(questionView, questionID) {
      Ti.API.info("questionid:" + questionID);
      Ti.API.info("content:" + questionView.getValueField().getValue());
      return {
        'question_id' : questionID,
        'content' : questionView.getValueField().getValue()
      };
    });
    var responseErrors = Response.validate(answersData, status);
    if (!_.isEmpty(responseErrors)) {
      responseViewHelper.displayErrors(responseErrors, questionViews);
      pagesWithErrors = responseViewHelper.scrollToFirstErrorPage(scrollableView, responseErrors);
      pagesWithErrors = _(pagesWithErrors).map(function(pageNumber) {
        return pageNumber + 1 ;
      });
      alert("There were errors in page(s) " + _(pagesWithErrors).uniq().toString());
    } else {
      Response.createRecord(surveyID, status, answersData, responseLocation);
      new Toast('Response saved').show();
      self.fireEvent('ResponsesNewView:savedResponse');
    }
    activityIndicator.hide();
  };

  responseViewHelper.paginate(questions, scrollableView, null, validateAndSaveAnswers);

  var activityIndicator = Ti.UI.Android.createProgressIndicator({
    message : L('saving_response'),
    location : Ti.UI.Android.PROGRESS_INDICATOR_DIALOG,
    type : Ti.UI.Android.PROGRESS_INDICATOR_INDETERMINANT
  });
  self.add(activityIndicator);

  var getCurrentLocation = function() {
    var location = {};
    Titanium.Geolocation.getCurrentPosition(function(e) {
      if (e.error) {
        Ti.API.info("Error getting location");
        return;
      }
      location.longitude = e.coords.longitude;
      location.latitude = e.coords.latitude;
      Ti.API.info("longitude = " + e.coords.longitude);
      Ti.API.info("latitude = " + e.coords.latitude);
    });
    return location;
  };

  var responseLocation = getCurrentLocation();

  self.cleanup = function() {
    self.remove(scrollableView);
    scrollableView = null;
  };
  return self;
}

module.exports = ResponsesNewView;
<commit_msg>Added a check for scrollableView in ResponsesNewView<commit_after>//All the questoin in a survey
function ResponsesNewView(surveyID) {
  var _ = require('lib/underscore')._;
  var Question = require('models/question');
  var Survey = require('models/survey');
  var Response = require('models/response');
  var QuestionView = require('ui/common/questions/QuestionView');
  var ResponseViewHelper = require('ui/common/responses/ResponseViewHelper');
  var TopLevelView = require('ui/common/components/TopLevelView');
  var responseViewHelper = new ResponseViewHelper();
  var Toast = require('ui/common/components/Toast');

  var self = new TopLevelView('New Response');

  var scrollableView = Ti.UI.createScrollableView({
    top : self.headerHeight,
    showPagingControl : true
  });
  self.add(scrollableView);

  var survey = Survey.findOneById(surveyID);
  var questions = survey.firstLevelQuestionsAndCategories();

  var validateAndSaveAnswers = function(e, status) {
    activityIndicator.show();
    if(scrollableView)
      var questionViews = responseViewHelper.getQuestionViews(scrollableView.getViews());
    var answersData = _(questionViews).map(function(questionView, questionID) {
      Ti.API.info("questionid:" + questionID);
      Ti.API.info("content:" + questionView.getValueField().getValue());
      return {
        'question_id' : questionID,
        'content' : questionView.getValueField().getValue()
      };
    });
    var responseErrors = Response.validate(answersData, status);
    if (!_.isEmpty(responseErrors)) {
      responseViewHelper.displayErrors(responseErrors, questionViews);
      pagesWithErrors = responseViewHelper.scrollToFirstErrorPage(scrollableView, responseErrors);
      pagesWithErrors = _(pagesWithErrors).map(function(pageNumber) {
        return pageNumber + 1 ;
      });
      alert("There were errors in page(s) " + _(pagesWithErrors).uniq().toString());
    } else {
      Response.createRecord(surveyID, status, answersData, responseLocation);
      new Toast('Response saved').show();
      self.fireEvent('ResponsesNewView:savedResponse');
    }
    activityIndicator.hide();
  };

  responseViewHelper.paginate(questions, scrollableView, null, validateAndSaveAnswers);

  var activityIndicator = Ti.UI.Android.createProgressIndicator({
    message : L('saving_response'),
    location : Ti.UI.Android.PROGRESS_INDICATOR_DIALOG,
    type : Ti.UI.Android.PROGRESS_INDICATOR_INDETERMINANT
  });
  self.add(activityIndicator);

  var getCurrentLocation = function() {
    var location = {};
    Titanium.Geolocation.getCurrentPosition(function(e) {
      if (e.error) {
        Ti.API.info("Error getting location");
        return;
      }
      location.longitude = e.coords.longitude;
      location.latitude = e.coords.latitude;
      Ti.API.info("longitude = " + e.coords.longitude);
      Ti.API.info("latitude = " + e.coords.latitude);
    });
    return location;
  };

  var responseLocation = getCurrentLocation();

  self.cleanup = function() {
    self.remove(scrollableView);
    scrollableView = null;
  };
  return self;
}

module.exports = ResponsesNewView;
<|endoftext|>


<commit_before>'use strict';

angular.module('app')
    .controller('rgiNewResourceDialogCtrl', function (
        $scope,
        $route,
        rgiNotifier,
        rgiResourcesMethodSrvc,
        rgiIdentitySrvc
    ) {
        $scope.current_user = rgiIdentitySrvc.currentUser;

        $scope.new_resource = {
            order: $scope.$parent.resources.length + 1,
            type: $scope.$parent.resource_type
        };

        $scope.resourceCreate = function () {
            if (!$scope.new_resource.order) {
                rgiNotifier.error('You must supply the order you want the block to appear!');
            } else if (!$scope.new_resource.head) {
                rgiNotifier.error('You must supply a title!');
            } else if (!$scope.new_resource.body) {
                rgiNotifier.error('You must supply content!');
            } else {
                rgiResourcesMethodSrvc.createResource($scope.new_resource)
                    .then(function () {
                        rgiNotifier.notify('New resource created!');
                        $scope.closeThisDialog();
                        $route.reload();
                    }, function (reason) {
                        rgiNotifier.error(reason);
                    });
            }
        };
    });
<commit_msg>clean up the 'New Resource' dialog controller<commit_after>'use strict';

angular.module('app')
    .controller('rgiNewResourceDialogCtrl', function (
        $scope,
        $route,
        rgiNotifier,
        rgiResourcesMethodSrvc
    ) {
        $scope.new_resource = {
            order: $scope.$parent.resources.length + 1,
            type: $scope.$parent.resource_type
        };

        $scope.resourceCreate = function () {
            if (!$scope.new_resource.head) {
                rgiNotifier.error('You must supply a title!');
            } else if (!$scope.new_resource.body) {
                rgiNotifier.error('You must supply content!');
            } else {
                rgiResourcesMethodSrvc.createResource($scope.new_resource)
                    .then(function () {
                        rgiNotifier.notify('New resource created!');
                        $scope.closeThisDialog();
                        $route.reload();
                    }, function (reason) {
                        rgiNotifier.error(reason);
                    });
            }
        };
    });
<|endoftext|>


<commit_before>'use strict';

//Setting up route
angular.module('about-us').config(['$stateProvider',
	function($stateProvider) {
		// Projects state routing
		$stateProvider.
		state('about-us', {
			url: '/about-us',
			templateUrl: 'modules/about-us/views/about-us.client.view.html'
		})
	}
]);<commit_msg>Add semicolon<commit_after>'use strict';

//Setting up route
angular.module('about-us').config(['$stateProvider',
	function($stateProvider) {
		// Projects state routing
		$stateProvider.
		state('about-us', {
			url: '/about-us',
			templateUrl: 'modules/about-us/views/about-us.client.view.html'
		});
	}
]);
<|endoftext|>


<commit_before>const Sentry = require('@sentry/node');
const express = require('@feathersjs/express');
const { Configuration } = require('@hpi-schul-cloud/commons');
const jwt = require('jsonwebtoken');
const OpenApiValidator = require('express-openapi-validator');

const reqlib = require('app-root-path').require;

const { SilentError, PageNotFound, AutoLogout, BruteForcePrevention, BadRequest } = reqlib('src/errors');
const { convertToFeathersError, cleanupIncomingMessage } = reqlib('src/errors/utils');

const logger = require('../logger');

const MAX_LEVEL_FILTER = 12;

const getRequestInfo = (req) => {
	const info = {
		url: req.originalUrl,
		data: req.body,
		method: req.method,
	};

	try {
		let decodedJWT;
		if (req.headers.authorization) {
			decodedJWT = jwt.decode(req.headers.authorization.replace('Bearer ', ''));
		}

		if (decodedJWT && decodedJWT.accountId) {
			info.user = {
				accountId: decodedJWT.accountId,
				userId: decodedJWT.userId,
			};
			if (decodedJWT.support === true) {
				info.support = {
					supportJWT: true,
					supportUserId: decodedJWT.supportUserId,
				};
			}
		} else if (req.headers.authorization) {
			info.user = 'Can not decode jwt.';
		} else {
			info.user = 'No jwt is set.';
		}
	} catch (err) {
		// Maybe we found a better solution, but we can not display the full error message with stack trace
		// it can contain jwt informations
		info.user = err.message;
	}

	return info;
};

const formatAndLogErrors = (error, req, res, next) => {
	if (error) {
		// sanitize
		const err = convertToFeathersError(error);
		const requestInfo = getRequestInfo(req);
		// type is override by logger for logging type
		err.errorType = err.type;

		// nested in errors
		cleanupIncomingMessage(err.errors);
		// for tests level is set to emerg, set LOG_LEVEL=debug for see it
		// Logging the error object won't print error's stack trace. You need to ask for it specifically
		logger.error({ ...err, ...requestInfo });
	}
	next(error);
};

// map to lower case and test as lower case
const secretDataKeys = (() =>
	[
		'headers',
		'password',
		'passwort',
		'new_password',
		'new-password',
		'oauth-password',
		'current-password',
		'passwort_1',
		'passwort_2',
		'password_1',
		'password_2',
		'password-1',
		'password-2',
		'password_verification',
		'password_control',
		'PASSWORD_HASH',
		'password_new',
		'accessToken',
		'ticket',
		'firstName',
		'lastName',
		'email',
		'birthday',
		'description',
		'gradeComment',
		'_csrf',
		'searchUserPassword',
	].map((k) => k.toLocaleLowerCase()))();

const filterSecretValue = (key, value) => {
	if (secretDataKeys.includes(key.toLocaleLowerCase())) {
		return '<secret>';
	}
	return value;
};

const filterDeep = (newData, level = 0) => {
	if (level > MAX_LEVEL_FILTER) {
		return '<max level exceeded>';
	}

	if (typeof newData === 'object' && newData !== null) {
		Object.entries(newData).forEach(([key, value]) => {
			const newValue = filterSecretValue(key, value);
			if (typeof newValue === 'string') {
				newData[key] = newValue;
			} else {
				filterDeep(value, level + 1);
			}
		});
	}
	return newData;
};

const filter = (data) => filterDeep({ ...data });

const secretQueryKeys = (() => ['accessToken', 'access_token'].map((k) => k.toLocaleLowerCase()))();
const filterQuery = (url) => {
	let newUrl = url;
	secretQueryKeys.forEach((key) => {
		// key is lower case
		if (newUrl.toLocaleLowerCase().includes(key)) {
			// first step cut complet query
			// maybe todo later add query as object of keys and remove keys with filter
			newUrl = url.split('?')[0];
			newUrl += '?<secretQuery>';
		}
	});
	return newUrl;
};

// important that it is not sent to sentry, or added it to logs
const filterSecrets = (error, req, res, next) => {
	if (error) {
		// req.url = filterQuery(req.url);
		// originalUrl is used by sentry
		req.originalUrl = filterQuery(req.originalUrl);
		req.body = filter(req.body);
		error.data = filter(error.data);
		error.options = filter(error.options);
	}
	next(error);
};

const saveResponseFilter = (error) => ({
	name: error.name,
	message: error.message instanceof Error && error.message.message ? error.message.message : error.message,
	code: error.code,
	traceId: error.traceId,
});

const sendError = (res, error) => {
	res.status(error.code).json(saveResponseFilter(error));
};

const handleSilentError = (error, req, res, next) => {
	if (error instanceof SilentError || (error && error.error instanceof SilentError)) {
		if (Configuration.get('SILENT_ERROR_ENABLED')) {
			res.append('x-silent-error', true);
		}
		res.status(200).json({ success: 'success' });
	} else {
		next(error);
	}
};

const handleValidationError = (error, req, res, next) => {
	// todo: handle other validation errors so they are formatted properly
	if (error instanceof OpenApiValidator.error.NotFound) {
		// sanitize
		const err = new PageNotFound(error);
		next(err);
	} else if (error instanceof OpenApiValidator.error.BadRequest) {
		const err = new BadRequest(error);
		next(err);
	} else {
		next(error);
	}
};

const skipErrorLogging = (error, req, res, next) => {
	if (
		error instanceof PageNotFound ||
		error.code === 405 ||
		error instanceof AutoLogout ||
		error instanceof BruteForcePrevention
	) {
		logger.debug(error);
		logger.warning(error.name);
		sendError(res, error);
	} else {
		next(error);
	}
};

const returnAsJson = express.errorHandler({
	html: (error, req, res) => {
		sendError(res, error);
	},
	json: (error, req, res) => {
		sendError(res, error);
	},
});

const addTraceId = (error, req, res, next) => {
	error.traceId = (req.headers || {}).requestId || error.traceId;
	next(error);
};

const errorHandler = (app) => {
	app.use(addTraceId);
	app.use(filterSecrets);
	app.use(Sentry.Handlers.errorHandler());
	app.use(handleValidationError);
	// TODO make skipErrorLogging configruable if middleware is added
	app.use(skipErrorLogging);
	app.use(formatAndLogErrors);
	// TODO make handleSilentError configruable if middleware is added
	// Configuration.get('SILENT_ERROR_ENABLED')
	app.use(handleSilentError);
	app.use(returnAsJson);
};
module.exports = errorHandler;
<commit_msg>Simplify error handler<commit_after>const Sentry = require('@sentry/node');
const express = require('@feathersjs/express');
const { Configuration } = require('@hpi-schul-cloud/commons');
const jwt = require('jsonwebtoken');
const OpenApiValidator = require('express-openapi-validator');

const reqlib = require('app-root-path').require;

const { SilentError, PageNotFound, AutoLogout, BruteForcePrevention, BadRequest } = reqlib('src/errors');
const { convertToFeathersError, cleanupIncomingMessage } = reqlib('src/errors/utils');

const logger = require('../logger');

const MAX_LEVEL_FILTER = 12;

const getRequestInfo = (req) => {
	const info = {
		url: req.originalUrl,
		data: req.body,
		method: req.method,
	};

	try {
		let decodedJWT;
		if (req.headers.authorization) {
			decodedJWT = jwt.decode(req.headers.authorization.replace('Bearer ', ''));
		}

		if (decodedJWT && decodedJWT.accountId) {
			info.user = {
				accountId: decodedJWT.accountId,
				userId: decodedJWT.userId,
			};
			if (decodedJWT.support === true) {
				info.support = {
					supportJWT: true,
					supportUserId: decodedJWT.supportUserId,
				};
			}
		} else if (req.headers.authorization) {
			info.user = 'Can not decode jwt.';
		} else {
			info.user = 'No jwt is set.';
		}
	} catch (err) {
		// Maybe we found a better solution, but we can not display the full error message with stack trace
		// it can contain jwt informations
		info.user = err.message;
	}

	return info;
};

const formatAndLogErrors = (error, req, res, next) => {
	if (error) {
		// sanitize
		const err = convertToFeathersError(error);
		const requestInfo = getRequestInfo(req);
		// type is override by logger for logging type
		err.errorType = err.type;

		// nested in errors
		cleanupIncomingMessage(err.errors);
		// for tests level is set to emerg, set LOG_LEVEL=debug for see it
		// Logging the error object won't print error's stack trace. You need to ask for it specifically
		logger.error({ ...err, ...requestInfo });
	}
	next(error);
};

// map to lower case and test as lower case
const secretDataKeys = (() =>
	[
		'headers',
		'password',
		'passwort',
		'new_password',
		'new-password',
		'oauth-password',
		'current-password',
		'passwort_1',
		'passwort_2',
		'password_1',
		'password_2',
		'password-1',
		'password-2',
		'password_verification',
		'password_control',
		'PASSWORD_HASH',
		'password_new',
		'accessToken',
		'ticket',
		'firstName',
		'lastName',
		'email',
		'birthday',
		'description',
		'gradeComment',
		'_csrf',
		'searchUserPassword',
	].map((k) => k.toLocaleLowerCase()))();

const filterSecretValue = (key, value) => {
	if (secretDataKeys.includes(key.toLocaleLowerCase())) {
		return '<secret>';
	}
	return value;
};

const filterDeep = (newData, level = 0) => {
	if (level > MAX_LEVEL_FILTER) {
		return '<max level exceeded>';
	}

	if (typeof newData === 'object' && newData !== null) {
		Object.entries(newData).forEach(([key, value]) => {
			const newValue = filterSecretValue(key, value);
			if (typeof newValue === 'string') {
				newData[key] = newValue;
			} else {
				filterDeep(value, level + 1);
			}
		});
	}
	return newData;
};

const filter = (data) => filterDeep({ ...data });

const secretQueryKeys = (() => ['accessToken', 'access_token'].map((k) => k.toLocaleLowerCase()))();
const filterQuery = (url) => {
	let newUrl = url;
	secretQueryKeys.forEach((key) => {
		// key is lower case
		if (newUrl.toLocaleLowerCase().includes(key)) {
			// first step cut complet query
			// maybe todo later add query as object of keys and remove keys with filter
			newUrl = url.split('?')[0];
			newUrl += '?<secretQuery>';
		}
	});
	return newUrl;
};

// important that it is not sent to sentry, or added it to logs
const filterSecrets = (error, req, res, next) => {
	if (error) {
		// req.url = filterQuery(req.url);
		// originalUrl is used by sentry
		req.originalUrl = filterQuery(req.originalUrl);
		req.body = filter(req.body);
		error.data = filter(error.data);
		error.options = filter(error.options);
	}
	next(error);
};

const saveResponseFilter = (error) => ({
	name: error.name,
	message: error.message instanceof Error && error.message.message ? error.message.message : error.message,
	code: error.code,
	traceId: error.traceId,
});

const sendError = (res, error) => {
	res.status(error.code).json(saveResponseFilter(error));
};

const handleSilentError = (error, req, res, next) => {
	if (error instanceof SilentError || (error && error.error instanceof SilentError)) {
		if (Configuration.get('SILENT_ERROR_ENABLED')) {
			res.append('x-silent-error', true);
		}
		res.status(200).json({ success: 'success' });
	} else {
		next(error);
	}
};

const handleValidationError = (error, req, res, next) => {
	// todo: handle other validation errors so they are formatted properly
	let err = error;
	if (error instanceof OpenApiValidator.error.NotFound) {
		err = new PageNotFound(error);
	} else if (err instanceof OpenApiValidator.error.BadRequest) {
		err = new BadRequest(error);
	}
	next(err);
};

const skipErrorLogging = (error, req, res, next) => {
	if (
		error instanceof PageNotFound ||
		error.code === 405 ||
		error instanceof AutoLogout ||
		error instanceof BruteForcePrevention
	) {
		logger.debug(error);
		logger.warning(error.name);
		sendError(res, error);
	} else {
		next(error);
	}
};

const returnAsJson = express.errorHandler({
	html: (error, req, res) => {
		sendError(res, error);
	},
	json: (error, req, res) => {
		sendError(res, error);
	},
});

const addTraceId = (error, req, res, next) => {
	error.traceId = (req.headers || {}).requestId || error.traceId;
	next(error);
};

const errorHandler = (app) => {
	app.use(addTraceId);
	app.use(filterSecrets);
	app.use(Sentry.Handlers.errorHandler());
	app.use(handleValidationError);
	// TODO make skipErrorLogging configruable if middleware is added
	app.use(skipErrorLogging);
	app.use(formatAndLogErrors);
	// TODO make handleSilentError configruable if middleware is added
	// Configuration.get('SILENT_ERROR_ENABLED')
	app.use(handleSilentError);
	app.use(returnAsJson);
};
module.exports = errorHandler;
<|endoftext|>


<commit_before>//  Package imports.
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import {
  defineMessages,
  FormattedMessage,
} from 'react-intl';
import Textarea from 'react-textarea-autosize';

//  Components.
import EmojiPicker from 'flavours/glitch/features/emoji_picker';
import ComposerTextareaIcons from './icons';
import ComposerTextareaSuggestions from './suggestions';

//  Utils.
import { isRtl } from 'flavours/glitch/util/rtl';
import {
  assignHandlers,
  hiddenComponent,
} from 'flavours/glitch/util/react_helpers';

//  Messages.
const messages = defineMessages({
  placeholder: {
    defaultMessage: 'What is on your mind?',
    id: 'compose_form.placeholder',
  },
});

//  Handlers.
const handlers = {

  //  When blurring the textarea, suggestions are hidden.
  handleBlur () {
    this.setState({ suggestionsHidden: true });
  },

  //  When the contents of the textarea change, we have to pull up new
  //  autosuggest suggestions if applicable, and also change the value
  //  of the textarea in our store.
  handleChange ({
    target: {
      selectionStart,
      value,
    },
  }) {
    const {
      onChange,
      onSuggestionsFetchRequested,
      onSuggestionsClearRequested,
    } = this.props;
    const { lastToken } = this.state;

    //  This gets the token at the caret location, if it begins with an
    //  `@` (mentions) or `:` (shortcodes).
    const left = value.slice(0, selectionStart).search(/[^\s\u200B]+$/);
    const right = value.slice(selectionStart).search(/[\s\u200B]/);
    const token = function () {
      switch (true) {
      case left < 0 || !/[@:]/.test(value[left]):
        return null;
      case right < 0:
        return value.slice(left);
      default:
        return value.slice(left, right + selectionStart).trim().toLowerCase();
      }
    }();

    //  We only request suggestions for tokens which are at least 3
    //  characters long.
    if (onSuggestionsFetchRequested && token && token.length >= 3) {
      if (lastToken !== token) {
        this.setState({
          lastToken: token,
          selectedSuggestion: 0,
          tokenStart: left,
        });
        onSuggestionsFetchRequested(token);
      }
    } else {
      this.setState({ lastToken: null });
      if (onSuggestionsClearRequested) {
        onSuggestionsClearRequested();
      }
    }

    //  Updates the value of the textarea.
    if (onChange) {
      onChange(value);
    }
  },

  //  Handles a click on an autosuggestion.
  handleClickSuggestion (index) {
    const { textarea } = this;
    const {
      onSuggestionSelected,
      suggestions,
    } = this.props;
    const {
      lastToken,
      tokenStart,
    } = this.state;
    onSuggestionSelected(tokenStart, lastToken, suggestions.get(index));
    textarea.focus();
  },

  //  Handles a keypress.  If the autosuggestions are visible, we need
  //  to allow keypresses to navigate and sleect them.
  handleKeyDown (e) {
    const {
      disabled,
      onSubmit,
      onSuggestionSelected,
      suggestions,
    } = this.props;
    const {
      lastToken,
      suggestionsHidden,
      selectedSuggestion,
      tokenStart,
    } = this.state;

    //  Keypresses do nothing if the composer is disabled.
    if (disabled) {
      e.preventDefault();
      return;
    }

    //  Switches over the pressed key.
    switch(e.key) {

    //  On arrow down, we pick the next suggestion.
    case 'ArrowDown':
      if (suggestions && suggestions.size > 0 && !suggestionsHidden) {
        e.preventDefault();
        this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
      }
      return;

    //  On arrow up, we pick the previous suggestion.
    case 'ArrowUp':
      if (suggestions && suggestions.size > 0 && !suggestionsHidden) {
        e.preventDefault();
        this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
      }
      return;

    //  On enter or tab, we select the suggestion.
    case 'Enter':
    case 'Tab':
      if (onSuggestionSelected && lastToken !== null && suggestions && suggestions.size > 0 && !suggestionsHidden) {
        e.preventDefault();
        e.stopPropagation();
        onSuggestionSelected(tokenStart, lastToken, suggestions.get(selectedSuggestion));
      }
      return;
    }

    //  We submit the status on control/meta + enter.
    if (onSubmit && e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
      onSubmit();
    }
  },

  //  When the escape key is released, we either close the suggestions
  //  window or focus the UI.
  handleKeyUp ({ key }) {
    const { suggestionsHidden } = this.state;
    if (key === 'Escape') {
      if (!suggestionsHidden) {
        this.setState({ suggestionsHidden: true });
      } else {
        document.querySelector('.ui').parentElement.focus();
      }
    }
  },

  //  Handles the pasting of images into the composer.
  handlePaste (e) {
    const { onPaste } = this.props;
    let d;
    if (onPaste && (d = e.clipboardData) && (d = d.files).length === 1) {
      onPaste(d);
      e.preventDefault();
    }
  },

  //  Saves a reference to the textarea.
  handleRefTextarea (textarea) {
    this.textarea = textarea;
  },
};

//  The component.
export default class ComposerTextarea extends React.Component {

  //  Constructor.
  constructor (props) {
    super(props);
    assignHandlers(this, handlers);
    this.state = {
      suggestionsHidden: false,
      selectedSuggestion: 0,
      lastToken: null,
      tokenStart: 0,
    };

    //  Instance variables.
    this.textarea = null;
  }

  //  When we receive new suggestions, we unhide the suggestions window
  //  if we didn't have any suggestions before.
  componentWillReceiveProps (nextProps) {
    const { suggestions } = this.props;
    const { suggestionsHidden } = this.state;
    if (nextProps.suggestions && nextProps.suggestions !== suggestions && nextProps.suggestions.size > 0 && suggestionsHidden) {
      this.setState({ suggestionsHidden: false });
    }
  }

  //  Rendering.
  render () {
    const {
      handleBlur,
      handleChange,
      handleClickSuggestion,
      handleKeyDown,
      handleKeyUp,
      handlePaste,
      handleRefTextarea,
    } = this.handlers;
    const {
      advancedOptions,
      autoFocus,
      disabled,
      intl,
      onPickEmoji,
      suggestions,
      value,
    } = this.props;
    const {
      selectedSuggestion,
      suggestionsHidden,
    } = this.state;

    //  The result.
    return (

          <FormattedMessage {...messages.placeholder} />
          <ComposerTextareaIcons
            advancedOptions={advancedOptions}
            intl={intl}
          />
          <Textarea
            aria-autocomplete='list'
            autoFocus={autoFocus}
            className='textarea'
            disabled={disabled}
            inputRef={handleRefTextarea}
            onBlur={handleBlur}
            onChange={handleChange}
            onKeyDown={handleKeyDown}
            onKeyUp={handleKeyUp}
            onPaste={handlePaste}
            placeholder={intl.formatMessage(messages.placeholder)}
            value={value}
            style={{ direction: isRtl(value) ? 'rtl' : 'ltr' }}
          />

        <EmojiPicker onPickEmoji={onPickEmoji} />
        <ComposerTextareaSuggestions
          hidden={suggestionsHidden}
          onSuggestionClick={handleClickSuggestion}
          suggestions={suggestions}
          value={selectedSuggestion}
        />

    );
  }

}

//  Props.
ComposerTextarea.propTypes = {
  advancedOptions: ImmutablePropTypes.map,
  autoFocus: PropTypes.bool,
  disabled: PropTypes.bool,
  intl: PropTypes.object.isRequired,
  onChange: PropTypes.func,
  onPaste: PropTypes.func,
  onPickEmoji: PropTypes.func,
  onSubmit: PropTypes.func,
  onSuggestionsClearRequested: PropTypes.func,
  onSuggestionsFetchRequested: PropTypes.func,
  onSuggestionSelected: PropTypes.func,
  suggestions: ImmutablePropTypes.list,
  value: PropTypes.string,
};

//  Default props.
ComposerTextarea.defaultProps = { autoFocus: true };
<commit_msg>fixed ctrl enter<commit_after>//  Package imports.
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import {
  defineMessages,
  FormattedMessage,
} from 'react-intl';
import Textarea from 'react-textarea-autosize';

//  Components.
import EmojiPicker from 'flavours/glitch/features/emoji_picker';
import ComposerTextareaIcons from './icons';
import ComposerTextareaSuggestions from './suggestions';

//  Utils.
import { isRtl } from 'flavours/glitch/util/rtl';
import {
  assignHandlers,
  hiddenComponent,
} from 'flavours/glitch/util/react_helpers';

//  Messages.
const messages = defineMessages({
  placeholder: {
    defaultMessage: 'What is on your mind?',
    id: 'compose_form.placeholder',
  },
});

//  Handlers.
const handlers = {

  //  When blurring the textarea, suggestions are hidden.
  handleBlur () {
    this.setState({ suggestionsHidden: true });
  },

  //  When the contents of the textarea change, we have to pull up new
  //  autosuggest suggestions if applicable, and also change the value
  //  of the textarea in our store.
  handleChange ({
    target: {
      selectionStart,
      value,
    },
  }) {
    const {
      onChange,
      onSuggestionsFetchRequested,
      onSuggestionsClearRequested,
    } = this.props;
    const { lastToken } = this.state;

    //  This gets the token at the caret location, if it begins with an
    //  `@` (mentions) or `:` (shortcodes).
    const left = value.slice(0, selectionStart).search(/[^\s\u200B]+$/);
    const right = value.slice(selectionStart).search(/[\s\u200B]/);
    const token = function () {
      switch (true) {
      case left < 0 || !/[@:]/.test(value[left]):
        return null;
      case right < 0:
        return value.slice(left);
      default:
        return value.slice(left, right + selectionStart).trim().toLowerCase();
      }
    }();

    //  We only request suggestions for tokens which are at least 3
    //  characters long.
    if (onSuggestionsFetchRequested && token && token.length >= 3) {
      if (lastToken !== token) {
        this.setState({
          lastToken: token,
          selectedSuggestion: 0,
          tokenStart: left,
        });
        onSuggestionsFetchRequested(token);
      }
    } else {
      this.setState({ lastToken: null });
      if (onSuggestionsClearRequested) {
        onSuggestionsClearRequested();
      }
    }

    //  Updates the value of the textarea.
    if (onChange) {
      onChange(value);
    }
  },

  //  Handles a click on an autosuggestion.
  handleClickSuggestion (index) {
    const { textarea } = this;
    const {
      onSuggestionSelected,
      suggestions,
    } = this.props;
    const {
      lastToken,
      tokenStart,
    } = this.state;
    onSuggestionSelected(tokenStart, lastToken, suggestions.get(index));
    textarea.focus();
  },

  //  Handles a keypress.  If the autosuggestions are visible, we need
  //  to allow keypresses to navigate and sleect them.
  handleKeyDown (e) {
    const {
      disabled,
      onSubmit,
      onSuggestionSelected,
      suggestions,
    } = this.props;
    const {
      lastToken,
      suggestionsHidden,
      selectedSuggestion,
      tokenStart,
    } = this.state;

    //  Keypresses do nothing if the composer is disabled.
    if (disabled) {
      e.preventDefault();
      return;
    }

    //  We submit the status on control/meta + enter.
    if (onSubmit && e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
      onSubmit();
    }

    //  Switches over the pressed key.
    switch(e.key) {

    //  On arrow down, we pick the next suggestion.
    case 'ArrowDown':
      if (suggestions && suggestions.size > 0 && !suggestionsHidden) {
        e.preventDefault();
        this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
      }
      return;

    //  On arrow up, we pick the previous suggestion.
    case 'ArrowUp':
      if (suggestions && suggestions.size > 0 && !suggestionsHidden) {
        e.preventDefault();
        this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
      }
      return;

    //  On enter or tab, we select the suggestion.
    case 'Enter':
    case 'Tab':
      if (onSuggestionSelected && lastToken !== null && suggestions && suggestions.size > 0 && !suggestionsHidden) {
        e.preventDefault();
        e.stopPropagation();
        onSuggestionSelected(tokenStart, lastToken, suggestions.get(selectedSuggestion));
      }
      return;
    }
  },

  //  When the escape key is released, we either close the suggestions
  //  window or focus the UI.
  handleKeyUp ({ key }) {
    const { suggestionsHidden } = this.state;
    if (key === 'Escape') {
      if (!suggestionsHidden) {
        this.setState({ suggestionsHidden: true });
      } else {
        document.querySelector('.ui').parentElement.focus();
      }
    }
  },

  //  Handles the pasting of images into the composer.
  handlePaste (e) {
    const { onPaste } = this.props;
    let d;
    if (onPaste && (d = e.clipboardData) && (d = d.files).length === 1) {
      onPaste(d);
      e.preventDefault();
    }
  },

  //  Saves a reference to the textarea.
  handleRefTextarea (textarea) {
    this.textarea = textarea;
  },
};

//  The component.
export default class ComposerTextarea extends React.Component {

  //  Constructor.
  constructor (props) {
    super(props);
    assignHandlers(this, handlers);
    this.state = {
      suggestionsHidden: false,
      selectedSuggestion: 0,
      lastToken: null,
      tokenStart: 0,
    };

    //  Instance variables.
    this.textarea = null;
  }

  //  When we receive new suggestions, we unhide the suggestions window
  //  if we didn't have any suggestions before.
  componentWillReceiveProps (nextProps) {
    const { suggestions } = this.props;
    const { suggestionsHidden } = this.state;
    if (nextProps.suggestions && nextProps.suggestions !== suggestions && nextProps.suggestions.size > 0 && suggestionsHidden) {
      this.setState({ suggestionsHidden: false });
    }
  }

  //  Rendering.
  render () {
    const {
      handleBlur,
      handleChange,
      handleClickSuggestion,
      handleKeyDown,
      handleKeyUp,
      handlePaste,
      handleRefTextarea,
    } = this.handlers;
    const {
      advancedOptions,
      autoFocus,
      disabled,
      intl,
      onPickEmoji,
      suggestions,
      value,
    } = this.props;
    const {
      selectedSuggestion,
      suggestionsHidden,
    } = this.state;

    //  The result.
    return (

          <FormattedMessage {...messages.placeholder} />
          <ComposerTextareaIcons
            advancedOptions={advancedOptions}
            intl={intl}
          />
          <Textarea
            aria-autocomplete='list'
            autoFocus={autoFocus}
            className='textarea'
            disabled={disabled}
            inputRef={handleRefTextarea}
            onBlur={handleBlur}
            onChange={handleChange}
            onKeyDown={handleKeyDown}
            onKeyUp={handleKeyUp}
            onPaste={handlePaste}
            placeholder={intl.formatMessage(messages.placeholder)}
            value={value}
            style={{ direction: isRtl(value) ? 'rtl' : 'ltr' }}
          />

        <EmojiPicker onPickEmoji={onPickEmoji} />
        <ComposerTextareaSuggestions
          hidden={suggestionsHidden}
          onSuggestionClick={handleClickSuggestion}
          suggestions={suggestions}
          value={selectedSuggestion}
        />

    );
  }

}

//  Props.
ComposerTextarea.propTypes = {
  advancedOptions: ImmutablePropTypes.map,
  autoFocus: PropTypes.bool,
  disabled: PropTypes.bool,
  intl: PropTypes.object.isRequired,
  onChange: PropTypes.func,
  onPaste: PropTypes.func,
  onPickEmoji: PropTypes.func,
  onSubmit: PropTypes.func,
  onSuggestionsClearRequested: PropTypes.func,
  onSuggestionsFetchRequested: PropTypes.func,
  onSuggestionSelected: PropTypes.func,
  suggestions: ImmutablePropTypes.list,
  value: PropTypes.string,
};

//  Default props.
ComposerTextarea.defaultProps = { autoFocus: true };
<|endoftext|>