<commit_before>/**
 * Copyright (C) 2012-2019 Mailvelope GmbH
 * Licensed under the GNU Affero General Public License version 3
 */

import React from 'react';
import PropTypes from 'prop-types';
import * as l10n from '../../lib/l10n';
import {LARGE_FRAME} from '../../lib/constants';
import {encodeHTML, getHash, str2ab} from '../../lib/util';
import EventHandler from '../../lib/EventHandler';
import ContentSandbox from './components/ContentSandbox';
import {FileDownloadPanel} from '../util/FilePanel';
import SecurityBG from '../util/SecurityBG';
import Alert from '../util/Alert';
import Toast from '../util/Toast';
import Spinner from '../util/Spinner';
import Terminate from '../util/Terminate';

import './DecryptMessage.scss';

// register language strings
l10n.register([
  'alert_header_error',
  'decrypt_attachment_label',
  'decrypt_digital_signature',
  'decrypt_digital_signature_failure',
  'decrypt_digital_signature_null',
  'decrypt_show_message_btn',
  'decrypt_signer_label',
  'security_background_button_title'
]);

export default class DecryptMessage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: '',
      signer: null,
      showSig: false,
      waiting: true,
      files: [],
      error: null,
      showError: false,
      pwdDialog: null,
      terminate: false,
      large: false
    };
    this.port = EventHandler.connect(`dDialog-${this.props.id}`, this);
    this.registerEventListeners();
    // emit event to backend that editor has initialized
    this.port.emit('decrypt-message-init');
  }

  componentDidMount() {
    this.setState({large: this.element.clientHeight > LARGE_FRAME ? true : false});
  }

  registerEventListeners() {
    this.port.on('decrypted-message', this.onDecryptedMessage);
    this.port.on('verified-message', this.onVerifiedMessage);
    this.port.on('add-decrypted-attachment', this.onDecryptedAttachment);
    this.port.on('signature-verification', this.onSignatureVerification);
    this.port.on('error-message', this.showErrorMsg);
    this.port.on('show-password-required', this.onShowPwdRequired);
    this.port.on('show-pwd-dialog', this.onShowPwdDialog);
    this.port.on('hide-pwd-dialog', this.onHidePwdDialog);
    this.port.on('terminate', this.onTerminate);
  }

  onTerminate() {
    this.setState({terminate: true}, () => this.port.disconnect());
  }

  onShowPwdDialog(msg) {
    this.setState({pwdDialog: msg, error: null});
  }

  onHidePwdDialog() {
    this.setState({pwdDialog: null});
  }

  onShowPwdRequired() {
    this.setState({waiting: false});
  }

  onVerifiedMessage(msg) {
    this.onSignatureVerification(msg);
    this.setState({message: encodeHTML(msg.message), waiting: false});
  }

  onDecryptedMessage({message}) {
    this.setState({message, waiting: false});
  }

  onDecryptedAttachment({attachment}) {
    const file = {
      id: getHash(),
      name: attachment.filename
    };
    const content = str2ab(attachment.content);
    // set MIME type fix to application/octet-stream as other types can be exploited in Chrome
    attachment.mimeType = 'application/octet-stream';
    const blob = new Blob([content], {type: attachment.mimeType});
    file.objectURL = window.URL.createObjectURL(blob);
    this.setState(prevState => ({
      files: [...prevState.files, file],
      waiting: false
    }));
  }

  onSignatureVerification({signers}) {
    let signer = null;
    if (!signers.length) {
      return this.setState({signer});
    }
    const validSig = signers.filter(signer => signer.valid === true);
    const invalidSig = signers.filter(signer => signer.valid === false);
    if (validSig.length) {
      signer = validSig[0];
    } else if (invalidSig.length) {
      signer = invalidSig[0];
    }
    this.setState({signer});
  }

  handleClickFile() {
    this.logUserInput('security_log_attachment_download');
  }

  showErrorMsg({error}) {
    this.setState({
      error: {
        header: l10n.map.alert_header_error,
        message: error,
        type: 'danger'
      },
      waiting: false,
      showError: true
    });
  }

  logUserInput(type) {
    this.port.emit('decrypt-inline-user-input', {
      source: 'security_log_email_viewer',
      type
    });
  }

  handleCancel() {
    this.port.emit('decrypt-dialog-cancel');
    this.setState({showError: false});
  }

  handleDecrypt() {
    if (!this.state.showError && !this.state.waiting) {
      this.setState({waiting: true}, () => this.port.emit('decrypt-message'));
    }
  }

  signatureStatus() {
    let labelClass;
    let labelText;
    if (!this.state.signer) {
      return null;
    }
    switch (this.state.signer.valid) {
      case true:
        labelClass = 'success';
        labelText = l10n.map.decrypt_digital_signature;
        break;
      case false:
        labelClass = 'danger';
        labelText = l10n.map.decrypt_digital_signature_failure;
        break;
      default:
        labelClass = 'warning';
        labelText = l10n.map.decrypt_digital_signature_null;
    }
    return (
      <span className={`${labelClass} text-nowrap mr-3`}><span className={`icon icon-marker text-${labelClass}`} aria-hidden="true"> {labelText}
    );
  }

  render() {
    return (
      <SecurityBG className={`decrypt-msg ${this.props.embedded ? 'embedded' : ''}`} port={this.port}>

 this.element = node} className="modal-dialog h-100 mw-100 m-0">
            {!this.state.message ? (
              <div className={`locked ${this.state.large ? 'large' : ''} modal-content overflow-hidden shadow-lg border-0 h-100 ${(!this.state.showError && !this.state.waiting) ? 'cursor' : ''}`} onClick={() => this.handleDecrypt()}>

                {this.state.waiting ? (
                  <Spinner />
                ) : (

{l10n.map.decrypt_show_message_btn}

                )}

            ) : (

                {this.state.files.length > 0 && (

                      <FileDownloadPanel files={this.state.files} onClickFile={() => this.handleClickFile()} />

                )}

                    <ContentSandbox value={this.state.message} />

                {this.state.signer && (

                      {this.signatureStatus()}
                      <Alert type="info" className="my-2 flex-shrink-1">

                        {this.state.signer.keyDetails.name} {`<${this.state.signer.keyDetails.email}> #${this.state.signer.keyId ? this.state.signer.keyId.toUpperCase() : this.state.signer.keyDetails.keyId.toUpperCase()}`}
                      </Alert>

                )}

            )}

        {this.state.pwdDialog && <iframe className="decrypt-popup-pwd-dialog modal-content" src={`../enter-password/passwordDialog.html?id=${this.state.pwdDialog.id}`} frameBorder={0} />}
        {this.state.error &&

            <Toast isOpen={this.state.showError} header={this.state.error.header} toggle={() => this.setState(prevState => ({showError: !prevState.showError}))} type="error" transition={{timeout: 150, unmountOnExit: true, onExited: () => this.handleCancel()}}>
              {this.state.error.message}
            </Toast>

        }
        {this.state.pwdDialog &&

}
        {this.state.terminate && <Terminate />}
      </SecurityBG>
    );
  }
}

DecryptMessage.propTypes = {
  id: PropTypes.string,
  embedded: PropTypes.bool
};
<commit_msg>Fix show files only in decrypt message component<commit_after>/**
 * Copyright (C) 2012-2019 Mailvelope GmbH
 * Licensed under the GNU Affero General Public License version 3
 */

import React from 'react';
import PropTypes from 'prop-types';
import * as l10n from '../../lib/l10n';
import {LARGE_FRAME} from '../../lib/constants';
import {encodeHTML, getHash, str2ab} from '../../lib/util';
import EventHandler from '../../lib/EventHandler';
import ContentSandbox from './components/ContentSandbox';
import {FileDownloadPanel} from '../util/FilePanel';
import SecurityBG from '../util/SecurityBG';
import Alert from '../util/Alert';
import Toast from '../util/Toast';
import Spinner from '../util/Spinner';
import Terminate from '../util/Terminate';

import './DecryptMessage.scss';

// register language strings
l10n.register([
  'alert_header_error',
  'decrypt_attachment_label',
  'decrypt_digital_signature',
  'decrypt_digital_signature_failure',
  'decrypt_digital_signature_null',
  'decrypt_show_message_btn',
  'decrypt_signer_label',
  'security_background_button_title'
]);

