<commit_before>import React, { Component, PropTypes } from 'react';
import Message from '../Message';
import Checkbox from 'material-ui/Checkbox';
import FlatButton from 'material-ui/FlatButton';
import Dialog from 'material-ui/Dialog';
import TextField from 'material-ui/TextField';
import { FormattedMessage, defineMessages, injectIntl, intlShape } from 'react-intl';
import { MdCancel, MdCheckBoxOutlineBlank } from 'react-icons/lib/md';

const messages = defineMessages({
  addValue: {
    id: 'multiSelectTask.addValue',
    defaultMessage: 'Add Value'
  },
  value: {
    id: 'multiSelectTask.value',
    defaultMessage: 'Value'
  },
  addOther: {
    id: 'multiSelectTask.addOther',
    defaultMessage: 'Add "Other"'
  },
  other: {
    id: 'multiSelectTask.other',
    defaultMessage: 'Other'
  }
});

class MultiSelectTask extends Component {
  constructor(props) {
    super(props);

    this.state = {
      label: null,
      description: null,
      message: null,
      options: [{label: ''}, {label: ''}],
      responseOther: null,
      submitDisabled: true,
      taskAnswerDisabled: true
    };
  }

  handleSubmitTask() {
    const jsonoptions = JSON.stringify(this.state.options.filter(item => item.label != ''));

    if (!this.state.submitDisabled){
      this.props.onSubmit(this.state.label, this.state.description, jsonoptions);
    }
  }

  handleSubmitResponse() {
    if (!this.state.taskAnswerDisabled){
      const props_response = this.props.jsonresponse ? JSON.parse(this.props.jsonresponse) : {};
      let response_obj = {};

      response_obj.selected = Array.isArray(this.state.response) ? this.state.response.slice(0) : props_response.selected;
      response_obj.other = (typeof this.state.responseOther !== 'undefined' && this.state.responseOther !== null) ? this.state.responseOther : (props_response.other || null);

      this.props.onSubmit(JSON.stringify(response_obj), this.state.note);
    }
  }

  canSubmit(){
    const props_response = this.props.jsonresponse ? JSON.parse(this.props.jsonresponse) : {};
    let response_obj = {};

    response_obj.selected = Array.isArray(this.state.response) ? this.state.response.slice(0) : (props_response.selected || []);
    response_obj.other = (typeof this.state.responseOther !== 'undefined' && this.state.responseOther !== null) ? this.state.responseOther.trim() : (props_response.other || null);

    const can_submit = (response_obj.selected.length + !!response_obj.other) > 0;

    this.setState({ taskAnswerDisabled: !can_submit });

    return can_submit;
  }

  handleChange(e) {
    this.setState({ note: e.target.value }, this.canSubmit);
  }

  handleLabelChange(e) {
    this.setState({ label: e.target.value });
    this.validateMultiSelect(e.target.value, this.state.options);
  }

  handleDescriptionChange(e) {
    this.setState({ description: e.target.value });
  }

  handleAddValue(){
    let options = Array.isArray(this.state.options) ? this.state.options.slice(0) : [];
    this.state.hasOther ? options.splice(-1, 0, { label: '' }) : options.push({ label: '' });
    this.setState({ options });

    this.validateMultiSelect(this.state.label, options);
  }

  handleAddOther(){
    let options = Array.isArray(this.state.options) ? this.state.options.slice(0) : [];
    let other = true;
    let label = '';

    if (!this.state.hasOther) {
      options.push({ label, other });
      this.setState({ options, hasOther: true });
    }

    this.validateMultiSelect(this.state.label, options);
  }

  handleEditOption(e){
    let options = this.state.options.slice(0);
    options[parseInt(e.target.id)].label = e.target.value;
    this.setState({ options });

    this.validateMultiSelect(this.state.label, options);
  }

  handleRemoveOption(index){
    let options = this.state.options.slice(0);
    let hasOther = null;

    if (this.state.hasOther) {
      hasOther = (index === options.length - 1) ? false : true;
    } else {
      hasOther = false;
    }

    options.splice(index, 1);
    this.setState({ options, hasOther });

    this.validateMultiSelect(this.state.label, options);
  }

  validateMultiSelect(label, options) {
    const valid = !!(label && label.trim()) && (options.filter(item => item.label != '').length > 1);
    this.setState({ submitDisabled: !valid });
    return valid;
  }

  handleSelectCheckbox(e, inputChecked) {
    inputChecked ? this.addToResponse(e.target.id) : this.removeFromResponse(e.target.id);
    this.setState({ focus: true });
  }

  addToResponse(value) {
    const state_response = Array.isArray(this.state.response) ? this.state.response.slice(0) : null;
    const props_response = !state_response && this.props.jsonresponse ? JSON.parse(this.props.jsonresponse).selected : null;
    let response = state_response || props_response || [];

    response.push(value);
    this.setState({ response }, this.canSubmit);
  }

  removeFromResponse(value) {
    const state_response = Array.isArray(this.state.response) ? this.state.response.slice(0) : null;
    const props_response = !state_response && this.props.jsonresponse ? JSON.parse(this.props.jsonresponse).selected : null;
    let response = state_response || props_response || [];

    const responseIndex = response.findIndex(item => item === value);
    if (responseIndex > -1) {
      response.splice(responseIndex, 1);
      this.setState({ response }, this.canSubmit);
    }
  }

  handleEditOther(e) {
    const value = e.target.value;
    this.setState({ focus: true, responseOther: value }, this.canSubmit);
  }

  handleKeyPress(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
      if (this.canSubmit()) {
        this.setState({ taskAnswerDisabled: true });
        this.handleSubmitResponse();
      }
      e.preventDefault();
    }
  }

  renderCreateDialog() {
    const canRemove = (this.state.options.length > 2);
    const { formatMessage } = this.props.intl;
    const actions = [
      <FlatButton label={<FormattedMessage id="tasks.cancelAdd" defaultMessage="Cancel" />} primary onClick={this.props.onDismiss.bind(this)} />,
      <FlatButton className="create-task__dialog-submit-button" label={<FormattedMessage id="tasks.add" defaultMessage="Add" />} primary keyboardFocused onClick={this.handleSubmitTask.bind(this)} disabled={this.state.submitDisabled}/>,
    ];

    return (

        <Dialog actions={actions} modal={false} open={this.props.mode === 'create'} onRequestClose={this.props.onDismiss}>
          <Message message={this.state.message} />

            <TextField id="task-label-input" className="tasks__task-label-input" floatingLabelText={<FormattedMessage id="tasks.taskPrompt" defaultMessage="Prompt" />} onChange={this.handleLabelChange.bind(this)} multiLine />
              { this.state.options.map((item, index) =>

                  <MdCheckBoxOutlineBlank />
                  <TextField
                    style={{ padding: '5px' }}
                    id={index.toString()}
                    onChange={this.handleEditOption.bind(this)}
                    placeholder={ item.other ? formatMessage(messages.other) : formatMessage(messages.value) + ' ' + (index + 1) }
                    value={item.label} />
                  { canRemove ? <MdCancel onClick={this.handleRemoveOption.bind(this, index)}/> : null }

)
              }

              <FlatButton label={this.props.intl.formatMessage(messages.addValue)} primary onClick={this.handleAddValue.bind(this)} />
              {<FlatButton label={this.props.intl.formatMessage(messages.addOther)} primary onClick={this.handleAddOther.bind(this)} disabled={this.state.hasOther} />}

          <input className="create-task__add-task-description" id="create-task__add-task-description" type="checkbox" />
          <TextField id="task-description-input" className="create-task__task-description-input" floatingLabelText={<FormattedMessage id="tasks.description" defaultMessage="Description" />} onChange={this.handleDescriptionChange.bind(this)} multiLine />

            + <FormattedMessage id="tasks.addDescription" defaultMessage="Add a description" />

        </Dialog>

    );
  }

  isChecked(value, index) {
    if (this.state.response) {
      return (this.state.response.findIndex(item => item === value) > -1);
    } else if (this.props.jsonresponse) {
      const response = JSON.parse(this.props.jsonresponse).selected || [];
      return (response.findIndex(item => item === value) > -1);
    }

    return false;
  }

  renderOptions(jsonresponse, note, jsonoptions) {
    let options = null;

    const editable =  (jsonresponse == null) || (this.props.mode === 'edit_response');
    const submitCallback = this.handleSubmitResponse.bind(this);
    const cancelCallback = this.props.onDismiss;
    const keyPressCallback = this.handleKeyPress.bind(this);

    const actionBtns = (

        <FlatButton label={<FormattedMessage id="tasks.cancelEdit" defaultMessage="Cancel" />} primary onClick={cancelCallback} />
        <FlatButton className="task__submit" label={<FormattedMessage id="tasks.submit" defaultMessage="Submit" />} primary onClick={submitCallback} disabled={this.state.taskAnswerDisabled}/>

);

    if (jsonoptions) {
      options = JSON.parse(jsonoptions);
    }

    if (Array.isArray(options) && options.length > 0) {
      const otherIndex = options.findIndex( item => item.other );
      const other = (otherIndex >= 0) ? options.splice(otherIndex, 1).pop() : null;

      const response = jsonresponse ? JSON.parse(jsonresponse) : {};
      let responseOther = (typeof this.state.responseOther !== 'undefined' && this.state.responseOther !== null) ? this.state.responseOther : (response.other || '');

      return (

        { options.map( (item, index) => <Checkbox label={item.label} checked={this.isChecked(item.label, index)} onCheck={this.handleSelectCheckbox.bind(this)} id={item.label} style={{ padding: '5px' }} disabled={!editable} />) }
        { other ? <TextField
          placeholder={other.label}
          value={responseOther}
          name={'response'}
          onKeyPress={keyPressCallback}
          onChange={this.handleEditOther.bind(this)}
          disabled={!editable}
        /> : null }
        { editable ?
          <TextField
              className="task__response-note-input"
              hintText={<FormattedMessage id="task.noteLabel" defaultMessage="Note any additional details here." />}
              name={'note'}
              defaultValue={note}
              onKeyPress={keyPressCallback}
              onChange={this.handleChange.bind(this)}
              fullWidth
              multiLine
            /> : null
        }
        { (this.state.focus && editable) || (this.props.mode === 'edit_response') ? actionBtns : null }

);
    }
  }

  render() {
    const { jsonresponse } = this.props;
    const { note } = this.props;
    const { jsonoptions } = this.props;

    return (

        { this.props.mode === 'create' ? this.renderCreateDialog() : null }
        { this.props.mode === 'respond' ? this.renderOptions(jsonresponse, note, jsonoptions) : null }
        { this.props.mode === 'show_response' ? this.renderOptions(jsonresponse, note, jsonoptions) : null }
        { this.props.mode === 'edit_response' ? this.renderOptions(jsonresponse, note, jsonoptions) : null }
        {/* this.props.mode === 'edit_task' ? this.renderCreateDialog() : null */}

    );
  }
}

