<commit_before>module("uce.timer", {teardown: function() {
    $('#timer').timer('destroy');
}});

jackTest("create basic structure", function() {
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);
    $('#timer').timer({ucemeeting: ucemeeting});
    ok($('#timer').hasClass('ui-widget'), 'class ui-widget');
    ok($('#timer').hasClass('ui-timer'), 'class ui-timer');
    ok($('#timer > div').hasClass('ui-widget-content'), 'class ui-widget-content');
});

jackTest("the widget retrieves the meeting's informations", function () {
    stop();
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(0, "never"), undefined);
	        start();
        });

    $('#timer').timer({ucemeeting: ucemeeting});
});

jackTest("test widget default state", function () {
    stop();
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(0, 10000), undefined);
	        start();
	        equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:00 / 00:00:10 / 00:00:10");
        });

    $('#timer').timer({ucemeeting: ucemeeting});
});

jackTest("test widget increment every second", function () {
    expect(4);
    stop();
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(0, 10000), undefined);

	        equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:00 / 00:00:10 / 00:00:10");
	        setTimeout(function() {
		        equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:01 / 00:00:09 / 00:00:10");
		        setTimeout(function() {
		            start();
		            equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:02 / 00:00:08 / 00:00:10");
		        }, 1200);
	        }, 1200);
        });

    $('#timer').timer({ucemeeting: ucemeeting});
});

jackTest("test remaining time before open", function () {
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(30000, 50000), undefined);
	        equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "-00:00:20 / 00:00:40 / 00:00:20");
        });

    $('#timer').timer({ucemeeting: ucemeeting, start: 10000});
});

jackTest("test time elapsed after close", function () {
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(10000, 20000), undefined);
	        equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:10 / 00:00:00 / 00:00:10");
        });

    $('#timer').timer({ucemeeting: ucemeeting, start: 30000});
});

jackTest("test meeting infinity", function () {
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(10000, "never"), undefined);
	        equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:20 / \u221E");
        });

    $('#timer').timer({ucemeeting: ucemeeting, start: 30000});
});

//test("destroy everything", function() {
//});
<commit_msg>Untabify and reindent.<commit_after>module("uce.timer", {teardown: function() {
    $('#timer').timer('destroy');
}});

jackTest("create basic structure", function() {
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);
    $('#timer').timer({ucemeeting: ucemeeting});
    ok($('#timer').hasClass('ui-widget'), 'class ui-widget');
    ok($('#timer').hasClass('ui-timer'), 'class ui-timer');
    ok($('#timer > div').hasClass('ui-widget-content'), 'class ui-widget-content');
});

jackTest("the widget retrieves the meeting's informations", function () {
    stop();
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(0, "never"), undefined);
            start();
        });

    $('#timer').timer({ucemeeting: ucemeeting});
});

jackTest("test widget default state", function () {
    stop();
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(0, 10000), undefined);
            start();
            equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:00 / 00:00:10 / 00:00:10");
        });

    $('#timer').timer({ucemeeting: ucemeeting});
});

jackTest("test widget increment every second", function () {
    expect(4);
    stop();
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(0, 10000), undefined);

            equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:00 / 00:00:10 / 00:00:10");
            setTimeout(function() {
                equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:01 / 00:00:09 / 00:00:10");
                setTimeout(function() {
                    start();
                    equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:02 / 00:00:08 / 00:00:10");
                }, 1200);
            }, 1200);
        });

    $('#timer').timer({ucemeeting: ucemeeting});
});

jackTest("test remaining time before open", function () {
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(30000, 50000), undefined);
            equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "-00:00:20 / 00:00:40 / 00:00:20");
        });

    $('#timer').timer({ucemeeting: ucemeeting, start: 10000});
});

jackTest("test time elapsed after close", function () {
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(10000, 20000), undefined);
            equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:10 / 00:00:00 / 00:00:10");
        });

    $('#timer').timer({ucemeeting: ucemeeting, start: 30000});
});

jackTest("test meeting infinity", function () {
    var ucemeeting = jack.create("ucemeeting", ['bind', 'get']);

    jack.expect("ucemeeting.get")
        .exactly("1 time")
        .mock(function(callback) {
            callback(undefined, Factories.createMeeting(10000, "never"), undefined);
            equals($('#timer > .ui-widget-content > .ui-timer-value').text(), "00:00:20 / \u221E");
        });

    $('#timer').timer({ucemeeting: ucemeeting, start: 30000});
});
<|endoftext|>


<commit_before>import React from 'react';
import {
  Animated,
  InteractionManager,
  Platform,
  StyleSheet,
  View,
} from 'react-native';

import ActionSheet from '@exponent/react-native-action-sheet';
import dismissKeyboard from 'react-native-dismiss-keyboard';
import moment from 'moment/min/moment-with-locales.min';

import Actions from './Actions';
import Avatar from './Avatar';
import Bubble from './Bubble';
import MessageImage from './MessageImage';
import MessageText from './MessageText';
import Composer from './Composer';
import Day from './Day';
import InputToolbar from './InputToolbar';
import LoadEarlier from './LoadEarlier';
import Message from './Message';
import MessageContainer from './MessageContainer';
import Send from './Send';
import Time from './Time';