export default class DecryptMessage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: '',
      signer: null,
      showSig: false,
      waiting: true,
      files: [],
      error: null,
      showError: false,
      pwdDialog: null,
      terminate: false,
      large: false
    };
    this.port = EventHandler.connect(`dDialog-${this.props.id}`, this);
    this.registerEventListeners();
    // emit event to backend that editor has initialized
    this.port.emit('decrypt-message-init');
  }

  componentDidMount() {
    this.setState({large: this.element.clientHeight > LARGE_FRAME ? true : false});
  }

  registerEventListeners() {
    this.port.on('decrypted-message', this.onDecryptedMessage);
    this.port.on('verified-message', this.onVerifiedMessage);
    this.port.on('add-decrypted-attachment', this.onDecryptedAttachment);
    this.port.on('signature-verification', this.onSignatureVerification);
    this.port.on('error-message', this.showErrorMsg);
    this.port.on('show-password-required', this.onShowPwdRequired);
    this.port.on('show-pwd-dialog', this.onShowPwdDialog);
    this.port.on('hide-pwd-dialog', this.onHidePwdDialog);
    this.port.on('terminate', this.onTerminate);
  }

  onTerminate() {
    this.setState({terminate: true}, () => this.port.disconnect());
  }

  onShowPwdDialog(msg) {
    this.setState({pwdDialog: msg, error: null});
  }

  onHidePwdDialog() {
    this.setState({pwdDialog: null});
  }

  onShowPwdRequired() {
    this.setState({waiting: false});
  }

  onVerifiedMessage(msg) {
    this.onSignatureVerification(msg);
    this.setState({message: encodeHTML(msg.message), waiting: false});
  }

  onDecryptedMessage({message}) {
    this.setState({message, waiting: false});
  }

  onDecryptedAttachment({attachment}) {
    const file = {
      id: getHash(),
      name: attachment.filename
    };
    const content = str2ab(attachment.content);
    // set MIME type fix to application/octet-stream as other types can be exploited in Chrome
    attachment.mimeType = 'application/octet-stream';
    const blob = new Blob([content], {type: attachment.mimeType});
    file.objectURL = window.URL.createObjectURL(blob);
    this.setState(prevState => ({
      files: [...prevState.files, file],
      waiting: false
    }));
  }

  onSignatureVerification({signers}) {
    let signer = null;
    if (!signers.length) {
      return this.setState({signer});
    }
    const validSig = signers.filter(signer => signer.valid === true);
    const invalidSig = signers.filter(signer => signer.valid === false);
    if (validSig.length) {
      signer = validSig[0];
    } else if (invalidSig.length) {
      signer = invalidSig[0];
    }
    this.setState({signer});
  }

  handleClickFile() {
    this.logUserInput('security_log_attachment_download');
  }

  showErrorMsg({error}) {
    this.setState({
      error: {
        header: l10n.map.alert_header_error,
        message: error,
        type: 'danger'
      },
      waiting: false,
      showError: true
    });
  }

  logUserInput(type) {
    this.port.emit('decrypt-inline-user-input', {
      source: 'security_log_email_viewer',
      type
    });
  }

  handleCancel() {
    this.port.emit('decrypt-dialog-cancel');
    this.setState({showError: false});
  }

  handleDecrypt() {
    if (!this.state.showError && !this.state.waiting) {
      this.setState({waiting: true}, () => this.port.emit('decrypt-message'));
    }
  }

  signatureStatus() {
    let labelClass;
    let labelText;
    if (!this.state.signer) {
      return null;
    }
    switch (this.state.signer.valid) {
      case true:
        labelClass = 'success';
        labelText = l10n.map.decrypt_digital_signature;
        break;
      case false:
        labelClass = 'danger';
        labelText = l10n.map.decrypt_digital_signature_failure;
        break;
      default:
        labelClass = 'warning';
        labelText = l10n.map.decrypt_digital_signature_null;
    }
    return (
      <span className={`${labelClass} text-nowrap mr-3`}><span className={`icon icon-marker text-${labelClass}`} aria-hidden="true"> {labelText}
    );
  }

  render() {
    return (
      <SecurityBG className={`decrypt-msg ${this.props.embedded ? 'embedded' : ''}`} port={this.port}>

 this.element = node} className="modal-dialog h-100 mw-100 m-0">
            {(this.state.message || this.state.files.length) ? (

                {this.state.files.length > 0 && (

                      <FileDownloadPanel files={this.state.files} onClickFile={() => this.handleClickFile()} />

                )}

                    <ContentSandbox value={this.state.message} />

                {this.state.signer && (

                      {this.signatureStatus()}
                      <Alert type="info" className="my-2 flex-shrink-1">

                        {this.state.signer.keyDetails.name} {`<${this.state.signer.keyDetails.email}> #${this.state.signer.keyId ? this.state.signer.keyId.toUpperCase() : this.state.signer.keyDetails.keyId.toUpperCase()}`}
                      </Alert>

                )}

            ) : (
              <div className={`locked ${this.state.large ? 'large' : ''} modal-content overflow-hidden shadow-lg border-0 h-100 ${(!this.state.showError && !this.state.waiting) ? 'cursor' : ''}`} onClick={() => this.handleDecrypt()}>

                {this.state.waiting ? (
                  <Spinner />
                ) : (

{l10n.map.decrypt_show_message_btn}

                )}

            )}

        {this.state.pwdDialog && <iframe className="decrypt-popup-pwd-dialog modal-content" src={`../enter-password/passwordDialog.html?id=${this.state.pwdDialog.id}`} frameBorder={0} />}
        {this.state.error &&

            <Toast isOpen={this.state.showError} header={this.state.error.header} toggle={() => this.setState(prevState => ({showError: !prevState.showError}))} type="error" transition={{timeout: 150, unmountOnExit: true, onExited: () => this.handleCancel()}}>
              {this.state.error.message}
            </Toast>

        }
        {this.state.pwdDialog &&

}
        {this.state.terminate && <Terminate />}
      </SecurityBG>
    );
  }
}

DecryptMessage.propTypes = {
  id: PropTypes.string,
  embedded: PropTypes.bool
};
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import debounce from 'lodash/function/debounce';
import ComponentBaseBehaviour from '../../../behaviours/component-base';
import MDBehaviour from '../../../behaviours/material';

const ENTER_KEY_CODE = 13;
const TAB_KEY_CODE = 27;
const UP_ARROW_KEY_CODE = 38;
const DOWN_ARROW_KEY_CODE = 40;

@MDBehaviour('loader')
@MDBehaviour('inputText')
@ComponentBaseBehaviour
class Autocomplete extends Component {
    static propTypes = {
        customError: PropTypes.string,
        inputTimeout: PropTypes.number.isRequired,
        keyName: PropTypes.string.isRequired,
        keyResolver: PropTypes.func.isRequired,
        labelName: PropTypes.string.isRequired,
        onBadInput: PropTypes.func,
        onChange: PropTypes.func.isRequired,
        placeholder: PropTypes.string,
        querySearcher: PropTypes.func.isRequired,
        renderOptions: PropTypes.func,
        value: PropTypes.string
    };

    static defaultProps = {
        keyName: 'key',
        labelName: 'label',
        inputTimeout: 200
    };

    state = {
        focus: false,
        inputValue: this.props.value,
        options: new Map(),
        active: null,
        selected: this.props.value,
        fromKeyResolver: false,
        isLoading: false,
        customError: this.props.customError,
        totalCount: 0
    };

    componentDidMount() {
        const {value, keyResolver, inputTimeout} = this.props;
        if (value !== undefined && value !== null) { // value is defined, call the keyResolver to get the associated label
            keyResolver(value).then(inputValue => {
                this.setState({inputValue, fromKeyResolver: true});
            }).catch(error => this.setState({customError: error.message}));
        }
        document.addEventListener('click', this._handleDocumentClick);
        this._debouncedQuerySearcher = debounce(this._querySearcher, inputTimeout);
    }

    componentWillReceiveProps({value, customError}) {
        const {keyResolver} = this.props;
        if (value !== this.props.value && value !== undefined && value !== null) { // value is defined, call the keyResolver to get the associated label
            this.setState({inputValue: value, customError}, () => keyResolver(value).then(inputValue => {
                this.setState({inputValue, fromKeyResolver: true});
            }).catch(error => this.setState({customError: error.message})));
        } else if (customError !== this.props.customError) {
            this.setState({customError});
        }
    }

    componentDidUpdate() {
        if (this.props.customError) {
            this.refs.inputText.classList.add('is-invalid');
        } else {
            this.refs.inputText.classList.remove('is-invalid');
        }
    }

    componentWillUnmount() {
        document.removeEventListener('click', this._handleDocumentClick);
    }

    getValue() {
        const {labelName, keyName, value} = this.props;
        const {inputValue, selected, options, fromKeyResolver} = this.state;
        const resolvedLabel = options.get(selected);
        if (inputValue === '') { // The user cleared the field, return a null
            return null;
        } else if (fromKeyResolver) { // Value was received from the keyResolver, give it firectly
            return value;
        } else if (resolvedLabel !== inputValue) { // The user typed something without selecting any option, return a null
            return null;
        } else { // The user selected an option (or no value was provided), return it
            return selected || null;
        }
    }

    _handleDocumentClick = ({target}) => {
        const {focus, inputValue} = this.state;
        const {onBadInput} = this.props;
        if (focus) {
            const dataset = target ? target.dataset: null;
            const reactid = dataset ? dataset.reactid : null;
            const [options, input] = ['options', 'htmlInput'].map(ref => ReactDOM.findDOMNode(this.refs[ref]));
            const optionsId = options ? options.dataset.reactid : null;
            const inputId = input ? input.dataset.reactid : null;
            if (reactid && optionsId && inputId && !reactid.indexOf(optionsId) !== -1 && !reactid.indexOf(inputId) !== -1) {
                this.setState({focus: false}, () => {
                    if (onBadInput && this.getValue() === null && inputValue !== '') {
                        onBadInput(inputValue);
                    }
                });
            }
        }
    };

    _handleQueryChange = ({target: {value}}) => {
        if (value === '') { // the user cleared the input, don't call the querySearcher
            const {onChange} = this.props;
            this.setState({inputValue: value, fromKeyResolver: false});
            if (onChange) onChange(null);
        } else {
            this.setState({inputValue: value, fromKeyResolver: false, isLoading: true});
            this._debouncedQuerySearcher(value);
        }
    };

    _querySearcher = value => {
        const {querySearcher, keyName, labelName} = this.props;
        querySearcher(value).then(({data, totalCount}) => {
            // TODO handle the incomplete option list case
            const options = new Map();
            data.forEach(item => {
                options.set(item[keyName], item[labelName]);
            });
            this.setState({options, isLoading: false, totalCount});
        }).catch(error => this.setState({customError: error.message}));
    };

    _handleQueryFocus = () => {
        this.refs.options.scrollTop = 0;
        this.setState({active: '', focus: true});
    };

    _handleQueryKeyDown = (event) => {
        event.stopPropagation();
        const {which} = event;
        const {active, options} = this.state;
        if (which === ENTER_KEY_CODE && active) this._select(active);
        if (which === TAB_KEY_CODE) this.setState({focus: false}, () => this.refs.htmlInput.blur());
        if ([DOWN_ARROW_KEY_CODE, UP_ARROW_KEY_CODE].indexOf(which) !== -1) { // the user pressed on an arrow key, change the active key
            const optionKeys = [];
            for (let key of options.keys()) {
                optionKeys.push(key);
            }
            const currentIndex = optionKeys.indexOf(active);
            let newIndex = currentIndex + (which === DOWN_ARROW_KEY_CODE ? 1 : -1);
            if (newIndex >= options.size) {
                newIndex -= options.size
            }
            if (newIndex < 0) {
                newIndex += options.size;
            }
            this.setState({active: optionKeys[newIndex]});
        }
    };

    _handleSuggestionHover = key => {
        this.setState({active: key});
    };

    _select(key) {
        const {options} = this.state;
        const {onChange, keyName, labelName} = this.props;
        const resolvedLabel = options.get(key) || '';
        this.refs.htmlInput.blur();
        this.setState({inputValue: this.i18n(resolvedLabel), selected: key, focus: false});
        if (onChange) onChange(key);
    }

    _renderOptions = () => {
        const {active, options, focus} = this.state;
        const renderedOptions = [];
        for (let [key, value] of options) {
            const isActive = active === key;
            renderedOptions.push(

                    {this.i18n(value)}

            );
        }
        return (

                {renderedOptions}

        );
    };