MultiSelectTask.propTypes = {
  intl: intlShape.isRequired,
};

export default injectIntl(MultiSelectTask);
<commit_msg>MultiSelect response dismiss callback #5792<commit_after>import React, { Component, PropTypes } from 'react';
import Message from '../Message';
import Checkbox from 'material-ui/Checkbox';
import FlatButton from 'material-ui/FlatButton';
import Dialog from 'material-ui/Dialog';
import TextField from 'material-ui/TextField';
import { FormattedMessage, defineMessages, injectIntl, intlShape } from 'react-intl';
import { MdCancel, MdCheckBoxOutlineBlank } from 'react-icons/lib/md';

const messages = defineMessages({
  addValue: {
    id: 'multiSelectTask.addValue',
    defaultMessage: 'Add Value'
  },
  value: {
    id: 'multiSelectTask.value',
    defaultMessage: 'Value'
  },
  addOther: {
    id: 'multiSelectTask.addOther',
    defaultMessage: 'Add "Other"'
  },
  other: {
    id: 'multiSelectTask.other',
    defaultMessage: 'Other'
  }
});

class MultiSelectTask extends Component {
  constructor(props) {
    super(props);

    this.state = {
      label: null,
      description: null,
      message: null,
      options: [{label: ''}, {label: ''}],
      responseOther: null,
      submitDisabled: true,
      taskAnswerDisabled: true
    };
  }

  handleSubmitTask() {
    const jsonoptions = JSON.stringify(this.state.options.filter(item => item.label != ''));

    if (!this.state.submitDisabled){
      this.props.onSubmit(this.state.label, this.state.description, jsonoptions);
    }
  }

  handleSubmitResponse() {
    if (!this.state.taskAnswerDisabled){
      const props_response = this.props.jsonresponse ? JSON.parse(this.props.jsonresponse) : {};
      let response_obj = {};

      response_obj.selected = Array.isArray(this.state.response) ? this.state.response.slice(0) : props_response.selected;
      response_obj.other = (typeof this.state.responseOther !== 'undefined' && this.state.responseOther !== null) ? this.state.responseOther : (props_response.other || null);

      this.props.onSubmit(JSON.stringify(response_obj), this.state.note);
    }
  }

  canSubmit(){
    const props_response = this.props.jsonresponse ? JSON.parse(this.props.jsonresponse) : {};
    let response_obj = {};

    response_obj.selected = Array.isArray(this.state.response) ? this.state.response.slice(0) : (props_response.selected || []);
    response_obj.other = (typeof this.state.responseOther !== 'undefined' && this.state.responseOther !== null) ? this.state.responseOther.trim() : (props_response.other || null);

    const can_submit = (response_obj.selected.length + !!response_obj.other) > 0;

    this.setState({ taskAnswerDisabled: !can_submit });

    return can_submit;
  }

  handleChange(e) {
    this.setState({ note: e.target.value }, this.canSubmit);
  }

  handleLabelChange(e) {
    this.setState({ label: e.target.value });
    this.validateMultiSelect(e.target.value, this.state.options);
  }

  handleDescriptionChange(e) {
    this.setState({ description: e.target.value });
  }

  handleAddValue(){
    let options = Array.isArray(this.state.options) ? this.state.options.slice(0) : [];
    this.state.hasOther ? options.splice(-1, 0, { label: '' }) : options.push({ label: '' });
    this.setState({ options });

    this.validateMultiSelect(this.state.label, options);
  }

  handleAddOther(){
    let options = Array.isArray(this.state.options) ? this.state.options.slice(0) : [];
    let other = true;
    let label = '';

    if (!this.state.hasOther) {
      options.push({ label, other });
      this.setState({ options, hasOther: true });
    }

    this.validateMultiSelect(this.state.label, options);
  }

  handleEditOption(e){
    let options = this.state.options.slice(0);
    options[parseInt(e.target.id)].label = e.target.value;
    this.setState({ options });

    this.validateMultiSelect(this.state.label, options);
  }

  handleRemoveOption(index){
    let options = this.state.options.slice(0);
    let hasOther = null;

    if (this.state.hasOther) {
      hasOther = (index === options.length - 1) ? false : true;
    } else {
      hasOther = false;
    }

    options.splice(index, 1);
    this.setState({ options, hasOther });

    this.validateMultiSelect(this.state.label, options);
  }

  validateMultiSelect(label, options) {
    const valid = !!(label && label.trim()) && (options.filter(item => item.label != '').length > 1);
    this.setState({ submitDisabled: !valid });
    return valid;
  }

  handleCancelResponse() {
    this.setState({ response: null, responseOther: null, note: '', focus: false }, this.canSubmit);
    if (this.props.onDismiss) { this.props.onDismiss(); }
  }

  handleSelectCheckbox(e, inputChecked) {
    inputChecked ? this.addToResponse(e.target.id) : this.removeFromResponse(e.target.id);
    this.setState({ focus: true });
  }

  addToResponse(value) {
    const state_response = Array.isArray(this.state.response) ? this.state.response.slice(0) : null;
    const props_response = !state_response && this.props.jsonresponse ? JSON.parse(this.props.jsonresponse).selected : null;
    let response = state_response || props_response || [];

    response.push(value);
    this.setState({ response }, this.canSubmit);
  }

  removeFromResponse(value) {
    const state_response = Array.isArray(this.state.response) ? this.state.response.slice(0) : null;
    const props_response = !state_response && this.props.jsonresponse ? JSON.parse(this.props.jsonresponse).selected : null;
    let response = state_response || props_response || [];

    const responseIndex = response.findIndex(item => item === value);
    if (responseIndex > -1) {
      response.splice(responseIndex, 1);
      this.setState({ response }, this.canSubmit);
    }
  }

  handleEditOther(e) {
    const value = e.target.value;
    this.setState({ focus: true, responseOther: value }, this.canSubmit);
  }

