AppSaloon/socket.io-tester

View on GitHub
src/app/js/components/column/messageSender/MessageSenderView.js

Summary

Maintainability
C
1 day
Test Coverage
/**
 * MessageSenderView
 *
 * Message editor
 */

import React, { Component, Fragment } from 'react'
import Autosuggest from 'react-autosuggest'
import Editor from './Editor'

// import TriangleBottomIcon from '../../../icons/TriangleBottom'

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

        this.state = {
            tab: this.getThisTab(props),
            eventName: '',
            messageCollection: [{type: 'String', value: '', isValid: true}],
            messageInEditor: 0,
            autosuggestResults: []
        }

        this.handleMessageSend = this.handleMessageSend.bind(this)
        this.handleEventNameChange = this.handleEventNameChange.bind(this)
        this.handleMessageChange = this.handleMessageChange.bind(this)
        this.handleClearClick = this.handleClearClick.bind(this)
        this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
        this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
        this.getSuggestionValue = this.getSuggestionValue.bind(this)
        this.addMessageArgument = this.addMessageArgument.bind(this)
        this.removeMessageArgument = this.removeMessageArgument.bind(this)
        this.noMessageArgument = this.noMessageArgument.bind(this)
        this.changeType = this.changeType.bind(this)
    }

    componentWillReceiveProps(nextProps) {
        this.setState({
            tab: this.getThisTab(nextProps)
        })
    }

    /**
     * Returns active tab
     * 
     * @param {Object} props
     * 
     * @return {Object}
     */
    getThisTab (props) {
        const connections = props.connections.connections
        const list = props.connections.list
        let activeTab = props.activeTab

        return list[connections[activeTab].index]
    }

    handleEventNameChange (e, {newValue, method}) {
        this.setState({
            eventName: newValue
        })
    }

    /**
     * Change message type and value if required
     * @param {event} e - input change event
     */
    changeType (e) {
        const state = this.state
        const messageCollection = state.messageCollection.slice()
        const type = e.target.value
        let isValid = true
        let value = messageCollection[state.messageInEditor].value
        switch ( type ) {
            case 'Boolean':
            value = 'true'
            break

            case 'Number':
            value = ~~value
            break

            case 'Object':
            case 'Array':
            const parsedValue = this.testObjValid(value)
            if ( parsedValue && Object.prototype.toString.apply(parsedValue).slice(8, -1) === type )
                isValid = true
            else
                isValid = false
            break

            case 'String':
            try { // stringify current value in case it's an object or it'll look like "[object Object]", if JSON.parse works it's already a string, so we don't need to change it
                JSON.parse(value)
                value = value + '' // convert boolean to string or codemirror throws an error
            } catch ( e ) { // if it throws we know it's not a JSON string already and we have to stringify it
                value = JSON.stringify(value)
            }
            break

            case 'JSON':
            try { // if JSON.parse works it's valid JSON
                JSON.parse(value)
            } catch ( e ) {
                isValid = false
            }
            break
        }
        messageCollection[state.messageInEditor] = {
            value,
            type,
            isValid
        }
        this.setState({
            messageCollection
        })
    }

    /**
     * Update message and validation
     */
    handleMessageChange (value) {
        const state = this.state
        const messageCollection = state.messageCollection.slice()
        const messageInEditor = state.messageInEditor
        const message = messageCollection[messageInEditor]

        const type = message.type
        let isValid = message.isValid

        switch ( type ) {
            case 'Object':
            case 'Array':
            isValid = !!this.testObjValid(value)
            break

            case 'JSON':
            try {
                JSON.parse(value) // if it doesn't throw it's a valid JSON string
                isValid = true
            } catch ( e ) {
                isValid = false
            }
        }

        messageCollection[messageInEditor] = {
            value,
            isValid,
            type
        }
        this.setState({
            messageCollection
        })
    }

    testObjValid (value) {
        let evalResult, JSONResult
        try {
            eval(`evalResult = ${value}`) // if it doesn't throw it's a valid array or object
        } catch ( e ) {}
        try {
            JSONResult = JSON.parse(value) // if it doesn't throw it's a valid array or object
        } catch ( e ) {}
        return evalResult || JSONResult
    }

    /**
     * Saves new message in redux store if name and message are valid
     */
    handleMessageSend () {
        if ( !this.state.tab.connected )
            return

        if ( this.state.eventName )
            this.props.sendMessage(this.props.activeTab, this.state.eventName, this.state.messageCollection)
    }

    /**
     * Clears message
     */
    handleClearClick () {
        const state = this.state
        const messageCollection = state.messageCollection.slice()
        messageCollection[state.messageInEditor] = ''
        this.setState({
            messageCollection
        })
    }

    /**
     * Autocomplete, filter dropdown results
     */
    onSuggestionsFetchRequested ({value}) {
        const result = []
        const events = this.state.tab.events
        let event
        for ( let x = 0, l = events.length; x < l; x++ ) {
            event = events[x]
            if ( event.name.toLowerCase().indexOf(value.toLowerCase()) !== -1 )
                result.push(event)
        }
        this.setState({
            autosuggestResults: result
        })
    }

    /**
     * Auotocomplete, show all event names
     */
    onSuggestionsClearRequested () {
        this.setState({
            autosuggestResults: this.state.tab.events
        })
    }

    getSuggestionValue (s) {
        return s.name
    }

    renderSuggestion (s, query) {
        return (
            <span>
                {s.name}
            </span>
        )
    }

    shouldRenderSuggestions () {
        return true
    }

    /**
     * Add one mesage argument
     */
    addMessageArgument () {
        const messageCollection = this.state.messageCollection
        this.setState({
            messageCollection: messageCollection.concat({type: 'String', value: '', isValid: true}),
            messageInEditor: messageCollection.length
        })
    }

    /**
     * Remove a specific message argument
     */
    removeMessageArgument () {
        const editing = this.state.messageInEditor
        const messageCollection = this.state.messageCollection

        if ( messageCollection.length <= 1 ) return // don't remove the last item in the array

        const newMessageCollection = [].concat(messageCollection.slice(0, editing), messageCollection.slice(editing+1))
        const newMessageInEditor = editing - 1
        this.setState({
            messageCollection: newMessageCollection,
            messageInEditor: ~newMessageInEditor ? newMessageInEditor : 0
        })
    }

    /**
     * Remove all message content
     */
    noMessageArgument () {
        this.setState({
            messageCollection: [],
            messageInEditor: -1
        })
    }

    render () {
        const state = this.state
        const connected = state.tab.connected
        const messageInEditor = state.messageInEditor
        const messageInEditorObject = state.messageCollection[state.messageInEditor]

        const autosuggestInputProps = {
            placeholder: 'Event name',
            value: state.eventName,
            onChange: this.handleEventNameChange
        };

        let sendIsEnabled = true
        if (  !connected || !state.eventName || ( ~messageInEditor && !state.messageCollection.map( m => m.isValid ).reduce( (a, b) => a && b) ) )
            sendIsEnabled = false

        return (
            <div className="column-block">
                <h3 className="column-title">Send Message</h3>
                <div
                    className="column-block-inputwrapper"
                >

                    <div className="column-block-autosuggest">
                        {/*<TriangleBottomIcon
                            size={15}
                            color={'#d6c5eb'}
                            customStyle={{
                                border: '2px solid #d6c5eb',
                                borderRadius: 5,
                                position: 'absolute',
                                right: 6,
                                top: 6
                            }}
                        />*/}

                        <Autosuggest
                            suggestions={state.autosuggestResults}
                            onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
                            onSuggestionsClearRequested={this.onSuggestionsClearRequested}
                            getSuggestionValue={this.getSuggestionValue}
                            renderSuggestion={this.renderSuggestion}
                            inputProps={autosuggestInputProps}
                            focusInputOnSuggestionClick={false}
                            shouldRenderSuggestions={this.shouldRenderSuggestions}
                        />
                    </div>

                    <div>
                        <span>Message arguments</span>
                        <div className="message-arguments-button-group">
                            <div className="message-arguments-buttons">
                                {
                                    state.messageCollection.map( (m, i) =>
                                        <button key={i} className={`message-arguments-buttons-button ${ messageInEditor === i ? 'active' : '' }`} onClick={() => this.setState({messageInEditor: i})}>{i+1}</button>
                                    )
                                }
                            </div>
                            <div className="message-arguments-buttons">
                                <button
                                    onClick={this.addMessageArgument}
                                    className="message-arguments-buttons-button"
                                >
                                    Add
                                </button>
                                <button
                                    onClick={this.removeMessageArgument}
                                    className="message-arguments-buttons-button"
                                >
                                    Remove
                                </button>
                                <button
                                    onClick={this.noMessageArgument}
                                    className="message-arguments-buttons-button"
                                >
                                    None
                                </button>
                            </div>
                        </div>
                    </div>

                    { ~messageInEditor ?
                        <Fragment>
                            <div className="column-string">
                                <span>
                                    <select value={messageInEditorObject.type} onChange={this.changeType}>
                                        <option value="String">String</option>
                                        <option value="JSON">JSON</option>
                                        <option value="Array">Array</option>
                                        <option value="Object">Object</option>
                                        <option value="Number">Number</option>
                                        <option value="Boolean">Boolean</option>
                                    </select>
                                </span>
                            </div>

                            <button
                                className="column-button"
                                onClick={this.handleClearClick}
                            >
                                Clear
                            </button>

                            <Editor message={messageInEditorObject} handleMessageChange={this.handleMessageChange} />
                        </Fragment>
                        : null
                    }

                    <button
                        className="column-button"
                        disabled={!sendIsEnabled}
                        onClick={this.handleMessageSend}
                    >
                        Send message
                    </button>
                </div>

            </div>
        )
    }
}

export default MessageSender