    render () {
        const {customError, placeholder, renderOptions, ...inputProps} = this.props;
        const {inputValue, isLoading} = this.state;
        const {_handleQueryFocus, _handleQueryKeyDown, _handleQueryChange} = this;
        return (

                <div className={`mdl-textfield mdl-js-textfield${customError ? ' is-invalid' : ''}`} data-focus='input-text' ref='inputText'>

                    <input
                        className='mdl-textfield__input'
                        {...inputProps}
                        onChange={_handleQueryChange}
                        onFocus={_handleQueryFocus}
                        onKeyDown={_handleQueryKeyDown}
                        ref='htmlInput'
                        type='text'
                        value={inputValue}
                    />
                    {this.i18n(placeholder)}
                    {customError}

                {renderOptions ? renderOptions.call(this) : this._renderOptions()}

        );
    }
}

export default Autocomplete;
<commit_msg>Add onFocus callback<commit_after>import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import debounce from 'lodash/function/debounce';
import ComponentBaseBehaviour from '../../../behaviours/component-base';
import MDBehaviour from '../../../behaviours/material';

const ENTER_KEY_CODE = 13;
const TAB_KEY_CODE = 27;
const UP_ARROW_KEY_CODE = 38;
const DOWN_ARROW_KEY_CODE = 40;

@MDBehaviour('loader')
@MDBehaviour('inputText')
@ComponentBaseBehaviour
class Autocomplete extends Component {
    static propTypes = {
        customError: PropTypes.string,
        inputTimeout: PropTypes.number.isRequired,
        keyName: PropTypes.string.isRequired,
        keyResolver: PropTypes.func.isRequired,
        labelName: PropTypes.string.isRequired,
        onBadInput: PropTypes.func,
        onChange: PropTypes.func.isRequired,
        placeholder: PropTypes.string,
        querySearcher: PropTypes.func.isRequired,
        renderOptions: PropTypes.func,
        value: PropTypes.string
    };

    static defaultProps = {
        keyName: 'key',
        labelName: 'label',
        inputTimeout: 200
    };

    state = {
        focus: false,
        inputValue: this.props.value,
        options: new Map(),
        active: null,
        selected: this.props.value,
        fromKeyResolver: false,
        isLoading: false,
        customError: this.props.customError,
        totalCount: 0
    };

    componentDidMount() {
        const {value, keyResolver, inputTimeout} = this.props;
        if (value !== undefined && value !== null) { // value is defined, call the keyResolver to get the associated label
            keyResolver(value).then(inputValue => {
                this.setState({inputValue, fromKeyResolver: true});
            }).catch(error => this.setState({customError: error.message}));
        }
        document.addEventListener('click', this._handleDocumentClick);
        this._debouncedQuerySearcher = debounce(this._querySearcher, inputTimeout);
    }

    componentWillReceiveProps({value, customError}) {
        const {keyResolver} = this.props;
        if (value !== this.props.value && value !== undefined && value !== null) { // value is defined, call the keyResolver to get the associated label
            this.setState({inputValue: value, customError}, () => keyResolver(value).then(inputValue => {
                this.setState({inputValue, fromKeyResolver: true});
            }).catch(error => this.setState({customError: error.message})));
        } else if (customError !== this.props.customError) {
            this.setState({customError});
        }
    }

    componentDidUpdate() {
        if (this.props.customError) {
            this.refs.inputText.classList.add('is-invalid');
        } else {
            this.refs.inputText.classList.remove('is-invalid');
        }
    }

    componentWillUnmount() {
        document.removeEventListener('click', this._handleDocumentClick);
    }

    getValue() {
        const {labelName, keyName, value} = this.props;
        const {inputValue, selected, options, fromKeyResolver} = this.state;
        const resolvedLabel = options.get(selected);
        if (inputValue === '') { // The user cleared the field, return a null
            return null;
        } else if (fromKeyResolver) { // Value was received from the keyResolver, give it firectly
            return value;
        } else if (resolvedLabel !== inputValue) { // The user typed something without selecting any option, return a null
            return null;
        } else { // The user selected an option (or no value was provided), return it
            return selected || null;
        }
    }

    _handleDocumentClick = ({target}) => {
        const {focus, inputValue} = this.state;
        const {onBadInput} = this.props;
        if (focus) {
            const dataset = target ? target.dataset: null;
            const reactid = dataset ? dataset.reactid : null;
            const [options, input] = ['options', 'htmlInput'].map(ref => ReactDOM.findDOMNode(this.refs[ref]));
            const optionsId = options ? options.dataset.reactid : null;
            const inputId = input ? input.dataset.reactid : null;
            if (reactid && optionsId && inputId && !reactid.indexOf(optionsId) !== -1 && !reactid.indexOf(inputId) !== -1) {
                this.setState({focus: false}, () => {
                    if (onBadInput && this.getValue() === null && inputValue !== '') {
                        onBadInput(inputValue);
                    }
                });
            }
        }
    };

    _handleQueryChange = ({target: {value}}) => {
        if (value === '') { // the user cleared the input, don't call the querySearcher
            const {onChange} = this.props;
            this.setState({inputValue: value, fromKeyResolver: false});
            if (onChange) onChange(null);
        } else {
            this.setState({inputValue: value, fromKeyResolver: false, isLoading: true});
            this._debouncedQuerySearcher(value);
        }
    };

    _querySearcher = value => {
        const {querySearcher, keyName, labelName} = this.props;
        querySearcher(value).then(({data, totalCount}) => {
            // TODO handle the incomplete option list case
            const options = new Map();
            data.forEach(item => {
                options.set(item[keyName], item[labelName]);
            });
            this.setState({options, isLoading: false, totalCount});
        }).catch(error => this.setState({customError: error.message}));
    };

    _handleQueryFocus = () => {
        this.refs.options.scrollTop = 0;
        if (this.props.onFocus) {
            this.props.onFocus.call(this);
        }
        this.setState({active: '', focus: true});
    };

    _handleQueryKeyDown = (event) => {
        event.stopPropagation();
        const {which} = event;
        const {active, options} = this.state;
        if (which === ENTER_KEY_CODE && active) this._select(active);
        if (which === TAB_KEY_CODE) this.setState({focus: false}, () => this.refs.htmlInput.blur());
        if ([DOWN_ARROW_KEY_CODE, UP_ARROW_KEY_CODE].indexOf(which) !== -1) { // the user pressed on an arrow key, change the active key
            const optionKeys = [];
            for (let key of options.keys()) {
                optionKeys.push(key);
            }
            const currentIndex = optionKeys.indexOf(active);
            let newIndex = currentIndex + (which === DOWN_ARROW_KEY_CODE ? 1 : -1);
            if (newIndex >= options.size) {
                newIndex -= options.size
            }
            if (newIndex < 0) {
                newIndex += options.size;
            }
            this.setState({active: optionKeys[newIndex]});
        }
    };

    _handleSuggestionHover = key => {
        this.setState({active: key});
    };

    _select(key) {
        const {options} = this.state;
        const {onChange, keyName, labelName} = this.props;
        const resolvedLabel = options.get(key) || '';
        this.refs.htmlInput.blur();
        this.setState({inputValue: this.i18n(resolvedLabel), selected: key, focus: false});
        if (onChange) onChange(key);
    }

    _renderOptions = () => {
        const {active, options, focus} = this.state;
        const renderedOptions = [];
        for (let [key, value] of options) {
            const isActive = active === key;
            renderedOptions.push(

                    {this.i18n(value)}

            );
        }
        return (

                {renderedOptions}

        );
    };

    render () {
        const {customError, placeholder, renderOptions, ...inputProps} = this.props;
        const {inputValue, isLoading} = this.state;
        const {_handleQueryFocus, _handleQueryKeyDown, _handleQueryChange} = this;
        return (

                <div className={`mdl-textfield mdl-js-textfield${customError ? ' is-invalid' : ''}`} data-focus='input-text' ref='inputText'>

                    <input
                        className='mdl-textfield__input'
                        {...inputProps}
                        onChange={_handleQueryChange}
                        onFocus={_handleQueryFocus}
                        onKeyDown={_handleQueryKeyDown}
                        ref='htmlInput'
                        type='text'
                        value={inputValue}
                    />
                    {this.i18n(placeholder)}
                    {customError}

                {renderOptions ? renderOptions.call(this) : this._renderOptions()}

        );
    }
}

export default Autocomplete;
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import ComponentBaseBehaviour from '../../../behaviours/component-base';
import MDBehaviour from '../../../behaviours/material';
import filterProps from '../../../utils/filter-html-attributes';
import closest from 'closest';
import debounce from 'lodash/function/debounce';
import uniqueId from 'lodash/utility/uniqueId';

const ENTER_KEY_CODE = 13;
const TAB_KEY_CODE = 27;
const UP_ARROW_KEY_CODE = 38;
const DOWN_ARROW_KEY_CODE = 40;

const propTypes = {
    customError: PropTypes.string,
    inputTimeout: PropTypes.number.isRequired,
    keyName: PropTypes.string.isRequired,
    keyResolver: PropTypes.func.isRequired,
    labelName: PropTypes.string.isRequired,
    onBadInput: PropTypes.func,
    onChange: PropTypes.func.isRequired,
    placeholder: PropTypes.string,
    querySearcher: PropTypes.func.isRequired,
    renderOptions: PropTypes.func,
    value: PropTypes.string,
    onSelectClear: PropTypes.bool,
    clearOnNullValue: PropTypes.bool
};

const defaultProps = {
    keyName: 'key',
    labelName: 'label',
    inputTimeout: 200,
    onSelectClear: false,
    clearOnNullValue: true
};

@MDBehaviour('loader')
@MDBehaviour('inputText')
@ComponentBaseBehaviour
class Autocomplete extends Component {
    constructor(props) {
        super(props);
        const state = {
            focus: false,
            inputValue: this.props.value,
            options: new Map(),
            active: null,
            selected: this.props.value,
            fromKeyResolver: false,
            isLoading: false,
            customError: this.props.customError,
            totalCount: 0
        };
        this.state = state;
        this.autocompleteId = uniqueId('autocomplete-text-');
    }

    componentDidMount() {
        const {value, keyResolver, inputTimeout} = this.props;
        if (value !== undefined && value !== null) { // value is defined, call the keyResolver to get the associated label
            keyResolver(value).then(inputValue => {
                this.setState({inputValue, fromKeyResolver: true});
            }).catch(error => this.setState({customError: error.message}));
        }
        document.addEventListener('click', this._handleDocumentClick);
        this._debouncedQuerySearcher = debounce(this._querySearcher, inputTimeout);
    }