  handleKeyPress(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
      if (this.canSubmit()) {
        this.setState({ taskAnswerDisabled: true });
        this.handleSubmitResponse();
      }
      e.preventDefault();
    }
  }

  renderCreateDialog() {
    const canRemove = (this.state.options.length > 2);
    const { formatMessage } = this.props.intl;
    const actions = [
      <FlatButton label={<FormattedMessage id="tasks.cancelAdd" defaultMessage="Cancel" />} primary onClick={this.props.onDismiss.bind(this)} />,
      <FlatButton className="create-task__dialog-submit-button" label={<FormattedMessage id="tasks.add" defaultMessage="Add" />} primary keyboardFocused onClick={this.handleSubmitTask.bind(this)} disabled={this.state.submitDisabled}/>,
    ];

    return (

        <Dialog actions={actions} modal={false} open={this.props.mode === 'create'} onRequestClose={this.props.onDismiss}>
          <Message message={this.state.message} />

            <TextField id="task-label-input" className="tasks__task-label-input" floatingLabelText={<FormattedMessage id="tasks.taskPrompt" defaultMessage="Prompt" />} onChange={this.handleLabelChange.bind(this)} multiLine />
              { this.state.options.map((item, index) =>

                  <MdCheckBoxOutlineBlank />
                  <TextField
                    style={{ padding: '5px' }}
                    id={index.toString()}
                    onChange={this.handleEditOption.bind(this)}
                    placeholder={ item.other ? formatMessage(messages.other) : formatMessage(messages.value) + ' ' + (index + 1) }
                    value={item.label} />
                  { canRemove ? <MdCancel onClick={this.handleRemoveOption.bind(this, index)}/> : null }

)
              }

              <FlatButton label={this.props.intl.formatMessage(messages.addValue)} primary onClick={this.handleAddValue.bind(this)} />
              {<FlatButton label={this.props.intl.formatMessage(messages.addOther)} primary onClick={this.handleAddOther.bind(this)} disabled={this.state.hasOther} />}

          <input className="create-task__add-task-description" id="create-task__add-task-description" type="checkbox" />
          <TextField id="task-description-input" className="create-task__task-description-input" floatingLabelText={<FormattedMessage id="tasks.description" defaultMessage="Description" />} onChange={this.handleDescriptionChange.bind(this)} multiLine />

            + <FormattedMessage id="tasks.addDescription" defaultMessage="Add a description" />

        </Dialog>

    );
  }

  isChecked(value, index) {
    if (this.state.response) {
      return (this.state.response.findIndex(item => item === value) > -1);
    } else if (this.props.jsonresponse) {
      const response = JSON.parse(this.props.jsonresponse).selected || [];
      return (response.findIndex(item => item === value) > -1);
    }

    return false;
  }

  renderOptions(jsonresponse, note, jsonoptions) {
    let options = null;

    const editable =  (jsonresponse == null) || (this.props.mode === 'edit_response');
    const submitCallback = this.handleSubmitResponse.bind(this);
    const cancelCallback = this.handleCancelResponse.bind(this);
    const keyPressCallback = this.handleKeyPress.bind(this);

    const actionBtns = (

        <FlatButton label={<FormattedMessage id="tasks.cancelEdit" defaultMessage="Cancel" />} primary onClick={cancelCallback} />
        <FlatButton className="task__submit" label={<FormattedMessage id="tasks.submit" defaultMessage="Submit" />} primary onClick={submitCallback} disabled={this.state.taskAnswerDisabled}/>

);

    if (jsonoptions) {
      options = JSON.parse(jsonoptions);
    }

    if (Array.isArray(options) && options.length > 0) {
      const otherIndex = options.findIndex( item => item.other );
      const other = (otherIndex >= 0) ? options.splice(otherIndex, 1).pop() : null;

      const response = jsonresponse ? JSON.parse(jsonresponse) : {};
      let responseOther = (typeof this.state.responseOther !== 'undefined' && this.state.responseOther !== null) ? this.state.responseOther : (response.other || '');
      let responseNote = (typeof this.state.note !== 'undefined' && this.state.note !== null) ? this.state.note : (note || '');

      return (

        { options.map( (item, index) => <Checkbox label={item.label} checked={this.isChecked(item.label, index)} onCheck={this.handleSelectCheckbox.bind(this)} id={item.label} style={{ padding: '5px' }} disabled={!editable} />) }
        { other ? <TextField
          placeholder={other.label}
          value={responseOther}
          name={'response'}
          onKeyPress={keyPressCallback}
          onChange={this.handleEditOther.bind(this)}
          disabled={!editable}
        /> : null }
        { editable ?
          <TextField
              className="task__response-note-input"
              hintText={<FormattedMessage id="task.noteLabel" defaultMessage="Note any additional details here." />}
              name={'note'}
              value={responseNote}
              onKeyPress={keyPressCallback}
              onChange={this.handleChange.bind(this)}
              fullWidth
              multiLine
            /> : null
        }
        { (this.state.focus && editable) || (this.props.mode === 'edit_response') ? actionBtns : null }

);
    }
  }

  render() {
    const { jsonresponse } = this.props;
    const { note } = this.props;
    const { jsonoptions } = this.props;

    return (

        { this.props.mode === 'create' ? this.renderCreateDialog() : null }
        { this.props.mode === 'respond' ? this.renderOptions(jsonresponse, note, jsonoptions) : null }
        { this.props.mode === 'show_response' ? this.renderOptions(jsonresponse, note, jsonoptions) : null }
        { this.props.mode === 'edit_response' ? this.renderOptions(jsonresponse, note, jsonoptions) : null }
        {/* this.props.mode === 'edit_task' ? this.renderCreateDialog() : null */}

    );
  }
}

MultiSelectTask.propTypes = {
  intl: intlShape.isRequired,
};

export default injectIntl(MultiSelectTask);
<|endoftext|>


<commit_before>import React from 'react';

import {
    BLUR_DELAY_MS,
    debounce,
    getClassName,
    KEY_CODES,
    noop
} from '../../utils';

class SelectBox extends React.Component {
    constructor(...args) {
        super(...args);

        this.state = {
            highlightIndex: -1,
            showDropDown: false,
            value: this.props.defaultValue
        };
        this.delayBlur = debounce(
            this.onBlur.bind(this),
            BLUR_DELAY_MS
        );
        this.delaySearch = debounce(
            this.onSearch.bind(this),
            this.props.delay
        );
        this.onClick = this.onClick.bind(this);
        this.onDropDownClick = this.onDropDownClick.bind(this);
        this.onSearchFocus = this.onSearchFocus.bind(this);
        this.onClearClick = this.onClearClick.bind(this);
    }

    render() {
        const className = getClassName(
            'react-ui-select-box',
            this.props.className,
            this.state.showDropDown ? 'react-ui-select-box-open' : '',
            this.props.disabled ? 'react-ui-select-box-disabled' : ''
        );

        return (

                    {this.renderValue()}

                        {this.renderClear()}
                        {this.renderTrigger()}

                {this.renderDropDown()}

        );
    }

    renderValue() {
        const className = getClassName(
            'react-ui-select-box-value',
            this.props.valueClassName,
            !this.state.value ? 'react-ui-select-box-placeholder' : ''
        );
        const display = (
            this.state.value ?
            this.state.value[this.props.displayProp] :
            this.props.placeholder
        );
        const value = (
            this.state.value ?
            this.state.value[this.props.valueProp] :
            this.state.value
        );

        return (

                <input
                disabled={this.props.disabled}
                name={this.props.name}
                type="hidden"
                value={value} />

                {display}

        );
    }

    renderDropDown() {
        const className = getClassName(
            'react-ui-select-box-drop-down',
            this.props.dropDownClassName
        );
        const optionsClassName = getClassName(
            'react-ui-select-box-options',
            this.props.optionsClassName
        );

        return this.state.showDropDown ? (

                {this.renderSearch()}

                    {this.renderOptions()}

        ) : null;
    }

    renderClear() {
        const className = getClassName(
            'react-ui-select-box-clear',
            this.props.clearClassName
        );

        return this.props.showClear && this.state.value ? (

        ) : null;
    }

    renderTrigger() {
        const className = getClassName(
            'react-ui-select-box-trigger',
            this.props.triggerClassName
        );

        return ();
    }

    renderSearch() {
        const className = getClassName(
            'react-ui-select-box-search',
            this.props.searchClassName
        );
        const options = this.getOptions();
        const filteredOptions = this.filterOptions(options);

        return options.length >= this.props.searchThreshold ? (

                <input
                autoFocus={true}
                onClick={this.onSearchFocus}
                onFocus={this.onSearchFocus}
                onChange={this.delaySearch}
                onKeyDown={this.onSearchKeyDown.bind(this, filteredOptions)}
                ref="search"
                type="text" />

        ) : null;
    }

    renderOptions() {
        return this.filterOptions().map((option, i) => {
            const className = getClassName(
                'react-ui-select-box-option',
                this.props.optionClassName,
                (
                    this.isOptionSelected(option) ?
                    'react-ui-select-box-option-selected' :
                    ''
                ),
                (
                    i === this.state.highlightIndex ?
                    'react-ui-select-box-option-highlighted' :
                    ''
                )
            );

            return (

                    {this.renderOption(option)}

            );
        });
    }

    renderOption(option) {
        return (
            this.props.renderOption(option) ||
            option[this.props.displayProp]
        );
    }

    onChange(option, evt) {
        this.props.onChange(evt, option);

        this.setState({
            highlightIndex: -1,
            showDropDown: false,
            value: option
        });
    }

    onClearClick(evt) {
        evt.stopPropagation();
        this.props.onClearClick(evt);
        this.delayBlur.cancel();
        this.clear();
    }

    onClick(evt) {
        this.props.onClick(evt, this.state.showDropDown, this.props.disabled);

        if (!this.props.disabled) {
            if (this.state.showDropDown) {
                this.hideDropDown();
            } else {
                this.showDropDown();
            }
        }
    }

    onDropDownClick() {
        this.delayBlur.cancel();
    }

    onBlur() {
        this.hideDropDown();
        this.clearQuery();
    }

    onSearch() {
        const query = React.findDOMNode(
            this.refs.search
        ).value.toLowerCase();
        const skipSetState = this.props.onSearch(query);

        if (!skipSetState) {
            this.setState({query});
        }
    }

    onSearchFocus(evt) {
        evt.stopPropagation();
        this.delayBlur.cancel();
    }