// Min and max heights of ToolbarInput and Composer
// Needed for Composer auto grow and ScrollView animation
// TODO move these values to Constants.js (also with used colors #b2b2b2)
const MIN_COMPOSER_HEIGHT = Platform.select({
  ios: 33,
  android: 41,
});
const MAX_COMPOSER_HEIGHT = 100;
const MIN_INPUT_TOOLBAR_HEIGHT = 44;

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

    // default values
    this._isMounted = false;
    this._keyboardHeight = 0;
    this._maxHeight = null;
    this._touchStarted = false;
    this._isTypingDisabled = false;
    this._locale = 'en';
    this._messages = [];

    this.state = {
      isInitialized: false, // initialization will calculate maxHeight before rendering the chat
    };

    this.onTouchStart = this.onTouchStart.bind(this);
    this.onTouchMove = this.onTouchMove.bind(this);
    this.onTouchEnd = this.onTouchEnd.bind(this);
    this.onKeyboardWillShow = this.onKeyboardWillShow.bind(this);
    this.onKeyboardWillHide = this.onKeyboardWillHide.bind(this);
    this.onKeyboardDidShow = this.onKeyboardDidShow.bind(this);
    this.onKeyboardDidHide = this.onKeyboardDidHide.bind(this);
    this.onType = this.onType.bind(this);
    this.onSend = this.onSend.bind(this);
    this.getLocale = this.getLocale.bind(this);

    this.invertibleScrollViewProps = {
      inverted: true,
      keyboardShouldPersistTaps: true,
      onTouchStart: this.onTouchStart,
      onTouchMove: this.onTouchMove,
      onTouchEnd: this.onTouchEnd,
      onKeyboardWillShow: this.onKeyboardWillShow,
      onKeyboardWillHide: this.onKeyboardWillHide,
      onKeyboardDidShow: this.onKeyboardDidShow,
      onKeyboardDidHide: this.onKeyboardDidHide,
    };
  }

  static append(currentMessages = [], messages) {
    if (!Array.isArray(messages)) {
      messages = [messages];
    }
    return messages.concat(currentMessages);
  }

  static prepend(currentMessages = [], messages) {
    if (!Array.isArray(messages)) {
      messages = [messages];
    }
    return currentMessages.concat(messages);
  }

  getChildContext() {
    return {
      actionSheet: () => this._actionSheetRef,
      getLocale: this.getLocale,
    };
  }

  componentWillMount() {
    this.setIsMounted(true);
    this.initLocale();
    this.initMessages(this.props.messages);
  }

  componentWillUnmount() {
    this.setIsMounted(false);
  }

  componentWillReceiveProps(nextProps = {}) {
    this.initMessages(nextProps.messages);
  }

  initLocale() {
    if (this.props.locale === null || moment.locales().indexOf(this.props.locale) === -1) {
      this.setLocale('en');
    } else {
      this.setLocale(this.props.locale);
    }
  }

  initMessages(messages = []) {
    this.setMessages(messages);
  }

  setLocale(locale) {
    this._locale = locale;
  }

  getLocale() {
    return this._locale;
  }

  setMessages(messages) {
    this._messages = messages;
  }

  getMessages() {
    return this._messages;
  }

  setMaxHeight(height) {
    this._maxHeight = height;
  }

  getMaxHeight() {
    return this._maxHeight;
  }

  setKeyboardHeight(height) {
    this._keyboardHeight = height;
  }

  getKeyboardHeight() {
    return this._keyboardHeight;
  }

  setIsTypingDisabled(value) {
    this._isTypingDisabled = value;
  }

  getIsTypingDisabled() {
    return this._isTypingDisabled;
  }

  setIsMounted(value) {
    this._isMounted = value;
  }

  getIsMounted() {
    return this._isMounted;
  }

  // TODO
  // setMinInputToolbarHeight
  getMinInputToolbarHeight() {
    if (this.props.renderAccessory) {
      return MIN_INPUT_TOOLBAR_HEIGHT * 2;
    }
    return MIN_INPUT_TOOLBAR_HEIGHT;
  }

  prepareMessagesContainerHeight(value) {
    if (this.props.isAnimated === true) {
      return new Animated.Value(value);
    }
    return value;
  }

  onKeyboardWillShow(e) {
    this.setIsTypingDisabled(true);
    this.setKeyboardHeight(e.endCoordinates.height);
    const newMessagesContainerHeight = (this.getMaxHeight() - (this.state.composerHeight + (this.getMinInputToolbarHeight() - MIN_COMPOSER_HEIGHT))) - this.getKeyboardHeight() + this.props.bottomOffset;
    if (this.props.isAnimated === true) {
      Animated.timing(this.state.messagesContainerHeight, {
        toValue: newMessagesContainerHeight,
        duration: 210,
      }).start();
    } else {
      this.setState((previousState) => {
        return {
          messagesContainerHeight: newMessagesContainerHeight,
        };
      });
    }
  }

  onKeyboardWillHide() {
    this.setIsTypingDisabled(true);
    this.setKeyboardHeight(0);
    const newMessagesContainerHeight = this.getMaxHeight() - (this.state.composerHeight + (this.getMinInputToolbarHeight() - MIN_COMPOSER_HEIGHT));
    if (this.props.isAnimated === true) {
      Animated.timing(this.state.messagesContainerHeight, {
        toValue: newMessagesContainerHeight,
        duration: 210,
      }).start();
    } else {
      this.setState((previousState) => {
        return {
          messagesContainerHeight: newMessagesContainerHeight,
        };
      });
    }
  }

  onKeyboardDidShow(e) {
    if (Platform.OS === 'android') {
      this.onKeyboardWillShow(e);
    }
    this.setIsTypingDisabled(false);
  }

  onKeyboardDidHide(e) {
    if (Platform.OS === 'android') {
      this.onKeyboardWillHide(e);
    }
    this.setIsTypingDisabled(false);
  }

  scrollToBottom(animated = true) {
    this._messageContainerRef.scrollTo({
      y: 0,
      animated,
    });
  }

  onTouchStart() {
    this._touchStarted = true;
  }

  onTouchMove() {
    this._touchStarted = false;
  }

  // handle Tap event to dismiss keyboard
  onTouchEnd() {
    if (this._touchStarted === true) {
      dismissKeyboard();
    }
    this._touchStarted = false;
  }

  renderMessages() {
    const AnimatedView = this.props.isAnimated === true ? Animated.View : View;
    return (
      <AnimatedView style={{
        height: this.state.messagesContainerHeight,
      }}>
        <MessageContainer
          {...this.props}

          invertibleScrollViewProps={this.invertibleScrollViewProps}

          messages={this.getMessages()}

          ref={component => this._messageContainerRef = component}
        />
        {this.renderChatFooter()}
      </AnimatedView>
    );
  }

  onSend(messages = [], shouldResetInputToolbar = false) {
    if (!Array.isArray(messages)) {
      messages = [messages];
    }

    messages = messages.map((message) => {
      return {
        ...message,
        user: this.props.user,
        createdAt: new Date(),
        _id: 'temp-id-' + Math.round(Math.random() * 1000000),
      };
    });

    if (shouldResetInputToolbar === true) {
      this.setIsTypingDisabled(true);
      this.resetInputToolbar();
    }

    this.props.onSend(messages);
    this.scrollToBottom();

    if (shouldResetInputToolbar === true) {
      setTimeout(() => {
        if (this.getIsMounted() === true) {
          this.setIsTypingDisabled(false);
        }
      }, 200);
    }
  }

  resetInputToolbar() {
    this.setState((previousState) => {
      return {
        text: '',
        composerHeight: MIN_COMPOSER_HEIGHT,
        messagesContainerHeight: this.prepareMessagesContainerHeight(this.getMaxHeight() - this.getMinInputToolbarHeight() - this.getKeyboardHeight() + this.props.bottomOffset),
      };
    });
  }

  calculateInputToolbarHeight(newComposerHeight) {
    return newComposerHeight + (this.getMinInputToolbarHeight() - MIN_COMPOSER_HEIGHT);
  }

  onType(e) {
    if (this.getIsTypingDisabled() === true) {
      return;
    }
    const newComposerHeight = Math.max(MIN_COMPOSER_HEIGHT, Math.min(MAX_COMPOSER_HEIGHT, e.nativeEvent.contentSize.height));
    const newMessagesContainerHeight = this.getMaxHeight() - this.calculateInputToolbarHeight(newComposerHeight) - this.getKeyboardHeight() + this.props.bottomOffset;
    const newText = e.nativeEvent.text;
    this.setState((previousState) => {
      return {
        text: newText,
        composerHeight: newComposerHeight,
        messagesContainerHeight: this.prepareMessagesContainerHeight(newMessagesContainerHeight),
      };
    });
  }

  renderInputToolbar() {
    const inputToolbarProps = {
      ...this.props,
      text: this.state.text,
      composerHeight: Math.max(MIN_COMPOSER_HEIGHT, this.state.composerHeight),
      onChange: this.onType,
      onSend: this.onSend,
    };

    if (this.props.renderInputToolbar) {
      return this.props.renderInputToolbar(inputToolbarProps);
    }
    return (
      <InputToolbar
        {...inputToolbarProps}
      />
    );
  }

  renderChatFooter() {
    if (this.props.renderChatFooter) {
      const footerProps = {
        ...this.props,
      };
      return this.props.renderChatFooter(footerProps);
    }
    return null;
  }

  renderLoading() {
    if (this.props.renderLoading) {
      return this.props.renderLoading();
    }
    return null;
  }

  render() {
    if (this.state.isInitialized === true) {
      return (
        <ActionSheet ref={component => this._actionSheetRef = component}>
          <View style={styles.container}>
            {this.renderMessages()}
            {this.renderInputToolbar()}
          </View>
        </ActionSheet>
      );
    }
    return (
      <View
        style={styles.container}
        onLayout={(e) => {
          const layout = e.nativeEvent.layout;
          this.setMaxHeight(layout.height);
          InteractionManager.runAfterInteractions(() => {
            this.setState({
              isInitialized: true,
              text: '',
              composerHeight: MIN_COMPOSER_HEIGHT,
              messagesContainerHeight: this.prepareMessagesContainerHeight(this.getMaxHeight() - this.getMinInputToolbarHeight()),
            });
          });
        }}
      >
        {this.renderLoading()}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

GiftedChat.childContextTypes = {
  actionSheet: React.PropTypes.func,
  getLocale: React.PropTypes.func,
};

GiftedChat.defaultProps = {
  messages: [],
  onSend: () => {
  },
  loadEarlier: false,
  onLoadEarlier: () => {
  },
  locale: null,
  isAnimated: Platform.select({
    ios: true,
    android: false,
  }),
  renderAccessory: null,
  renderActions: null,
  renderAvatar: null,
  renderBubble: null,
  renderFooter: null,
  renderChatFooter: null,
  renderMessageText: null,
  renderMessageImage: null,
  renderComposer: null,
  renderCustomView: null,
  renderDay: null,
  renderInputToolbar: null,
  renderLoadEarlier: null,
  renderLoading: null,
  renderMessage: null,
  renderSend: null,
  renderTime: null,
  user: {},
  bottomOffset: 0,
  isLoadingEarlier: false,
};

GiftedChat.propTypes = {
  messages: React.PropTypes.array,
  onSend: React.PropTypes.func,
  loadEarlier: React.PropTypes.bool,
  onLoadEarlier: React.PropTypes.func,
  locale: React.PropTypes.string,
  isAnimated: React.PropTypes.bool,
  renderAccessory: React.PropTypes.func,
  renderActions: React.PropTypes.func,
  renderAvatar: React.PropTypes.func,
  renderBubble: React.PropTypes.func,
  renderFooter: React.PropTypes.func,
  renderChatFooter: React.PropTypes.func,
  renderMessageText: React.PropTypes.func,
  renderMessageImage: React.PropTypes.func,
  renderComposer: React.PropTypes.func,
  renderCustomView: React.PropTypes.func,
  renderDay: React.PropTypes.func,
  renderInputToolbar: React.PropTypes.func,
  renderLoadEarlier: React.PropTypes.func,
  renderLoading: React.PropTypes.func,
  renderMessage: React.PropTypes.func,
  renderSend: React.PropTypes.func,
  renderTime: React.PropTypes.func,
  user: React.PropTypes.object,
  bottomOffset: React.PropTypes.number,
  isLoadingEarlier: React.PropTypes.bool,
};

export {
  GiftedChat,
  Actions,
  Avatar,
  Bubble,
  MessageImage,
  MessageText,
  Composer,
  Day,
  InputToolbar,
  LoadEarlier,
  Message,
  Send,
  Time,
};
<commit_msg>fix an issue when keyboard is dismissing during the initialization<commit_after>import React from 'react';
import {
  Animated,
  InteractionManager,
  Platform,
  StyleSheet,
  View,
} from 'react-native';

import ActionSheet from '@exponent/react-native-action-sheet';
import dismissKeyboard from 'react-native-dismiss-keyboard';
import moment from 'moment/min/moment-with-locales.min';

import Actions from './Actions';
import Avatar from './Avatar';
import Bubble from './Bubble';
import MessageImage from './MessageImage';
import MessageText from './MessageText';
import Composer from './Composer';
import Day from './Day';
import InputToolbar from './InputToolbar';
import LoadEarlier from './LoadEarlier';
import Message from './Message';
import MessageContainer from './MessageContainer';
import Send from './Send';
import Time from './Time';

// Min and max heights of ToolbarInput and Composer
// Needed for Composer auto grow and ScrollView animation
// TODO move these values to Constants.js (also with used colors #b2b2b2)
const MIN_COMPOSER_HEIGHT = Platform.select({
  ios: 33,
  android: 41,
});
const MAX_COMPOSER_HEIGHT = 100;
const MIN_INPUT_TOOLBAR_HEIGHT = 44;

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

    // default values
    this._isMounted = false;
    this._keyboardHeight = 0;
    this._maxHeight = null;
    this._touchStarted = false;
    this._isFirstLayout = true;
    this._isTypingDisabled = false;
    this._locale = 'en';
    this._messages = [];

    this.state = {
      isInitialized: false, // initialization will calculate maxHeight before rendering the chat
    };

    this.onTouchStart = this.onTouchStart.bind(this);
    this.onTouchMove = this.onTouchMove.bind(this);
    this.onTouchEnd = this.onTouchEnd.bind(this);
    this.onKeyboardWillShow = this.onKeyboardWillShow.bind(this);
    this.onKeyboardWillHide = this.onKeyboardWillHide.bind(this);
    this.onKeyboardDidShow = this.onKeyboardDidShow.bind(this);
    this.onKeyboardDidHide = this.onKeyboardDidHide.bind(this);
    this.onType = this.onType.bind(this);
    this.onSend = this.onSend.bind(this);
    this.getLocale = this.getLocale.bind(this);

    this.invertibleScrollViewProps = {
      inverted: true,
      keyboardShouldPersistTaps: true,
      onTouchStart: this.onTouchStart,
      onTouchMove: this.onTouchMove,
      onTouchEnd: this.onTouchEnd,
      onKeyboardWillShow: this.onKeyboardWillShow,
      onKeyboardWillHide: this.onKeyboardWillHide,
      onKeyboardDidShow: this.onKeyboardDidShow,
      onKeyboardDidHide: this.onKeyboardDidHide,
    };
  }

  static append(currentMessages = [], messages) {
    if (!Array.isArray(messages)) {
      messages = [messages];
    }
    return messages.concat(currentMessages);
  }

  static prepend(currentMessages = [], messages) {
    if (!Array.isArray(messages)) {
      messages = [messages];
    }
    return currentMessages.concat(messages);
  }

  getChildContext() {
    return {
      actionSheet: () => this._actionSheetRef,
      getLocale: this.getLocale,
    };
  }

  componentWillMount() {
    this.setIsMounted(true);
    this.initLocale();
    this.initMessages(this.props.messages);
  }

  componentWillUnmount() {
    this.setIsMounted(false);
  }

  componentWillReceiveProps(nextProps = {}) {
    this.initMessages(nextProps.messages);
  }

  initLocale() {
    if (this.props.locale === null || moment.locales().indexOf(this.props.locale) === -1) {
      this.setLocale('en');
    } else {
      this.setLocale(this.props.locale);
    }
  }

  initMessages(messages = []) {
    this.setMessages(messages);
  }

  setLocale(locale) {
    this._locale = locale;
  }

  getLocale() {
    return this._locale;
  }

  setMessages(messages) {
    this._messages = messages;
  }

  getMessages() {
    return this._messages;
  }

  setMaxHeight(height) {
    this._maxHeight = height;
  }

  getMaxHeight() {
    return this._maxHeight;
  }

  setKeyboardHeight(height) {
    this._keyboardHeight = height;
  }

  getKeyboardHeight() {
    return this._keyboardHeight;
  }

  setIsFirstLayout(value) {
    this._isFirstLayout = value;
  }

  getIsFirstLayout() {
    return this._isFirstLayout;
  }

  setIsTypingDisabled(value) {
    this._isTypingDisabled = value;
  }

  getIsTypingDisabled() {
    return this._isTypingDisabled;
  }

  setIsMounted(value) {
    this._isMounted = value;
  }

  getIsMounted() {
    return this._isMounted;
  }

  // TODO
  // setMinInputToolbarHeight
  getMinInputToolbarHeight() {
    if (this.props.renderAccessory) {
      return MIN_INPUT_TOOLBAR_HEIGHT * 2;
    }
    return MIN_INPUT_TOOLBAR_HEIGHT;
  }

  prepareMessagesContainerHeight(value) {
    if (this.props.isAnimated === true) {
      return new Animated.Value(value);
    }
    return value;
  }

  onKeyboardWillShow(e) {
    this.setIsTypingDisabled(true);
    this.setKeyboardHeight(e.endCoordinates.height);
    const newMessagesContainerHeight = (this.getMaxHeight() - (this.state.composerHeight + (this.getMinInputToolbarHeight() - MIN_COMPOSER_HEIGHT))) - this.getKeyboardHeight() + this.props.bottomOffset;
    if (this.props.isAnimated === true) {
      Animated.timing(this.state.messagesContainerHeight, {
        toValue: newMessagesContainerHeight,
        duration: 210,
      }).start();
    } else {
      this.setState((previousState) => {
        return {
          messagesContainerHeight: newMessagesContainerHeight,
        };
      });
    }
  }

  onKeyboardWillHide() {
    this.setIsTypingDisabled(true);
    this.setKeyboardHeight(0);
    const newMessagesContainerHeight = this.getMaxHeight() - (this.state.composerHeight + (this.getMinInputToolbarHeight() - MIN_COMPOSER_HEIGHT));
    if (this.props.isAnimated === true) {
      Animated.timing(this.state.messagesContainerHeight, {
        toValue: newMessagesContainerHeight,
        duration: 210,
      }).start();
    } else {
      this.setState((previousState) => {
        return {
          messagesContainerHeight: newMessagesContainerHeight,
        };
      });
    }
  }

  onKeyboardDidShow(e) {
    if (Platform.OS === 'android') {
      this.onKeyboardWillShow(e);
    }
    this.setIsTypingDisabled(false);
  }

  onKeyboardDidHide(e) {
    if (Platform.OS === 'android') {
      this.onKeyboardWillHide(e);
    }
    this.setIsTypingDisabled(false);
  }

  scrollToBottom(animated = true) {
    this._messageContainerRef.scrollTo({
      y: 0,
      animated,
    });
  }

  onTouchStart() {
    this._touchStarted = true;
  }

  onTouchMove() {
    this._touchStarted = false;
  }

  // handle Tap event to dismiss keyboard
  onTouchEnd() {
    if (this._touchStarted === true) {
      dismissKeyboard();
    }
    this._touchStarted = false;
  }

  renderMessages() {
    const AnimatedView = this.props.isAnimated === true ? Animated.View : View;
    return (
      <AnimatedView style={{
        height: this.state.messagesContainerHeight,
      }}>
        <MessageContainer
          {...this.props}

          invertibleScrollViewProps={this.invertibleScrollViewProps}

          messages={this.getMessages()}

          ref={component => this._messageContainerRef = component}
        />
        {this.renderChatFooter()}
      </AnimatedView>
    );
  }

  onSend(messages = [], shouldResetInputToolbar = false) {
    if (!Array.isArray(messages)) {
      messages = [messages];
    }

    messages = messages.map((message) => {
      return {
        ...message,
        user: this.props.user,
        createdAt: new Date(),
        _id: 'temp-id-' + Math.round(Math.random() * 1000000),
      };
    });

    if (shouldResetInputToolbar === true) {
      this.setIsTypingDisabled(true);
      this.resetInputToolbar();
    }

    this.props.onSend(messages);
    this.scrollToBottom();

    if (shouldResetInputToolbar === true) {
      setTimeout(() => {
        if (this.getIsMounted() === true) {
          this.setIsTypingDisabled(false);
        }
      }, 200);
    }
  }

  resetInputToolbar() {
    this.setState((previousState) => {
      return {
        text: '',
        composerHeight: MIN_COMPOSER_HEIGHT,
        messagesContainerHeight: this.prepareMessagesContainerHeight(this.getMaxHeight() - this.getMinInputToolbarHeight() - this.getKeyboardHeight() + this.props.bottomOffset),
      };
    });
  }

  calculateInputToolbarHeight(newComposerHeight) {
    return newComposerHeight + (this.getMinInputToolbarHeight() - MIN_COMPOSER_HEIGHT);
  }

  onType(e) {
    if (this.getIsTypingDisabled() === true) {
      return;
    }
    const newComposerHeight = Math.max(MIN_COMPOSER_HEIGHT, Math.min(MAX_COMPOSER_HEIGHT, e.nativeEvent.contentSize.height));
    const newMessagesContainerHeight = this.getMaxHeight() - this.calculateInputToolbarHeight(newComposerHeight) - this.getKeyboardHeight() + this.props.bottomOffset;
    const newText = e.nativeEvent.text;
    this.setState((previousState) => {
      return {
        text: newText,
        composerHeight: newComposerHeight,
        messagesContainerHeight: this.prepareMessagesContainerHeight(newMessagesContainerHeight),
      };
    });
  }

  renderInputToolbar() {
    const inputToolbarProps = {
      ...this.props,
      text: this.state.text,
      composerHeight: Math.max(MIN_COMPOSER_HEIGHT, this.state.composerHeight),
      onChange: this.onType,
      onSend: this.onSend,
    };

    if (this.props.renderInputToolbar) {
      return this.props.renderInputToolbar(inputToolbarProps);
    }
    return (
      <InputToolbar
        {...inputToolbarProps}
      />
    );
  }

  renderChatFooter() {
    if (this.props.renderChatFooter) {
      const footerProps = {
        ...this.props,
      };
      return this.props.renderChatFooter(footerProps);
    }
    return null;
  }

  renderLoading() {
    if (this.props.renderLoading) {
      return this.props.renderLoading();
    }
    return null;
  }

  render() {
    if (this.state.isInitialized === true) {
      return (
        <ActionSheet ref={component => this._actionSheetRef = component}>
          <View
            style={styles.container}
            onLayout={(e) => {
              if (Platform.OS === 'android') {
                // fix an issue when keyboard is dismissing during the initialization
                const layout = e.nativeEvent.layout;
                if (this.getMaxHeight() !== layout.height && this.getIsFirstLayout() === true) {
                  this.setMaxHeight(layout.height);
                  this.setState({
                    messagesContainerHeight: this.prepareMessagesContainerHeight(this.getMaxHeight() - this.getMinInputToolbarHeight()),
                  });
                }
              }
              if (this.getIsFirstLayout() === true) {
                this.setIsFirstLayout(false);
              }
            }}
          >
            {this.renderMessages()}
            {this.renderInputToolbar()}
          </View>
        </ActionSheet>
      );
    }
    return (
      <View
        style={styles.container}
        onLayout={(e) => {
          const layout = e.nativeEvent.layout;
          this.setMaxHeight(layout.height);
          InteractionManager.runAfterInteractions(() => {
            this.setState({
              isInitialized: true,
              text: '',
              composerHeight: MIN_COMPOSER_HEIGHT,
              messagesContainerHeight: this.prepareMessagesContainerHeight(this.getMaxHeight() - this.getMinInputToolbarHeight()),
            });
          });
        }}
      >
        {this.renderLoading()}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

GiftedChat.childContextTypes = {
  actionSheet: React.PropTypes.func,
  getLocale: React.PropTypes.func,
};

GiftedChat.defaultProps = {
  messages: [],
  onSend: () => {
  },
  loadEarlier: false,
  onLoadEarlier: () => {
  },
  locale: null,
  isAnimated: Platform.select({
    ios: true,
    android: false,
  }),
  renderAccessory: null,
  renderActions: null,
  renderAvatar: null,
  renderBubble: null,
  renderFooter: null,
  renderChatFooter: null,
  renderMessageText: null,
  renderMessageImage: null,
  renderComposer: null,
  renderCustomView: null,
  renderDay: null,
  renderInputToolbar: null,
  renderLoadEarlier: null,
  renderLoading: null,
  renderMessage: null,
  renderSend: null,
  renderTime: null,
  user: {},
  bottomOffset: 0,
  isLoadingEarlier: false,
};

GiftedChat.propTypes = {
  messages: React.PropTypes.array,
  onSend: React.PropTypes.func,
  loadEarlier: React.PropTypes.bool,
  onLoadEarlier: React.PropTypes.func,
  locale: React.PropTypes.string,
  isAnimated: React.PropTypes.bool,
  renderAccessory: React.PropTypes.func,
  renderActions: React.PropTypes.func,
  renderAvatar: React.PropTypes.func,
  renderBubble: React.PropTypes.func,
  renderFooter: React.PropTypes.func,
  renderChatFooter: React.PropTypes.func,
  renderMessageText: React.PropTypes.func,
  renderMessageImage: React.PropTypes.func,
  renderComposer: React.PropTypes.func,
  renderCustomView: React.PropTypes.func,
  renderDay: React.PropTypes.func,
  renderInputToolbar: React.PropTypes.func,
  renderLoadEarlier: React.PropTypes.func,
  renderLoading: React.PropTypes.func,
  renderMessage: React.PropTypes.func,
  renderSend: React.PropTypes.func,
  renderTime: React.PropTypes.func,
  user: React.PropTypes.object,
  bottomOffset: React.PropTypes.number,
  isLoadingEarlier: React.PropTypes.bool,
};

export {
  GiftedChat,
  Actions,
  Avatar,
  Bubble,
  MessageImage,
  MessageText,
  Composer,
  Day,
  InputToolbar,
  LoadEarlier,
  Message,
  Send,
  Time,
};
<|endoftext|>


<commit_before>// Show a weekday
// Write a function weekday(date) to find the weekday for a given Date object,
// returing either 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', or 'Sun'.
const weekday = (date) => {
  const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
// check to see if date argument object's class is an instance of Date, then call .getDay
// and return daysOfWeek day at that index.
  if (Object.prototype.toString.call(date) === "[object Date]") {
    return daysOfWeek[date.getDay()];
  };
  throw new Error('You entered an invalid date! Try again.');
};

// console.log(weekday('today'));
console.log(weekday(new Date(2017, 5, 19)));

// Get a snippet from text
// Write a function snippet(string, maxlength) that shortens the string given to the maxlength
// and adds the ellipsis character ("…") to the end of string, and then returns the shortened
// string. If the string is shorter than the maxlength, just return the string unmodified.

const snippet = (string, maxlength) => {
  if (string.length < maxlength) {
    return string;
  } else {
  return `${string.slice(0, maxlength)}...`;
  }
};

console.log(snippet('For the following exercises,', 10));

// Number of properties
// Write a function numProps(obj) that returns the number of properties an object has.
// Ignore symbolic properties and count only the "own properties" (not inherited) of the
// object.
const numProps = (obj) => {
  if (typeof obj === 'object') {
    const objectKeys = Object.keys(obj);
    return objectKeys.length;
  }
  throw new Error('numProps must be passed an object');
};

console.log(numProps({}));

// Filter between
// Write a function filterBetween(array, min, max) that takes an array of numbers,
// a min value, and a max value. It returns a new array containing only the elements that
// are greater than or equal to min and less than or equal to max.
const filterBetween = (arr, min, max) => {
// first will check input to make sure arguments only contain numbers.
  arr.forEach((element) => {
    if (typeof (element) !== 'number') {
      throw new Error('whoops! check that array passed to filterBetween only contains numbers');
    }
  });
  if (typeof (min) !== 'number') {
    throw new Error('whoops! check that \'min\' passed to filterBetween only contains numbers');
  }
  if (typeof (max) !== 'number') {
    throw new Error('whoops! check that \'max\' passed to filterBetween only contains numbers');
  }

  const result = arr.filter((num) => num >= min && num <= max);

  if (result.length === 0) {
    throw new Error('Results empty: check array being passed');
  } else {
    return result;
  }
};

const arr = [5, 10, 15, 20, 25, 30, 35];
console.log(filterBetween(arr, 0, 16));

module.exports = {
  weekday,
  snippet,
  numProps,
  filterBetween,
};
<commit_msg>added comment code to numProps and filterBetween<commit_after>// Show a weekday
// Write a function weekday(date) to find the weekday for a given Date object,
// returing either 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', or 'Sun'.
const weekday = (date) => {
  const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
// check to see if date argument object's class is an instance of Date, then call .getDay
// and return daysOfWeek day at that index.
  if (Object.prototype.toString.call(date) === "[object Date]") {
    return daysOfWeek[date.getDay()];
  };
  throw new Error('You entered an invalid date! Try again.');
};

// console.log(weekday('today'));
console.log(weekday(new Date(2017, 5, 19)));

// Get a snippet from text
// Write a function snippet(string, maxlength) that shortens the string given to the maxlength
// and adds the ellipsis character ("…") to the end of string, and then returns the shortened
// string. If the string is shorter than the maxlength, just return the string unmodified.

const snippet = (string, maxlength) => {
  if (string.length < maxlength) {
    return string;
  } else {
  return `${string.slice(0, maxlength)}...`;
  }
};

console.log(snippet('For the following exercises,', 10));

// Number of properties
// Write a function numProps(obj) that returns the number of properties an object has.
// Ignore symbolic properties and count only the "own properties" (not inherited) of the
// object.
const numProps = (obj) => {
  if (typeof obj === 'object') {
    const objectKeys = Object.keys(obj);
    return objectKeys.length;
  }
  throw new Error('numProps must be passed an object');
};

console.log(numProps({}));

// Filter between
// Write a function filterBetween(array, min, max) that takes an array of numbers,
// a min value, and a max value. It returns a new array containing only the elements that
// are greater than or equal to min and less than or equal to max.

const filterBetween = (arr, min, max) => {
// first check input to make sure arguments only contain numbers.
  arr.forEach((element) => {
    if (typeof (element) !== 'number') {
      throw new Error('whoops! check that array passed to filterBetween only contains numbers');
    }
  });
  if (typeof (min) !== 'number') {
    throw new Error('whoops! check that \'min\' passed to filterBetween only contains numbers');
  }
  if (typeof (max) !== 'number') {
    throw new Error('whoops! check that \'max\' passed to filterBetween only contains numbers');
  }
  // create an array of numbers that fall between or equal the min and max arguments.
  const result = arr.filter((num) => num >= min && num <= max);

  // check that the result array is not empty then return result.
  if (result.length === 0) {
    throw new Error('Results empty: check arguments being passed to filterBetween');
  } else {
    return result;
  }
};

const arr = [5, 10, 15, 20, 25, 30, 35];
console.log(filterBetween(arr, 0, 16));

module.exports = {
  weekday,
  snippet,
  numProps,
  filterBetween,
};
<|endoftext|>


<commit_before>/**
 * This kind holds the consistency between the project.json and the
 * in-memory representation of the project configuration.
 *
 * After creation of the kind, use #init(service, folderId) to
 * set the location of the project.  Those values can no longer be
 * changed.
 *
 * - Use #setData and #getData to change the in-memory configuration Javascript object.
 *   data is an object containing the *whole* configuration.
 * - Then use #save to send the whole configuration to remote storage
 *
 * As it is, this kind performs zero checks on the content of the file.
 */
enyo.kind({
	name: "ProjectConfig",
	kind: "enyo.Object",
	published: {
		data: {}
	},
	service: null,		// filesystem service
	folderId: null,
	fileId: null,
	debug: false,
	create: function() {
		this.inherited(arguments);
	},
	/**
	 * Initializer: load data from project.json file
	 * @param {Object} inLocation gives information to access project.json
	 *  (expects { service: <object>, folderId: <string>)} )
	 * @param {Object} next is a CommonJS callback
	 */
	init: function(inLocation, next) {
		this.data = null;
		this.service = inLocation.service;
		this.folderId = inLocation.folderId;
		var req = this.service.propfind(this.folderId, 1);
		req.response(this, function _projectLoaded(inSender, inResponse) {
			var prj = inResponse.children.filter(function(node){
				return node.name === "project.json";
			});
			if (prj.length === 0) {
				// does not exist: create & save it immediatelly
				this.setData(ProjectConfig.checkConfig());
				// Use the folder name by default for the project name
				this.data.name = inResponse.name;
				this.save(next);
			} else {
				// already exists: load it
				this.fileId = prj[0].id;
				this.load(next);
			}
		});
		req.error(this, function(inSender, inError){
			enyo.error("ProjectConfig.init:", inError);
			if (next instanceof Function) next(inError);
		});
	},
	/**
	 * Force reloading the configuration from the storage
	 * @param {Function} next CommonJS callback
	 */
	load: function(next) {
		var data,
		    req = this.service.getFile(this.fileId);
		req.response(this, function _projectReloaded(inRequest, inResponse) {
			if (this.debug) this.log("ProjectConfig.load: file=", inResponse);
			if (typeof inResponse.content === 'string') {
				try {
					data = JSON.parse(inResponse.content);
				} catch(e) {
					enyo.error("ProjectConfig.load:", e);
					inRequest.fail(e);
				}
			} else {
				data = inResponse.content;
			}
			this.data = ProjectConfig.checkConfig(data);
			if (this.debug) this.log("ProjectConfig.load config=", this.data);
			if (this.data !== data) {
				this.save(next);
			} else {
				if (typeof next === 'function') next();
			}
		});
		req.error(this, function(inSender, inError) {
			this.error("ProjectConfig.load:", inError);
			if (next instanceof Function) next(inError);
		});
	},
	/**
	 * Save the current configuration to storage
	 * @param {Function} next CommonJS callback
	 */
	save: function(next) {
		var req;
		if (this.debug) this.log("data=", this.data);
		if (this.fileId) {
			req = this.service.putFile(this.fileId, JSON.stringify(this.data, null, 2));
		} else {
			req = this.service.createFile(this.folderId, "project.json", JSON.stringify(this.data, null, 2));
		}
		req.response(this, function _projectSaved(inSender, inResponse) {
			if (this.debug) enyo.log("ProjectConfig.save: inResponse=", inResponse);
			this.fileId = inResponse.id;
			if (next instanceof Function) next();
		});
		req.error(this, function(inSender, inError) {
			enyo.error("ProjectConfig.save: error=", inError);
			if (next instanceof Function) next(inError);
		});
	},
	statics: {
		checkConfig: function(inConfig) {
			var config = ares.clone(ProjectConfig.DEFAULT_PROJECT_CONFIG);

			// Get default project configuration for each service that can
			ServiceRegistry.instance.forEach(function(provider) {
				if ((typeof provider.getDefaultProjectConfig === 'function') &&
				    (typeof provider.getId === 'function')) {
					var providerId = provider.getId();
					config.providers[providerId] = provider.getDefaultProjectConfig();
				}
			});

			// backward compatibility: turn "build: {}" into "providers: {}"
			if (inConfig && inConfig.build) {
				inConfig.providers = inConfig.build;
				delete inConfig.providers;
			}

			// Overlay the actual configuration over the default one
			ares.extend(config, inConfig);

			enyo.log("ProjectConfig#checkConfig()", "checked config:", config);
			return config;
		},

		// used to pre-fill properties of a new project
		// contains default values
		PREFILLED_CONFIG_FOR_UI: {
			providers: {},
			preview: {
				"top_file": 'debug.html'
			}
		},

		// FIXME: the below should be replaced by proper JSON
		// schema validation with default values.
		DEFAULT_PROJECT_CONFIG: {
			id: "com.examples.apps.myapp",
			name: "BUG IF YOU SEE THIS",
			version: "0.0.1",
			title: "Example: My Application",
			description: "Description of My Application",
			author: {
				name: "An Example Company",
				href: "http://www.example.com"
			},
			providers: {},
			preview: {
				"top_file": 'debug.html'
			}
		}
	}
});
<commit_msg>ENYO-2458: drop trace & fix bad conversion of legacy project.json<commit_after>/**
 * This kind holds the consistency between the project.json and the
 * in-memory representation of the project configuration.
 *
 * After creation of the kind, use #init(service, folderId) to
 * set the location of the project.  Those values can no longer be
 * changed.
 *
 * - Use #setData and #getData to change the in-memory configuration Javascript object.
 *   data is an object containing the *whole* configuration.
 * - Then use #save to send the whole configuration to remote storage
 *
 * As it is, this kind performs zero checks on the content of the file.
 */
enyo.kind({
	name: "ProjectConfig",
	kind: "enyo.Object",
	published: {
		data: {}
	},
	service: null,		// filesystem service
	folderId: null,
	fileId: null,
	debug: false,
	create: function() {
		this.inherited(arguments);
	},
	/**
	 * Initializer: load data from project.json file
	 * @param {Object} inLocation gives information to access project.json
	 *  (expects { service: <object>, folderId: <string>)} )
	 * @param {Object} next is a CommonJS callback
	 */
	init: function(inLocation, next) {
		this.data = null;
		this.service = inLocation.service;
		this.folderId = inLocation.folderId;
		var req = this.service.propfind(this.folderId, 1);
		req.response(this, function _projectLoaded(inSender, inResponse) {
			var prj = inResponse.children.filter(function(node){
				return node.name === "project.json";
			});
			if (prj.length === 0) {
				// does not exist: create & save it immediatelly
				this.setData(ProjectConfig.checkConfig());
				// Use the folder name by default for the project name
				this.data.name = inResponse.name;
				this.save(next);
			} else {
				// already exists: load it
				this.fileId = prj[0].id;
				this.load(next);
			}
		});
		req.error(this, function(inSender, inError){
			enyo.error("ProjectConfig.init:", inError);
			if (next instanceof Function) next(inError);
		});
	},
	/**
	 * Force reloading the configuration from the storage
	 * @param {Function} next CommonJS callback
	 */
	load: function(next) {
		var data,
		    req = this.service.getFile(this.fileId);
		req.response(this, function _projectReloaded(inRequest, inResponse) {
			if (this.debug) this.log("ProjectConfig.load: file=", inResponse);
			if (typeof inResponse.content === 'string') {
				try {
					data = JSON.parse(inResponse.content);
				} catch(e) {
					enyo.error("ProjectConfig.load:", e);
					inRequest.fail(e);
				}
			} else {
				data = inResponse.content;
			}
			this.data = ProjectConfig.checkConfig(data);
			if (this.debug) this.log("ProjectConfig.load config=", this.data);
			if (this.data !== data) {
				this.save(next);
			} else {
				if (typeof next === 'function') next();
			}
		});
		req.error(this, function(inSender, inError) {
			this.error("ProjectConfig.load:", inError);
			if (next instanceof Function) next(inError);
		});
	},
	/**
	 * Save the current configuration to storage
	 * @param {Function} next CommonJS callback
	 */
	save: function(next) {
		var req;
		if (this.debug) this.log("data=", this.data);
		if (this.fileId) {
			req = this.service.putFile(this.fileId, JSON.stringify(this.data, null, 2));
		} else {
			req = this.service.createFile(this.folderId, "project.json", JSON.stringify(this.data, null, 2));
		}
		req.response(this, function _projectSaved(inSender, inResponse) {
			if (this.debug) enyo.log("ProjectConfig.save: inResponse=", inResponse);
			this.fileId = inResponse.id;
			if (next instanceof Function) next();
		});
		req.error(this, function(inSender, inError) {
			enyo.error("ProjectConfig.save: error=", inError);
			if (next instanceof Function) next(inError);
		});
	},
	statics: {
		checkConfig: function(inConfig) {
			var config = ares.clone(ProjectConfig.DEFAULT_PROJECT_CONFIG);

			// Get default project configuration for each service that can
			ServiceRegistry.instance.forEach(function(provider) {
				if ((typeof provider.getDefaultProjectConfig === 'function') &&
				    (typeof provider.getId === 'function')) {
					var providerId = provider.getId();
					config.providers[providerId] = provider.getDefaultProjectConfig();
				}
			});

			// backward compatibility: turn "build: {}" into "providers: {}"
			if (inConfig && inConfig.build) {
				inConfig.providers = inConfig.build;
				delete inConfig.build;
			}

			// Overlay the actual configuration over the default one
			config = ares.extend(config, inConfig);

			return config;
		},

		// used to pre-fill properties of a new project
		// contains default values
		PREFILLED_CONFIG_FOR_UI: {
			providers: {},
			preview: {
				"top_file": 'debug.html'
			}
		},

		// FIXME: the below should be replaced by proper JSON
		// schema validation with default values.
		DEFAULT_PROJECT_CONFIG: {
			id: "com.examples.apps.myapp",
			name: "BUG IF YOU SEE THIS",
			version: "0.0.1",
			title: "Example: My Application",
			description: "Description of My Application",
			author: {
				name: "An Example Company",
				href: "http://www.example.com"
			},
			providers: {},
			preview: {
				"top_file": 'debug.html'
			}
		}
	}
});
<|endoftext|>


<commit_before>var Store = require("./Store");

function joinClassFactory( join ) {
    return class extends Store {
        constructor() {
            super();

            var listenables = [ ...arguments, 'trigger' ];
            this[join]( ...listenables );
        }
    };
}

var JoinStrictStore     = joinClassFactory( 'joinStrict' );
var JoinLeadingStore    = joinClassFactory( 'joinLeading' );
var JoinTrailingStore   = joinClassFactory( 'joinTrailing' );
var JoinConcatStore     = joinClassFactory( 'joinConcat' );

module.exports = {
    JoinStrict  : function() { return new JoinStrictStore( ...arguments ); },
    JoinLeading : function() { return new JoinLeadingStore( ...arguments ); },
    JoinTrailing: function() { return new JoinTrailingStore( ...arguments ); },
    JoinConcat  : function() { return new JoinConcatStore( ...arguments ); }
};
<commit_msg>... need to be at the end<commit_after>var Store = require("./Store");

function joinClassFactory( join ) {
    return class extends Store {
        constructor() {
            super();

            var listenables = [ 'trigger', ...arguments ];
            this[join]( ...listenables );
        }
    };
}

var JoinStrictStore     = joinClassFactory( 'joinStrict' );
var JoinLeadingStore    = joinClassFactory( 'joinLeading' );
var JoinTrailingStore   = joinClassFactory( 'joinTrailing' );
var JoinConcatStore     = joinClassFactory( 'joinConcat' );

module.exports = {
    JoinStrict  : function() { return new JoinStrictStore( ...arguments ); },
    JoinLeading : function() { return new JoinLeadingStore( ...arguments ); },
    JoinTrailing: function() { return new JoinTrailingStore( ...arguments ); },
    JoinConcat  : function() { return new JoinConcatStore( ...arguments ); }
};
<|endoftext|>


<commit_before>giphyToken = "dc6zaTOxFJmzC"

Slack = require("slack-client")
giphy = require("giphy")(giphyToken)
var sentiment = require('sentiment');
var events = require('events');

Gifbot = function(slackToken) {
	this.users = {}

	// see link below...
	events.EventEmitter.call(this)

	this.slackToken = slackToken
	this.slack = new Slack(slackToken, false, true)

	this.slack.login()

	this.slack.on("open", function () {
		console.log("Connected at", new Date());
	})

	this.slack.on("error", function(err) {
		console.log(err);
		msg = err
		if (err == "invalid_auth") {
			msg = "Invalid slack API token: " + this.slackToken
		}
		this.emit("error", msg)
	}.bind(this))

	this.dmGif = function(searchTerms, userId, channelId) {
		// scrub search terms
		searchTerms = searchTerms.replace(/:/, "").trim()

		giphy.random({
			tag: searchTerms,
			rating: "pg-13"
		}, function(err, gif, res) {
			// 2. direct message it to the user
			dm = this.slack.getDMByName(this.slack.getUserByID(userId).name)
			if (gif.data.length == 0) {
				console.log("Gifbot couldn't find any gifs for", userId, "in", channelId, "with search terms:", searchTerms);
				dm.send("I couldn't find any gifs of `" + searchTerms + "` :(")
			}
			else {
				console.log("Gifbot suggested", gif.data.url, "for", userId, "in", channelId, "with search terms:", searchTerms);
				dm.send("> How does this one look?\n> " + gif.data.url)
				this.users[userId] = {
					"search": searchTerms,
					"latestGif": gif.data.url,
					"channelId": channelId
				}
			}
		}.bind(this))
	}

	this.slack.on("message", function(message) {
		var channel = this.slack.getChannelGroupOrDMByID(message.channel);
		var user = this.slack.getUserByID(message.user);
		// If a user mentions gifbot in a channel...
		if (message.type === 'message' && !("subtype" in message) && isMention(this.slack.self.id, message.text)) {
			searchTerms = message.text.replace(makeMention(this.slack.self.id), "")
			console.log(message.user, "requested a gif for", searchTerms, "in", message.channel);
			this.dmGif(searchTerms, user.id, channel.id)
		}
		// If the user isn't gifbot, and the message is a direct message...
		else if ((message.user != this.slack.self.id) && !("subtype" in message) && isDirect(message.channel)) {
			// If this user isn't in our list of current gifs for users, ignore it
			if (this.users[message.user] == null) {
				return
			}
			// If the user says something like "yes", post the latest gif we sent them
			else if (sentiment(message.text).score > "0") {
				// post the latest gif for the user into the channel
				console.log(message.user, "accepted and posted gif", this.users[message.user].latestGif, "to", message.channel, "with search terms:", searchTerms);
				this.slack.getChannelGroupOrDMByID(this.users[message.user].channelId).send(this.users[message.user].latestGif)
				delete this.users[message.user]
			}
			// If the user says something like "no", find a new gif for them
			else if (sentiment(message.text).score < "0") {
				console.log(message.user, "rejected gif", this.users[message.user].latestGif, "for", message.channel, "with search terms:", searchTerms);
				this.dmGif(this.users[message.user].search, user.id, this.users[message.user].channelId)
			}
			// If we didn't understand what the user just said, let the user know that they should try again
			else {
				dm = this.slack.getDMByName(this.slack.getUserByID(user.id).name)
				dm.send("I didn't quite understand that :( try again? Maybe try responding with `yes` or `no`.")
			}
		}
	}.bind(this))
}

// ???
// http://www.sitepoint.com/nodejs-events-and-eventemitter/
Gifbot.prototype.__proto__ = events.EventEmitter.prototype

makeMention = function(userId) {
	// wraps userid in slack tag
	return '<@' + userId + '>';
};

isDirect = function (channel) {
	return (channel[0] == 'D')
}

isMention = function(userId, messageText) {
	var userTag = makeMention(userId);

	return (messageText.indexOf(userTag) != -1)
};

module.exports = Gifbot
<commit_msg>basic DM-initiated suggesting happening; still trying to handle 'no channel ' error<commit_after>giphyToken = "dc6zaTOxFJmzC"

Slack = require("slack-client")
giphy = require("giphy")(giphyToken)
var sentiment = require('sentiment');
var events = require('events');

Gifbot = function(slackToken) {
	this.users = {}

	// see link below...
	events.EventEmitter.call(this)

	this.slackToken = slackToken
	this.slack = new Slack(slackToken, false, true)

	this.slack.login()

	this.slack.on("open", function () {
		console.log("Connected at", new Date());
	})

	this.slack.on("error", function(err) {
		console.log(err);
		msg = err
		if (err.msg == "invalid channel id") {
			console.log("invalid channel");
			return
		}

		if (err == "invalid_auth") {
			msg = "Invalid slack API token: " + this.slackToken
		}

		this.emit("error", msg)
	}.bind(this))

	this.dmGif = function(searchTerms, userId, channelId) {
		// scrub search terms
		searchTerms = searchTerms.replace(/:/, "").trim()

		giphy.random({
			tag: searchTerms,
			rating: "pg-13"
		}, function(err, gif, res) {
			// 2. direct message it to the user
			dm = this.slack.getDMByName(this.slack.getUserByID(userId).name)
			if (gif.data.length == 0) {
				console.log("Gifbot couldn't find any gifs for", userId, "in", channelId, "with search terms:", searchTerms);
				dm.send("I couldn't find any gifs of `" + searchTerms + "` :(")
			}
			else {
				console.log("Gifbot suggested", gif.data.url, "for", userId, "in", channelId, "with search terms:", searchTerms);
				dm.send("> How does this one look?\n> " + gif.data.url)
				this.users[userId] = {
					"search": searchTerms,
					"latestGif": gif.data.url,
					"channelId": channelId
				}
			}
		}.bind(this))
	}

	this.slack.on("message", function(message) {
		var channel = this.slack.getChannelGroupOrDMByID(message.channel);
		var user = this.slack.getUserByID(message.user);

		// If a user mentions gifbot in a channel...
		if (message.type === 'message' && !("subtype" in message) && isMention(this.slack.self.id, message.text)) {
			searchTerms = message.text.replace(makeMention(this.slack.self.id), "")
			console.log(message.user, "requested a gif for", searchTerms, "in", message.channel);
			this.dmGif(searchTerms, user.id, channel.id)
		}

		// If the user isn't gifbot, and the message is a direct message...
		else if ((message.user != this.slack.self.id) && !("subtype" in message) && isDirect(message.channel)) {
			// If the message contains a reference to a channel, then start suggesting a gif
			targetChannel = containsChannel(message.text)
			if (targetChannel) {
				searchTerms = message.text.replace(makeChannel(targetChannel), "")
				console.log(message.user, "requested a gif for", searchTerms, "in", targetChannel);
				this.dmGif(searchTerms, user.id, targetChannel)
			}

			// If this user isn't in our list of current gifs for users, ignore it
			else if (this.users[message.user] == null) {
				return
			}

			// If the user says something like "yes", post the latest gif we sent them
			else if (sentiment(message.text).score > "0") {
				// post the latest gif for the user into the channel
				console.log(message.user, "accepted and posted gif", this.users[message.user].latestGif, "to", message.channel, "with search terms:", searchTerms);
				this.slack.getChannelGroupOrDMByID(this.users[message.user].channelId).send(this.users[message.user].latestGif)
				delete this.users[message.user]
			}

			// If the user says something like "no", find a new gif for them
			else if (sentiment(message.text).score < "0") {
				console.log(message.user, "rejected gif", this.users[message.user].latestGif, "for", message.channel, "with search terms:", searchTerms);
				this.dmGif(this.users[message.user].search, user.id, this.users[message.user].channelId)
			}

			// If we didn't understand what the user just said, let the user know that they should try again
			else {
				dm = this.slack.getDMByName(this.slack.getUserByID(user.id).name)
				dm.send("I didn't quite understand that :( try again? Maybe try responding with `yes` or `no`.")
			}
		}
	}.bind(this))
}

// ???
// http://www.sitepoint.com/nodejs-events-and-eventemitter/
Gifbot.prototype.__proto__ = events.EventEmitter.prototype

makeMention = function(userId) {
	// wraps userid in slack tag
	return '<@' + userId + '>';
};

isDirect = function (channel) {
	return (channel[0] == 'D')
}

isMention = function(userId, messageText) {
	var userTag = makeMention(userId);

	return (messageText.indexOf(userTag) != -1)
};

containsChannel = function (messageText) {
	// e.g. "<#C078S7E81>"
	regex = /<#(C.*?)>/
	result = regex.exec(messageText)
	if (result) {
		return result[1]
	}
	else {
		return null
	}
}

makeChannel = function (channelId) {
	return '<#' + channelId + '>';
}

module.exports = Gifbot
<|endoftext|>


<commit_before>giphyToken = "dc6zaTOxFJmzC"

Slack = require("slack-client")
giphy = require("giphy")(giphyToken)
var sentiment = require('sentiment');
var events = require('events');
var utils = require('./utils');
var Log = require('log');
log = new Log("info")

Gifbot = function(slackToken) {
	this.users = {}

	// see link below...
	events.EventEmitter.call(this)

	this.slackToken = slackToken
	this.slack = new Slack(slackToken, false, true)

	this.slack.login()

	this.slack.on("open", function () {
		log.info("Connected");
	})

	this.slack.on("error", function(err) {
		log.error(err);
		msg = err
		if (err.msg == "invalid channel id") {
			log.info("Gifbot attempted to post a gif to an invalid channel, or a channel that gifbot isn't a member of");
			return
		}

		if (err == "invalid_auth") {
			msg = "Invalid slack API token: " + this.slackToken
		}

		this.emit("error", msg)
	}.bind(this))

	this.dmGif = function(searchTerms, userId, channelId) {

		searchTerms = utils.scrubSearchString(searchTerms)

		giphy.random({
			tag: searchTerms,
			rating: "pg-13"
		}, function(err, gif, res) {
			// 2. direct message it to the user
			dm = this.slack.getDMByName(this.slack.getUserByID(userId).name)
			if (gif.data.length == 0) {
				// gifbot can't find any gifs
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", userId, channelId, searchTerms, "no_gifs", null)
				dm.send("I couldn't find any gifs of `" + searchTerms + "` :(")
			}
			else {
				// gifbot suggests a gif
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", userId, channelId, searchTerms, "suggestion", gif.data.url)
				dm.send("> How does this one look?\n> " + gif.data.url)
				this.users[userId] = {
					"search": searchTerms,
					"latestGif": gif.data.url,
					"channelId": channelId,
					"isSearching": true,
					"datetime": new Date()
				}
			}
		}.bind(this))
	}

	this.slack.on("message", function(message) {
		var channel = this.slack.getChannelGroupOrDMByID(message.channel);
		var user = this.slack.getUserByID(message.user);

		searchTerms = message.text

		// If a user mentions gifbot in a channel...
		if (message.type === 'message' && !("subtype" in message) && utils.isMention(this.slack.self.id, message.text)) {
			// log.info(message.user, "requested a gif for", searchTerms, "in", message.channel);
			log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", message.user, message.channel, utils.scrubSearchString(searchTerms), "request", null)

			this.dmGif(searchTerms, user.id, channel.id)
		}

		// If the user isn't gifbot, and the message is a direct message...
		else if ((message.user != this.slack.self.id) && !("subtype" in message) && utils.isDirect(message.channel)) {

			// If the message contains a reference to a channel, then start suggesting a gif
			targetChannel = utils.containsChannel(message.text)
			if (targetChannel) {
				// log.info(message.user, "requested a gif for", searchTerms, "in", targetChannel);
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", message.user, targetChannel, utils.scrubSearchString(searchTerms), "request", null)

				this.dmGif(searchTerms, user.id, targetChannel)
			}

			// If we've never seen this user before, ignore it
			else if (this.users[message.user] == null) {
				return
			}

			// If the user says something like "yes", post the latest gif we sent them
			else if (sentiment(message.text).score > "0") {
				// post the latest gif for the user into the channel
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", message.user, this.users[message.user].channelId, this.users[message.user].search, "accept", this.users[message.user].latestGif)
				// log.info(message.user, "accepted and posted gif", this.users[message.user].latestGif, "to", message.channel, "with search terms:", searchTerms);
				this.slack.getChannelGroupOrDMByID(this.users[message.user].channelId).send(this.users[message.user].latestGif)
				// this.users[message.user]["isSearching"] = false
				delete this.users[message.user]
			}

			// If the user says something like "no", find a new gif for them
			else if (sentiment(message.text).score < "0") {
				// log.info(message.user, "rejected gif", this.users[message.user].latestGif, "for", message.channel, "with search terms:", searchTerms);
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", message.user, this.users[message.user].channelId, this.users[message.user].search, "reject", this.users[message.user].latestGif)
				this.dmGif(this.users[message.user].search, user.id, this.users[message.user].channelId)
			}

			// If we didn't understand what the user just said, let the user know that they should try again
			else {
				dm = this.slack.getDMByName(this.slack.getUserByID(user.id).name)
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", message.user, this.users[message.user].channelId, this.users[message.user].search, "bad_response", this.users[message.user].latestGif)
				dm.send("I didn't quite understand that :( try again? Maybe try responding with `yes` or `no`.")
			}
		}
	}.bind(this))
}

// ???
// http://www.sitepoint.com/nodejs-events-and-eventemitter/
Gifbot.prototype.__proto__ = events.EventEmitter.prototype

module.exports = Gifbot
<commit_msg>mobile-dm-initiated channel name fix<commit_after>giphyToken = "dc6zaTOxFJmzC"

Slack = require("slack-client")
giphy = require("giphy")(giphyToken)
var sentiment = require('sentiment');
var events = require('events');
var utils = require('./utils');
var Log = require('log');
log = new Log("info")

Gifbot = function(slackToken) {
	this.users = {}

	// see link below...
	events.EventEmitter.call(this)

	this.slackToken = slackToken
	this.slack = new Slack(slackToken, false, true)

	this.slack.login()

	this.slack.on("open", function () {
		log.info("Connected");
	})

	this.slack.on("error", function(err) {
		log.error(err);
		msg = err
		if (err.msg == "invalid channel id") {
			log.info("Gifbot attempted to post a gif to an invalid channel, or a channel that gifbot isn't a member of");
			return
		}

		if (err == "invalid_auth") {
			msg = "Invalid slack API token: " + this.slackToken
		}

		this.emit("error", msg)
	}.bind(this))

	this.dmGif = function(searchTerms, userId, channelId) {

		searchTerms = utils.scrubSearchString(searchTerms)

		giphy.random({
			tag: searchTerms,
			rating: "pg-13"
		}, function(err, gif, res) {
			// 2. direct message it to the user
			dm = this.slack.getDMByName(this.slack.getUserByID(userId).name)
			if (gif.data.length == 0) {
				// gifbot can't find any gifs
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", userId, channelId, searchTerms, "no_gifs", null)
				dm.send("I couldn't find any gifs of `" + searchTerms + "` :(")
			}
			else {
				// gifbot suggests a gif
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", userId, channelId, searchTerms, "suggestion", gif.data.url)
				dm.send("> How does this one look?\n> " + gif.data.url)
				this.users[userId] = {
					"search": searchTerms,
					"latestGif": gif.data.url,
					"channelId": channelId,
					"isSearching": true,
					"datetime": new Date()
				}
			}
		}.bind(this))
	}

	this.slack.on("message", function(message) {
		var channel = this.slack.getChannelGroupOrDMByID(message.channel);
		var user = this.slack.getUserByID(message.user);

		searchTerms = message.text

		// If a user mentions gifbot in a channel...
		if (message.type === 'message' && !("subtype" in message) && utils.isMention(this.slack.self.id, message.text)) {
			// log.info(message.user, "requested a gif for", searchTerms, "in", message.channel);
			log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", message.user, message.channel, utils.scrubSearchString(searchTerms), "request", null)

			this.dmGif(searchTerms, user.id, channel.id)
		}

		// If the user isn't gifbot, and the message is a direct message...
		else if ((message.user != this.slack.self.id) && !("subtype" in message) && utils.isDirect(message.channel)) {

			// If the message contains a reference to a channel, then start suggesting a gif
			targetChannel = utils.containsChannel(message.text)
			if (targetChannel) {
				if (targetChannel.indexOf("|") >= 0) {
					targetChannel = targetChannel.split("|")[0]
				}

				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", message.user, targetChannel, utils.scrubSearchString(searchTerms), "request", null)

				this.dmGif(searchTerms, user.id, targetChannel)
			}

			// If we've never seen this user before, ignore it
			else if (this.users[message.user] == null) {
				return
			}

			// If the user says something like "yes", post the latest gif we sent them
			else if (sentiment(message.text).score > "0") {
				// post the latest gif for the user into the channel
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", message.user, this.users[message.user].channelId, this.users[message.user].search, "accept", this.users[message.user].latestGif)
				// log.info(message.user, "accepted and posted gif", this.users[message.user].latestGif, "to", message.channel, "with search terms:", searchTerms);
				this.slack.getChannelGroupOrDMByID(this.users[message.user].channelId).send(this.users[message.user].latestGif)
				// this.users[message.user]["isSearching"] = false
				delete this.users[message.user]
			}

			// If the user says something like "no", find a new gif for them
			else if (sentiment(message.text).score < "0") {
				// log.info(message.user, "rejected gif", this.users[message.user].latestGif, "for", message.channel, "with search terms:", searchTerms);
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", message.user, this.users[message.user].channelId, this.users[message.user].search, "reject", this.users[message.user].latestGif)
				this.dmGif(this.users[message.user].search, user.id, this.users[message.user].channelId)
			}

			// If we didn't understand what the user just said, let the user know that they should try again
			else {
				dm = this.slack.getDMByName(this.slack.getUserByID(user.id).name)
				log.info("User: %s \t Channel: %s \t Search terms: %s \t Action: %s \t Gif: %s", message.user, this.users[message.user].channelId, this.users[message.user].search, "bad_response", this.users[message.user].latestGif)
				dm.send("I didn't quite understand that :( try again? Maybe try responding with `yes` or `no`.")
			}
		}
	}.bind(this))
}

// ???
// http://www.sitepoint.com/nodejs-events-and-eventemitter/
Gifbot.prototype.__proto__ = events.EventEmitter.prototype

module.exports = Gifbot
<|endoftext|>


<commit_before>/**
 * Copyright 2015 IBM Corp. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* global OfflineAudioContext */
'use strict';

var utils = require('./utils');
/**
 * Captures microphone input from the browser.
 * Works at least on latest versions of Firefox and Chrome
 */
function Microphone(_options) {
  var options = _options || {};

  // we record in mono because the speech recognition service
  // does not support stereo.
  this.bufferSize = options.bufferSize || 8192;
  this.inputChannels = options.inputChannels || 1;
  this.outputChannels = options.outputChannels || 1;
  this.recording = false;
  this.requestedAccess = false;
  this.sampleRate = 16000;
  // auxiliar buffer to keep unused samples (used when doing downsampling)
  this.bufferUnusedSamples = new Float32Array(0);

  // Chrome or Firefox or IE User media
  if (!navigator.getUserMedia) {
    navigator.getUserMedia = navigator.webkitGetUserMedia ||
    navigator.mozGetUserMedia || navigator.msGetUserMedia;
  }

}

/**
 * Called when the user reject the use of the michrophone
 * @param  error The error
 */
Microphone.prototype.onPermissionRejected = function() {
  console.log('Microphone.onPermissionRejected()');
  this.requestedAccess = false;
  this.onError('Permission to access the microphone rejeted.');
};

Microphone.prototype.onError = function(error) {
  console.log('Microphone.onError():', error);
};

/**
 * Called when the user authorizes the use of the microphone.
 * @param  {Object} stream The Stream to connect to
 *
 */
Microphone.prototype.onMediaStream =  function(stream) {
  var AudioCtx = window.AudioContext || window.webkitAudioContext;

  if (!AudioCtx)
    throw new Error('AudioContext not available');

  if (!this.audioContext)
    this.audioContext = new AudioCtx();

  var gain = this.audioContext.createGain();
  var audioInput = this.audioContext.createMediaStreamSource(stream);

  audioInput.connect(gain);

  this.mic = this.audioContext.createScriptProcessor(this.bufferSize,
    this.inputChannels, this.outputChannels);

  // uncomment the following line if you want to use your microphone sample rate
  //this.sampleRate = this.audioContext.sampleRate;
  console.log('Microphone.onMediaStream(): sampling rate is:', this.sampleRate);

  this.mic.onaudioprocess = this._onaudioprocess.bind(this);
  this.stream = stream;

  gain.connect(this.mic);
  this.mic.connect(this.audioContext.destination);
  this.recording = true;
  this.requestedAccess = false;
  this.onStartRecording();
};

/**
 * callback that is being used by the microphone
 * to send audio chunks.
 * @param  {object} data audio
 */
Microphone.prototype._onaudioprocess = function(data) {
  if (!this.recording) {
    // We speak but we are not recording
    return;
  }

  // Single channel
  var chan = data.inputBuffer.getChannelData(0);

  //resampler(this.audioContext.sampleRate,data.inputBuffer,this.onAudio);

  this.onAudio(this._exportDataBufferTo16Khz(new Float32Array(chan)));

  //export with microphone mhz, remember to update the this.sampleRate
  // with the sample rate from your microphone
  // this.onAudio(this._exportDataBuffer(new Float32Array(chan)));

};

/**
 * Start the audio recording
 */
Microphone.prototype.record = function() {
  if (!navigator.getUserMedia){
    this.onError('Browser doesn\'t support microphone input');
    return;
  }
  if (this.requestedAccess) {
    return;
  }

  this.requestedAccess = true;
  navigator.getUserMedia({ audio: true },
    this.onMediaStream.bind(this), // Microphone permission granted
    this.onPermissionRejected.bind(this)); // Microphone permission rejected
};

/**
 * Stop the audio recording
 */
Microphone.prototype.stop = function() {
  if (!this.recording)
    return;
  this.recording = false;
  this.stream.stop();
  this.requestedAccess = false;
  this.mic.disconnect(0);
  this.mic = null;
  this.onStopRecording();
};

/**
 * Creates a Blob type: 'audio/l16' with the chunk and downsampling to 16 kHz
 * coming from the microphone.
 * Explanation for the math: The raw values captured from the Web Audio API are
 * in 32-bit Floating Point, between -1 and 1 (per the specification).
 * The values for 16-bit PCM range between -32768 and +32767 (16-bit signed integer).
 * Multiply to control the volume of the output. We store in little endian.
 * @param  {Object} buffer Microphone audio chunk
 * @return {Blob} 'audio/l16' chunk
 * @deprecated This method is depracated
 */
Microphone.prototype._exportDataBufferTo16Khz = function(bufferNewSamples) {
  var buffer = null,
    newSamples = bufferNewSamples.length,
    unusedSamples = this.bufferUnusedSamples.length;

  if (unusedSamples > 0) {
    buffer = new Float32Array(unusedSamples + newSamples);
    for (var i = 0; i < unusedSamples; ++i) {
      buffer[i] = this.bufferUnusedSamples[i];
    }
    for (i = 0; i < newSamples; ++i) {
      buffer[unusedSamples + i] = bufferNewSamples[i];
    }
  } else {
    buffer = bufferNewSamples;
  }

  // downsampling variables
  var filter = [
      -0.037935, -0.00089024, 0.040173, 0.019989, 0.0047792, -0.058675, -0.056487,
      -0.0040653, 0.14527, 0.26927, 0.33913, 0.26927, 0.14527, -0.0040653, -0.056487,
      -0.058675, 0.0047792, 0.019989, 0.040173, -0.00089024, -0.037935
    ],
    samplingRateRatio = this.audioContext.sampleRate / 16000,
    nOutputSamples = Math.floor((buffer.length - filter.length) / (samplingRateRatio)) + 1,
    pcmEncodedBuffer16k = new ArrayBuffer(nOutputSamples * 2),
    dataView16k = new DataView(pcmEncodedBuffer16k),
    index = 0,
    volume = 0x7FFF, //range from 0 to 0x7FFF to control the volume
    nOut = 0;

  for (var i = 0; i + filter.length - 1 < buffer.length; i = Math.round(samplingRateRatio * nOut)) {
    var sample = 0;
    for (var j = 0; j < filter.length; ++j) {
      sample += buffer[i + j] * filter[j];
    }
    sample *= volume;
    dataView16k.setInt16(index, sample, true); // 'true' -> means little endian
    index += 2;
    nOut++;
  }

  var indexSampleAfterLastUsed = Math.round(samplingRateRatio * nOut);
  var remaining = buffer.length - indexSampleAfterLastUsed;
  if (remaining > 0) {
    this.bufferUnusedSamples = new Float32Array(remaining);
    for (i = 0; i < remaining; ++i) {
      this.bufferUnusedSamples[i] = buffer[indexSampleAfterLastUsed + i];
    }
  } else {
    this.bufferUnusedSamples = new Float32Array(0);
  }

  return new Blob([dataView16k], {
    type: 'audio/l16'
  });
  };

// native way of resampling captured audio
var resampler = function(sampleRate, audioBuffer, callbackProcessAudio) {

	console.log('length: ' + audioBuffer.length + ' ' + sampleRate);
	var channels = 1;
	var targetSampleRate = 16000;
   var numSamplesTarget = audioBuffer.length * targetSampleRate / sampleRate;

   var offlineContext = new OfflineAudioContext(channels, numSamplesTarget, targetSampleRate);
   var bufferSource = offlineContext.createBufferSource();
   bufferSource.buffer = audioBuffer;

	// callback that is called when the resampling finishes
   offlineContext.oncomplete = function(event) {
      var samplesTarget = event.renderedBuffer.getChannelData(0);
      console.log('Done resampling: ' + samplesTarget.length + ' samples produced');

		// convert from [-1,1] range of floating point numbers to [-32767,32767] range of integers
		var index = 0;
		var volume = 0x7FFF;
  		var pcmEncodedBuffer = new ArrayBuffer(samplesTarget.length*2);    // short integer to byte
  		var dataView = new DataView(pcmEncodedBuffer);
      for (var i = 0; i < samplesTarget.length; i++) {
    		dataView.setInt16(index, samplesTarget[i]*volume, true);
    		index += 2;
  		}

      // l16 is the MIME type for 16-bit PCM
      callbackProcessAudio(new Blob([dataView], { type: 'audio/l16' }));
   };

   bufferSource.connect(offlineContext.destination);
   bufferSource.start(0);
   offlineContext.startRendering();
};

/**
 * Creates a Blob type: 'audio/l16' with the
 * chunk coming from the microphone.
 */
var exportDataBuffer = function(buffer, bufferSize) {
  var pcmEncodedBuffer = null,
    dataView = null,
    index = 0,
    volume = 0x7FFF; //range from 0 to 0x7FFF to control the volume

  pcmEncodedBuffer = new ArrayBuffer(bufferSize * 2);
  dataView = new DataView(pcmEncodedBuffer);

  /* Explanation for the math: The raw values captured from the Web Audio API are
   * in 32-bit Floating Point, between -1 and 1 (per the specification).
   * The values for 16-bit PCM range between -32768 and +32767 (16-bit signed integer).
   * Multiply to control the volume of the output. We store in little endian.
   */
  for (var i = 0; i < buffer.length; i++) {
    dataView.setInt16(index, buffer[i] * volume, true);
    index += 2;
  }

  // l16 is the MIME type for 16-bit PCM
  return new Blob([dataView], { type: 'audio/l16' });
};

Microphone.prototype._exportDataBuffer = function(buffer){
  utils.exportDataBuffer(buffer, this.bufferSize);
};

// Functions used to control Microphone events listeners.
Microphone.prototype.onStartRecording =  function() {};
Microphone.prototype.onStopRecording =  function() {};
Microphone.prototype.onAudio =  function() {};

module.exports = Microphone;
<commit_msg>Update Microphone.js<commit_after>/**
 * Copyright 2015 IBM Corp. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* global OfflineAudioContext */
'use strict';

var utils = require('./utils');
/**
 * Captures microphone input from the browser.
 * Works at least on latest versions of Firefox and Chrome
 */
function Microphone(_options) {
  var options = _options || {};

  // we record in mono because the speech recognition service
  // does not support stereo.
  this.bufferSize = options.bufferSize || 8192;
  this.inputChannels = options.inputChannels || 1;
  this.outputChannels = options.outputChannels || 1;
  this.recording = false;
  this.requestedAccess = false;
  this.sampleRate = 16000;
  // auxiliar buffer to keep unused samples (used when doing downsampling)
  this.bufferUnusedSamples = new Float32Array(0);

  // Chrome or Firefox or IE User media
  if (!navigator.getUserMedia) {
    navigator.getUserMedia = navigator.webkitGetUserMedia ||
    navigator.mozGetUserMedia || navigator.msGetUserMedia;
  }

}

/**
 * Called when the user reject the use of the michrophone
 * @param  error The error
 */
Microphone.prototype.onPermissionRejected = function() {
  console.log('Microphone.onPermissionRejected()');
  this.requestedAccess = false;
  this.onError('Permission to access the microphone rejeted.');
};

Microphone.prototype.onError = function(error) {
  console.log('Microphone.onError():', error);
};

/**
 * Called when the user authorizes the use of the microphone.
 * @param  {Object} stream The Stream to connect to
 *
 */
Microphone.prototype.onMediaStream =  function(stream) {
  var AudioCtx = window.AudioContext || window.webkitAudioContext;

  if (!AudioCtx)
    throw new Error('AudioContext not available');

  if (!this.audioContext)
    this.audioContext = new AudioCtx();

  var gain = this.audioContext.createGain();
  var audioInput = this.audioContext.createMediaStreamSource(stream);

  audioInput.connect(gain);

  this.mic = this.audioContext.createScriptProcessor(this.bufferSize,
    this.inputChannels, this.outputChannels);

  // uncomment the following line if you want to use your microphone sample rate
  //this.sampleRate = this.audioContext.sampleRate;
  console.log('Microphone.onMediaStream(): sampling rate is:', this.sampleRate);

  this.mic.onaudioprocess = this._onaudioprocess.bind(this);
  this.stream = stream;

  gain.connect(this.mic);
  this.mic.connect(this.audioContext.destination);
  this.recording = true;
  this.requestedAccess = false;
  this.onStartRecording();
};

/**
 * callback that is being used by the microphone
 * to send audio chunks.
 * @param  {object} data audio
 */
Microphone.prototype._onaudioprocess = function(data) {
  if (!this.recording) {
    // We speak but we are not recording
    return;
  }

  // Single channel
  var chan = data.inputBuffer.getChannelData(0);

  //resampler(this.audioContext.sampleRate,data.inputBuffer,this.onAudio);

  this.onAudio(this._exportDataBufferTo16Khz(new Float32Array(chan)));

  //export with microphone mhz, remember to update the this.sampleRate
  // with the sample rate from your microphone
  // this.onAudio(this._exportDataBuffer(new Float32Array(chan)));

};

/**
 * Start the audio recording
 */
Microphone.prototype.record = function() {
  if (!navigator.getUserMedia){
    this.onError('Browser doesn\'t support microphone input');
    return;
  }
  if (this.requestedAccess) {
    return;
  }

  this.requestedAccess = true;
  navigator.getUserMedia({ audio: true },
    this.onMediaStream.bind(this), // Microphone permission granted
    this.onPermissionRejected.bind(this)); // Microphone permission rejected
};

/**
 * Stop the audio recording
 */
Microphone.prototype.stop = function() {
  if (!this.recording)
    return;
  this.recording = false;
  this.stream.getTracks()[0].stop();
  this.requestedAccess = false;
  this.mic.disconnect(0);
  this.mic = null;
  this.onStopRecording();
};

/**
 * Creates a Blob type: 'audio/l16' with the chunk and downsampling to 16 kHz
 * coming from the microphone.
 * Explanation for the math: The raw values captured from the Web Audio API are
 * in 32-bit Floating Point, between -1 and 1 (per the specification).
 * The values for 16-bit PCM range between -32768 and +32767 (16-bit signed integer).
 * Multiply to control the volume of the output. We store in little endian.
 * @param  {Object} buffer Microphone audio chunk
 * @return {Blob} 'audio/l16' chunk
 * @deprecated This method is depracated
 */
Microphone.prototype._exportDataBufferTo16Khz = function(bufferNewSamples) {
  var buffer = null,
    newSamples = bufferNewSamples.length,
    unusedSamples = this.bufferUnusedSamples.length;

  if (unusedSamples > 0) {
    buffer = new Float32Array(unusedSamples + newSamples);
    for (var i = 0; i < unusedSamples; ++i) {
      buffer[i] = this.bufferUnusedSamples[i];
    }
    for (i = 0; i < newSamples; ++i) {
      buffer[unusedSamples + i] = bufferNewSamples[i];
    }
  } else {
    buffer = bufferNewSamples;
  }

  // downsampling variables
  var filter = [
      -0.037935, -0.00089024, 0.040173, 0.019989, 0.0047792, -0.058675, -0.056487,
      -0.0040653, 0.14527, 0.26927, 0.33913, 0.26927, 0.14527, -0.0040653, -0.056487,
      -0.058675, 0.0047792, 0.019989, 0.040173, -0.00089024, -0.037935
    ],
    samplingRateRatio = this.audioContext.sampleRate / 16000,
    nOutputSamples = Math.floor((buffer.length - filter.length) / (samplingRateRatio)) + 1,
    pcmEncodedBuffer16k = new ArrayBuffer(nOutputSamples * 2),
    dataView16k = new DataView(pcmEncodedBuffer16k),
    index = 0,
    volume = 0x7FFF, //range from 0 to 0x7FFF to control the volume
    nOut = 0;

  for (var i = 0; i + filter.length - 1 < buffer.length; i = Math.round(samplingRateRatio * nOut)) {
    var sample = 0;
    for (var j = 0; j < filter.length; ++j) {
      sample += buffer[i + j] * filter[j];
    }
    sample *= volume;
    dataView16k.setInt16(index, sample, true); // 'true' -> means little endian
    index += 2;
    nOut++;
  }

  var indexSampleAfterLastUsed = Math.round(samplingRateRatio * nOut);
  var remaining = buffer.length - indexSampleAfterLastUsed;
  if (remaining > 0) {
    this.bufferUnusedSamples = new Float32Array(remaining);
    for (i = 0; i < remaining; ++i) {
      this.bufferUnusedSamples[i] = buffer[indexSampleAfterLastUsed + i];
    }
  } else {
    this.bufferUnusedSamples = new Float32Array(0);
  }

  return new Blob([dataView16k], {
    type: 'audio/l16'
  });
  };

// native way of resampling captured audio
var resampler = function(sampleRate, audioBuffer, callbackProcessAudio) {

	console.log('length: ' + audioBuffer.length + ' ' + sampleRate);
	var channels = 1;
	var targetSampleRate = 16000;
   var numSamplesTarget = audioBuffer.length * targetSampleRate / sampleRate;

   var offlineContext = new OfflineAudioContext(channels, numSamplesTarget, targetSampleRate);
   var bufferSource = offlineContext.createBufferSource();
   bufferSource.buffer = audioBuffer;

	// callback that is called when the resampling finishes
   offlineContext.oncomplete = function(event) {
      var samplesTarget = event.renderedBuffer.getChannelData(0);
      console.log('Done resampling: ' + samplesTarget.length + ' samples produced');

		// convert from [-1,1] range of floating point numbers to [-32767,32767] range of integers
		var index = 0;
		var volume = 0x7FFF;
  		var pcmEncodedBuffer = new ArrayBuffer(samplesTarget.length*2);    // short integer to byte
  		var dataView = new DataView(pcmEncodedBuffer);
      for (var i = 0; i < samplesTarget.length; i++) {
    		dataView.setInt16(index, samplesTarget[i]*volume, true);
    		index += 2;
  		}

      // l16 is the MIME type for 16-bit PCM
      callbackProcessAudio(new Blob([dataView], { type: 'audio/l16' }));
   };

   bufferSource.connect(offlineContext.destination);
   bufferSource.start(0);
   offlineContext.startRendering();
};

/**
 * Creates a Blob type: 'audio/l16' with the
 * chunk coming from the microphone.
 */
var exportDataBuffer = function(buffer, bufferSize) {
  var pcmEncodedBuffer = null,
    dataView = null,
    index = 0,
    volume = 0x7FFF; //range from 0 to 0x7FFF to control the volume

  pcmEncodedBuffer = new ArrayBuffer(bufferSize * 2);
  dataView = new DataView(pcmEncodedBuffer);

  /* Explanation for the math: The raw values captured from the Web Audio API are
   * in 32-bit Floating Point, between -1 and 1 (per the specification).
   * The values for 16-bit PCM range between -32768 and +32767 (16-bit signed integer).
   * Multiply to control the volume of the output. We store in little endian.
   */
  for (var i = 0; i < buffer.length; i++) {
    dataView.setInt16(index, buffer[i] * volume, true);
    index += 2;
  }

  // l16 is the MIME type for 16-bit PCM
  return new Blob([dataView], { type: 'audio/l16' });
};

Microphone.prototype._exportDataBuffer = function(buffer){
  utils.exportDataBuffer(buffer, this.bufferSize);
};

// Functions used to control Microphone events listeners.
Microphone.prototype.onStartRecording =  function() {};
Microphone.prototype.onStopRecording =  function() {};
Microphone.prototype.onAudio =  function() {};

module.exports = Microphone;
<|endoftext|>


<commit_before>import {MS, MOZ, WEBKIT, RULESET, KEYFRAMES, DECLARATION} from './Enum.js'
import {match, charat, substr, strlen, sizeof, replace, combine} from './Utility.js'
import {copy, tokenize} from './Tokenizer.js'
import {serialize} from './Serializer.js'
import {prefix} from './Prefixer.js'

/**
 * @param {function[]} collection
 * @return {function}
 */
export function middleware (collection) {
	var length = sizeof(collection)

	return function (element, index, children, callback) {
		var output = ''

		for (var i = 0; i < length; i++)
			output += collection[i](element, index, children, callback) || ''

		return output
	}
}

/**
 * @param {function} callback
 * @return {function}
 */
export function rulesheet (callback) {
	return function (element) {
		if (!element.root)
			if (element = element.return)
				callback(element)
	}
}

/**
 * @param {object} element
 * @param {number} index
 * @param {object[]} children
 * @param {function} callback
 */
export function prefixer (element, index, children, callback) {
	if (element.length > -1)
		if (!element.return)
			switch (element.type) {
				case DECLARATION: element.return = prefix(element.value, element.length)
					break
				case KEYFRAMES:
					return serialize([copy(element, {value: replace(element.value, '@', '@' + WEBKIT)})], callback)
				case RULESET:
					if (element.length)
						return combine(element.props, function (value) {
							switch (match(value, /(::plac\w+|:read-\w+)/)) {
								// :read-(only|write)
								case ':read-only': case ':read-write':
									return serialize([copy(element, {props: [replace(value, /:(read-\w+)/, ':' + MOZ + '$1')]})], callback)
								// :placeholder
								case '::placeholder':
									return serialize([
										copy(element, {props: [replace(value, /:(plac\w+)/, ':' + WEBKIT + 'input-$1')]}),
										copy(element, {props: [replace(value, /:(plac\w+)/, ':' + MOZ + '$1')]}),
										copy(element, {props: [replace(value, /:(plac\w+)/, MS + 'input-$1')]})
									], callback)
							}

							return ''
						})
			}
}

/**
 * @param {object} element
 * @param {number} index
 * @param {object[]} children
 */
export function namespace (element) {
	switch (element.type) {
		case RULESET:
			element.props = element.props.map(function (value) {
				return combine(tokenize(value), function (value, index, children) {
					switch (charat(value, 0)) {
						// \f
						case 12:
							return substr(value, 1, strlen(value))
						// \0 ( + > ~
						case 0: case 40: case 43: case 62: case 126:
							return value
						// :
						case 58:
							if (children[++index] === 'global')
								children[index] = '', children[++index] = '\f' + substr(children[index], index = 1, -1)
						// \s
						case 32:
							return index === 1 ? '' : value
						default:
							switch (index) {
								case 0: element = value
									return sizeof(children) > 1 ? '' : value
								case index = sizeof(children) - 1: case 2:
									return index === 2 ? value + element + element : value + element
								default:
									return value
							}
					}
				})
			})
	}
}
<commit_msg>feat(#119): implement grid-(column|row)-(start|end)<commit_after>import {MS, MOZ, WEBKIT, RULESET, KEYFRAMES, DECLARATION} from './Enum.js'
import {match, charat, substr, strlen, sizeof, replace, combine} from './Utility.js'
import {copy, tokenize} from './Tokenizer.js'
import {serialize} from './Serializer.js'
import {prefix} from './Prefixer.js'

/**
 * @param {function[]} collection
 * @return {function}
 */
export function middleware (collection) {
	var length = sizeof(collection)

	return function (element, index, children, callback) {
		var output = ''

		for (var i = 0; i < length; i++)
			output += collection[i](element, index, children, callback) || ''

		return output
	}
}

/**
 * @param {function} callback
 * @return {function}
 */
export function rulesheet (callback) {
	return function (element) {
		if (!element.root)
			if (element = element.return)
				callback(element)
	}
}

/**
 * @param {object} element
 * @param {number} index
 * @param {object[]} children
 * @param {function} callback
 */
export function prefixer (element, index, children, callback) {
	if (element.length > -1)
		if (!element.return)
			switch (element.type) {
				case DECLARATION:
					switch (element.props) {
						case 'grid-row-start': case 'grid-column-start':
							var end
							if (
								end = element	&& element.parent && element.parent.children && element.parent.children.find(
									item => item.type === DECLARATION && match(item.props, /grid-(row|column)-end/)
								)
							) {
								element.return = match(element.value, /span/)
									? element.value
									: (
										MS + replace(element.value, '-start', '')
										+ element.value
										+ MS + 'grid-row-span:' + (+match(end.value, /\d+/) - +match(element.value, /\d+/)) + ';'
									)
							} else {
								element.return = MS + replace(element.value, '-start', '') + element.value
							}
							break
						case 'grid-row-end': case 'grid-column-end':
							element.return = element && element.parent && element.parent.children && element.parent.children.some(
								item => item.type === DECLARATION && match(item.props, /grid-(row|column)-start/)
							)
								? element.value
								: MS + replace(replace(element.value, '-end', '-span'), 'span ', '') + element.value
							break
						default:
							element.return = prefix(element.value, element.length)
							break
					}
					break
				case KEYFRAMES:
					return serialize([copy(element, {value: replace(element.value, '@', '@' + WEBKIT)})], callback)
				case RULESET:
					if (element.length)
						return combine(element.props, function (value) {
							switch (match(value, /(::plac\w+|:read-\w+)/)) {
								// :read-(only|write)
								case ':read-only': case ':read-write':
									return serialize([copy(element, {props: [replace(value, /:(read-\w+)/, ':' + MOZ + '$1')]})], callback)
								// :placeholder
								case '::placeholder':
									return serialize([
										copy(element, {props: [replace(value, /:(plac\w+)/, ':' + WEBKIT + 'input-$1')]}),
										copy(element, {props: [replace(value, /:(plac\w+)/, ':' + MOZ + '$1')]}),
										copy(element, {props: [replace(value, /:(plac\w+)/, MS + 'input-$1')]})
									], callback)
							}

							return ''
						})
			}
}

/**
 * @param {object} element
 * @param {number} index
 * @param {object[]} children
 */
export function namespace (element) {
	switch (element.type) {
		case RULESET:
			element.props = element.props.map(function (value) {
				return combine(tokenize(value), function (value, index, children) {
					switch (charat(value, 0)) {
						// \f
						case 12:
							return substr(value, 1, strlen(value))
						// \0 ( + > ~
						case 0: case 40: case 43: case 62: case 126:
							return value
						// :
						case 58:
							if (children[++index] === 'global')
								children[index] = '', children[++index] = '\f' + substr(children[index], index = 1, -1)
						// \s
						case 32:
							return index === 1 ? '' : value
						default:
							switch (index) {
								case 0: element = value
									return sizeof(children) > 1 ? '' : value
								case index = sizeof(children) - 1: case 2:
									return index === 2 ? value + element + element : value + element
								default:
									return value
							}
					}
				})
			})
	}
}
<|endoftext|>