    componentWillReceiveProps({value, customError, error}) {
        const {keyResolver} = this.props;
        if (value !== this.props.value && value !== undefined && value !== null) { // value is defined, call the keyResolver to get the associated label
            this.setState({inputValue: value, customError}, () => keyResolver(value).then(inputValue => {
                this.setState({inputValue, fromKeyResolver: true});
            }).catch(error => this.setState({customError: error.message})));
        } else if (customError !== this.props.customError) {
            this.setState({customError});
        }

        if (error) {
            this.setState({customError: error});
        }

        if (this.props.clearOnNullValue && this.props.clearOnNullValue === true && value === null && this.state.inputValue !== null) {
            this.setState({inputValue: ''});
        }
    }

    componentDidUpdate() {
        if (this.props.customError) {
            this.refs.inputText.classList.add('is-invalid');
        } else {
            this.refs.inputText.classList.remove('is-invalid');
        }
    }

    componentWillUnmount() {
        document.removeEventListener('click', this._handleDocumentClick);
    }

    getValue() {
        const {labelName, keyName, value} = this.props;
        const {inputValue, selected, options, fromKeyResolver} = this.state;
        const resolvedLabel = options.get(selected);
        if (inputValue === '') { // The user cleared the field, return a null
            return null;
        } else if (fromKeyResolver) { // Value was received from the keyResolver, give it firectly
            return value;
        } else if (resolvedLabel !== inputValue && selected !== inputValue) { // The user typed something without selecting any option, return a null
            return null;
        } else { // The user selected an option (or no value was provided), return it
            return selected || null;
        }
    }

    _handleDocumentClick = ({target}) => {
        const {focus, inputValue} = this.state;
        const {onBadInput} = this.props;
        if (focus) {
            const closestACParent = closest(target, `[data-id='${this.autocompleteId}']`, true);
            if (closestACParent === undefined) {
                this.setState({focus: false}, () => {
                    if (onBadInput && this.getValue() === null && inputValue !== '') {
                        onBadInput(inputValue);
                    }
                });
            }
        }
    };

    _handleQueryChange = ({target: {value}}) => {
        if (value === '') { // the user cleared the input, don't call the querySearcher
            const {onChange} = this.props;
            this.setState({inputValue: value, fromKeyResolver: false});
            if (onChange) onChange(null);
        } else {
            this.setState({inputValue: value, fromKeyResolver: false, isLoading: true});
            this._debouncedQuerySearcher(value);
        }
    };

    _querySearcher = value => {
        const {querySearcher, keyName, labelName} = this.props;
        querySearcher(value).then(({data, totalCount}) => {
            // TODO handle the incomplete option list case
            const options = new Map();
            data.forEach(item => {
                options.set(item[keyName], item[labelName]);
            });
            this.setState({options, isLoading: false, totalCount});
        }).catch(error => this.setState({customError: error.message}));
    };

    _handleQueryFocus = () => {
        this.refs.options.scrollTop = 0;
        if (this.props.onFocus) {
            this.props.onFocus.call(this);
        }
        this.setState({active: '', focus: true});
    };

    _handleQueryKeyDown = (event) => {
        event.stopPropagation();
        const {which} = event;
        const {active, options} = this.state;
        if (which === ENTER_KEY_CODE && active) this._select(active);
        if (which === TAB_KEY_CODE) this.setState({focus: false}, () => this.refs.htmlInput.blur());
        if ([DOWN_ARROW_KEY_CODE, UP_ARROW_KEY_CODE].indexOf(which) !== -1) { // the user pressed on an arrow key, change the active key
            const optionKeys = [];
            for (let key of options.keys()) {
                optionKeys.push(key);
            }
            const currentIndex = optionKeys.indexOf(active);
            let newIndex = currentIndex + (which === DOWN_ARROW_KEY_CODE ? 1 : -1);
            if (newIndex >= options.size) {
                newIndex -= options.size
            }
            if (newIndex < 0) {
                newIndex += options.size;
            }
            this.setState({active: optionKeys[newIndex]});
        }
    };

    _handleSuggestionHover = key => {
        this.setState({active: key});
    };

    _select(key) {
        const {options} = this.state;
        const {onChange} = this.props;
        const resolvedLabel = options.get(key) || '';
        this.refs.htmlInput.blur();
        let newState = {inputValue: this.i18n(resolvedLabel), selected: key, focus: false};
        if (this.props.onSelectClear && this.props.onSelectClear === true) {
            newState = {inputValue: null, selected: null, focus: false};
        }
        this.setState(newState, () => {
            if (onChange) {
                onChange(key);
            }
        });
    }

    _renderOptions = () => {
        const {active, options, focus} = this.state;
        const renderedOptions = [];
        for (let [key, value] of options) {
            const isActive = active === key;
            renderedOptions.push(

                    {this.i18n(value)}

            );
        }
        return (

                {renderedOptions}

        );
    };

    render () {
        const {inputValue, isLoading} = this.state;
        const {customError, renderOptions} = this.props;

        const validInputProps = filterProps(this.props);

        const { placeholder } = validInputProps;

        validInputProps.value = inputValue;
        validInputProps.onFocus = this._handleQueryFocus;
        validInputProps.onKeyDown = this._handleQueryKeyDown;
        validInputProps.onChange = this._handleQueryChange;

        const inputProps = {...validInputProps};

        return (

                <div className={`mdl-textfield mdl-js-textfield${customError ? ' is-invalid' : ''}`} data-focus='input-text' ref='inputText'>

                    <input
                        className='mdl-textfield__input'
                        {...inputProps}
                        ref='htmlInput'
                        type='text'
                    />
                    {this.i18n(placeholder)}
                    {this.i18n(customError)}

                {renderOptions ? renderOptions.call(this) : this._renderOptions()}

        );
    }
}

Autocomplete.displayName = 'Autocomplete';
Autocomplete.defaultProps = defaultProps;
Autocomplete.propTypes = propTypes;

export default Autocomplete;
<commit_msg>Correcting input value in cas of onSelectClear<commit_after>import React, {Component, PropTypes} from 'react';
import ComponentBaseBehaviour from '../../../behaviours/component-base';
import MDBehaviour from '../../../behaviours/material';
import filterProps from '../../../utils/filter-html-attributes';
import closest from 'closest';
import debounce from 'lodash/function/debounce';
import uniqueId from 'lodash/utility/uniqueId';

const ENTER_KEY_CODE = 13;
const TAB_KEY_CODE = 27;
const UP_ARROW_KEY_CODE = 38;
const DOWN_ARROW_KEY_CODE = 40;

const propTypes = {
    customError: PropTypes.string,
    inputTimeout: PropTypes.number.isRequired,
    keyName: PropTypes.string.isRequired,
    keyResolver: PropTypes.func.isRequired,
    labelName: PropTypes.string.isRequired,
    onBadInput: PropTypes.func,
    onChange: PropTypes.func.isRequired,
    placeholder: PropTypes.string,
    querySearcher: PropTypes.func.isRequired,
    renderOptions: PropTypes.func,
    value: PropTypes.string,
    onSelectClear: PropTypes.bool,
    clearOnNullValue: PropTypes.bool
};

const defaultProps = {
    keyName: 'key',
    labelName: 'label',
    inputTimeout: 200,
    onSelectClear: false,
    clearOnNullValue: true
};

@MDBehaviour('loader')
@MDBehaviour('inputText')
@ComponentBaseBehaviour
class Autocomplete extends Component {
    constructor(props) {
        super(props);
        const state = {
            focus: false,
            inputValue: this.props.value,
            options: new Map(),
            active: null,
            selected: this.props.value,
            fromKeyResolver: false,
            isLoading: false,
            customError: this.props.customError,
            totalCount: 0
        };
        this.state = state;
        this.autocompleteId = uniqueId('autocomplete-text-');
    }

    componentDidMount() {
        const {value, keyResolver, inputTimeout} = this.props;
        if (value !== undefined && value !== null) { // value is defined, call the keyResolver to get the associated label
            keyResolver(value).then(inputValue => {
                this.setState({inputValue, fromKeyResolver: true});
            }).catch(error => this.setState({customError: error.message}));
        }
        document.addEventListener('click', this._handleDocumentClick);
        this._debouncedQuerySearcher = debounce(this._querySearcher, inputTimeout);
    }

    componentWillReceiveProps({value, customError, error}) {
        const {keyResolver} = this.props;
        if (value !== this.props.value && value !== undefined && value !== null) { // value is defined, call the keyResolver to get the associated label
            this.setState({inputValue: value, customError}, () => keyResolver(value).then(inputValue => {
                this.setState({inputValue, fromKeyResolver: true});
            }).catch(error => this.setState({customError: error.message})));
        } else if (customError !== this.props.customError) {
            this.setState({customError});
        }

        if (error) {
            this.setState({customError: error});
        }

        if (this.props.clearOnNullValue && this.props.clearOnNullValue === true && value === null && this.state.inputValue !== null) {
            this.setState({inputValue: ''});
        }
    }

    componentDidUpdate() {
        if (this.props.customError) {
            this.refs.inputText.classList.add('is-invalid');
        } else {
            this.refs.inputText.classList.remove('is-invalid');
        }
    }

    componentWillUnmount() {
        document.removeEventListener('click', this._handleDocumentClick);
    }

    getValue() {
        const {labelName, keyName, value} = this.props;
        const {inputValue, selected, options, fromKeyResolver} = this.state;
        const resolvedLabel = options.get(selected);
        if (inputValue === '') { // The user cleared the field, return a null
            return null;
        } else if (fromKeyResolver) { // Value was received from the keyResolver, give it firectly
            return value;
        } else if (resolvedLabel !== inputValue && selected !== inputValue) { // The user typed something without selecting any option, return a null
            return null;
        } else { // The user selected an option (or no value was provided), return it
            return selected || null;
        }
    }

    _handleDocumentClick = ({target}) => {
        const {focus, inputValue} = this.state;
        const {onBadInput} = this.props;
        if (focus) {
            const closestACParent = closest(target, `[data-id='${this.autocompleteId}']`, true);
            if (closestACParent === undefined) {
                this.setState({focus: false}, () => {
                    if (onBadInput && this.getValue() === null && inputValue !== '') {
                        onBadInput(inputValue);
                    }
                });
            }
        }
    };

    _handleQueryChange = ({target: {value}}) => {
        if (value === '') { // the user cleared the input, don't call the querySearcher
            const {onChange} = this.props;
            this.setState({inputValue: value, fromKeyResolver: false});
            if (onChange) onChange(null);
        } else {
            this.setState({inputValue: value, fromKeyResolver: false, isLoading: true});
            this._debouncedQuerySearcher(value);
        }
    };