    onSearchKeyDown(options, evt) {
        if (evt.keyCode === KEY_CODES.ENTER && this.state.highlightIndex > -1) {
            this.onChange(
                options[this.state.highlightIndex],
                evt
            );
        } else if (evt.keyCode === KEY_CODES.ARROW_DOWN) {
            this.highlightIndex(this.state.highlightIndex + 1, options);
        } else if (evt.keyCode === KEY_CODES.ARROW_UP) {
            this.highlightIndex(this.state.highlightIndex - 1, options);
        }
    }

    getOptions() {
        return this.props.options || (
            this.props.children && this.props.children.length !== undefined ?
            this.props.children : [this.props.children]
        ).filter(
            (child) => child && child.type === 'option'
        ).map((child) => ({
            [this.props.displayProp]: child.props.children,
            [this.props.valueProp]: child.props.value || child.props.children
        }));
    }

    filterOptions(options) {
        options = options || this.getOptions();

        return this.state.query ? options.filter(
            option => option[this.props.displayProp].toLowerCase().includes(
                this.state.query
            )
        ) : options;
    }

    isOptionSelected(option) {
        const value = this.state.value;

        return !!(
            option &&
            value &&
            option[this.props.valueProp] === value[this.props.valueProp] &&
            option[this.props.displayProp] === value[this.props.displayProp]
        );
    }

    highlightIndex(index, options) {
        if (index >= options.length) {
            index = options.length - 1;
        }

        if (index < 0) {
            index = 0;
        }

        this.setState({highlightIndex: index});
    }

    clear() {
        this.setState({
            highlightIndex: -1,
            value: undefined
        });
    }

    clearQuery() {
        this.setState({query: ''});
    }

    hideDropDown() {
        this.setState({showDropDown: false});
    }

    showDropDown() {
        this.setState({showDropDown: true});
    }
}

SelectBox.propTypes = {
    className: React.PropTypes.string,
    clearClassName: React.PropTypes.string,
    displayProp: React.PropTypes.string,
    dropDownClassName: React.PropTypes.string,
    name: React.PropTypes.string,
    onChange: React.PropTypes.func,
    onClearClick: React.PropTypes.func,
    onClick: React.PropTypes.func,
    options: React.PropTypes.array,
    optionClassName: React.PropTypes.string,
    optionsClassName: React.PropTypes.string,
    renderOption: React.PropTypes.func,
    placeholder: React.PropTypes.string,
    searchThreshold: React.PropTypes.number,
    showClear: React.PropTypes.bool,
    valueClassName: React.PropTypes.string,
    valueProp: React.PropTypes.string
};

SelectBox.defaultProps = {
    delay: 400,
    disabled: false,
    displayProp: 'display',
    onChange: noop,
    onClearClick: noop,
    onClick: noop,
    onSearch: noop,
    placeholder: '',
    remote: false,
    renderOption: noop,
    searchThreshold: 5,
    showClear: true,
    valueProp: 'value'
};

export default SelectBox;
<commit_msg>clear button does not show for disabled SelectBox<commit_after>import React from 'react';

import {
    BLUR_DELAY_MS,
    debounce,
    getClassName,
    KEY_CODES,
    noop
} from '../../utils';

class SelectBox extends React.Component {
    constructor(...args) {
        super(...args);

        this.state = {
            highlightIndex: -1,
            showDropDown: false,
            value: this.props.defaultValue
        };
        this.delayBlur = debounce(
            this.onBlur.bind(this),
            BLUR_DELAY_MS
        );
        this.delaySearch = debounce(
            this.onSearch.bind(this),
            this.props.delay
        );
        this.onClick = this.onClick.bind(this);
        this.onDropDownClick = this.onDropDownClick.bind(this);
        this.onSearchFocus = this.onSearchFocus.bind(this);
        this.onClearClick = this.onClearClick.bind(this);
    }

    render() {
        const className = getClassName(
            'react-ui-select-box',
            this.props.className,
            this.state.showDropDown ? 'react-ui-select-box-open' : '',
            this.props.disabled ? 'react-ui-select-box-disabled' : ''
        );

        return (

                    {this.renderValue()}

                        {this.renderClear()}
                        {this.renderTrigger()}

                {this.renderDropDown()}

        );
    }

    renderValue() {
        const className = getClassName(
            'react-ui-select-box-value',
            this.props.valueClassName,
            !this.state.value ? 'react-ui-select-box-placeholder' : ''
        );
        const display = (
            this.state.value ?
            this.state.value[this.props.displayProp] :
            this.props.placeholder
        );
        const value = (
            this.state.value ?
            this.state.value[this.props.valueProp] :
            this.state.value
        );

        return (

                <input
                disabled={this.props.disabled}
                name={this.props.name}
                type="hidden"
                value={value} />

                {display}

        );
    }

    renderDropDown() {
        const className = getClassName(
            'react-ui-select-box-drop-down',
            this.props.dropDownClassName
        );
        const optionsClassName = getClassName(
            'react-ui-select-box-options',
            this.props.optionsClassName
        );

        return this.state.showDropDown ? (

                {this.renderSearch()}

                    {this.renderOptions()}

        ) : null;
    }

    renderClear() {
        const className = getClassName(
            'react-ui-select-box-clear',
            this.props.clearClassName
        );
        const showClear = (
            this.props.showClear &&
            this.state.value &&
            !this.state.disabled
        );

        return showClear ? (

        ) : null;
    }

    renderTrigger() {
        const className = getClassName(
            'react-ui-select-box-trigger',
            this.props.triggerClassName
        );

        return ();
    }

    renderSearch() {
        const className = getClassName(
            'react-ui-select-box-search',
            this.props.searchClassName
        );
        const options = this.getOptions();
        const filteredOptions = this.filterOptions(options);

        return options.length >= this.props.searchThreshold ? (

                <input
                autoFocus={true}
                onClick={this.onSearchFocus}
                onFocus={this.onSearchFocus}
                onChange={this.delaySearch}
                onKeyDown={this.onSearchKeyDown.bind(this, filteredOptions)}
                ref="search"
                type="text" />

        ) : null;
    }

    renderOptions() {
        return this.filterOptions().map((option, i) => {
            const className = getClassName(
                'react-ui-select-box-option',
                this.props.optionClassName,
                (
                    this.isOptionSelected(option) ?
                    'react-ui-select-box-option-selected' :
                    ''
                ),
                (
                    i === this.state.highlightIndex ?
                    'react-ui-select-box-option-highlighted' :
                    ''
                )
            );

            return (

                    {this.renderOption(option)}

            );
        });
    }

    renderOption(option) {
        return (
            this.props.renderOption(option) ||
            option[this.props.displayProp]
        );
    }

    onChange(option, evt) {
        this.props.onChange(evt, option);

        this.setState({
            highlightIndex: -1,
            showDropDown: false,
            value: option
        });
    }

    onClearClick(evt) {
        evt.stopPropagation();
        this.props.onClearClick(evt);
        this.delayBlur.cancel();
        this.clear();
    }

    onClick(evt) {
        this.props.onClick(evt, this.state.showDropDown, this.props.disabled);

        if (!this.props.disabled) {
            if (this.state.showDropDown) {
                this.hideDropDown();
            } else {
                this.showDropDown();
            }
        }
    }

    onDropDownClick() {
        this.delayBlur.cancel();
    }

    onBlur() {
        this.hideDropDown();
        this.clearQuery();
    }

    onSearch() {
        const query = React.findDOMNode(
            this.refs.search
        ).value.toLowerCase();
        const skipSetState = this.props.onSearch(query);

        if (!skipSetState) {
            this.setState({query});
        }
    }

    onSearchFocus(evt) {
        evt.stopPropagation();
        this.delayBlur.cancel();
    }

    onSearchKeyDown(options, evt) {
        if (evt.keyCode === KEY_CODES.ENTER && this.state.highlightIndex > -1) {
            this.onChange(
                options[this.state.highlightIndex],
                evt
            );
        } else if (evt.keyCode === KEY_CODES.ARROW_DOWN) {
            this.highlightIndex(this.state.highlightIndex + 1, options);
        } else if (evt.keyCode === KEY_CODES.ARROW_UP) {
            this.highlightIndex(this.state.highlightIndex - 1, options);
        }
    }

    getOptions() {
        return this.props.options || (
            this.props.children && this.props.children.length !== undefined ?
            this.props.children : [this.props.children]
        ).filter(
            (child) => child && child.type === 'option'
        ).map((child) => ({
            [this.props.displayProp]: child.props.children,
            [this.props.valueProp]: child.props.value || child.props.children
        }));
    }

    filterOptions(options) {
        options = options || this.getOptions();

        return this.state.query ? options.filter(
            option => option[this.props.displayProp].toLowerCase().includes(
                this.state.query
            )
        ) : options;
    }

    isOptionSelected(option) {
        const value = this.state.value;

        return !!(
            option &&
            value &&
            option[this.props.valueProp] === value[this.props.valueProp] &&
            option[this.props.displayProp] === value[this.props.displayProp]
        );
    }

    highlightIndex(index, options) {
        if (index >= options.length) {
            index = options.length - 1;
        }

        if (index < 0) {
            index = 0;
        }

        this.setState({highlightIndex: index});
    }

    clear() {
        this.setState({
            highlightIndex: -1,
            value: undefined
        });
    }

    clearQuery() {
        this.setState({query: ''});
    }

    hideDropDown() {
        this.setState({showDropDown: false});
    }

    showDropDown() {
        this.setState({showDropDown: true});
    }
}

SelectBox.propTypes = {
    className: React.PropTypes.string,
    clearClassName: React.PropTypes.string,
    displayProp: React.PropTypes.string,
    dropDownClassName: React.PropTypes.string,
    name: React.PropTypes.string,
    onChange: React.PropTypes.func,
    onClearClick: React.PropTypes.func,
    onClick: React.PropTypes.func,
    options: React.PropTypes.array,
    optionClassName: React.PropTypes.string,
    optionsClassName: React.PropTypes.string,
    renderOption: React.PropTypes.func,
    placeholder: React.PropTypes.string,
    searchThreshold: React.PropTypes.number,
    showClear: React.PropTypes.bool,
    valueClassName: React.PropTypes.string,
    valueProp: React.PropTypes.string
};

SelectBox.defaultProps = {
    delay: 400,
    disabled: false,
    displayProp: 'display',
    onChange: noop,
    onClearClick: noop,
    onClick: noop,
    onSearch: noop,
    placeholder: '',
    remote: false,
    renderOption: noop,
    searchThreshold: 5,
    showClear: true,
    valueProp: 'value'
};

export default SelectBox;
<|endoftext|>


<commit_before>import _ from 'lodash';
import SerialPort from 'serialport';
import log from '../../lib/log';
import GCodeSender from '../../lib/gcode-sender';
import Grbl from './Grbl';
import {
    WORKFLOW_STATE_RUNNING,
    WORKFLOW_STATE_PAUSED,
    WORKFLOW_STATE_IDLE
} from '../../constants';
import {
    GRBL,
    GRBL_REALTIME_COMMANDS
} from './constants';

const PREFIX = '[Grbl]';

const noop = () => {};

class Connection {
    socket = null;
    sentCommand = '';

    constructor(socket) {
        this.socket = socket;
    }
}

class GrblController {
    type = GRBL;

    // Connections
    connections = [];

    // SerialPort
    options = {
        port: '',
        baudrate: 115200
    };
    serialport = null;

    // Grbl
    grbl = null;
    ready = false;
    state = {};
    queryTimer = null;
    queryResponse = {
        status: false,
        parserstate: false,
        parserstateEnd: false
    };

    // G-code sender
    sender = null;

    // Workflow state
    workflowState = WORKFLOW_STATE_IDLE;

    constructor(port, options) {
        const { baudrate } = { ...options };

        this.options = {
            ...this.options,
            port: port,
            baudrate: baudrate
        };

        // SerialPort
        this.serialport = new SerialPort(this.options.port, {
            autoOpen: false,
            baudrate: this.options.baudrate,
            parser: SerialPort.parsers.readline('\n')
        });

        // GCodeSender
        this.sender = new GCodeSender();
        this.sender.on('progress', (res) => {
            if (this.isClose()) {
                log.error(`${PREFIX} Serial port "${this.options.port}" not accessible`);
                return;
            }

            let { gcode = '' } = { ...res };
            gcode = ('' + gcode).trim();
            if (gcode.length > 0) {
                this.serialport.write(gcode + '\n');
            }
        });

        // Grbl
        this.grbl = new Grbl(this.serialport);

        this.grbl.on('raw', (res) => {});

        this.grbl.on('status', (res) => {
            this.queryResponse.status = false;

            this.connections.forEach((c) => {
                if (c.sentCommand.indexOf('?') === 0) {
                    c.sentCommand = '';
                    c.socket.emit('serialport:read', res.raw);
                }
            });
        });

        this.grbl.on('ok', (res) => {
            if (this.queryResponse.parserstateEnd) {
                this.connections.forEach((c) => {
                    if (c.sentCommand.indexOf('$G') === 0) {
                        c.sentCommand = '';
                        c.socket.emit('serialport:read', res.raw);
                    }
                });
                this.queryResponse.parserstateEnd = false;
                return;
            }

            if (this.workflowState === WORKFLOW_STATE_RUNNING) {
                this.sender.next();
                return;
            }

            this.connections.forEach((c) => {
                c.socket.emit('serialport:read', res.raw);
            });
        });

        this.grbl.on('error', (res) => {
            if (this.workflowState === WORKFLOW_STATE_RUNNING) {
                const length = this.sender.sent.length;
                if (length > 0) {
                    const lastDataSent = this.sender.sent[length - 1];
                    const msg = '> (' + length + ') ' + lastDataSent;
                    this.connections.forEach((c) => {
                        c.socket.emit('serialport:read', msg);
                    });
                }

                this.sender.next();
            }

            this.connections.forEach((c) => {
                c.socket.emit('serialport:read', res.raw);
            });
        });

        this.grbl.on('alarm', (res) => {
            this.connections.forEach((c) => {
                c.socket.emit('serialport:read', res.raw);
            });
        });

        this.grbl.on('parserstate', (res) => {
            this.queryResponse.parserstate = false;
            this.queryResponse.parserstateEnd = true; // wait for ok response

            this.connections.forEach((c) => {
                if (c.sentCommand.indexOf('$G') === 0) {
                    c.socket.emit('serialport:read', res.raw);
                }
            });
        });

        this.grbl.on('parameters', (res) => {
            this.connections.forEach((c) => {
                c.socket.emit('serialport:read', res.raw);
            });
        });

        this.grbl.on('feedback', (res) => {
            this.connections.forEach((c) => {
                c.socket.emit('serialport:read', res.raw);
            });
        });

        this.grbl.on('settings', (res) => {
            this.connections.forEach((c) => {
                c.socket.emit('serialport:read', res.raw);
            });
        });

        this.grbl.on('startup', (res) => {
            this.ready = true;
            this.queryResponse.status = false;
            this.queryResponse.parserstate = false;
            this.queryResponse.parserstateEnd = false;

            this.connections.forEach((c) => {
                c.socket.emit('serialport:read', res.raw);
            });
        });

        this.grbl.on('others', (res) => {
            this.connections.forEach((c) => {
                c.socket.emit('serialport:read', res.raw);
            });
        });

        this.queryTimer = setInterval(() => {
            if (this.isClose()) {
                // Serial port is closed
                return;
            }

            if (!(this.ready)) {
                // Not ready yet
                return;
            }

            if (this.state !== this.grbl.state) {
                this.state = this.grbl.state;
                this.connections.forEach((c) => {
                    c.socket.emit('Grbl:state', this.state);
                });
            }

            // ? - Current Status
            if (!(this.queryResponse.status)) {
                this.queryResponse.status = true;
                this.serialport.write('?');
            }

            // $G - Parser State
            if (!(this.queryResponse.parserstate) && !(this.queryResponse.parserstateEnd)) {
                this.queryResponse.parserstate = true;
                this.queryResponse.parserstateEnd = false;
                this.serialport.write('$G\n');
            }

            // Detect for any G-code status changes
            if (this.sender.peek()) {
                this.connections.forEach((c) => {
                    c.socket.emit('gcode:statuschange', {
                        'remain': this.sender.remain.length,
                        'sent': this.sender.sent.length,
                        'total': this.sender.total,
                        'createdTime': this.sender.createdTime,
                        'startedTime': this.sender.startedTime,
                        'finishedTime': this.sender.finishedTime
                    });
                });
            }
        }, 250);
    }
    destroy() {
        if (this.queryTimer) {
            clearInterval(this.queryTimer);
            this.queryTimer = null;
        }

        if (this.grbl) {
            this.grbl.removeAllListeners();
            this.grbl = null;
        }
    }
    init(callback = noop) {
        // Reset Grbl
        this.command(null, 'reset');

        callback();
    }
    get status() {
        return {
            port: this.options.port,
            baudrate: this.options.baudrate,
            connections: _.size(this.connections),
            ready: this.ready,
            controller: {
                type: this.type,
                state: this.state
            },
            workflowState: this.workflowState,
            gcode: {
                name: this.sender.name,
                size: this.sender.gcode.length,
                remain: this.sender.remain.length,
                sent: this.sender.sent.length,
                total: this.sender.total,
                createdTime: this.sender.createdTime,
                startedTime: this.sender.startedTime,
                finishedTime: this.sender.finishedTime
            }
        };
    }
    reset() {
        this.ready = false;
        this.workflowState = WORKFLOW_STATE_IDLE;
        this.queryResponse.status = false;
        this.queryResponse.parserstate = false;
        this.queryResponse.parserstateEnd = false;
    }
    open(callback = noop) {
        const { port } = this.options;

        // Assertion check
        if (this.isOpen()) {
            callback(new Error('Cannot open serial port ' + port));
            return;
        }

        this.serialport.open((err) => {
            if (err) {
                callback(err);
                return;
            }

            this.serialport.on('data', (data) => {
                this.grbl.parse('' + data);
                log.raw('silly', _.trimEnd('Grbl> ' + data));
            });

            this.serialport.on('disconnect', (err) => {
                log.warn(`${PREFIX} Disconnected from serial port "${port}": err=${JSON.stringify(err)}`);
                this.destroy();
            });

            this.serialport.on('error', (err) => {
                log.error(`${PREFIX} Unexpected error while reading/writing serial port "${port}": err=${JSON.stringify(err)}`);
                this.destroy();
            });

            log.debug(`${PREFIX} Connected to serial port "${port}"`);

            // Reset
            this.reset();

            // Unload G-code
            this.command(null, 'unload');

            // Initialize the controller
            this.init(callback);
        });
    }
    close(callback = noop) {
        const { port } = this.options;

        // Assertion check
        if (this.isClose()) {
            callback(new Error('Cannot close serial port ' + port));
            return;
        }

        // Reset Grbl while closing serial port
        this.command(null, 'reset');

        this.serialport.close((err) => {
            this.destroy();
            callback(err);
        });
    }
    isOpen() {
        return this.serialport.isOpen();
    }
    isClose() {
        return !(this.isOpen());
    }
    addConnection(socket) {
        this.connections.push(new Connection(socket));

        if (!_.isEmpty(this.state)) {
            // Send current state to the connected client
            socket.emit('Grbl:state', this.state);
        }
    }
    removeConnection(socket) {
        const index = _.findIndex(this.connections, (c) => {
            return c.socket === socket;
        });
        this.connections.splice(index, 1);
    }
    command(socket, cmd, ...args) {
        const handler = {
            'load': () => {
                const [name, gcode, callback] = args;

                this.sender.load(name, gcode, (err) => {
                    if (err) {
                        callback(err);
                        return;
                    }

                    log.debug(`${PREFIX} Load G-code: name="${this.sender.name}", size=${this.sender.gcode.length}, total=${this.sender.total}`);

                    this.workflowState = WORKFLOW_STATE_IDLE;
                    callback();
                });
            },
            'unload': () => {
                this.workflowState = WORKFLOW_STATE_IDLE;
                this.sender.unload();
            },
            'start': () => {
                this.workflowState = WORKFLOW_STATE_RUNNING;
                this.sender.next();
            },
            'stop': () => {
                this.workflowState = WORKFLOW_STATE_IDLE;
                this.sender.rewind();
            },
            'pause': () => {
                this.workflowState = WORKFLOW_STATE_PAUSED;
                this.write(socket, '!');
            },
            'resume': () => {
                this.write(socket, '~');
                this.workflowState = WORKFLOW_STATE_RUNNING;
                this.sender.next();
            },
            'feedhold': () => {
                this.write(socket, '!');
            },
            'cyclestart': () => {
                this.write(socket, '~');
            },
            'reset': () => {
                this.write(socket, '\x18');
            },
            'homing': () => {
                this.writeln(socket, '$H');
            },
            'unlock': () => {
                this.writeln(socket, '$X');
            },
            'gcode': () => {
                const gcode = args.join(' ');
                this.writeln(socket, gcode);
            }
        }[cmd];

        if (!handler) {
            log.error(`${PREFIX} Unknown command: ${cmd}`);
            return;
        }

        handler();
    }
    write(socket, data) {
        socket && socket.emit('serialport:write', data);
        const index = _.findIndex(this.connections, (c) => {
            return c.socket === socket;
        });
        if (index >= 0) {
            this.connections[index].sentCommand = data;
        }
        this.serialport.write(data);
        log.raw('silly', _.trimEnd('Grbl> ' + data));
    }
    writeln(socket, data) {
        if (_.includes(GRBL_REALTIME_COMMANDS, data)) {
            this.write(socket, data);
        } else {
            this.write(socket, data + '\n');
        }
    }
}