    _querySearcher = value => {
        const {querySearcher, keyName, labelName} = this.props;
        querySearcher(value).then(({data, totalCount}) => {
            // TODO handle the incomplete option list case
            const options = new Map();
            data.forEach(item => {
                options.set(item[keyName], item[labelName]);
            });
            this.setState({options, isLoading: false, totalCount});
        }).catch(error => this.setState({customError: error.message}));
    };

    _handleQueryFocus = () => {
        this.refs.options.scrollTop = 0;
        if (this.props.onFocus) {
            this.props.onFocus.call(this);
        }
        this.setState({active: '', focus: true});
    };

    _handleQueryKeyDown = (event) => {
        event.stopPropagation();
        const {which} = event;
        const {active, options} = this.state;
        if (which === ENTER_KEY_CODE && active) this._select(active);
        if (which === TAB_KEY_CODE) this.setState({focus: false}, () => this.refs.htmlInput.blur());
        if ([DOWN_ARROW_KEY_CODE, UP_ARROW_KEY_CODE].indexOf(which) !== -1) { // the user pressed on an arrow key, change the active key
            const optionKeys = [];
            for (let key of options.keys()) {
                optionKeys.push(key);
            }
            const currentIndex = optionKeys.indexOf(active);
            let newIndex = currentIndex + (which === DOWN_ARROW_KEY_CODE ? 1 : -1);
            if (newIndex >= options.size) {
                newIndex -= options.size
            }
            if (newIndex < 0) {
                newIndex += options.size;
            }
            this.setState({active: optionKeys[newIndex]});
        }
    };

    _handleSuggestionHover = key => {
        this.setState({active: key});
    };

    _select(key) {
        const {options} = this.state;
        const {onChange} = this.props;
        const resolvedLabel = options.get(key) || '';
        this.refs.htmlInput.blur();
        let newState = {inputValue: this.i18n(resolvedLabel), selected: key, focus: false};
        if (this.props.onSelectClear === true) {
            newState = {inputValue: '', selected: null, focus: false};
        }
        this.setState(newState, () => {
            if (onChange) {
                onChange(key);
            }
        });
    }

    _renderOptions = () => {
        const {active, options, focus} = this.state;
        const renderedOptions = [];
        for (let [key, value] of options) {
            const isActive = active === key;
            renderedOptions.push(

                    {this.i18n(value)}

            );
        }
        return (

                {renderedOptions}

        );
    };

    render () {
        const {inputValue, isLoading} = this.state;
        const {customError, renderOptions} = this.props;

        const validInputProps = filterProps(this.props);

        const { placeholder } = validInputProps;

        validInputProps.value = inputValue;
        validInputProps.onFocus = this._handleQueryFocus;
        validInputProps.onKeyDown = this._handleQueryKeyDown;
        validInputProps.onChange = this._handleQueryChange;

        const inputProps = {...validInputProps};

        return (

                <div className={`mdl-textfield mdl-js-textfield${customError ? ' is-invalid' : ''}`} data-focus='input-text' ref='inputText'>

                    <input
                        className='mdl-textfield__input'
                        {...inputProps}
                        ref='htmlInput'
                        type='text'
                    />
                    {this.i18n(placeholder)}
                    {this.i18n(customError)}

                {renderOptions ? renderOptions.call(this) : this._renderOptions()}

        );
    }
}

Autocomplete.displayName = 'Autocomplete';
Autocomplete.defaultProps = defaultProps;
Autocomplete.propTypes = propTypes;

export default Autocomplete;
<|endoftext|>


<commit_before>import PropTypes from 'prop-types'
import React from 'react'
import CDGCanvas from './CDGCanvas'
import classes from './CDGPlayer.css'

class CDGPlayer extends React.Component {
  static propTypes = {
    width: PropTypes.number.isRequired,
    height: PropTypes.number.isRequired,
    volume: PropTypes.number.isRequired,
    isPlaying: PropTypes.bool.isRequired,
    song: PropTypes.object.isRequired,
    getMedia: PropTypes.func.isRequired, // action
    getMediaSuccess: PropTypes.func.isRequired, // action
    onMediaError: PropTypes.func.isRequired, // action
    onMediaEnd: PropTypes.func.isRequired, // action
    onStatus: PropTypes.func.isRequired, // action
  }

  state = {
    audioPos: 0, // ms
  }

  isAudioLoaded = false
  isCDGLoaded = false
  cdgData = []

  componentDidMount () {
    this.updateSources()
    this.setVolume(this.props.volume)
  }

  componentDidUpdate (prevProps) {
    if (prevProps.song !== this.props.song) {
      this.updateSources()
    }

    if (prevProps.isPlaying !== this.props.isPlaying) {
      this.updateIsPlaying()
    }

    if (prevProps.volume !== this.props.volume) {
      this.setVolume(this.props.volume)
    }
  }

  render () {
    const { width, height } = this.props
    let canvasScale = Math.floor(width / 300)

    // make sure height would fit the viewport's
    while (height < canvasScale * 300 * 0.72) {
      canvasScale -= 1
    }

    return (

        <CDGCanvas
          width={canvasScale * 300}
          height={canvasScale * 300 * 0.72}
          isPlaying={this.props.isPlaying}
          audioPos={this.state.audioPos}
          cdgData={this.cdgData}
        />

        <audio src={'/api/provider/cdg/resource?type=audio&songId=' + this.props.song.songId}
          preload='none'
          onCanPlayThrough={this.handleOnCanPlayThrough}
          onTimeUpdate={this.handleOnTimeUpdate}
          onEnded={this.props.onMediaEnd}
          onError={this.handleAudioError}
          ref={(c) => { this.audio = c }}
        />

    )
  }

  updateSources = () => {
    // notification
    this.props.getMedia(this.audio.src)

    // tell audio element its source updated
    this.audio.load()

    // get cdgData
    const url = '/api/provider/cdg/resource?type=cdg&songId=' + this.props.song.songId

    // notification
    this.props.getMedia(this.url)

    fetch(url, fetchConfig)
      .then(checkStatus)
      .then(res => res.arrayBuffer())
      .then(res => {
        // arrayBuffer to Uint8Array to standard Array
        this.cdgData = Array.from(new Uint8Array(res))
      }).then(() => { this.handleOnCdgLoaded() })
      .catch((err) => {
        this.props.onMediaError(err.message)
      })
  }

  setVolume (vol) {
    this.audio.volume = vol
  }

/**
 * <audio> event handlers
 */
  updateIsPlaying = () => {
    if (this.props.isPlaying) {
      this.audio.play()
    } else {
      this.audio.pause()
    }
  }

  handleOnCanPlayThrough = () => {
    this.isAudioLoaded = true

    if (this.isCDGLoaded) {
      this.props.getMediaSuccess()
      this.updateIsPlaying()
    }
  }

  handleOnTimeUpdate = () => {
    this.setState({
      audioPos: this.audio.currentTime * 1000
    })

    this.props.onStatus({
      isPlaying: !this.audio.paused,
      position: this.audio.currentTime,
      volume: this.audio.volume,
    })
  }

  handleAudioError = (err) => {
    this.props.onMediaError('Could not load audio (error ' + err.target.error.code + ')')
  }

  /**
   * CDGPlayer event handlers
   */
  handleOnCdgLoaded = () => {
    this.isCDGLoaded = true

    if (this.isAudioLoaded) {
      this.props.getMediaSuccess()
      this.updateIsPlaying()
    }
  }
}

export default CDGPlayer

// helpers for fetch response
const fetchConfig = {
  headers: new Headers({
    'Content-Type': 'application/json'
  }),
  // include the cookie that contains our JWT
  credentials: 'same-origin'
}

function checkStatus (response) {
  if (response.status >= 200 && response.status < 300) {
    return response
  } else {
    return response.text().then((txt) => {
      var error = new Error(txt)
      error.response = response
      throw error
    })
  }
}
<commit_msg>Queue item object references can change mid-song; blarg<commit_after>import PropTypes from 'prop-types'
import React from 'react'
import CDGCanvas from './CDGCanvas'
import classes from './CDGPlayer.css'

class CDGPlayer extends React.Component {
  static propTypes = {
    width: PropTypes.number.isRequired,
    height: PropTypes.number.isRequired,
    volume: PropTypes.number.isRequired,
    isPlaying: PropTypes.bool.isRequired,
    song: PropTypes.object.isRequired,
    getMedia: PropTypes.func.isRequired, // action
    getMediaSuccess: PropTypes.func.isRequired, // action
    onMediaError: PropTypes.func.isRequired, // action
    onMediaEnd: PropTypes.func.isRequired, // action
    onStatus: PropTypes.func.isRequired, // action
  }

  state = {
    audioPos: 0, // ms
  }

  isAudioLoaded = false
  isCDGLoaded = false
  cdgData = []

  componentDidMount () {
    this.updateSources()
    this.setVolume(this.props.volume)
  }

  componentDidUpdate (prevProps) {
    if (prevProps.song.songId !== this.props.song.songId) {
      this.updateSources()
    }

    if (prevProps.isPlaying !== this.props.isPlaying) {
      this.updateIsPlaying()
    }

    if (prevProps.volume !== this.props.volume) {
      this.setVolume(this.props.volume)
    }
  }

  render () {
    const { width, height } = this.props
    let canvasScale = Math.floor(width / 300)

    // make sure height would fit the viewport's
    while (height < canvasScale * 300 * 0.72) {
      canvasScale -= 1
    }

    return (

        <CDGCanvas
          width={canvasScale * 300}
          height={canvasScale * 300 * 0.72}
          isPlaying={this.props.isPlaying}
          audioPos={this.state.audioPos}
          cdgData={this.cdgData}
        />

        <audio src={'/api/provider/cdg/resource?type=audio&songId=' + this.props.song.songId}
          preload='none'
          onCanPlayThrough={this.handleOnCanPlayThrough}
          onTimeUpdate={this.handleOnTimeUpdate}
          onEnded={this.props.onMediaEnd}
          onError={this.handleAudioError}
          ref={(c) => { this.audio = c }}
        />

    )
  }