export default GrblController;
<commit_msg>Grbl controller enhancements<commit_after>import _ from 'lodash';
import SerialPort from 'serialport';
import log from '../../lib/log';
import GCodeSender from '../../lib/gcode-sender';
import store from '../../store';
import Grbl from './Grbl';
import {
    WORKFLOW_STATE_RUNNING,
    WORKFLOW_STATE_PAUSED,
    WORKFLOW_STATE_IDLE
} from '../../constants';
import {
    GRBL,
    GRBL_REALTIME_COMMANDS
} from './constants';

const dbg = (...args) => {
    log.raw.apply('silly', [].concat(args));
};

class Connection {
    socket = null;
    sentCommand = '';

    constructor(socket) {
        this.socket = socket;
    }
}

class GrblController {
    type = GRBL;

    // Connections
    connections = [];

    // SerialPort
    options = {
        port: '',
        baudrate: 115200
    };
    serialport = null;

    // Grbl
    grbl = null;
    ready = false;
    state = {};
    queryTimer = null;
    queryResponse = {
        status: false,
        parserstate: false,
        parserstateEnd: false
    };

    // G-code sender
    sender = null;

    // Workflow state
    workflowState = WORKFLOW_STATE_IDLE;

    constructor(port, options) {
        const { baudrate } = { ...options };

        this.options = {
            ...this.options,
            port: port,
            baudrate: baudrate
        };

        // GCodeSender
        this.sender = new GCodeSender();
        this.sender.on('progress', (res) => {
            if (this.isClose()) {
                log.error(`[Grbl] The serial port "${this.options.port}" is not accessible`);
                return;
            }

            let { gcode = '' } = { ...res };
            gcode = ('' + gcode).trim();
            if (gcode.length > 0) {
                this.serialport.write(gcode + '\n');
            }
        });

        // Grbl
        this.grbl = new Grbl(this.serialport);

        this.grbl.on('raw', (res) => {});

        this.grbl.on('status', (res) => {
            this.queryResponse.status = false;

            this.connections.forEach((c) => {
                if (c.sentCommand.indexOf('?') === 0) {
                    c.sentCommand = '';
                    c.socket.emit('serialport:read', res.raw);
                }
            });
        });

        this.grbl.on('ok', (res) => {
            if (this.queryResponse.parserstateEnd) {
                this.connections.forEach((c) => {
                    if (c.sentCommand.indexOf('$G') === 0) {
                        c.sentCommand = '';
                        c.socket.emit('serialport:read', res.raw);
                    }
                });
                this.queryResponse.parserstateEnd = false;
                return;
            }

            if (this.workflowState === WORKFLOW_STATE_RUNNING) {
                this.sender.next();
                return;
            }

            this.emitAll('serialport:read', res.raw);
        });

        this.grbl.on('error', (res) => {
            if (this.workflowState === WORKFLOW_STATE_RUNNING) {
                const length = this.sender.sent.length;
                if (length > 0) {
                    const lastDataSent = this.sender.sent[length - 1];
                    const msg = '> (' + length + ') ' + lastDataSent;
                    this.emitAll('serialport:read', msg);
                }

                this.sender.next();
            }

            this.emitAll('serialport:read', res.raw);
        });

        this.grbl.on('alarm', (res) => {
            this.emitAll('serialport:read', res.raw);
        });

        this.grbl.on('parserstate', (res) => {
            this.queryResponse.parserstate = false;
            this.queryResponse.parserstateEnd = true; // wait for ok response

            this.connections.forEach((c) => {
                if (c.sentCommand.indexOf('$G') === 0) {
                    c.socket.emit('serialport:read', res.raw);
                }
            });
        });

        this.grbl.on('parameters', (res) => {
            this.emitAll('serialport:read', res.raw);
        });

        this.grbl.on('feedback', (res) => {
            this.emitAll('serialport:read', res.raw);
        });

        this.grbl.on('settings', (res) => {
            this.emitAll('serialport:read', res.raw);
        });

        this.grbl.on('startup', (res) => {
            this.ready = true;
            this.queryResponse.status = false;
            this.queryResponse.parserstate = false;
            this.queryResponse.parserstateEnd = false;

            this.emitAll('serialport:read', res.raw);
        });

        this.grbl.on('others', (res) => {
            this.emitAll('serialport:read', res.raw);
        });

        // SerialPort
        this.serialport = new SerialPort(this.options.port, {
            autoOpen: false,
            baudrate: this.options.baudrate,
            parser: SerialPort.parsers.readline('\n')
        });

        this.serialport.on('data', (data) => {
            this.grbl.parse('' + data);
            dbg(`[Grbl] < ${data}`);
        });

        this.serialport.on('disconnect', (err) => {
            if (err) {
                log.warn(`[Grbl] Disconnected from serial port "${port}":`, err);
            }

            this.close();
        });

        this.serialport.on('error', (err) => {
            if (err) {
                log.error(`[Grbl] Unexpected error while reading/writing serial port "${port}":`, err);
            }
        });

        // Timer
        this.queryTimer = setInterval(() => {
            if (this.isClose()) {
                // Serial port is closed
                return;
            }

            if (!(this.ready)) {
                // Not ready yet
                return;
            }

            if (this.state !== this.grbl.state) {
                this.state = this.grbl.state;
                this.emitAll('Grbl:state', this.state);
            }

            // ? - Current Status
            if (!(this.queryResponse.status)) {
                this.queryResponse.status = true;
                this.serialport.write('?');
            }

            // $G - Parser State
            if (!(this.queryResponse.parserstate) && !(this.queryResponse.parserstateEnd)) {
                this.queryResponse.parserstate = true;
                this.queryResponse.parserstateEnd = false;
                this.serialport.write('$G\n');
            }

            // Detect for any G-code status changes
            if (this.sender.peek()) {
                this.emitAll('gcode:statuschange', {
                    'remain': this.sender.remain.length,
                    'sent': this.sender.sent.length,
                    'total': this.sender.total,
                    'createdTime': this.sender.createdTime,
                    'startedTime': this.sender.startedTime,
                    'finishedTime': this.sender.finishedTime
                });
            }
        }, 250);
    }
    destroy() {
        if (this.queryTimer) {
            clearInterval(this.queryTimer);
            this.queryTimer = null;
        }

        if (this.grbl) {
            this.grbl.removeAllListeners();
            this.grbl = null;
        }
    }
    initController() {
        // Reset Grbl
        this.command(null, 'reset');
    }
    get status() {
        return {
            port: this.options.port,
            baudrate: this.options.baudrate,
            connections: _.size(this.connections),
            ready: this.ready,
            controller: {
                type: this.type,
                state: this.state
            },
            workflowState: this.workflowState,
            gcode: {
                name: this.sender.name,
                size: this.sender.gcode.length,
                remain: this.sender.remain.length,
                sent: this.sender.sent.length,
                total: this.sender.total,
                createdTime: this.sender.createdTime,
                startedTime: this.sender.startedTime,
                finishedTime: this.sender.finishedTime
            }
        };
    }
    reset() {
        this.ready = false;
        this.workflowState = WORKFLOW_STATE_IDLE;
        this.queryResponse.status = false;
        this.queryResponse.parserstate = false;
        this.queryResponse.parserstateEnd = false;
    }
    open() {
        const { port, baudrate } = this.options;

        // Assertion check
        if (this.isOpen()) {
            callback(new Error('Cannot open serial port ' + port));
            return;
        }

        this.serialport.open((err) => {
            if (err) {
                callback(err);
                return;
            }

            if (store.get('controllers["' + port + '"]')) {
                log.error(`[Grbl] Serial port "${port}" was not properly closed`);
            }

            store.set('controllers["' + port + '"]', this);

            this.emitAll('serialport:open', {
                port: port,
                baudrate: baudrate,
                controllerType: this.type,
                inuse: true
            });

            log.debug(`[Grbl] Connected to serial port "${port}"`);

            // Reset
            this.reset();

            // Unload G-code
            this.command(null, 'unload');

            // Initialize Grbl controller
            this.initController();
        });
    }
    close() {
        const { port } = this.options;

        // Assertion check
        if (this.isClose()) {
            log.error(`[Grbl] The serial port "${port}" was already closed`);
            return;
        }

        this.emitAll('serialport:close', {
            port: port,
            inuse: false
        });
        store.unset('controllers["' + port + '"]');

        this.destroy();

        this.serialport.close((err) => {
            if (err) {
                log.error(`[Grbl] Error closing serial port "${port}":`, err);
            }
        });
    }
    isOpen() {
        return this.serialport.isOpen();
    }
    isClose() {
        return !(this.isOpen());
    }
    addConnection(socket) {
        this.connections.push(new Connection(socket));

        if (!_.isEmpty(this.state)) {
            // Send current state to the connected client
            socket.emit('Grbl:state', this.state);
        }
    }
    removeConnection(socket) {
        const index = _.findIndex(this.connections, (c) => {
            return c.socket === socket;
        });
        this.connections.splice(index, 1);
    }
    emitAll(eventName, ...args) {
        this.connections.forEach((c) => {
            c.socket.emit.apply(c.socket, [eventName].concat(args));
        });
    }
    command(socket, cmd, ...args) {
        const handler = {
            'load': () => {
                const [name, gcode, callback] = args;

                this.sender.load(name, gcode, (err) => {
                    if (err) {
                        callback(err);
                        return;
                    }

                    log.debug(`[Grbl] Load G-code: name="${this.sender.name}", size=${this.sender.gcode.length}, total=${this.sender.total}`);

                    this.workflowState = WORKFLOW_STATE_IDLE;
                    callback();
                });
            },
            'unload': () => {
                this.workflowState = WORKFLOW_STATE_IDLE;
                this.sender.unload();
            },
            'start': () => {
                this.workflowState = WORKFLOW_STATE_RUNNING;
                this.sender.next();
            },
            'stop': () => {
                this.workflowState = WORKFLOW_STATE_IDLE;
                this.sender.rewind();
            },
            'pause': () => {
                this.workflowState = WORKFLOW_STATE_PAUSED;
                this.write(socket, '!');
            },
            'resume': () => {
                this.write(socket, '~');
                this.workflowState = WORKFLOW_STATE_RUNNING;
                this.sender.next();
            },
            'feedhold': () => {
                this.write(socket, '!');
            },
            'cyclestart': () => {
                this.write(socket, '~');
            },
            'reset': () => {
                this.write(socket, '\x18');
            },
            'homing': () => {
                this.writeln(socket, '$H');
            },
            'unlock': () => {
                this.writeln(socket, '$X');
            },
            'gcode': () => {
                const gcode = args.join(' ');
                this.writeln(socket, gcode);
            }
        }[cmd];

        if (!handler) {
            log.error(`[Grbl] Unknown command: ${cmd}`);
            return;
        }

        handler();
    }
    write(socket, data) {
        socket && socket.emit('serialport:write', data);
        const index = _.findIndex(this.connections, (c) => {
            return c.socket === socket;
        });
        if (index >= 0) {
            this.connections[index].sentCommand = data;
        }
        this.serialport.write(data);

        dbg(`[Grbl] > ${data}`);
    }
    writeln(socket, data) {
        if (_.includes(GRBL_REALTIME_COMMANDS, data)) {
            this.write(socket, data);
        } else {
            this.write(socket, data + '\n');
        }
    }
}

export default GrblController;
<|endoftext|>


<commit_before>describe("cdb.admin.LayerPanelView", function() {

  var view;
  var map, table, sqlView, user, layer, vis;
  beforeEach(function() {
    map = new cdb.admin.Map();

    user = new cdb.admin.User({
      username: 'testusername',
      api_key: 'rabos'
    });

    vis = new cdb.admin.Visualization({
      map_id:  96,
      name:    "test_table",
      privacy: "PUBLIC",
      type:    "table"
    });

    var globalError = new cdb.admin.GlobalError({
      el: $('<div>')
    });

    layer = new cdb.admin.CartoDBLayer({
      table_name: 'test',
      id: 10,
      visible: 'true'
    });

    table = layer.table;
    map.layers.add(layer);

    view = new cdb.admin.LayerPanelView({
      model: layer,
      vis: vis,
      map: map,
      user: user,
      globalError: globalError
    });
  });

  it("should add map_key in layer", function() {
    expect(layer.get('extra_params').map_key).toEqual('rabos');
  });

  it("should hide/show the layer", function() {
    view.hide();
    expect(view.$('.sidebar').css('display')).toEqual('none');
    expect(view.$('.views').css('display')).toEqual('none');

    view.show();
    expect(view.$('.sidebar').css('display')).toEqual('block');
    expect(view.$('.views').css('display')).toEqual('block');
  });

  describe("layer initialization", function() {

    beforeEach(function() {
      //view.vis.set('type', 'derived');
    });

    it("should have 5 modules + 4 action buttons", function() {
      expect(view.buttons.length).toBe(9);
    });
  });

  describe("buttons behavior", function() {

    it("should show all the buttons when no sql is applied", function() {
      view.setActiveWorkView('table');
      var e = _(view.enabledButtonsForSection('table')).filter(function(b) {
        return b.$el.css('display') != 'none'
      });
      expect(e.length).toEqual(5);

      view.setActiveWorkView('map');
      var e = _(view.enabledButtonsForSection('map')).filter(function(b) {
        return b.$el.css('display') != 'none'
      });
      expect(e.length).toEqual(6);
    });

    it("should show readonly buttons when sql is applied", function() {
      table.useSQLView(view.sqlView);
      view.setActiveWorkView('table');
      var e = _(view.enabledButtonsForSection('table')).filter(function(b) {
        return b.$el.css('display') != 'none'
      });
      expect(e.length).toEqual(2);

      view.setActiveWorkView('map');
      var e = _(view.enabledButtonsForSection('map')).filter(function(b) {
        return b.$el.css('display') != 'none'
      });
      expect(e.length).toEqual(5);
    });

    it("should change to readonly buttons when sql is applied", function() {
      view.setActiveWorkView('table');
      table.useSQLView(view.sqlView);
      view.sqlView.modify_rows = false;
      view.sqlView.trigger('reset');
      var e = _(view.enabledButtonsForSection('table')).filter(function(b) {
        return b.$el.css('display') != 'none'
      });
      expect(e.length).toEqual(2);
    });
  });

  describe("layer info", function() {

    it("should set layer name, order and options", function() {
      expect(view.$('.layer_info a.info .name').text()).toBe('test');
      expect(view.$('.layer_info a.info .order').text()).toBe('1');
      expect(view.$('.layer_info div.right').css("display")).toBe('none');
    });

    it("should show layer option buttons if the visualization is the type derived", function() {
      view.vis.set('type', 'derived');
      expect(view.$('.layer_info div.right').css("display")).toBe('block');
    });

    it("should change the layer name if it changes", function() {
      view.dataLayer.set('table_name', 'jam');
      expect(view.$('.layer_info a.info .name').text()).toBe('jam');
    });

    it("shouldn't change the layer order if it changes, due to the visualization is the type table", function() {
      view.dataLayer.set('order', '4');
      expect(view.$('.layer_info a.info .order').text()).toBe('1');
    });

    it("should change the layer order if it changes", function() {
      view.vis.set('type', 'derived');
      view.dataLayer.set('order', '4');
      expect(view.$('.layer_info a.info .order').text()).toBe('4');
    });

  });

  describe("layer actions", function() {

    beforeEach(function() {
      view.vis.set('type', 'derived');
    });

    it("should remove the layer", function() {
      view.$('a.remove').trigger('click');
      var removed = false;
      view.bind('delete', function() {
        removed = true;
      })
      view.trigger('delete', this);
      expect(removed).toBeTruthy();
    });

    it("should toggle the layer", function() {
      view.$('a.visibility').trigger('click');
      expect(view.model.get('visible')).toBeFalsy();
    });

    it("should active the layer", function() {
      var called = false;
      view.bind('switchTo', function() {
        called = true;
      })
      view.$('a.info').trigger('click');
      expect(called).toBeTruthy();
    });

    it("shouldn't remove a layer if visualization is a table type", function() {
      view.vis.set('type', 'table');
      view.$('a.remove').trigger('click');
      expect(view.remove_dlg).toBe(undefined);
    });

    it("shouldn't change layer visibility if visualization is a table type", function() {
      view.vis.set('type', 'table');
      view.$('a.visibility').trigger('click');
      expect(view.model.get('visible')).toBeTruthy();
    });

    it("should have a list of filters", function() {
      expect(view.filters).toBeDefined();
    });

    it("should open a dropdown to choose a geometry", function() {
      view.$('.add_feature').click();
      expect(view.newGeomDropdown).toBeDefined();
      spyOn(view, '_addGeometry');
      view.newGeomDropdown.$('li:eq(0) a').click();
      expect(view._addGeometry).toHaveBeenCalled();
      view.newGeomDropdown.clean();
    });
  });
});<commit_msg>Removes unnecesary test<commit_after>describe("cdb.admin.LayerPanelView", function() {

  var view;
  var map, table, sqlView, user, layer, vis;
  beforeEach(function() {
    map = new cdb.admin.Map();

    user = new cdb.admin.User({
      username: 'testusername',
      api_key: 'rabos'
    });

    vis = new cdb.admin.Visualization({
      map_id:  96,
      name:    "test_table",
      privacy: "PUBLIC",
      type:    "table"
    });

    var globalError = new cdb.admin.GlobalError({
      el: $('<div>')
    });

    layer = new cdb.admin.CartoDBLayer({
      table_name: 'test',
      id: 10,
      visible: 'true'
    });

    table = layer.table;
    map.layers.add(layer);

    view = new cdb.admin.LayerPanelView({
      model: layer,
      vis: vis,
      map: map,
      user: user,
      globalError: globalError
    });
  });

  it("should add map_key in layer", function() {
    expect(layer.get('extra_params').map_key).toEqual('rabos');
  });

  it("should hide/show the layer", function() {
    view.hide();
    expect(view.$('.sidebar').css('display')).toEqual('none');
    expect(view.$('.views').css('display')).toEqual('none');

    view.show();
    expect(view.$('.sidebar').css('display')).toEqual('block');
    expect(view.$('.views').css('display')).toEqual('block');
  });

  describe("layer initialization", function() {

    beforeEach(function() {
      //view.vis.set('type', 'derived');
    });

    it("should have 5 modules + 4 action buttons", function() {
      expect(view.buttons.length).toBe(9);
    });
  });

  describe("buttons behavior", function() {

    it("should show all the buttons when no sql is applied", function() {
      view.setActiveWorkView('table');
      var e = _(view.enabledButtonsForSection('table')).filter(function(b) {
        return b.$el.css('display') != 'none'
      });
      expect(e.length).toEqual(5);

      view.setActiveWorkView('map');
      var e = _(view.enabledButtonsForSection('map')).filter(function(b) {
        return b.$el.css('display') != 'none'
      });
      expect(e.length).toEqual(6);
    });

    it("should show readonly buttons when sql is applied", function() {
      table.useSQLView(view.sqlView);
      view.setActiveWorkView('table');
      var e = _(view.enabledButtonsForSection('table')).filter(function(b) {
        return b.$el.css('display') != 'none'
      });
      expect(e.length).toEqual(2);

      view.setActiveWorkView('map');
      var e = _(view.enabledButtonsForSection('map')).filter(function(b) {
        return b.$el.css('display') != 'none'
      });
      expect(e.length).toEqual(5);
    });

    it("should change to readonly buttons when sql is applied", function() {
      view.setActiveWorkView('table');
      table.useSQLView(view.sqlView);
      view.sqlView.modify_rows = false;
      view.sqlView.trigger('reset');
      var e = _(view.enabledButtonsForSection('table')).filter(function(b) {
        return b.$el.css('display') != 'none'
      });
      expect(e.length).toEqual(2);
    });
  });

  describe("layer info", function() {

    it("should set layer name, order and options", function() {
      expect(view.$('.layer_info a.info .name').text()).toBe('test');
      expect(view.$('.layer_info a.info .order').text()).toBe('1');
      expect(view.$('.layer_info div.right').css("display")).toBe('none');
    });

    it("should show layer option buttons if the visualization is the type derived", function() {
      view.vis.set('type', 'derived');
      expect(view.$('.layer_info div.right').css("display")).toBe('block');
    });

    it("should change the layer name if it changes", function() {
      view.dataLayer.set('table_name', 'jam');
      expect(view.$('.layer_info a.info .name').text()).toBe('jam');
    });

    it("shouldn't change the layer order if it changes, due to the visualization is the type table", function() {
      view.dataLayer.set('order', '4');
      expect(view.$('.layer_info a.info .order').text()).toBe('1');
    });

    it("should change the layer order if it changes", function() {
      view.vis.set('type', 'derived');
      view.dataLayer.set('order', '4');
      expect(view.$('.layer_info a.info .order').text()).toBe('4');
    });

  });

  describe("layer actions", function() {

    beforeEach(function() {
      view.vis.set('type', 'derived');
    });

    it("should remove the layer", function() {
      view.$('a.remove').trigger('click');
      var removed = false;
      view.bind('delete', function() {
        removed = true;
      })
      view.trigger('delete', this);
      expect(removed).toBeTruthy();
    });

    it("should toggle the layer", function() {
      view.$('a.visibility').trigger('click');
      expect(view.model.get('visible')).toBeFalsy();
    });

    it("should active the layer", function() {
      var called = false;
      view.bind('switchTo', function() {
        called = true;
      })
      view.$('a.info').trigger('click');
      expect(called).toBeTruthy();
    });

    it("shouldn't remove a layer if visualization is a table type", function() {
      view.vis.set('type', 'table');
      view.$('a.remove').trigger('click');
      expect(view.remove_dlg).toBe(undefined);
    });

    it("shouldn't change layer visibility if visualization is a table type", function() {
      view.vis.set('type', 'table');
      view.$('a.visibility').trigger('click');
      expect(view.model.get('visible')).toBeTruthy();
    });

    it("should open a dropdown to choose a geometry", function() {
      view.$('.add_feature').click();
      expect(view.newGeomDropdown).toBeDefined();
      spyOn(view, '_addGeometry');
      view.newGeomDropdown.$('li:eq(0) a').click();
      expect(view._addGeometry).toHaveBeenCalled();
      view.newGeomDropdown.clean();
    });
  });
});
<|endoftext|>


<commit_before>import { createElement } from 'rax';
import { action } from '@storybook/addon-actions';

import Button from 'rax-button';
import Text from 'rax-text';

export default {
  title: 'Button',
};

export const text = () => <Button onPress={action('clicked')}>Hello Button</Button>;

export const emoji = () => (
  <Button onPress={action('clicked')}>
    <Text role="img" aria-label="so cool">
      😀 😎 👍 💯
    </Text>
  </Button>
);
<commit_msg>FIX linting<commit_after>import { createElement } from 'rax';
import { action } from '@storybook/addon-actions';

import Text from 'rax-text';

export default {
  title: 'Button',
};

export const text = () => (
  <button onPress={action('clicked')} type="button">
    Hello button
  </button>
);

export const emoji = () => (
  <button onPress={action('clicked')} type="button">
    <Text role="img" aria-label="so cool">
      😀 😎 👍 💯
    </Text>
  </button>
);
<|endoftext|>