  updateSources = () => {
    // notification
    this.props.getMedia(this.audio.src)

    // tell audio element its source updated
    this.audio.load()

    // get cdgData
    const url = '/api/provider/cdg/resource?type=cdg&songId=' + this.props.song.songId

    // notification
    this.props.getMedia(this.url)

    fetch(url, fetchConfig)
      .then(checkStatus)
      .then(res => res.arrayBuffer())
      .then(res => {
        // arrayBuffer to Uint8Array to standard Array
        this.cdgData = Array.from(new Uint8Array(res))
      }).then(() => { this.handleOnCdgLoaded() })
      .catch((err) => {
        this.props.onMediaError(err.message)
      })
  }

  setVolume (vol) {
    this.audio.volume = vol
  }

/**
 * <audio> event handlers
 */
  updateIsPlaying = () => {
    if (this.props.isPlaying) {
      this.audio.play()
    } else {
      this.audio.pause()
    }
  }

  handleOnCanPlayThrough = () => {
    this.isAudioLoaded = true

    if (this.isCDGLoaded) {
      this.props.getMediaSuccess()
      this.updateIsPlaying()
    }
  }

  handleOnTimeUpdate = () => {
    this.setState({
      audioPos: this.audio.currentTime * 1000
    })

    this.props.onStatus({
      isPlaying: !this.audio.paused,
      position: this.audio.currentTime,
      volume: this.audio.volume,
    })
  }

  handleAudioError = (err) => {
    this.props.onMediaError('Could not load audio (error ' + err.target.error.code + ')')
  }

  /**
   * CDGPlayer event handlers
   */
  handleOnCdgLoaded = () => {
    this.isCDGLoaded = true

    if (this.isAudioLoaded) {
      this.props.getMediaSuccess()
      this.updateIsPlaying()
    }
  }
}

export default CDGPlayer

// helpers for fetch response
const fetchConfig = {
  headers: new Headers({
    'Content-Type': 'application/json'
  }),
  // include the cookie that contains our JWT
  credentials: 'same-origin'
}

function checkStatus (response) {
  if (response.status >= 200 && response.status < 300) {
    return response
  } else {
    return response.text().then((txt) => {
      var error = new Error(txt)
      error.response = response
      throw error
    })
  }
}
<|endoftext|>


<commit_before>define('AudioController', [
    'bufferLoader',
    'audioConfig',
    'audioService'
], function(bufferLoader, audioConfig, audioService) {
    'use strict';

    var buffer,
        context,
        timerWorkerBlobURL,
        timerWorker,
        startTime,
        tempo = audioConfig.tempo,
        matrix = audioConfig.matrix,
        loopLength = audioConfig.loopLength,
        quarterNoteTime = 60 / tempo,
        noteTime = 0.0,
        rhythmIndex = 0,
        glasbaOzadje = document.getElementById("glasba-ozadje"),
        gainNode
    ;

    return {
        init: init,
        startPlaying: startPlaying,
        stopPlaying: stopPlaying,
        setGainValue: setGainValue
    };

    function init() {
        glasbaOzadje.volume = 0.2;
        setAudioContext();
        if(context) {
            setAudioBuffer();
            setTimeWorker();
            setEffectsDefault();
        }
    }

    function startPlaying() {
        noteTime = 0.0;
        startTime = context.currentTime + 0.005;
        scheduleSounds(buffer.bufferlist);
         //tukaj mogoče z malo zamikom kliči timerWorkerpostmessage.
        timerWorker.postMessage("start");
    }

    function stopPlaying() {
        rhythmIndex = 0;
        timerWorker.postMessage("stop");
    }

    function setAudioContext() {
        try {
            context = new AudioContext();
        }
        catch(e) {
            alert('Web Audio API is not supported in this browser');
        }
    }

    function setAudioBuffer() {
        buffer = bufferLoader.getInstance(
            context,
            audioConfig.audioFiles,
            bufferLoadCompleted
        );
        buffer.load();
    }

    function setTimeWorker() {
        timerWorkerBlobURL = window.URL.createObjectURL(new Blob([
            "var timeoutID=0;function schedule(){timeoutID=setTimeout(function(){postMessage('schedule'); schedule();},10);} onmessage = function(e) { if (e.data == 'start') { if (!timeoutID) schedule();} else if (e.data == 'stop') {if (timeoutID) clearTimeout(timeoutID); timeoutID=0;};}"
        ]));

        timerWorker = new Worker(timerWorkerBlobURL);
        timerWorker.onmessage = function(e) {
            scheduleSounds();
        };
        timerWorker.postMessage('init');
    }

    function setEffectsDefault() {
        gainNode = context.createGain();
        gainNode.gain.value = 1;
    }

    function setGainValue(value) {
        gainNode.gain.value = value;
    }

    function scheduleSounds() {
            var currentTime = context.currentTime;
            currentTime -= startTime;

            while (noteTime < currentTime + 0.25) {
                var contextPlayTime = noteTime + startTime;
                for(var i=0; i < matrix.result.length; i++){
                    if(matrix.entities[matrix.result[i]][rhythmIndex]) {
                        playNote(buffer.bufferList[i], contextPlayTime);
                    }
                }
                advanceNote();
            }
    }

    function playNote(buffer, time) {
        var source = context.createBufferSource();
        source.buffer = buffer;

        source.connect(gainNode);

        gainNode.connect(context.destination);
        source.start(time);
    }

    function advanceNote() {
        var secondsPerBeat = 60.0 / tempo;

        document.querySelector('.timeline')
            .dispatchEvent(audioService.getRhytmIndexEvent(rhythmIndex));
        rhythmIndex++;
        if (rhythmIndex === loopLength) {
            rhythmIndex = 0;
        }
        noteTime += 0.50 * secondsPerBeat;
    }

    function bufferLoadCompleted() {
        console.log('buffer loaded');
    }
});
<commit_msg>fix for first animation twich<commit_after>define('AudioController', [
    'bufferLoader',
    'audioConfig',
    'audioService'
], function(bufferLoader, audioConfig, audioService) {
    'use strict';

    var buffer,
        context,
        timerWorkerBlobURL,
        timerWorker,
        startTime,
        tempo = audioConfig.tempo,
        matrix = audioConfig.matrix,
        loopLength = audioConfig.loopLength,
        quarterNoteTime = 60 / tempo,
        noteTime = 0.0,
        rhythmIndex = 0,
        glasbaOzadje = document.getElementById("glasba-ozadje"),
        gainNode
    ;

    return {
        init: init,
        startPlaying: startPlaying,
        stopPlaying: stopPlaying,
        setGainValue: setGainValue
    };

    function init() {
        glasbaOzadje.volume = 0.2;
        setAudioContext();
        if(context) {
            setAudioBuffer();
            setTimeWorker();
            setEffectsDefault();
        }
    }

    function startPlaying() {
        noteTime = 0.0;
        startTime = context.currentTime + 0.005;
        scheduleSounds(buffer.bufferlist);

        //timerWorker.postMessage("start");
        //fix for first twich
        setTimeout(function(){ timerWorker.postMessage("start"); }, 400);
    }

    function stopPlaying() {
        rhythmIndex = 0;
        timerWorker.postMessage("stop");
    }

    function setAudioContext() {
        try {
            context = new AudioContext();
        }
        catch(e) {
            alert('Web Audio API is not supported in this browser');
        }
    }

    function setAudioBuffer() {
        buffer = bufferLoader.getInstance(
            context,
            audioConfig.audioFiles,
            bufferLoadCompleted
        );
        buffer.load();
    }

    function setTimeWorker() {
        timerWorkerBlobURL = window.URL.createObjectURL(new Blob([
            "var timeoutID=0;function schedule(){timeoutID=setTimeout(function(){postMessage('schedule'); schedule();},10);} onmessage = function(e) { if (e.data == 'start') { if (!timeoutID) schedule();} else if (e.data == 'stop') {if (timeoutID) clearTimeout(timeoutID); timeoutID=0;};}"
        ]));

        timerWorker = new Worker(timerWorkerBlobURL);
        timerWorker.onmessage = function(e) {
            scheduleSounds();
        };
        timerWorker.postMessage('init');
    }

    function setEffectsDefault() {
        gainNode = context.createGain();
        gainNode.gain.value = 1;
    }

    function setGainValue(value) {
        gainNode.gain.value = value;
    }

    function scheduleSounds() {
            var currentTime = context.currentTime;
            currentTime -= startTime;

            while (noteTime < currentTime + 0.25) {
                var contextPlayTime = noteTime + startTime;
                for(var i=0; i < matrix.result.length; i++){
                    if(matrix.entities[matrix.result[i]][rhythmIndex]) {
                        playNote(buffer.bufferList[i], contextPlayTime);
                    }
                }
                advanceNote();
            }
    }

    function playNote(buffer, time) {
        var source = context.createBufferSource();
        source.buffer = buffer;

        source.connect(gainNode);

        gainNode.connect(context.destination);
        source.start(time);
    }

    function advanceNote() {
        var secondsPerBeat = 60.0 / tempo;

        document.querySelector('.timeline')
            .dispatchEvent(audioService.getRhytmIndexEvent(rhythmIndex));
        rhythmIndex++;
        if (rhythmIndex === loopLength) {
            rhythmIndex = 0;
        }
        noteTime += 0.50 * secondsPerBeat;
    }

    function bufferLoadCompleted() {
        console.log('buffer loaded');
    }
});
<|endoftext|>


<commit_before>var keystone = require('../../../');
var _ = require('underscore');
var async = require('async');

exports = module.exports = function(req, res) {

	var itemQuery = req.list.model.findById(req.params.item).select();

	itemQuery.exec(function(err, item) {

		if (err) {
			req.flash('error', 'A database error occurred.');
			return res.redirect('/keystone/' + req.list.path);
		}

		if (!item) {
			req.flash('error', 'Item ' + req.params.item + ' could not be found.');
			return res.redirect('/keystone/' + req.list.path);
		}

		var renderView = function() {

			var relationships = _.values(_.compact(_.map(req.list.relationships, function(i) {
				if (i.isValid) {
					return _.clone(i);
				} else {
					keystone.console.err('Relationship Configuration Error', 'Relationship: ' + i.path + ' on list: ' + req.list.key + ' links to an invalid list: ' + i.ref);
					return null;
				}
			})));

			async.each(relationships, function(rel, done) {

				// TODO: Handle invalid relationship config
				rel.list = keystone.list(rel.ref);
				rel.sortable = (rel.list.get('sortable') && rel.list.get('sortContext') === req.list.key + ':' + rel.path);

				// TODO: Handle relationships with more than 1 page of results
				var q = rel.list.paginate({ page: 1, perPage: 100 })
					.where(rel.refPath).equals(item.id)
					.sort(rel.list.defaultSort);

				// rel.columns = _.reject(rel.list.defaultColumns, function(col) { return (col.type == 'relationship' && col.refList == req.list) });
				rel.columns = rel.list.defaultColumns;
				rel.list.selectColumns(q, rel.columns);

				q.exec(function(err, results) {
					rel.items = results;
					done(err);
				});

			}, function(err) { //eslint-disable-line no-unused-vars, handle-callback-err

				// TODO: Handle err

				var showRelationships = _.some(relationships, function(rel) {
					return rel.items.results.length;
				});

				var appName = keystone.get('name') || 'Keystone';

				keystone.render(req, res, 'item', {
					section: keystone.nav.by.list[req.list.key] || {},
					title: appName + ': ' + req.list.singular + ': ' + req.list.getDocumentName(item),
					page: 'item',
					list: req.list,
					item: item,
					relationships: relationships,
					showRelationships: showRelationships
				});

			});

		};

		if (req.method === 'POST' && req.body.action === 'updateItem' && !req.list.get('noedit')) {

			if (!keystone.security.csrf.validate(req)) {
				console.error('CSRF failure', req.method, req.body);
				req.flash('error', 'There was a problem with your request, please try again.');
				res.status(403);
				return renderView();
			}

			item.getUpdateHandler(req).process(req.body, { flashErrors: true, logErrors: true }, function(err) {
				if (err) {
					return renderView();
				}
				req.flash('success', 'Your changes have been saved.');

				if(req.accepts('html'))
					return res.redirect('/keystone/' + req.list.path + '/' + item.id);
				else
					return res.json(item);
			});

		} else {
			renderView();
		}

	});

};
<commit_msg>Returning JSON if request doesn't accept HTML<commit_after>var keystone = require('../../../');
var _ = require('underscore');
var async = require('async');

exports = module.exports = function(req, res) {

	var itemQuery = req.list.model.findById(req.params.item).select();

	itemQuery.exec(function(err, item) {

		if (err) {
			req.flash('error', 'A database error occurred.');
			return res.redirect('/keystone/' + req.list.path);
		}

		if (!item) {
			req.flash('error', 'Item ' + req.params.item + ' could not be found.');
			return res.redirect('/keystone/' + req.list.path);
		}

		var renderView = function() {

			var relationships = _.values(_.compact(_.map(req.list.relationships, function(i) {
				if (i.isValid) {
					return _.clone(i);
				} else {
					keystone.console.err('Relationship Configuration Error', 'Relationship: ' + i.path + ' on list: ' + req.list.key + ' links to an invalid list: ' + i.ref);
					return null;
				}
			})));

			async.each(relationships, function(rel, done) {

				// TODO: Handle invalid relationship config
				rel.list = keystone.list(rel.ref);
				rel.sortable = (rel.list.get('sortable') && rel.list.get('sortContext') === req.list.key + ':' + rel.path);

				// TODO: Handle relationships with more than 1 page of results
				var q = rel.list.paginate({ page: 1, perPage: 100 })
					.where(rel.refPath).equals(item.id)
					.sort(rel.list.defaultSort);

				// rel.columns = _.reject(rel.list.defaultColumns, function(col) { return (col.type == 'relationship' && col.refList == req.list) });
				rel.columns = rel.list.defaultColumns;
				rel.list.selectColumns(q, rel.columns);

				q.exec(function(err, results) {
					rel.items = results;
					done(err);
				});

			}, function(err) { //eslint-disable-line no-unused-vars, handle-callback-err

				// TODO: Handle err

				var showRelationships = _.some(relationships, function(rel) {
					return rel.items.results.length;
				});

				var appName = keystone.get('name') || 'Keystone';

				keystone.render(req, res, 'item', {
					section: keystone.nav.by.list[req.list.key] || {},
					title: appName + ': ' + req.list.singular + ': ' + req.list.getDocumentName(item),
					page: 'item',
					list: req.list,
					item: item,
					relationships: relationships,
					showRelationships: showRelationships
				});

			});

		};

		if (req.method === 'POST' && req.body.action === 'updateItem' && !req.list.get('noedit')) {

			if (!keystone.security.csrf.validate(req)) {
				console.error('CSRF failure', req.method, req.body);
				req.flash('error', 'There was a problem with your request, please try again.');
				res.status(403);
				return renderView();
			}

			item.getUpdateHandler(req).process(req.body, { flashErrors: true, logErrors: true }, function(err) {
				if (err) {
					return renderView();
				}

				if(req.accepts('html')) {
					req.flash('success', 'Your changes have been saved.');
					return res.redirect('/keystone/' + req.list.path + '/' + item.id);
				} else {
					return res.json(item);
				}
			});

		} else {
			renderView();
		}

	});

};
<|endoftext|>


<commit_before>import { Component, PropTypes } from 'react';
import TextField from 'material-ui/TextField';
import IconButton from 'material-ui/IconButton';
import DoneIcon from 'material-ui/svg-icons/action/done';
import CancelIcon from 'material-ui/svg-icons/navigation/cancel';
import { TableRowColumn } from 'material-ui/Table';

import styles from './ArticlesTable.less';

const inlineStyles = {
  wrapWordColumn: {
    whiteSpace: 'normal',
    wordWrap: 'break-word',
    width: '200px',
    paddingLeft: '7px',
    paddingRight: '0',
  },
  tag: {
    backgroundColor: 'lightgrey',
    border: '1px solid gray',
    borderRadius: '3px',
    padding: '0 5px',
    marginRight: '7px',
  },
  tagEdit: {
    fontSize: '14px',
    lineHeight: '24px',
  },
  doneIconStyle: {
    color: '#2196F3',
  },
  cancelIconStyle: {
    color: '#f44336',
  },
};

const initialState = {
  isEditing: false,
  newTitle: null,
  newTags: [],
};

export class ArticleMainColumn extends Component {
  static propTypes = {
    read: PropTypes.string,
    tags: PropTypes.array,
    title: PropTypes.string,
    url: PropTypes.string,
    onArticleChanged: PropTypes.func,
  };

  constructor() {
    super();

    this.state = initialState;

    this.handleClick = this.handleClick.bind(this);
    this.handleTitleChange = this.handleTitleChange.bind(this);
    this.handleTagsChange = this.handleTagsChange.bind(this);
    this.handleChangeComplete = this.handleChangeComplete.bind(this);
    this.handleChangeCancel = this.handleChangeCancel.bind(this);
  }

  handleClick(e) {
    if (this.state.isEditing === true) {
      return;
    }

    this.setState({
      isEditing: true,
      newTitle: this.props.title,
      newTags: this.props.tags,
    });

    e.stopPropagation();
  }

  handleTitleChange(e, newTitle) {
    this.setState({
      newTitle,
    });
  }

  handleTagsChange(e, newTags) {
    this.setState({
      newTags: newTags !== ''
        ? newTags.
          split(',').
          map((s) => s.replace(/ /g, ''))
        : [],
    });
  }

  handleChangeComplete(e) {
    this.props.onArticleChanged({
      title: this.state.newTitle,
      tags: this.state.newTags,
    });

    this.setState(initialState);

    e.preventDefault();
  }

  handleChangeCancel() {
    this.setState(initialState);
  }

  render() {
    return (
      <TableRowColumn
        className={styles.preventCellClick}
        style={inlineStyles.wrapWordColumn}
        onClick={this.handleClick}
      >

          {this.state.isEditing === true
           ? <form onSubmit={this.handleChangeComplete}>
             <TextField
               autoFocus
               fullWidth
               id="title-field-input"
               value={this.state.newTitle}
               onChange={this.handleTitleChange}
             />
             <TextField
               hintText="tags"
               id="tags-field-input"
               style={inlineStyles.tagEdit}
               underlineShow={false}
               value={this.state.newTags.join(', ')}
               onChange={this.handleTagsChange}
             />

               <IconButton
                 iconStyle={inlineStyles.doneIconStyle}
                 type="submit"
               >
                 <DoneIcon />
               </IconButton>
               <IconButton
                 iconStyle={inlineStyles.cancelIconStyle}
                 style={{ float: 'right' }}
                 onClick={this.handleChangeCancel}
               >
                 <CancelIcon />
               </IconButton>

           </form>
           :

               {this.props.title}

             {
               this.props.tags.map((tag, i) =>
                 (
                   <span
                     key={`${tag}${i}`}
                     style={inlineStyles.tag}
                   >{tag}
                 )
               )
             }

          }

      </TableRowColumn>);
  }
}
<commit_msg>Fix old bug with entering editing mode on link click.<commit_after>import { Component, PropTypes } from 'react';
import TextField from 'material-ui/TextField';
import IconButton from 'material-ui/IconButton';
import DoneIcon from 'material-ui/svg-icons/action/done';
import CancelIcon from 'material-ui/svg-icons/navigation/cancel';
import { TableRowColumn } from 'material-ui/Table';

import styles from './ArticlesTable.less';

const inlineStyles = {
  wrapWordColumn: {
    whiteSpace: 'normal',
    wordWrap: 'break-word',
    width: '200px',
    paddingLeft: '7px',
    paddingRight: '0',
  },
  tag: {
    backgroundColor: 'lightgrey',
    border: '1px solid gray',
    borderRadius: '3px',
    padding: '0 5px',
    marginRight: '7px',
  },
  tagEdit: {
    fontSize: '14px',
    lineHeight: '24px',
  },
  doneIconStyle: {
    color: '#2196F3',
  },
  cancelIconStyle: {
    color: '#f44336',
  },
};

const initialState = {
  isEditing: false,
  newTitle: null,
  newTags: [],
};

export class ArticleMainColumn extends Component {
  static propTypes = {
    read: PropTypes.string,
    tags: PropTypes.array,
    title: PropTypes.string,
    url: PropTypes.string,
    onArticleChanged: PropTypes.func,
  };

  constructor() {
    super();

    this.state = initialState;

    this.handleClick = this.handleClick.bind(this);
    this.handleTitleChange = this.handleTitleChange.bind(this);
    this.handleTagsChange = this.handleTagsChange.bind(this);
    this.handleChangeComplete = this.handleChangeComplete.bind(this);
    this.handleChangeCancel = this.handleChangeCancel.bind(this);
  }

  handleClick() {
    if (this.state.isEditing === true) {
      return;
    }

    this.setState({
      isEditing: true,
      newTitle: this.props.title,
      newTags: this.props.tags,
    });
  }

  handleTitleChange(e, newTitle) {
    this.setState({
      newTitle,
    });
  }

  handleTagsChange(e, newTags) {
    this.setState({
      newTags: newTags !== ''
        ? newTags.
          split(',').
          map((s) => s.replace(/ /g, ''))
        : [],
    });
  }

  handleChangeComplete() {
    this.props.onArticleChanged({
      title: this.state.newTitle,
      tags: this.state.newTags,
    });

    this.setState(initialState);
  }

  handleChangeCancel() {
    this.setState(initialState);
  }

  render() {
    return (
      <TableRowColumn
        className={styles.preventCellClick}
        style={inlineStyles.wrapWordColumn}
        onClick={this.handleClick}
      >

          {this.state.isEditing === true
           ? <form onSubmit={this.handleChangeComplete}>
             <TextField
               autoFocus
               fullWidth
               id="title-field-input"
               value={this.state.newTitle}
               onChange={this.handleTitleChange}
             />
             <TextField
               hintText="tags"
               id="tags-field-input"
               style={inlineStyles.tagEdit}
               underlineShow={false}
               value={this.state.newTags.join(', ')}
               onChange={this.handleTagsChange}
             />

               <IconButton
                 iconStyle={inlineStyles.doneIconStyle}
                 type="submit"
               >
                 <DoneIcon />
               </IconButton>
               <IconButton
                 iconStyle={inlineStyles.cancelIconStyle}
                 style={{ float: 'right' }}
                 onClick={this.handleChangeCancel}
               >
                 <CancelIcon />
               </IconButton>

           </form>
           :

              e.stopPropagation()} // eslint-disable-line react/jsx-no-bind
             >
               {this.props.title}

             {
               this.props.tags.map((tag, i) =>
                 (
                   <span
                     key={`${tag}${i}`}
                     style={inlineStyles.tag}
                   >{tag}
                 )
               )
             }

          }

      </TableRowColumn>);
  }
}
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { MDXProvider } from '@mdx-js/react'
import { Box } from 'rebass'
import components from './components'
import { verticalRhythm } from './markdownStyles'

const MarkdownContent = ({ children, ...props }) => (
  <MDXProvider components={components}>
    <Box
      sx={{
        ...verticalRhythm,

        'b, strong, em, small, code': {
          lineHeight: 'solid',
        },

        'sup, sub': {
          verticalAlign: 'baseline',
          position: 'relative',
          top: '-0.4em',
        },

        sub: {
          top: '0.4em',
        },

        iframe: {
          marginY: 4,
          border: 1,
          borderColor: 'grays.2',
        },
      }}
      {...props}
    >
      {children}
    </Box>
  </MDXProvider>
)

MarkdownContent.propTypes = {
  children: PropTypes.node.isRequired,
}

export default MarkdownContent
<commit_msg>:lipstick: re-style details components<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import { MDXProvider } from '@mdx-js/react'
import { Box } from 'rebass'
import components from './components'
import { verticalRhythm, blockShape } from './markdownStyles'

const MarkdownContent = ({ children, ...props }) => (
  <MDXProvider components={components}>
    <Box
      sx={{
        ...verticalRhythm,

        'b, strong, em, small, code': {
          lineHeight: 'solid',
        },

        'sup, sub': {
          verticalAlign: 'baseline',
          position: 'relative',
          top: '-0.4em',
        },

        sub: {
          top: '0.4em',
        },

        iframe: {
          marginY: 4,
          border: 1,
          borderColor: 'grays.2',
        },

        details: {
          ...blockShape,
          marginY: 4,
          backgroundColor: 'grays.1',
        },
      }}
      {...props}
    >
      {children}
    </Box>
  </MDXProvider>
)

MarkdownContent.propTypes = {
  children: PropTypes.node.isRequired,
}

export default MarkdownContent
<|endoftext|>


<commit_before>import { Text } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';

import { styles } from './styles';
import BlankContainer from '../../library/BlankContainer';
import Heading from '../../library/Heading';
import I18n from '../../../i18n';
import Link from '../../library/Link';
import StandardButton from '../../library/StandardButton';
import Table from '../../library/Table';
import { primaryColor } from '../../../globals/colors';

const PublicBodyDetails = ({
  publicBody,
  changeFilter,
  navigateToFoiRequests1,
  navigateToFoiRequests2,
}) => {
  const _filterByPublicBody = () => {
    // FIXME: It'a hack because there was another problem with react-navigation. It wasn't able to navigate to the Request stack when the stack was empty. This fixes it.
    changeFilter({
      publicBody: { param: publicBody.id, label: publicBody.name },
      status: null, // reset status
    });
    navigateToFoiRequests1();
    navigateToFoiRequests2();
  };

  const {
    name,
    url,
    classification,
    email,
    address,
    contact,
    description,
    categories,
    number_of_requests: nRequests,
  } = publicBody;

  const printableCategories =
    categories.length > 0 ? categories.map(x => x.name).join(', ') : null;

  const tableData = [
    {
      label: I18n.t('jurisdiction'),
      value: (
        <Link
          label={publicBody.jurisdiction.name}
          url={publicBody.jurisdiction.site_url}
        />
      ),
    },
    {
      label: I18n.t('classification'),
      value: <Text selectable>{classification.name}</Text>,
    },
    { label: I18n.t('website'), value: <Link label={url} url={url} /> },
    {
      label: I18n.t('email'),
      value: <Text selectable>{email}</Text>,
    },
    {
      label: I18n.t('address'),
      value: <Text selectable>{address}</Text>,
    },
    {
      label: I18n.t('contact'),
      value: <Text selectable>{contact}</Text>,
    },
    {
      label: I18n.t('description'),
      value: <Text selectable>{description}</Text>,
    },
    {
      label: I18n.t('tags'),
      value: <Text selectable>{printableCategories}</Text>,
    },
  ];

  const title =
    (nRequests > 0 ? 'Show ' : '') +
    I18n.t('countingRequests', {
      count: nRequests,
    });

  const disabled = nRequests === 0;

  const showRequestsButton = (
    <StandardButton
      disabled={disabled}
      icon={{ name: 'mail', color: primaryColor }}
      title={title}
      onPress={_filterByPublicBody}
      containerViewStyle={{
        marginBottom: 20,
      }}
    />
  );

  return (
    <BlankContainer>
      <Heading style={styles.heading}>{name}</Heading>
      {/* <StandardButton
        icon={{ name: 'add-circle-outline' }}
        title="TODO: Create a Request"
      /> */}
      {showRequestsButton}

    </BlankContainer>
  );
};

PublicBodyDetails.propTypes = {
  publicBody: PropTypes.shape({
    name: PropTypes.string.isRequired,
    email: PropTypes.string.isRequired,
    address: PropTypes.string.isRequired,
    // classification: PropTypes.string.isRequired,
    url: PropTypes.string.isRequired,
    contact: PropTypes.string.isRequired,
    description: PropTypes.string.isRequired,
  }).isRequired,
  changeFilter: PropTypes.func.isRequired,
};

PublicBodyDetails.navigationOptions = {
  title: I18n.t('publicBody'),
  drawerLockMode: 'locked-closed', // disable global drawer
};

export default PublicBodyDetails;
<commit_msg>adapt to API change<commit_after>import { Text } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';

import { styles } from './styles';
import BlankContainer from '../../library/BlankContainer';
import Heading from '../../library/Heading';
import I18n from '../../../i18n';
import Link from '../../library/Link';
import StandardButton from '../../library/StandardButton';
import Table from '../../library/Table';
import { primaryColor } from '../../../globals/colors';

const PublicBodyDetails = ({
  publicBody,
  changeFilter,
  navigateToFoiRequests1,
  navigateToFoiRequests2,
}) => {
  const _filterByPublicBody = () => {
    // FIXME: It'a hack because there was another problem with react-navigation. It wasn't able to navigate to the Request stack when the stack was empty. This fixes it.
    changeFilter({
      publicBody: { param: publicBody.id, label: publicBody.name },
      status: null, // reset status
    });
    navigateToFoiRequests1();
    navigateToFoiRequests2();
  };

  const {
    name,
    url,
    classification,
    email,
    address,
    contact,
    description,
    categories,
    number_of_requests: nRequests,
    jurisdiction,
  } = publicBody;

  const printableCategories =
    categories.length > 0 ? categories.map(x => x.name).join(', ') : null;

  const tableData = [
    {
      label: I18n.t('jurisdiction'),
      value: <Link label={jurisdiction.name} url={jurisdiction.site_url} />,
    },
    // Stefan changed something
    // {
    //   label: I18n.t('classification'),
    //   value: <Text selectable>{classification.name}</Text>,
    // },
    { label: I18n.t('website'), value: <Link label={url} url={url} /> },
    {
      label: I18n.t('email'),
      value: <Text selectable>{email}</Text>,
    },
    {
      label: I18n.t('address'),
      value: <Text selectable>{address}</Text>,
    },
    {
      label: I18n.t('contact'),
      value: <Text selectable>{contact}</Text>,
    },
    {
      label: I18n.t('description'),
      value: <Text selectable>{description}</Text>,
    },
    {
      label: I18n.t('tags'),
      value: <Text selectable>{printableCategories}</Text>,
    },
  ];

  const title =
    (nRequests > 0 ? 'Show ' : '') +
    I18n.t('countingRequests', {
      count: nRequests,
    });

  const disabled = nRequests === 0;

  const showRequestsButton = (
    <StandardButton
      disabled={disabled}
      icon={{ name: 'mail', color: primaryColor }}
      title={title}
      onPress={_filterByPublicBody}
      containerViewStyle={{
        marginBottom: 20,
      }}
    />
  );

  return (
    <BlankContainer>
      <Heading style={styles.heading}>{name}</Heading>
      {/* <StandardButton
        icon={{ name: 'add-circle-outline' }}
        title="TODO: Create a Request"
      /> */}
      {showRequestsButton}

    </BlankContainer>
  );
};

PublicBodyDetails.propTypes = {
  publicBody: PropTypes.shape({
    name: PropTypes.string.isRequired,
    email: PropTypes.string.isRequired,
    address: PropTypes.string.isRequired,
    // classification: PropTypes.string.isRequired,
    url: PropTypes.string.isRequired,
    contact: PropTypes.string.isRequired,
    description: PropTypes.string.isRequired,
  }).isRequired,
  changeFilter: PropTypes.func.isRequired,
};

PublicBodyDetails.navigationOptions = {
  title: I18n.t('publicBody'),
  drawerLockMode: 'locked-closed', // disable global drawer
};

export default PublicBodyDetails;
<|endoftext|>