<commit_before>/**
 * @providesModule InputScrollView
 * @author Junjie.Bai
 * @license MIT
 */

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
    StyleSheet,
    View,
    ScrollView,
    TextInput,
    KeyboardAvoidingView,
    Keyboard,
    Platform,
    Animated,
    UIManager,
} from 'react-native';
import clone from 'lodash/clone';

const isIOS = Platform.OS === 'ios';

let debounce;

if (isIOS) {
    debounce = function(func, wait) {
        wait = wait || 0;
        let id, count;
        let action = function(event) {
            if (count) {
                count--;
                id = requestAnimationFrame(() => action.call(this, event));
            } else {
                func.call(this, event);
            }
        };
        return function(event) {
            const e = clone(event);
            cancelAnimationFrame(id);
            count = wait;
            action.call(this, e);
        };
    };
} else {
    debounce = function(func, wait) {
        wait = wait || 0;
        let id, count;
        let action = function(event) {
            if (count) {
                count--;
                id = setTimeout(() => action.call(this, event));
            } else {
                func.call(this, event);
            }
        };
        return function(event) {
            const e = clone(event);
            clearTimeout(id);
            count = wait;
            action.call(this, e);
        };
    };
}

export default class extends Component {
    static propTypes = {
        keyboardOffset: PropTypes.number,
        multilineInputStyle: PropTypes.oneOfType([
            PropTypes.object,
            PropTypes.array,
            PropTypes.number,
        ]),
        useAnimatedScrollView: PropTypes.bool,
    };

    static defaultProps = {
        keyboardOffset: 40,
        multilineInputStyle: null,
        useAnimatedScrollView: false,
    };

    state = {
        measureInputVisible: false,
        measureInputValue: '',
        measureInputWidth: 0,
        contentBottomOffset: 0,
    };

    componentWillMount() {
        this._root = null;
        this._curFocus = null;
        this._measureCallback = null;
        this._keyboardShow = false;
        this._topOffset = 0;
        this._inputInfoMap = {};

        this._addListener();
        this._extendScrollViewFunc();
    }

    componentWillUnmount() {
        this._removeListener();
    }

    render() {
        const {
            keyboardOffset,
            multilineInputStyle,
            children,
            useAnimatedScrollView,
            ...otherProps
        } = this.props;

        const {
            measureInputVisible,
            measureInputValue,
            measureInputWidth,
            contentBottomOffset,
        } = this.state;

        const newChildren = this._cloneDeepComponents(children);

        const ScrollComponent = useAnimatedScrollView ? Animated.ScrollView : ScrollView;

        return (
            <KeyboardAvoidingView behavior={isIOS ? 'padding' : null}>
                <View style={styles.wrap}>
                    <ScrollComponent ref={this._onRef}
                                     onMomentumScrollEnd={this._onMomentumScrollEnd}
                                     onFocus={this._onFocus}
                                     onBlur={this._onBlur} {...otherProps}>
                        <View style={{ marginBottom: contentBottomOffset }}
                              onStartShouldSetResponderCapture={isIOS ? this._onTouchStart : null}
                              onResponderMove={this._onTouchMove}
                              onResponderRelease={this._onTouchEnd}>
                            {newChildren}
                            <View style={styles.hidden}
                                  pointerEvents="none">
                                {
                                    measureInputVisible &&
                                    <TextInput style={[multilineInputStyle, { width: measureInputWidth }]}
                                               value={measureInputValue}
                                               onContentSizeChange={this._onContentSizeChangeMeasureInput}
                                               editable={false}
                                               multiline />
                                }
                            </View>
                        </View>
                    </ScrollComponent>
                </View>
            </KeyboardAvoidingView>
        );
    }

    _addListener() {
        this._keyboardShowListener = Keyboard.addListener(isIOS ? 'keyboardWillShow' : 'keyboardDidShow', this._onKeyboardShow);
        this._keyboardHideListener = Keyboard.addListener(isIOS ? 'keyboardWillHide' : 'keyboardDidHide', this._onKeyboardHide);
    }

    _removeListener() {
        this._keyboardShowListener && this._keyboardShowListener.remove();
        this._keyboardHideListener && this._keyboardHideListener.remove();
        this._keyboardShowListener = null;
        this._keyboardHideListener = null;
    }

    _extendScrollViewFunc() {
        const funcArray = [
            'scrollTo',
            'scrollToEnd',
        ];

        funcArray.forEach(funcName => {
            this[funcName] = (...args) => {
                this._root[funcName](...args);
            };
        });
    }

    _cloneDeepComponents(Component) {
        if (isArray(Component)) {
            return Component.map(subComponent => this._cloneDeepComponents(subComponent));
        } else if (Component && Component.props && Component.props.children) {
            const newComponent = { ...Component };
            newComponent.props = { ...Component.props };
            newComponent.props.children = this._cloneDeepComponents(Component.props.children);
            return newComponent;
        } else if (Component && Component.props && Component.props.multiline) {
            const newComponent = { ...Component };
            newComponent.props = { ...Component.props };
            return this._addMultilineHandle(newComponent);
        } else {
            return Component;
        }
    }

    _addMultilineHandle(Component) {
        const onChange = Component.props.onChange;
        const onSelectionChange = Component.props.onSelectionChange;
        const onContentSizeChange = Component.props.onContentSizeChange;

        Component.props.onChange = event => {
            this._onChange(event);
            onChange &&
                onChange(event);
        };

        Component.props.onSelectionChange = event => {
            const e = clone(event);
            if (isIOS) {
                // 确保处理代码在 onChange 之后执行
                // release 版本必须使用 requestAnimationFrame
                requestAnimationFrame(() => this._onSelectionChange(e));
            } else {
                setTimeout(() => this._onSelectionChange(e));
            }
            onSelectionChange &&
                onSelectionChange(event);
        };

        // 使用防抖函数有两个目的
        // - 确保 scrollToKeyboardRequest 在 onSelectionChange 之后执行
        // - 短时间内不会重复执行 onContentSizeChange，因为当一次粘贴进许多行文本时，可能会连续触发多次 onContentSizeChange
        Component.props.onContentSizeChange = debounce(event => {
            this._onContentSizeChange(event);
            onContentSizeChange &&
                onContentSizeChange(event);
        }, 2);

        return Component;
    }

    _getInputInfo(target) {
        return this._inputInfoMap[target] = this._inputInfoMap[target] || {};
    }

    _measureCursorPosition(text, width, callback) {
        this._measureCallback = callback;
        this.setState({
            measureInputVisible: true,
            measureInputValue: text,
            measureInputWidth: width,
        });
    }

    // 这里必须使用防抖函数
    // 因为在真机上，当行数增多时，每调整一次 measureInputValue 的值，onContentSizeChange 都会触发多次。
    // 如果不使用防抖函数，那么在 onContentSizeChange 第一次触发时，measureInputVisible 就会被设置为 false，导致无法获取正确的值。
    // 但在模拟器上没有这个问题。
    _onContentSizeChangeMeasureInput = debounce(event => {
        if (!this._measureCallback) return;
        this._measureCallback(event.nativeEvent.contentSize.height);
        this._measureCallback = null;
        this.setState({ measureInputVisible: false });
    }, 3);

    _onRef = root => {
        const { useAnimatedScrollView } = this.props;
        if (!root) return;
        this._root = root;

        if (useAnimatedScrollView && this._root._component) {
            this._root = this._root._component;
        }

        setTimeout(() => {
            this._root._innerViewRef &&
            this._root._innerViewRef.measureInWindow((x, y) => {
                this._topOffset = y;
            });
        });
    };

    _onMomentumScrollEnd = event => {
        if (!this._keyboardShow) return;
        const contentBottomOffset = Math.max(
            0,
            this.state.contentBottomOffset +
            event.nativeEvent.layoutMeasurement.height + // layoutMeasurement 可视区域的大小
            event.nativeEvent.contentOffset.y -
            event.nativeEvent.contentSize.height
        );
        this.setState({ contentBottomOffset });
    };

    _scrollToKeyboardRequest = () => {
        if (!this._keyboardShow) return;

        const curFocusTarget = this._curFocus;
        if (!curFocusTarget) return;

        const scrollResponder = this._root && this._root.getScrollResponder();
        if (!scrollResponder) return;

        UIManager.viewIsDescendantOf(
            curFocusTarget,
            scrollResponder.getInnerViewNode(),
            (isAncestor) => {
                if (!isAncestor) return;

                const { text, selectionEnd, width, height } = this._getInputInfo(curFocusTarget);
                const cursorAtLastLine = !text ||
                    selectionEnd === undefined ||
                    text.length === selectionEnd;

                if (cursorAtLastLine) {
                    return this._scrollToKeyboard(curFocusTarget, 0);
                }

                this._measureCursorPosition(
                    text.substr(0, selectionEnd),
                    width,
                    cursorRelativeTopOffset => {
                        this._scrollToKeyboard(
                            curFocusTarget,
                            Math.max(0, height - cursorRelativeTopOffset)
                        );
                    }
                );
            }
        );
    };

    _scrollToKeyboard = (target, offset) => {
        const toKeyboardOffset = this._topOffset + this.props.keyboardOffset - offset;
        this._root.scrollResponderScrollNativeHandleToKeyboard(target, toKeyboardOffset, true);
    };

    _onKeyboardShow = () => {
        this._keyboardShow = true;
        this._scrollToKeyboardRequest();
    };

    _onKeyboardHide = () => {
        this._keyboardShow = false;
        let atBottom = !!this.state.contentBottomOffset;
        this.setState({ contentBottomOffset: 0 }, () => {
            if (atBottom) {
                setTimeout(() => {
                    this._root.scrollToEnd({ animated: true });
                });
            }
        });
    };

    // 这个方法是为了防止 ScrollView 在滑动结束后触发 TextInput 的 focus 事件
    _onTouchStart = event => {
        const target = event.target || event.currentTarget;
        if (target === this._curFocus) return false;

        const targetInst = event._targetInst;
        let uiViewClassName;
        uiViewClassName = targetInst.type || // >= react-native 0.49
            targetInst.viewConfig.uiViewClassName; // <= react-native 0.48
        return uiViewClassName === 'RCTTextField' || uiViewClassName === 'RCTTextView';
    };

    // 在单行 TextInput 中
    // onFocus 在 keyboardWillShow 与 keyboardDidShow 之前触发
    // 在多行 TextInput 中
    // onFocus 在 keyboardDidShow 之前触发
    // onFocus 在 keyboardWillShow 之后触发
    _onFocus = event => {
        // 当 onStartShouldSetResponderCapture 返回 true 时
        // 被激活的 TextInput 无法使用 Keyboard.dismiss() 来收起键盘
        // TextInput.State.currentlyFocusedField() 也无法获取当前焦点ID
        // 原因可能是系统并未判定 TextInput 获取焦点，这可能是一个 bug
        // 通常需要在 onStartShouldSetResponderCapture 返回 false 的情况下再点击一次 TextInput 才能恢复正常
        // 所以这里手动再设置一次焦点
        const target = event.target || event.currentTarget;
        this._curFocus = target;
        focus(target);

        const inputInfo = this._getInputInfo(target);
        const multiline = getProps(event._targetInst).multiline;

        if (multiline) {
            if (inputInfo.text === undefined) {
                const props = getProps(event._targetInst);
                inputInfo.text = props.value || props.defaultValue;
            }

            if (!isIOS) return;

            inputInfo.onFocusRequireScroll = true;
            setTimeout(() => {
                // 如果 onSelectionChange 没有触发，则在这里执行
                if (this._keyboardShow && inputInfo.onFocusRequireScroll) {
                    inputInfo.onFocusRequireScroll = false;
                    this._scrollToKeyboardRequest();
                }
            }, 250);
        } else {
            if (isIOS) this._scrollToKeyboardRequest();
        }
    };

    _onBlur = event => {
        const target = event.target || event.currentTarget;
        if (this._curFocus === target) this._curFocus = null;
    }

    // onChange 在 onContentSizeChange 之前触发
    // onChange 在 onSelectionChange 之后触发
    _onChange = event => {
        const target = event.target || event.currentTarget;
        const inputInfo = this._getInputInfo(target);
        inputInfo.text = event.nativeEvent.text;
    }

    // onSelectionChange 在 keyboardDidShow 之前触发
    // onSelectionChange 在 onContentSizeChange 之前触发
    // onSelectionChange 在 onFocus 之后触发
    _onSelectionChange = event => {
        const target = event.target || event.currentTarget;
        const inputInfo = this._getInputInfo(target);
        inputInfo.selectionEnd = event.nativeEvent.selection.end;
        if (inputInfo.text === undefined) {
            inputInfo.text = getProps(event._targetInst).value;
        }

        if (!isIOS) return;

        if (inputInfo.onFocusRequireScroll) {
            inputInfo.onFocusRequireScroll = false;
            this._scrollToKeyboardRequest();
        }
    };

    _onContentSizeChange = event => {
        const target = event.target || event.currentTarget;
        const inputInfo = this._getInputInfo(target);
        inputInfo.width = event.nativeEvent.contentSize.width;
        inputInfo.height = event.nativeEvent.contentSize.height;
        if (inputInfo.text === undefined) {
            inputInfo.text = getProps(event._targetInst).value;
        }
        this._scrollToKeyboardRequest(true);
    };
}

function focus(targetTag) {
    if (isIOS) {
        UIManager.focus(targetTag);
    } else {
        UIManager.dispatchViewManagerCommand(
            targetTag,
            UIManager.AndroidTextInput.Commands.focusTextInput,
            null,
        );
    }
}

function getProps(targetNode) {
    return targetNode.memoizedProps || // >= react-native 0.49
        targetNode._currentElement.props; // <= react-native 0.48
}

function isArray(arr) {
    return Object.prototype.toString.call(arr) === '[object Array]';
}

const styles = StyleSheet.create({
    wrap: {
        height: '100%',
    },

    hidden: {
        position: 'absolute',
        top: 0,
        left: 0,
        opacity: 0,
    },
});
<commit_msg>Fix/native event is null (#1)<commit_after>/**
 * @providesModule InputScrollView
 * @author Junjie.Bai
 * @license MIT
 */

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
    StyleSheet,
    View,
    ScrollView,
    TextInput,
    KeyboardAvoidingView,
    Keyboard,
    Platform,
    Animated,
    UIManager,
} from 'react-native';
import clone from 'lodash/clone';

const isIOS = Platform.OS === 'ios';

let debounce;

if (isIOS) {
    debounce = function(func, wait) {
        wait = wait || 0;
        let id, count;
        let action = function(event) {
            if (count) {
                count--;
                id = requestAnimationFrame(() => action.call(this, event));
            } else {
                func.call(this, event);
            }
        };
        return function(event) {
            const e = clone(event);
            cancelAnimationFrame(id);
            count = wait;
            action.call(this, e);
        };
    };
} else {
    debounce = function(func, wait) {
        wait = wait || 0;
        let id, count;
        let action = function(event) {
            if (count) {
                count--;
                id = setTimeout(() => action.call(this, event));
            } else {
                func.call(this, event);
            }
        };
        return function(event) {
            const e = clone(event);
            clearTimeout(id);
            count = wait;
            action.call(this, e);
        };
    };
}

export default class extends Component {
    static propTypes = {
        keyboardOffset: PropTypes.number,
        multilineInputStyle: PropTypes.oneOfType([
            PropTypes.object,
            PropTypes.array,
            PropTypes.number,
        ]),
        useAnimatedScrollView: PropTypes.bool,
    };

    static defaultProps = {
        keyboardOffset: 40,
        multilineInputStyle: null,
        useAnimatedScrollView: false,
    };

    state = {
        measureInputVisible: false,
        measureInputValue: '',
        measureInputWidth: 0,
        contentBottomOffset: 0,
    };

    componentWillMount() {
        this._root = null;
        this._curFocus = null;
        this._measureCallback = null;
        this._keyboardShow = false;
        this._topOffset = 0;
        this._inputInfoMap = {};

        this._addListener();
        this._extendScrollViewFunc();
    }

    componentWillUnmount() {
        this._removeListener();
    }

    render() {
        const {
            keyboardOffset,
            multilineInputStyle,
            children,
            useAnimatedScrollView,
            ...otherProps
        } = this.props;

        const {
            measureInputVisible,
            measureInputValue,
            measureInputWidth,
            contentBottomOffset,
        } = this.state;

        const newChildren = this._cloneDeepComponents(children);

        const ScrollComponent = useAnimatedScrollView ? Animated.ScrollView : ScrollView;

        return (
            <KeyboardAvoidingView behavior={isIOS ? 'padding' : null}>
                <View style={styles.wrap}>
                    <ScrollComponent ref={this._onRef}
                                     onMomentumScrollEnd={this._onMomentumScrollEnd}
                                     onFocus={this._onFocus}
                                     onBlur={this._onBlur} {...otherProps}>
                        <View style={{ marginBottom: contentBottomOffset }}
                              onStartShouldSetResponderCapture={isIOS ? this._onTouchStart : null}
                              onResponderMove={this._onTouchMove}
                              onResponderRelease={this._onTouchEnd}>
                            {newChildren}
                            <View style={styles.hidden}
                                  pointerEvents="none">
                                {
                                    measureInputVisible &&
                                    <TextInput style={[multilineInputStyle, { width: measureInputWidth }]}
                                               value={measureInputValue}
                                               onContentSizeChange={event => this._onContentSizeChangeMeasureInput(clone(event))}
                                               editable={false}
                                               multiline />
                                }
                            </View>
                        </View>
                    </ScrollComponent>
                </View>
            </KeyboardAvoidingView>
        );
    }

    _addListener() {
        this._keyboardShowListener = Keyboard.addListener(isIOS ? 'keyboardWillShow' : 'keyboardDidShow', this._onKeyboardShow);
        this._keyboardHideListener = Keyboard.addListener(isIOS ? 'keyboardWillHide' : 'keyboardDidHide', this._onKeyboardHide);
    }

    _removeListener() {
        this._keyboardShowListener && this._keyboardShowListener.remove();
        this._keyboardHideListener && this._keyboardHideListener.remove();
        this._keyboardShowListener = null;
        this._keyboardHideListener = null;
    }

    _extendScrollViewFunc() {
        const funcArray = [
            'scrollTo',
            'scrollToEnd',
        ];

        funcArray.forEach(funcName => {
            this[funcName] = (...args) => {
                this._root[funcName](...args);
            };
        });
    }

    _cloneDeepComponents(Component) {
        if (isArray(Component)) {
            return Component.map(subComponent => this._cloneDeepComponents(subComponent));
        } else if (Component && Component.props && Component.props.children) {
            const newComponent = { ...Component };
            newComponent.props = { ...Component.props };
            newComponent.props.children = this._cloneDeepComponents(Component.props.children);
            return newComponent;
        } else if (Component && Component.props && Component.props.multiline) {
            const newComponent = { ...Component };
            newComponent.props = { ...Component.props };
            return this._addMultilineHandle(newComponent);
        } else {
            return Component;
        }
    }

    _onContentSizeChangeDebounce = debounce((event, onContentSizeChange) => {
        this._onContentSizeChange(event);
        onContentSizeChange && onContentSizeChange(event);
    }, 2);

    _addMultilineHandle(Component) {
        const onChange = Component.props.onChange;
        const onSelectionChange = Component.props.onSelectionChange;
        const onContentSizeChange = Component.props.onContentSizeChange;

        Component.props.onChange = event => {
            this._onChange(event);
            onChange &&
                onChange(event);
        };

        Component.props.onSelectionChange = event => {
            const e = clone(event);
            if (isIOS) {
                // 确保处理代码在 onChange 之后执行
                // release 版本必须使用 requestAnimationFrame
                requestAnimationFrame(() => this._onSelectionChange(e));
            } else {
                setTimeout(() => this._onSelectionChange(e));
            }
            onSelectionChange &&
                onSelectionChange(event);
        };

        // 使用防抖函数有两个目的
        // - 确保 scrollToKeyboardRequest 在 onSelectionChange 之后执行
        // - 短时间内不会重复执行 onContentSizeChange，因为当一次粘贴进许多行文本时，可能会连续触发多次 onContentSizeChange
        Component.props.onContentSizeChange = event => {
            const e = clone(event);
            this._onContentSizeChangeDebounce(e, onContentSizeChange);
        };

        return Component;
    }

    _getInputInfo(target) {
        return this._inputInfoMap[target] = this._inputInfoMap[target] || {};
    }

    _measureCursorPosition(text, width, callback) {
        this._measureCallback = callback;
        this.setState({
            measureInputVisible: true,
            measureInputValue: text,
            measureInputWidth: width,
        });
    }

    // 这里必须使用防抖函数
    // 因为在真机上，当行数增多时，每调整一次 measureInputValue 的值，onContentSizeChange 都会触发多次。
    // 如果不使用防抖函数，那么在 onContentSizeChange 第一次触发时，measureInputVisible 就会被设置为 false，导致无法获取正确的值。
    // 但在模拟器上没有这个问题。
    _onContentSizeChangeMeasureInput = debounce(event => {
        if (!this._measureCallback) return;
        this._measureCallback(event.nativeEvent.contentSize.height);
        this._measureCallback = null;
        this.setState({ measureInputVisible: false });
    }, 3);

    _onRef = root => {
        const { useAnimatedScrollView } = this.props;
        if (!root) return;
        this._root = root;

        if (useAnimatedScrollView && this._root._component) {
            this._root = this._root._component;
        }

        setTimeout(() => {
            this._root._innerViewRef &&
            this._root._innerViewRef.measureInWindow((x, y) => {
                this._topOffset = y;
            });
        });
    };

    _onMomentumScrollEnd = event => {
        if (!this._keyboardShow) return;
        const contentBottomOffset = Math.max(
            0,
            this.state.contentBottomOffset +
            event.nativeEvent.layoutMeasurement.height + // layoutMeasurement 可视区域的大小
            event.nativeEvent.contentOffset.y -
            event.nativeEvent.contentSize.height
        );
        this.setState({ contentBottomOffset });
    };

    _scrollToKeyboardRequest = () => {
        if (!this._keyboardShow) return;

        const curFocusTarget = this._curFocus;
        if (!curFocusTarget) return;

        const scrollResponder = this._root && this._root.getScrollResponder();
        if (!scrollResponder) return;

        UIManager.viewIsDescendantOf(
            curFocusTarget,
            scrollResponder.getInnerViewNode(),
            (isAncestor) => {
                if (!isAncestor) return;

                const { text, selectionEnd, width, height } = this._getInputInfo(curFocusTarget);
                const cursorAtLastLine = !text ||
                    selectionEnd === undefined ||
                    text.length === selectionEnd;

                if (cursorAtLastLine) {
                    return this._scrollToKeyboard(curFocusTarget, 0);
                }

                this._measureCursorPosition(
                    text.substr(0, selectionEnd),
                    width,
                    cursorRelativeTopOffset => {
                        this._scrollToKeyboard(
                            curFocusTarget,
                            Math.max(0, height - cursorRelativeTopOffset)
                        );
                    }
                );
            }
        );
    };

    _scrollToKeyboard = (target, offset) => {
        const toKeyboardOffset = this._topOffset + this.props.keyboardOffset - offset;
        this._root.scrollResponderScrollNativeHandleToKeyboard(target, toKeyboardOffset, true);
    };

    _onKeyboardShow = () => {
        this._keyboardShow = true;
        this._scrollToKeyboardRequest();
    };

    _onKeyboardHide = () => {
        this._keyboardShow = false;
        let atBottom = !!this.state.contentBottomOffset;
        this.setState({ contentBottomOffset: 0 }, () => {
            if (atBottom) {
                setTimeout(() => {
                    this._root.scrollToEnd({ animated: true });
                });
            }
        });
    };

    // 这个方法是为了防止 ScrollView 在滑动结束后触发 TextInput 的 focus 事件
    _onTouchStart = event => {
        const target = event.target || event.currentTarget;
        if (target === this._curFocus) return false;

        const targetInst = event._targetInst;
        let uiViewClassName;
        uiViewClassName = targetInst.type || // >= react-native 0.49
            targetInst.viewConfig.uiViewClassName; // <= react-native 0.48
        return uiViewClassName === 'RCTTextField' || uiViewClassName === 'RCTTextView';
    };

    // 在单行 TextInput 中
    // onFocus 在 keyboardWillShow 与 keyboardDidShow 之前触发
    // 在多行 TextInput 中
    // onFocus 在 keyboardDidShow 之前触发
    // onFocus 在 keyboardWillShow 之后触发
    _onFocus = event => {
        // 当 onStartShouldSetResponderCapture 返回 true 时
        // 被激活的 TextInput 无法使用 Keyboard.dismiss() 来收起键盘
        // TextInput.State.currentlyFocusedField() 也无法获取当前焦点ID
        // 原因可能是系统并未判定 TextInput 获取焦点，这可能是一个 bug
        // 通常需要在 onStartShouldSetResponderCapture 返回 false 的情况下再点击一次 TextInput 才能恢复正常
        // 所以这里手动再设置一次焦点
        const target = event.target || event.currentTarget;
        this._curFocus = target;
        focus(target);

        const inputInfo = this._getInputInfo(target);
        const multiline = getProps(event._targetInst).multiline;

        if (multiline) {
            if (inputInfo.text === undefined) {
                const props = getProps(event._targetInst);
                inputInfo.text = props.value || props.defaultValue;
            }

            if (!isIOS) return;

            inputInfo.onFocusRequireScroll = true;
            setTimeout(() => {
                // 如果 onSelectionChange 没有触发，则在这里执行
                if (this._keyboardShow && inputInfo.onFocusRequireScroll) {
                    inputInfo.onFocusRequireScroll = false;
                    this._scrollToKeyboardRequest();
                }
            }, 250);
        } else {
            if (isIOS) this._scrollToKeyboardRequest();
        }
    };

    _onBlur = event => {
        const target = event.target || event.currentTarget;
        if (this._curFocus === target) this._curFocus = null;
    }

    // onChange 在 onContentSizeChange 之前触发
    // onChange 在 onSelectionChange 之后触发
    _onChange = event => {
        const target = event.target || event.currentTarget;
        const inputInfo = this._getInputInfo(target);
        inputInfo.text = event.nativeEvent.text;
    }

    // onSelectionChange 在 keyboardDidShow 之前触发
    // onSelectionChange 在 onContentSizeChange 之前触发
    // onSelectionChange 在 onFocus 之后触发
    _onSelectionChange = event => {
        const target = event.target || event.currentTarget;
        const inputInfo = this._getInputInfo(target);
        inputInfo.selectionEnd = event.nativeEvent.selection.end;
        if (inputInfo.text === undefined) {
            inputInfo.text = getProps(event._targetInst).value;
        }

        if (!isIOS) return;

        if (inputInfo.onFocusRequireScroll) {
            inputInfo.onFocusRequireScroll = false;
            this._scrollToKeyboardRequest();
        }
    };

    _onContentSizeChange = event => {
        const target = event.target || event.currentTarget;
        const inputInfo = this._getInputInfo(target);
        inputInfo.width = event.nativeEvent.contentSize.width;
        inputInfo.height = event.nativeEvent.contentSize.height;
        if (inputInfo.text === undefined) {
            inputInfo.text = getProps(event._targetInst).value;
        }
        this._scrollToKeyboardRequest(true);
    };
}

function focus(targetTag) {
    if (isIOS) {
        UIManager.focus(targetTag);
    } else {
        UIManager.dispatchViewManagerCommand(
            targetTag,
            UIManager.AndroidTextInput.Commands.focusTextInput,
            null,
        );
    }
}

function getProps(targetNode) {
    return targetNode.memoizedProps || // >= react-native 0.49
        targetNode._currentElement.props; // <= react-native 0.48
}

function isArray(arr) {
    return Object.prototype.toString.call(arr) === '[object Array]';
}

const styles = StyleSheet.create({
    wrap: {
        height: '100%',
    },

    hidden: {
        position: 'absolute',
        top: 0,
        left: 0,
        opacity: 0,
    },
});
<|endoftext|>


<commit_before>var hubiquitus = require('hubiquitus-core');
var logger = hubiquitus.logger('hubiquitus:addons:qos');
var _ = require('lodash');
var util = require('util');
var EventEmitter = require('events').EventEmitter;

const timeout = 2000;

var targets = {};

/**
 * Target
 */
var Target = (function () {

  /**
   * Target
   * @param id {String} target identity
   * @constructor
   */
  function Target(id) {
    var _this = this;
    EventEmitter.call(this);
    this.id = id;
    this.rate = 1; // msg/s for this target
    this.queue = [];
    this._resDelays = [];
    this._reqCount = 0;

    this.on('req', function () {
      _this._reqCount++;
    });

    this.on('res', function (date) {
      _this._resDelays.push(now() - date);
    });

    this.loop();
  }

  util.inherits(Target, EventEmitter);

  /**
   * Target internal loop : every seconds
   * - Compute the new rate
   * - Flush the queue util rate is reached
   */
  Target.prototype.loop = function () {
    var _this = this;

    var loopIt = 0;
    setInterval(function () {
      var lockedLoopIt = loopIt++;

      // compute the new rate every seconds
      var len = _this._resDelays.length;
      if (len === 0) return;
      var average = _.reduce(_this._resDelays, function (average, item) {
        return average + item / len;
      });
      _this.rate = Math.floor(len * 1000 / average);
      _this._resDelays = [];
      _this._reqCount = 0;

      // flush queue (check that current sent count < rate to avoid dequeue-enqueue)
      while (lockedLoopIt === loopIt && _this.isOpen()) {
        var msg = _this.queue.shift();
        exports.send(msg.from, msg.to, msg.content, msg.done);
      }
    }, 1000);
  };

  Target.prototype.isOpen = function () {
    return this._reqCount < this.rate;
  };

  return Target;
})();

/**
 * Send a message
 * @param {String} from
 * @param {String} to
 * @param {Object} [content]
 * @param {Function} [done]
 */
exports.send = function (from, to, content, done) {
  logger.trace('send message with qos', {from: from, to: to, content: content, done: !!done});

  var target = targets[to];
  if (!target) target = targets[to] = new Target(to);

  if (target.isOpen()) {
    logger.trace('target opened, sending request', {target: target.id, rate: target.rate});
    target.emit('req');
    var date = now();
    hubiquitus.send(from, to, content, timeout, function (err) {
      logger.trace('response from target', {target: target.id, err: err});
      if (err && err.code === 'TIMEOUT') {
        target.queue.push({from: from, to: to, content: content, done: done});
        target.emit('res', date);
      } else {
        done && done(err);
        if (!err) target.emit('res', date);
      }
    }, {safe: true});
  } else {
    logger.trace('target not opened, queue message', {target: target.id});
    target.queue.push({from: from, to: to, content: content, done: done});
  }
};

/**
 * Middleware
 * @param {String} type
 * @param {Object} req
 * @param {Function} next
 */
exports.middleware = function (type, req, next) {
  if (type !== 'req_in' || !req.headers.safe) return next();

  logger.trace('middleware processing request...', {req: req});
  if (new Date().getTime() - req.date > req.timeout) {
    return logger.trace('timeout excedeed !', {req: req});
  }
  req.reply();
  next();
};

/**
 * Returns current timestamp
 * @returns {Number}
 */
function now() {
  return new Date().getTime();
}
<commit_msg>improve qos algo<commit_after>var hubiquitus = require('hubiquitus-core');
var logger = hubiquitus.logger('hubiquitus:addons:qos');
var _ = require('lodash');
var util = require('util');
var EventEmitter = require('events').EventEmitter;

const timeout = 2000;

var targets = {};

/**
 * Target
 */
var Target = (function () {

  /**
   * Target
   * @param id {String} target identity
   * @constructor
   */
  function Target(id) {
    var _this = this;
    EventEmitter.call(this);
    this.id = id;
    this.rate = 100; // msg/s for this target
    this.queue = [];
    this._resDelays = [];
    this._resAverageLatency = 0;
    this._reqCount = 0;

    this.on('req', function () {
      _this._reqCount++;
    });

    this.on('res', function (date) {
      _this._resDelays.push(date);
    });

    this.loop();
  }

  util.inherits(Target, EventEmitter);

  /**
   * Target internal loop : every seconds
   * - Compute the new rate
   * - Flush the queue util rate is reached
   */
  Target.prototype.loop = function () {
    var _this = this;

    var loopIt = 0;
    setInterval(function () {
      var lockedLoopIt = ++loopIt;

      // compute the new rate every seconds

      var len = _this._resDelays.length;
      if (len !== 0) {
        var sum = _.reduce(_this._resDelays, function (sum, item) {
          return sum + item;
        });
        _this._resAverageLatency = sum / len;
        /*
         * 1 msg <=> A ms = A/1000 s (average)
         * X msg <=> 1 s  ==> x = 1/A/1000 = 1000/A
         * Asynchronous model => we have to take care of parallelism => x = msgCount * x
         * 3 msg : [50ms, 100ms, 150ms] => 1 msg : 100ms (average) but msg are processed in parallel => 3 msg ~ 100ms
         */
        _this.rate = Math.floor(len * 1000 / _this._resAverageLatency) || 1;
        _this._resDelays = [];
        _this._reqCount = 0;
      }

      // flush queue (check that current sent count < rate to avoid dequeue-enqueue)
      while (lockedLoopIt === loopIt && _this.isOpen() && _this.queue.length > 0) {
        var msg = _this.queue.shift();
        exports.send(msg.from, msg.to, msg.content, msg.done);
      }
    }, 1000);
  };

  Target.prototype.isOpen = function () {
    return this._reqCount < this.rate;
  };

  Target.prototype.toString = function () {
    return this.id + '> rate : ' + this.rate + ' msg/s\n' +
      this._reqCount + ' items sent\n' +
      this._resDelays.length + ' ack|timeout received (average : ' + this._resAverageLatency + ' ms)\n' +
      this.queue.length + ' items in queue\n';
  };

  return Target;
})();

/**
 * Send a message
 * @param {String} from
 * @param {String} to
 * @param {Object} [content]
 * @param {Function} [done]
 */
exports.send = function (from, to, content, done) {
  logger.trace('send message with qos', {from: from, to: to, content: content, done: !!done});

  var target = targets[to];
  if (!target) target = targets[to] = new Target(to);

  if (target.isOpen()) {
    logger.trace('target opened, sending request', {target: target.id, rate: target.rate});
    target.emit('req');
    var date = now();
    hubiquitus.send(from, to, content, timeout, function (err) {
      var time = now() - date;
      logger.trace('response from target', {target: target.id, err: err});
      if (err && err.code === 'TIMEOUT') {
        target.queue.push({from: from, to: to, content: content, done: done});
        target.emit('res', date);
      } else {
        done && done(err);
        if (!err) target.emit('res', time);
      }
    }, {safe: true});
  } else {
    logger.trace('target not opened, queue message', {target: target.id, rate: target.rate});
    target.queue.push({from: from, to: to, content: content, done: done});
  }
};

/**
 * Middleware
 * @param {String} type
 * @param {Object} req
 * @param {Function} next
 */
exports.middleware = function (type, req, next) {
  if (type !== 'req_in' || !req.headers.safe) return next();

  logger.trace('middleware processing request...', {req: req});
  if (new Date().getTime() - req.date > req.timeout) {
    return logger.trace('timeout excedeed !', {req: req});
  }
  req.reply();
  next();
};

/**
 * Returns current timestamp
 * @returns {Number}
 */
function now() {
  return new Date().getTime();
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

require('lazy-ass');
var pkg = require('./package.json');
var ecstatic = require('ecstatic');
var check = require('check-types');
var istanbul = require('istanbul');
var Collector = istanbul.Collector;
var instrumenter = new istanbul.Instrumenter();
var fs = require('fs');
var path = require('path');
var http = require('http'),
  httpProxy = require('http-proxy');
var savedReportDir = './html-report';

require('./src/check-updates')();

var program = require('./src/cli-options')();

la(check.unemptyString(program.target), 'missing target server url', program);

function shouldInstrument(regex, url) {
  la(check.instance(regex, RegExp), 'not a regex', regex);
  la(check.unemptyString(url), 'expected url string');
  return regex.test(url);
}

var shouldBeInstrumented = shouldInstrument.bind(null, new RegExp(program.instrument));

var setupCoverageSend = require('./src/send-coverage');
la(check.fn(setupCoverageSend), 'missing send coverage function');

function writeCoverageReports(coverage) {
  la(check.object(coverage), 'missing coverage object');
  var Report = istanbul.Report;

  var collector = new Collector();
  collector.add(coverage);

  var summaryReport = Report.create('text-summary');
  summaryReport.writeReport(collector);
  var htmlReport = Report.create('html');
  htmlReport.writeReport(collector, true);
}

var coverageFilename = path.join(savedReportDir, 'coverage.json');

function resetCoverage() {
  if (fs.existsSync(coverageFilename)) {
    fs.unlinkSync(coverageFilename);
    console.log('deleted previous coverage file', coverageFilename);
  }
}

if (program.reset) {
  resetCoverage();
}

if (!fs.existsSync(savedReportDir)) {
  fs.mkdirSync(savedReportDir);
  console.log('created folder', savedReportDir);
}

function combineCoverage(coverage) {
  var collector = new Collector();
  if (fs.existsSync(coverageFilename)) {
    collector.add(JSON.parse(fs.readFileSync(coverageFilename, 'utf8')));
  }
  collector.add(coverage);
  var combined = collector.getFinalCoverage();
  fs.writeFileSync(coverageFilename, JSON.stringify(combined, null, '  '));
  console.log('saved combined coverage to', coverageFilename);
  return combined;
}

function prepareSaveDir() {
  var saveFolder = './scripts/';
  if (!fs.existsSync(saveFolder)) {
    fs.mkdirSync(saveFolder);
  }
  return saveFolder;
}
var saveFolder = prepareSaveDir();

function urlToFilename(url) {
  la(check.unemptyString(url), 'missing url');
  // just leave filename
  var stripParams = url.replace(/\?.*$/, '');
  var lastSlash = stripParams.lastIndexOf('/');
  if (lastSlash !== -1) {
    stripParams = stripParams.substr(lastSlash + 1);
  }
  return stripParams;
}

function saveSourceFile(src, url) {
  la(check.unemptyString(src), 'missing file source');
  la(check.unemptyString(url), 'missing url');

  var filename = urlToFilename(url);
  console.log('url', url, 'filename', filename);

  var fullFilename = path.join(saveFolder, filename);
  fs.writeFileSync(fullFilename, src);
  return fullFilename;
}

function prepareResponseSelectors(proxyRes, req, res) {
  var _write      = res.write;
  var _end        = res.end;
  var _writeHead  = res.writeHead;
  var scriptSrc = '';

  res.writeHead = function (code, headers) {
    console.log(code, req.method, req.url);
    if (code === 304) {
      scriptSrc = '';
      return _writeHead.apply(res, arguments);
    }
    var contentType = this.getHeader('content-type');
    la(check.unemptyString(contentType), 'missing content type for code', code);

    // Strip off the content length since it will change.
    res.removeHeader('Content-Length');
    if (headers) {
      delete headers['content-length'];
    }
    _writeHead.apply(res, arguments);
  };

  res.write = function (chunk) {
    scriptSrc += chunk;
  };

  res.end = function (data) {
    if (data) {
      scriptSrc += data;
    }
    if (scriptSrc) {
      var filename = saveSourceFile(scriptSrc, req.url);
      console.log('full filename', filename, 'for script', req.url);
      var instrumented = instrumenter.instrumentSync(scriptSrc, filename);
      instrumented += '\n\n';
      instrumented += setupCoverageSend.toString() + '\n';
      instrumented += 'setupCoverageSend();\n';
      _write.call(res, instrumented);
    }
    _end.call(res);
  };
}

//
// Create a proxy server with custom application logic
//
var proxyOpts = {};
if (program.rehost){
  proxyOpts.hostRewrite = program.rehost;
}
var proxy = httpProxy.createProxyServer(proxyOpts);
var getReportUrlPrefix = '__report/';

var reportServer = ecstatic({
  root: savedReportDir,
  baseDir: getReportUrlPrefix
});

function isGetReportRequest(url) {
  return url.indexOf(getReportUrlPrefix) === 1;
}

//
// Create your custom server and just call `proxy.web()` to proxy
// a web request to the target passed in the options
// also you can use `proxy.ws()` to proxy a websockets request
//
var server = http.createServer(function (req, res) {
  // You can define here your custom logic to handle the request
  // and then proxy the request.
  console.log(req.method, req.url);

  if (req.method === 'GET' && isGetReportRequest(req.url)) {
    console.log('coverage report', req.url);
    reportServer(req, res);
  } else if (req.method === 'GET' && req.url === '/__reset') {
    resetCoverage();
    res.writeHead(200);
    res.end();
  } else if (req.method === 'POST' && req.url === '/__coverage') {
    console.log('received coverage info');
    var str = '';
    req.on('data', function (chunk) {
      str += chunk;
    });
    req.on('end', function () {
      var coverage = JSON.parse(str);
      var combined = combineCoverage(coverage);
      writeCoverageReports(combined);
    });
    res.writeHead(200);
    res.end();
  } else {
    console.log('proxy', req.method, req.url);
    proxy.web(req, res, {
      target: program.target,
      xfwd: false
    });
  }
});

proxy.on('proxyReq', function(proxyReq) {
    if(program.host){
        proxyReq.setHeader('Host', program.host);
    }
});

proxy.on('proxyRes', function (proxyRes, req, res) {
  console.log('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2));
  if (shouldBeInstrumented(req.url)) {
    console.log('will instrument', req.url);
    prepareResponseSelectors(proxyRes, req, res);
  }
});

proxy.on('error', function (err, req, res) {
  console.error(err);
  res.writeHead(500, {
    'Content-Type': 'text/plain'
  });

  res.end('Something went wrong. And we are reporting a custom error message.');
});

var quote = require('quote');
console.log(pkg.name, 'listening on port', program.port,
  'instrumenting urls matching', quote(program.instrument));
server.listen(program.port);
<commit_msg>mapping urls to files<commit_after>#!/usr/bin/env node

require('lazy-ass');
var pkg = require('./package.json');
var ecstatic = require('ecstatic');
var check = require('check-types');
var istanbul = require('istanbul');
var Collector = istanbul.Collector;
var instrumenter = new istanbul.Instrumenter();
var fs = require('fs');
var path = require('path');
var http = require('http'),
  httpProxy = require('http-proxy');
var savedReportDir = './html-report';

require('./src/check-updates')();

var program = require('./src/cli-options')();

la(check.unemptyString(program.target), 'missing target server url', program);

function prepareSaveDir() {
  var saveFolder = './scripts/';
  if (!fs.existsSync(saveFolder)) {
    fs.mkdirSync(saveFolder);
  }
  return saveFolder;
}
var saveFolder = prepareSaveDir();

function shouldInstrument(regex, url) {
  la(check.instance(regex, RegExp), 'not a regex', regex);
  la(check.unemptyString(url), 'expected url string');
  return regex.test(url);
}

var shouldBeInstrumented = shouldInstrument.bind(null, new RegExp(program.instrument));

var setupCoverageSend = require('./src/send-coverage');
la(check.fn(setupCoverageSend), 'missing send coverage function');

function writeCoverageReports(coverage) {
  la(check.object(coverage), 'missing coverage object');
  // change names to file paths
  Object.keys(coverage).forEach(function (name) {
    la(check.unemptyString(coverage[name].path), 'missing path for', name);
    coverage[name].path = path.join(saveFolder, name);
    console.log('mapped', name, 'to', coverage[name].path);
  });

  var collector = new Collector();
  collector.add(coverage);

  var Report = istanbul.Report;
  var summaryReport = Report.create('text-summary');
  summaryReport.writeReport(collector);
  var htmlReport = Report.create('html');
  htmlReport.writeReport(collector, true);
}

var coverageFilename = path.join(saveFolder, 'coverage.json');

function resetCoverage() {
  if (fs.existsSync(coverageFilename)) {
    fs.unlinkSync(coverageFilename);
    console.log('deleted previous coverage file', coverageFilename);
  }
}

if (program.reset) {
  resetCoverage();
}

if (!fs.existsSync(savedReportDir)) {
  fs.mkdirSync(savedReportDir);
  console.log('created folder', savedReportDir);
}

function combineCoverage(coverage) {
  var collector = new Collector();
  if (fs.existsSync(coverageFilename)) {
    collector.add(JSON.parse(fs.readFileSync(coverageFilename, 'utf8')));
  }
  collector.add(coverage);
  var combined = collector.getFinalCoverage();
  fs.writeFileSync(coverageFilename, JSON.stringify(combined, null, '  '));
  console.log('saved combined coverage to', coverageFilename);
  return combined;
}

function urlToFilename(url) {
  la(check.unemptyString(url), 'missing url');
  // just leave filename
  var stripParams = url.replace(/\?.*$/, '');
  var lastSlash = stripParams.lastIndexOf('/');
  if (lastSlash !== -1) {
    stripParams = stripParams.substr(lastSlash + 1);
  }
  return stripParams;
}

function saveSourceFile(src, url) {
  la(check.unemptyString(src), 'missing file source');
  la(check.unemptyString(url), 'missing url');

  var filename = urlToFilename(url);

  var fullFilename = path.join(saveFolder, filename);
  fs.writeFileSync(fullFilename, src);
  console.log('saved url', url, 'as file', fullFilename);
  return fullFilename;
}

function prepareResponseSelectors(proxyRes, req, res) {
  var _write      = res.write;
  var _end        = res.end;
  var _writeHead  = res.writeHead;
  var scriptSrc = '';

  res.writeHead = function (code, headers) {
    console.log(code, req.method, req.url);
    if (code === 304) {
      scriptSrc = '';
      return _writeHead.apply(res, arguments);
    }
    var contentType = this.getHeader('content-type');
    la(check.unemptyString(contentType), 'missing content type for code', code);

    // Strip off the content length since it will change.
    res.removeHeader('Content-Length');
    if (headers) {
      delete headers['content-length'];
    }
    _writeHead.apply(res, arguments);
  };

  res.write = function (chunk) {
    scriptSrc += chunk;
  };

  res.end = function (data) {
    if (data) {
      scriptSrc += data;
    }
    if (scriptSrc) {
      var filename = saveSourceFile(scriptSrc, req.url);
      var shortName = path.basename(filename);
      var instrumented = instrumenter.instrumentSync(scriptSrc, shortName);
      console.log('short name', shortName, 'for script url', req.url);

      instrumented += '\n\n';
      instrumented += setupCoverageSend.toString() + '\n';
      instrumented += 'setupCoverageSend();\n';
      _write.call(res, instrumented);
    }
    _end.call(res);
  };
}

//
// Create a proxy server with custom application logic
//
var proxyOpts = {};
if (program.rehost){
  proxyOpts.hostRewrite = program.rehost;
}
var proxy = httpProxy.createProxyServer(proxyOpts);
var getReportUrlPrefix = '__report/';

var reportServer = ecstatic({
  root: savedReportDir,
  baseDir: getReportUrlPrefix
});

function isGetReportRequest(url) {
  return url.indexOf(getReportUrlPrefix) === 1;
}

//
// Create your custom server and just call `proxy.web()` to proxy
// a web request to the target passed in the options
// also you can use `proxy.ws()` to proxy a websockets request
//
var server = http.createServer(function (req, res) {
  // You can define here your custom logic to handle the request
  // and then proxy the request.
  console.log(req.method, req.url);

  if (req.method === 'GET' && isGetReportRequest(req.url)) {
    console.log('coverage report', req.url);
    reportServer(req, res);
  } else if (req.method === 'GET' && req.url === '/__reset') {
    resetCoverage();
    res.writeHead(200);
    res.end();
  } else if (req.method === 'POST' && req.url === '/__coverage') {
    console.log('received coverage info, current folder', process.cwd());
    var str = '';
    req.on('data', function (chunk) {
      str += chunk;
    });
    req.on('end', function () {
      var coverage = JSON.parse(str);
      var combined = combineCoverage(coverage);
      writeCoverageReports(combined);
    });
    res.writeHead(200);
    res.end();
  } else {
    console.log('proxy', req.method, req.url);
    proxy.web(req, res, {
      target: program.target,
      xfwd: false
    });
  }
});

proxy.on('proxyReq', function(proxyReq) {
    if(program.host){
        proxyReq.setHeader('Host', program.host);
    }
});

proxy.on('proxyRes', function (proxyRes, req, res) {
  console.log('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2));
  if (shouldBeInstrumented(req.url)) {
    console.log('will instrument', req.url);
    prepareResponseSelectors(proxyRes, req, res);
  }
});

proxy.on('error', function (err, req, res) {
  console.error(err);
  res.writeHead(500, {
    'Content-Type': 'text/plain'
  });

  res.end('Something went wrong. And we are reporting a custom error message.');
});

var quote = require('quote');
console.log(pkg.name, 'listening on port', program.port,
  'instrumenting urls matching', quote(program.instrument));
server.listen(program.port);
<|endoftext|>


<commit_before>var app = require("express")()
var http = require("http").Server(app)
var io = require('socket.io')(http)
var fs = require('fs')

var logFilePath = './logs/logs.json'
var port = Number(process.env.PORT || 3000)
var counter = 60
var spinning = false
var spin;
var db = {
	headP: 0,
	tailsP: 0,
	pool:[],
	users: [],
	bankBits: 300000,
	usersOnline: 0
}
var log = {
	spinRes: [],
	betsPlaced: [],
	transActions: [],
	events:[]
}
var util = {
	getUnixTime: function () {
		return Math.round(new Date() / 1000);
	},
	saveLogs: function () {
		var logJson = JSON.stringify(log);
		fs.writeFileSync(logFilePath, logJson)
		console.log(util.getUnixTime() + ' Saved log file! (hopefuly)');
	}
}
var banker = {
	giveBits: function (amount, name) {
		db.users.forEach(function (user) {
			if (user.name == name){
				user.bits += amount
				db.bankBits -= amount
				var transaction = {time:util.getUnixTime(), reciver:user, amount:amount, pout:util.getUnixTime()+' '+'Gave '+amount+'bits to '+user.name}
				log.transActions.push(transaction)
				console.log(transaction.pout)
				io.emit('bitsGiven', user, amount)
			}
		})
	},
	findUser: function (username) {
		var tempUser = null;
		db.users.forEach(function (user) {
			if(user.name == username){
				tempUser = user
			}
		})
		return tempUser
	},
	removeBits: function (amount, name) {
		db.users.forEach(function (user) {
			if (user.name == name){
				user.bits -= amount
				//db.bankBits -= amount
				var transaction = {time:util.getUnixTime(), reciver:user, amount:amount, pout:util.getUnixTime()+' '+'Removed '+amount+'bits from '+user.name}
				log.transActions.push(transaction)
				console.log(transaction.pout)
				io.emit('bitsRemoved', user, amount)
			}
		})
	},
	resetPool: function () {
		db.headP = 0
		db.tailsP = 0
		db.pool = []
		var poolEvent = {time:util.getUnixTime(), pout:util.getUnixTime()+' '+'Pool got reset'}
		log.events.push(poolEvent)
		io.emit('poolReset')
	},
	repayMoney: function (winside) {
		db.pool.forEach(function (bet) {
			if(bet.side == winside){
				var repay = bet.amount * 2
				var repayEvent = {time:util.getUnixTime(), side:winside, amount:repay, user:bet.name, pout:util.getUnixTime()+' '+'Repayed '+repay+'bits to '+bet.name+' on '+winside+' side'}
				log.transActions.push(repayEvent)
				console.log(repayEvent.pou);
				banker.giveBits(repay, bet.name)
			}
		})
	}
}

io.on('connection', function(socket){
	io.emit('newData', db.headP, db.tailsP)

	socket.on('newUser', function (data) {
		db.users.push(data)
		var newuser = {time:util.getUnixTime(), name:data.name, pout:util.getUnixTime()+' '+data.name+' has connected'}
		log.events.push(newuser)
		console.log(newuser.pout)
		banker.giveBits(300, data.name)
		db.usersOnline += 1
		io.emit('onlineStat', db.usersOnline)
	})
	socket.on('placeBet', function (data) {
		banker.removeBits(data.amount, data.user.name)
		if (data.side == 1) {
			db.headP += data.amount
		}else if (data.side == 2) {
			db.tailsP += data.amount
		}
		var newbet = {time:util.getUnixTime(), name:data.user.name, amount:data.amount, side:data.side, pout:util.getUnixTime()+' '+data.user.name+' placed a bet worth '+data.amount+'bits on the side '+data.side}
		log.betsPlaced.push(newbet)
		console.log(newbet.pout);
		io.emit('newBet', data.amount, db.headP, db.tailsP, data.user.name, data.side)
		db.pool.push({time:util.getUnixTime(), side:data.side, amount:data.amount, name:data.user.name})
	})
	socket.on('disconnect', function () {
		db.usersOnline -= 1
		io.emit('onlineStat', db.usersOnline)
	})
	io.emit('countDown', counter)
})

setInterval(function () {
	if(counter > 0){
		counter--
		io.emit('countDown', counter)
	}else if (counter == 0) {
		if (spinning == false) {
			spin = Math.floor(Math.random() * 2) + 1
			console.log(spin);
			io.emit('spin', spin)
			spinning = true
			setTimeout(function () {
				counter = 60
				var spinres = {time:util.getUnixTime(), side:spin}
				log.spinRes.push(spinres)
				banker.repayMoney(spin)
				banker.resetPool()
				spinning = false
			}, 4000)
		}
	}
},1000)

setInterval(function () {
	util.saveLogs()
}, 60000)

app.get("/", function(req, res) {
	res.sendFile(__dirname + '/www/index.html')
})

app.get(/^(.+)$/, function(req, res){
    console.log('static file request : ' + req.params);
    res.sendfile( __dirname + req.params[0])
})

http.listen(port, function(){
  console.log('listening on *:'+port)
})
<commit_msg>Fixed bug with the online counter<commit_after>var app = require("express")()
var http = require("http").Server(app)
var io = require('socket.io')(http)
var fs = require('fs')

var logFilePath = './logs/logs.json'
var port = Number(process.env.PORT || 3000)
var counter = 60
var spinning = false
var spin;
var db = {
	headP: 0,
	tailsP: 0,
	pool:[],
	users: [],
	bankBits: 300000,
	usersOnline: 0
}
var log = {
	spinRes: [],
	betsPlaced: [],
	transActions: [],
	events:[]
}
var util = {
	getUnixTime: function () {
		return Math.round(new Date() / 1000);
	},
	saveLogs: function () {
		var logJson = JSON.stringify(log);
		fs.writeFileSync(logFilePath, logJson)
		console.log(util.getUnixTime() + ' Saved log file! (hopefuly)');
	}
}
var banker = {
	giveBits: function (amount, name) {
		db.users.forEach(function (user) {
			if (user.name == name){
				user.bits += amount
				db.bankBits -= amount
				var transaction = {time:util.getUnixTime(), reciver:user, amount:amount, pout:util.getUnixTime()+' '+'Gave '+amount+'bits to '+user.name}
				log.transActions.push(transaction)
				console.log(transaction.pout)
				io.emit('bitsGiven', user, amount)
			}
		})
	},
	findUser: function (username) {
		var tempUser = null;
		db.users.forEach(function (user) {
			if(user.name == username){
				tempUser = user
			}
		})
		return tempUser
	},
	removeBits: function (amount, name) {
		db.users.forEach(function (user) {
			if (user.name == name){
				user.bits -= amount
				//db.bankBits -= amount
				var transaction = {time:util.getUnixTime(), reciver:user, amount:amount, pout:util.getUnixTime()+' '+'Removed '+amount+'bits from '+user.name}
				log.transActions.push(transaction)
				console.log(transaction.pout)
				io.emit('bitsRemoved', user, amount)
			}
		})
	},
	resetPool: function () {
		db.headP = 0
		db.tailsP = 0
		db.pool = []
		var poolEvent = {time:util.getUnixTime(), pout:util.getUnixTime()+' '+'Pool got reset'}
		log.events.push(poolEvent)
		io.emit('poolReset')
	},
	repayMoney: function (winside) {
		db.pool.forEach(function (bet) {
			if(bet.side == winside){
				var repay = bet.amount * 2
				var repayEvent = {time:util.getUnixTime(), side:winside, amount:repay, user:bet.name, pout:util.getUnixTime()+' '+'Repayed '+repay+'bits to '+bet.name+' on '+winside+' side'}
				log.transActions.push(repayEvent)
				console.log(repayEvent.pou);
				banker.giveBits(repay, bet.name)
			}
		})
	}
}

io.on('connection', function(socket){
	io.emit('newData', db.headP, db.tailsP)

	socket.on('newUser', function (data) {
		db.users.push(data)
		var newuser = {time:util.getUnixTime(), name:data.name, pout:util.getUnixTime()+' '+data.name+' has connected'}
		log.events.push(newuser)
		console.log(newuser.pout)
		banker.giveBits(300, data.name)
		db.usersOnline += 1
		io.emit('onlineStat', db.usersOnline)
	})
	socket.on('placeBet', function (data) {
		banker.removeBits(data.amount, data.user.name)
		if (data.side == 1) {
			db.headP += data.amount
		}else if (data.side == 2) {
			db.tailsP += data.amount
		}
		var newbet = {time:util.getUnixTime(), name:data.user.name, amount:data.amount, side:data.side, pout:util.getUnixTime()+' '+data.user.name+' placed a bet worth '+data.amount+'bits on the side '+data.side}
		log.betsPlaced.push(newbet)
		console.log(newbet.pout);
		io.emit('newBet', data.amount, db.headP, db.tailsP, data.user.name, data.side)
		db.pool.push({time:util.getUnixTime(), side:data.side, amount:data.amount, name:data.user.name})
	})
	socket.on('disconnect', function () {
		if(db.usersOnline > 0){
			db.usersOnline -= 1
		}else {
			db.usersOnline = 0
		}
		io.emit('onlineStat', db.usersOnline)
	})
	io.emit('countDown', counter)
})

setInterval(function () {
	if(counter > 0){
		counter--
		io.emit('countDown', counter)
	}else if (counter == 0) {
		if (spinning == false) {
			spin = Math.floor(Math.random() * 2) + 1
			console.log(spin);
			io.emit('spin', spin)
			spinning = true
			setTimeout(function () {
				counter = 60
				var spinres = {time:util.getUnixTime(), side:spin}
				log.spinRes.push(spinres)
				banker.repayMoney(spin)
				banker.resetPool()
				spinning = false
			}, 4000)
		}
	}
},1000)

setInterval(function () {
	util.saveLogs()
}, 60000)

app.get("/", function(req, res) {
	res.sendFile(__dirname + '/www/index.html')
})

app.get(/^(.+)$/, function(req, res){
    //console.log('static file request : ' + req.params[0]);
    res.sendfile( __dirname + req.params[0])
})

http.listen(port, function(){
  console.log('listening on *:'+port)
})
<|endoftext|>


<commit_before>"use strict";

const CHAPTERS = ["existance", "multiple", "types", "array", "number"];

let check = {};

// GENERAL

/** Checks whether the input is truthy
	@param input Test value
	*/
check.isTrue = function(input) {
	return !!input;
};

/** Checks whether the input is falsy
	@param input Test value
	*/
check.isFalse = function(input) {
	return !input;
};

// ***

let modules = CHAPTERS.map(chapter => `./check-${chapter}`).map(require);

for (let _module of modules)
	for (let key in _module)
		check[key] = _module[key];

module.exports = check;<commit_msg>reorder chapters<commit_after>"use strict";

const CHAPTERS = ["existance", "types", "array", "number", "multiple"];

let check = {};

// GENERAL

/** Checks whether the input is truthy
	@param input Test value
	*/
check.isTrue = function(input) {
	return !!input;
};

/** Checks whether the input is falsy
	@param input Test value
	*/
check.isFalse = function(input) {
	return !input;
};

// ***

let modules = CHAPTERS.map(chapter => `./check-${chapter}`).map(require);

for (let _module of modules)
	for (let key in _module)
		check[key] = _module[key];

module.exports = check;<|endoftext|>


<commit_before>var express = require('express'),
    path = require('path'),
    http = require('http'),
    mongoose = require('mongoose'),
    routes = require('./routes/routes.js');

mongoose.connect('mongodb://54.84.154.254/parking');
var db = mongoose.connection;

db.on('error', function(msg) {
  console.log('Mongoose connection error %s', msg);
});

db.once('open', function() {
  console.log('Mongoose connection established');
});

// set up the app

var app = express();

app.use(express.logger('dev'));  /* 'default', 'short', 'tiny', 'dev' */
app.use(express.json());
app.use(express.urlencoded());

app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'bower_components')));

routes.init(app);

app.set('port', process.env.PORT || 3000);
http.createServer(app).listen(app.get('port'), function () {
    console.log("Express server listening on port " + app.get('port'));
});

// For deployment
// module.exports = app;<commit_msg>Deploy<commit_after>var express = require('express'),
    path = require('path'),
    http = require('http'),
    mongoose = require('mongoose'),
    routes = require('./routes/routes.js');

mongoose.connect('mongodb://54.84.154.254/parking');
var db = mongoose.connection;

db.on('error', function(msg) {
  console.log('Mongoose connection error %s', msg);
});

db.once('open', function() {
  console.log('Mongoose connection established');
});

// set up the app

var app = express();

app.use(express.logger('dev'));  /* 'default', 'short', 'tiny', 'dev' */
app.use(express.json());
app.use(express.urlencoded());

app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'bower_components')));

routes.init(app);

// app.set('port', process.env.PORT || 3000);
// http.createServer(app).listen(app.get('port'), function () {
//     console.log("Express server listening on port " + app.get('port'));
// });

For deployment
module.exports = app;<|endoftext|>


<commit_before>'use strict';
var gutil = require('gulp-util');
var through = require('through2');
var es6transpiler = require('es6-transpiler');

module.exports = function (options) {
	options = options || {};
	delete options.filename;
	delete options.outputToConsole;
	delete options.outputFilename;

	return through.obj(function (file, enc, cb) {
		if (file.isNull()) {
			cb(null, file);
			return;
		}

		if (file.isStream()) {
			cb(new gutil.PluginError('gulp-es6-transpiler', 'Streaming not supported'));
			return;
		}

		options.src = file.contents.toString();

		try {
			var result = es6transpiler.run(options);

			if (result.errors.length > 0) {
				cb(new gutil.PluginError('gulp-es6-transpiler\n', result.errors.join('\n'), {
					fileName: file.path,
					showStack: false
				}));
				return;

			}

			file.contents = new Buffer(result.src);
			cb(null, file);
		} catch (err) {
			cb(new gutil.PluginError('gulp-es6-transpiler', err, {fileName: file.path}));
		}
	});
};
<commit_msg>minor tweaks<commit_after>'use strict';
var gutil = require('gulp-util');
var through = require('through2');
var es6transpiler = require('es6-transpiler');

module.exports = function (options) {
	options = options || {};
	delete options.filename;
	delete options.outputToConsole;
	delete options.outputFilename;

	return through.obj(function (file, enc, cb) {
		if (file.isNull()) {
			cb(null, file);
			return;
		}

		if (file.isStream()) {
			cb(new gutil.PluginError('gulp-es6-transpiler', 'Streaming not supported'));
			return;
		}

		options.src = file.contents.toString();

		try {
			var result = es6transpiler.run(options);

			if (result.errors.length > 0) {
				cb(new gutil.PluginError('gulp-es6-transpiler\n', result.errors.join('\n'), {
					fileName: file.path,
					showStack: false
				}));
				return;

			}

			file.contents = new Buffer(result.src);
			this.push(file);
		} catch (err) {
			this.emit('error', new gutil.PluginError('gulp-es6-transpiler', err, {fileName: file.path}));
		}

		cb();
	});
};
<|endoftext|>


<commit_before>const request = require('request');
const _ = require('lodash');

class IC {
    constructor(api_key = 'YOUR-API-KEY', version = 6) {
        this.api_key = api_key;
        this.url = `https://iatacodes.org/api/v${version}/`;
    }
    api(method='ping', params={}, callback) {
        request.post({
            url : `${this.url}${method}.json`,
            form : _.extend({api_key: this.api_key}, params)
        }, function(e, r, b) {
            if(!b) return callback(new Error('empty response'));
            let result = JSON.parse(b);
            callback(result.error, result.response, result.request);
        });
    }
}

module.exports = IC;
<commit_msg>Update index.js<commit_after>const request = require('request');
const _ = require('lodash');

class IC {
    constructor(api_key = 'YOUR-API-KEY', version = 6) {
        this.api_key = api_key;
        this.url = `http://iatacodes.org/api/v${version}/`;
    }
    api(method='ping', params={}, callback) {
        request.post({
            url : `${this.url}${method}.json`,
            form : _.extend({api_key: this.api_key}, params)
        }, function(e, r, b) {
            if(!b) return callback(new Error('empty response'));
            let result = JSON.parse(b);
            callback(result.error, result.response, result.request);
        });
    }
}

module.exports = IC;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  ScrollView,
  TouchableOpacity,
  View,
  TouchableWithoutFeedback,
} from 'react-native';
import PropTypes from 'prop-types';
import isEqual from 'lodash.isequal';

const PAGE_CHANGE_DELAY = 4000;

/**
 * Animates pages in cycle
 * (loop possible if children count > 1)
*/
export default class Carousel extends Component {
  static propTypes = {
    children: PropTypes.node.isRequired,
    autoplay: PropTypes.bool,
    delay: PropTypes.number,
    currentPage: PropTypes.number,
    style: View.propTypes.style,
    pageStyle: View.propTypes.style,
    contentContainerStyle: View.propTypes.style,
    pageInfo: PropTypes.bool,
    pageInfoBackgroundColor: PropTypes.string,
    pageInfoTextStyle: Text.propTypes.style,
    pageInfoBottomContainerStyle: View.propTypes.style,
    pageInfoTextSeparator: PropTypes.string,
    bullets: PropTypes.bool,
    bulletsContainerStyle: Text.propTypes.style,
    bulletStyle: Text.propTypes.style,
    arrows: PropTypes.bool,
    arrowsContainerStyle: Text.propTypes.style,
    arrowStyle: Text.propTypes.style,
    leftArrowText: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.element,
    ]),
    rightArrowText: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.element,
    ]),
    chosenBulletStyle: Text.propTypes.style,
    onAnimateNextPage: PropTypes.func,
    swipe: PropTypes.bool,
  };

  static defaultProps = {
    delay: PAGE_CHANGE_DELAY,
    autoplay: true,
    pageInfo: false,
    bullets: false,
    arrows: false,
    pageInfoBackgroundColor: 'rgba(0, 0, 0, 0.25)',
    pageInfoTextSeparator: ' / ',
    currentPage: 0,
    style: undefined,
    pageStyle: undefined,
    contentContainerStyle: undefined,
    pageInfoTextStyle: undefined,
    pageInfoBottomContainerStyle: undefined,
    bulletsContainerStyle: undefined,
    chosenBulletStyle: undefined,
    bulletStyle: undefined,
    arrowsContainerStyle: undefined,
    arrowStyle: undefined,
    leftArrowText: '',
    rightArrowText: '',
    onAnimateNextPage: undefined,
    swipe: true,
  };

  constructor(props) {
    super(props);
    const size = { width: 0, height: 0 };
    if (props.children) {
      const childrenLength = React.Children.count(props.children) || 1;
      this.state = {
        currentPage: props.currentPage,
        size,
        childrenLength,
        contents: null,
      };
    } else {
      this.state = { size };
    }
  }

  componentDidMount() {
    if (this.state.childrenLength) {
      this._setUpTimer();
    }
    // Set up pages but not content. Content will be set up via onLayout event.
    this._setUpPages().then(() => this.setState({ contents: this.pages }));
  }

  componentWillUnmount() {
    this._clearTimer();
  }

  componentWillReceiveProps(nextProps) {
    if (!isEqual(this.props.children, nextProps.children)) {
      let childrenLength = 0;
      this.setState({ currentPage: 0 });
      if (nextProps.children) {
        const length = React.Children.count(nextProps.children);
        childrenLength = length || 1;
      }
      this.setState({ childrenLength }, () => {
        this._setUpPages().then(() => this.setState({ contents: this.pages }));
      });
      this._setUpTimer();
    }
  }

  _setUpPages() {
    return new Promise(resolve => {
      const { size } = this.state;
      const children = React.Children.toArray(this.props.children);
      const pages = [];

      if (children && children.length > 1) {
        // add all pages
        for (let i = 0; i < children.length; i += 1) {
          pages.push(children[i]);
        }
        // We want to make infinite pages structure like this: 1-2-3-1-2
        // so we add first and second page again to the end
        pages.push(children[0]);
        pages.push(children[1]);
      } else if (children) {
        pages.push(children[0]);
      } else {
        pages.push(<View><Text>
            You are supposed to add children inside Carousel
        </Text></View>);
      }
      this.pages = pages.map((page, i) => (
        <TouchableWithoutFeedback style={[{ ...size }, this.props.pageStyle]} key={`page${i}`}>
          {page}
        </TouchableWithoutFeedback>
      ));
      resolve();
    });
  }

  getCurrentPage() {
    return this.state.currentPage;
  }

  _onScrollBegin = () => {
    this._clearTimer();
  }

  _setCurrentPage = (currentPage) => {
    this.setState({ currentPage }, () => {
      if (this.props.onAnimateNextPage) {
        // FIXME: called twice on ios with auto-scroll
        this.props.onAnimateNextPage(currentPage);
      }
    });
  }

  _onScrollEnd = (event) => {
    const offset = { ...event.nativeEvent.contentOffset };
    const page = this._calculateCurrentPage(offset.x);
    this._placeCritical(page);
    this._setCurrentPage(page);
    this._setUpTimer();
  }

  _onLayout = () => {
    this.container.measure((x, y, w, h) => {
      this.setState({ size: { width: w, height: h } });
      // remove setTimeout wrapper when https://github.com/facebook/react-native/issues/6849 is resolved.
      setTimeout(() => this._placeCritical(this.state.currentPage), 0);
    });
  }

  _clearTimer = () => {
    clearTimeout(this.timer);
  }

  _setUpTimer = () => {
    // only for cycling
    if (this.props.autoplay && React.Children.count(this.props.children) > 1) {
      this._clearTimer();
      this.timer = setTimeout(this._animateNextPage, this.props.delay);
    }
  }

  _scrollTo = ({ offset, animated, nofix }) => {
    if (this.scrollView) {
      this.scrollView.scrollTo({ y: 0, x: offset, animated });

      // Fix bug #50
      if (!nofix && Platform.OS === 'android' && !animated) {
        this.scrollView.scrollTo({ y: 0, x: offset, animated: true });
      }
    }
  }

  _animateNextPage = () => {
    const { currentPage } = this.state;
    this.animateToPage(this._normalizePageNumber(currentPage + 1));
  }

  animateToPage = (page) => {
    let currentPage = page;
    this._clearTimer();
    const { width } = this.state.size;
    const { childrenLength } = this.state;
    if (currentPage >= childrenLength) {
      currentPage = 0;
    }
    if (currentPage === 0) {
      // animate properly based on direction
      const scrollMultiplier = this.state.currentPage === 1 ? 1 : -1;
      this._scrollTo({
        offset: (childrenLength + (1 * scrollMultiplier)) * width,
        animated: false,
        nofix: true,
      });
      this._scrollTo({ offset: childrenLength * width, animated: true });
    } else if (currentPage === 1) {
      const scrollMultiplier = this.state.currentPage === 0 ? 0 : 2;
      this._scrollTo({ offset: width * scrollMultiplier, animated: false, nofix: true });
      this._scrollTo({ offset: width, animated: true });
    } else {
      this._scrollTo({ offset: currentPage * width, animated: true });
    }
    this._setCurrentPage(currentPage);
    this._setUpTimer();
  }

  _placeCritical = (page) => {
    const { childrenLength } = this.state;
    const { width } = this.state.size;
    if (childrenLength === 1) {
      this._scrollTo({ offset: 0, animated: false });
    } else if (page === 0) {
      this._scrollTo({ offset: childrenLength * width, animated: false });
    } else if (page === 1) {
      this._scrollTo({ offset: width, animated: false });
    } else {
      this._scrollTo({ offset: page * width, animated: false });
    }
  }

  _normalizePageNumber = (page) => {
    const { childrenLength } = this.state;
    if (page === childrenLength) {
      return 0;
    } else if (page >= childrenLength) {
      return 1;
    }
    return page;
  }

  _calculateCurrentPage = (offset) => {
    const { width } = this.state.size;
    const page = Math.floor(offset / width);
    return this._normalizePageNumber(page);
  }

  _renderPageInfo = (pageLength) =>
    (<View style={[styles.pageInfoBottomContainer, this.props.pageInfoBottomContainerStyle]} pointerEvents="none">
      <View style={styles.pageInfoContainer}>
        <View
          style={[styles.pageInfoPill, { backgroundColor: this.props.pageInfoBackgroundColor }]}
        >
          <Text
            style={[styles.pageInfoText, this.props.pageInfoTextStyle]}
          >
            {`${this.state.currentPage + 1}${this.props.pageInfoTextSeparator}${pageLength}`}
          </Text>
        </View>
      </View>
    </View>)

  _renderBullets = (pageLength) => {
    const bullets = [];
    for (let i = 0; i < pageLength; i += 1) {
      bullets.push(
        <TouchableWithoutFeedback onPress={() => this.animateToPage(i)} key={`bullet${i}`}>
          <View
            style={i === this.state.currentPage ?
              [styles.chosenBullet, this.props.chosenBulletStyle] :
              [styles.bullet, this.props.bulletStyle]}
          />
        </TouchableWithoutFeedback>);
    }
    return (
      <View style={styles.bullets} pointerEvents="box-none">
        <View style={[styles.bulletsContainer, this.props.bulletsContainerStyle]} pointerEvents="box-none">
          {bullets}
        </View>
      </View>
    );
  }

  _renderArrows = () => {
    let { currentPage } = this.state;
    const { childrenLength } = this.state;
    if (currentPage < 1) {
      currentPage = childrenLength;
    }
    return (
      <View style={styles.arrows} pointerEvents="box-none">
        <View style={[styles.arrowsContainer, this.props.arrowsContainerStyle]} pointerEvents="box-none">
          <TouchableOpacity onPress={() => this.animateToPage(this._normalizePageNumber(currentPage - 1))} style={this.props.arrowStyle}><Text>{this.props.leftArrowText ? this.props.leftArrowText : 'Left'}</Text></TouchableOpacity>
          <TouchableOpacity onPress={() => this.animateToPage(this._normalizePageNumber(currentPage + 1))} style={this.props.arrowStyle}><Text>{this.props.rightArrowText ? this.props.rightArrowText : 'Right'}</Text></TouchableOpacity>
        </View>
      </View>
    );
  }

  render() {
    const { contents } = this.state;

    const containerProps = {
      ref: (c) => { this.container = c; },
      onLayout: this._onLayout,
      style: [this.props.style],
    };

    const { size } = this.state;
    const childrenLength = React.Children.count(this.props.children);

    return (
      <View {...containerProps}>
        <ScrollView
          ref={(c) => { this.scrollView = c; }}
          onScrollBeginDrag={this._onScrollBegin}
          onMomentumScrollEnd={this._onScrollEnd}
          alwaysBounceHorizontal={false}
          alwaysBounceVertical={false}
          contentInset={{ top: 0 }}
          automaticallyAdjustContentInsets={false}
          showsHorizontalScrollIndicator={false}
          horizontal
          pagingEnabled
          bounces={false}
          scrollEnabled={this.props.swipe}
          contentContainerStyle={[
            styles.horizontalScroll,
            this.props.contentContainerStyle,
            {
              width: size.width * (childrenLength + (childrenLength > 1 ? 2 : 0)),
              height: size.height,
            },
          ]}
        >
          {contents}
        </ScrollView>
        {this.props.arrows && this._renderArrows(this.state.childrenLength)}
        {this.props.bullets && this._renderBullets(this.state.childrenLength)}
        {this.props.pageInfo && this._renderPageInfo(this.state.childrenLength)}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  horizontalScroll: {
    position: 'absolute',
  },
  pageInfoBottomContainer: {
    position: 'absolute',
    bottom: 20,
    left: 0,
    right: 0,
    backgroundColor: 'transparent',
  },
  pageInfoContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'transparent',
  },
  pageInfoPill: {
    width: 80,
    height: 20,
    borderRadius: 10,
    alignItems: 'center',
    justifyContent: 'center',
  },
  pageInfoText: {
    textAlign: 'center',
  },
  bullets: {
    position: 'absolute',
    left: 0,
    right: 0,
    bottom: 10,
    height: 30,
    backgroundColor: 'transparent',
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'row',
  },
  arrows: {
    position: 'absolute',
    left: 0,
    right: 0,
    bottom: 0,
    top: 0,
    backgroundColor: 'transparent',
  },
  arrowsContainer: {
    flex: 1,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  bulletsContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'row',
  },
  chosenBullet: {
    margin: 10,
    width: 10,
    height: 10,
    borderRadius: 20,
    backgroundColor: 'white',
  },
  bullet: {
    margin: 10,
    width: 10,
    height: 10,
    borderRadius: 20,
    backgroundColor: 'transparent',
    borderColor: 'white',
    borderWidth: 1,
  },
});
<commit_msg>Reset currentPage only when out of size<commit_after>import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  ScrollView,
  TouchableOpacity,
  View,
  TouchableWithoutFeedback,
} from 'react-native';
import PropTypes from 'prop-types';
import isEqual from 'lodash.isequal';

const PAGE_CHANGE_DELAY = 4000;

/**
 * Animates pages in cycle
 * (loop possible if children count > 1)
*/
export default class Carousel extends Component {
  static propTypes = {
    children: PropTypes.node.isRequired,
    autoplay: PropTypes.bool,
    delay: PropTypes.number,
    currentPage: PropTypes.number,
    style: View.propTypes.style,
    pageStyle: View.propTypes.style,
    contentContainerStyle: View.propTypes.style,
    pageInfo: PropTypes.bool,
    pageInfoBackgroundColor: PropTypes.string,
    pageInfoTextStyle: Text.propTypes.style,
    pageInfoBottomContainerStyle: View.propTypes.style,
    pageInfoTextSeparator: PropTypes.string,
    bullets: PropTypes.bool,
    bulletsContainerStyle: Text.propTypes.style,
    bulletStyle: Text.propTypes.style,
    arrows: PropTypes.bool,
    arrowsContainerStyle: Text.propTypes.style,
    arrowStyle: Text.propTypes.style,
    leftArrowText: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.element,
    ]),
    rightArrowText: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.element,
    ]),
    chosenBulletStyle: Text.propTypes.style,
    onAnimateNextPage: PropTypes.func,
    swipe: PropTypes.bool,
  };

  static defaultProps = {
    delay: PAGE_CHANGE_DELAY,
    autoplay: true,
    pageInfo: false,
    bullets: false,
    arrows: false,
    pageInfoBackgroundColor: 'rgba(0, 0, 0, 0.25)',
    pageInfoTextSeparator: ' / ',
    currentPage: 0,
    style: undefined,
    pageStyle: undefined,
    contentContainerStyle: undefined,
    pageInfoTextStyle: undefined,
    pageInfoBottomContainerStyle: undefined,
    bulletsContainerStyle: undefined,
    chosenBulletStyle: undefined,
    bulletStyle: undefined,
    arrowsContainerStyle: undefined,
    arrowStyle: undefined,
    leftArrowText: '',
    rightArrowText: '',
    onAnimateNextPage: undefined,
    swipe: true,
  };

  constructor(props) {
    super(props);
    const size = { width: 0, height: 0 };
    if (props.children) {
      const childrenLength = React.Children.count(props.children) || 1;
      this.state = {
        currentPage: props.currentPage,
        size,
        childrenLength,
        contents: null,
      };
    } else {
      this.state = { size };
    }
  }

  componentDidMount() {
    if (this.state.childrenLength) {
      this._setUpTimer();
    }
    // Set up pages but not content. Content will be set up via onLayout event.
    this._setUpPages().then(() => this.setState({ contents: this.pages }));
  }

  componentWillUnmount() {
    this._clearTimer();
  }

  componentWillReceiveProps(nextProps) {
    if (!isEqual(this.props.children, nextProps.children)) {
      let childrenLength = 0;
      if (nextProps.children) {
        const length = React.Children.count(nextProps.children);
        childrenLength = length || 1;
      }
      if (this.state.currentPage >= childrenLength) {
        this.setState({ currentPage: 0 });
      }
      this.setState({ childrenLength }, () => {
        this._setUpPages().then(() => this.setState({ contents: this.pages }));
      });
      this._setUpTimer();
    }
  }

  _setUpPages() {
    return new Promise(resolve => {
      const { size } = this.state;
      const children = React.Children.toArray(this.props.children);
      const pages = [];

      if (children && children.length > 1) {
        // add all pages
        for (let i = 0; i < children.length; i += 1) {
          pages.push(children[i]);
        }
        // We want to make infinite pages structure like this: 1-2-3-1-2
        // so we add first and second page again to the end
        pages.push(children[0]);
        pages.push(children[1]);
      } else if (children) {
        pages.push(children[0]);
      } else {
        pages.push(<View><Text>
            You are supposed to add children inside Carousel
        </Text></View>);
      }
      this.pages = pages.map((page, i) => (
        <TouchableWithoutFeedback style={[{ ...size }, this.props.pageStyle]} key={`page${i}`}>
          {page}
        </TouchableWithoutFeedback>
      ));
      resolve();
    });
  }

  getCurrentPage() {
    return this.state.currentPage;
  }

  _onScrollBegin = () => {
    this._clearTimer();
  }

  _setCurrentPage = (currentPage) => {
    this.setState({ currentPage }, () => {
      if (this.props.onAnimateNextPage) {
        // FIXME: called twice on ios with auto-scroll
        this.props.onAnimateNextPage(currentPage);
      }
    });
  }

  _onScrollEnd = (event) => {
    const offset = { ...event.nativeEvent.contentOffset };
    const page = this._calculateCurrentPage(offset.x);
    this._placeCritical(page);
    this._setCurrentPage(page);
    this._setUpTimer();
  }

  _onLayout = () => {
    this.container.measure((x, y, w, h) => {
      this.setState({ size: { width: w, height: h } });
      // remove setTimeout wrapper when https://github.com/facebook/react-native/issues/6849 is resolved.
      setTimeout(() => this._placeCritical(this.state.currentPage), 0);
    });
  }

  _clearTimer = () => {
    clearTimeout(this.timer);
  }

  _setUpTimer = () => {
    // only for cycling
    if (this.props.autoplay && React.Children.count(this.props.children) > 1) {
      this._clearTimer();
      this.timer = setTimeout(this._animateNextPage, this.props.delay);
    }
  }

  _scrollTo = ({ offset, animated, nofix }) => {
    if (this.scrollView) {
      this.scrollView.scrollTo({ y: 0, x: offset, animated });

      // Fix bug #50
      if (!nofix && Platform.OS === 'android' && !animated) {
        this.scrollView.scrollTo({ y: 0, x: offset, animated: true });
      }
    }
  }

  _animateNextPage = () => {
    const { currentPage } = this.state;
    this.animateToPage(this._normalizePageNumber(currentPage + 1));
  }

  animateToPage = (page) => {
    let currentPage = page;
    this._clearTimer();
    const { width } = this.state.size;
    const { childrenLength } = this.state;
    if (currentPage >= childrenLength) {
      currentPage = 0;
    }
    if (currentPage === 0) {
      // animate properly based on direction
      const scrollMultiplier = this.state.currentPage === 1 ? 1 : -1;
      this._scrollTo({
        offset: (childrenLength + (1 * scrollMultiplier)) * width,
        animated: false,
        nofix: true,
      });
      this._scrollTo({ offset: childrenLength * width, animated: true });
    } else if (currentPage === 1) {
      const scrollMultiplier = this.state.currentPage === 0 ? 0 : 2;
      this._scrollTo({ offset: width * scrollMultiplier, animated: false, nofix: true });
      this._scrollTo({ offset: width, animated: true });
    } else {
      this._scrollTo({ offset: currentPage * width, animated: true });
    }
    this._setCurrentPage(currentPage);
    this._setUpTimer();
  }

  _placeCritical = (page) => {
    const { childrenLength } = this.state;
    const { width } = this.state.size;
    if (childrenLength === 1) {
      this._scrollTo({ offset: 0, animated: false });
    } else if (page === 0) {
      this._scrollTo({ offset: childrenLength * width, animated: false });
    } else if (page === 1) {
      this._scrollTo({ offset: width, animated: false });
    } else {
      this._scrollTo({ offset: page * width, animated: false });
    }
  }

  _normalizePageNumber = (page) => {
    const { childrenLength } = this.state;
    if (page === childrenLength) {
      return 0;
    } else if (page >= childrenLength) {
      return 1;
    }
    return page;
  }

  _calculateCurrentPage = (offset) => {
    const { width } = this.state.size;
    const page = Math.floor(offset / width);
    return this._normalizePageNumber(page);
  }

  _renderPageInfo = (pageLength) =>
    (<View style={[styles.pageInfoBottomContainer, this.props.pageInfoBottomContainerStyle]} pointerEvents="none">
      <View style={styles.pageInfoContainer}>
        <View
          style={[styles.pageInfoPill, { backgroundColor: this.props.pageInfoBackgroundColor }]}
        >
          <Text
            style={[styles.pageInfoText, this.props.pageInfoTextStyle]}
          >
            {`${this.state.currentPage + 1}${this.props.pageInfoTextSeparator}${pageLength}`}
          </Text>
        </View>
      </View>
    </View>)

  _renderBullets = (pageLength) => {
    const bullets = [];
    for (let i = 0; i < pageLength; i += 1) {
      bullets.push(
        <TouchableWithoutFeedback onPress={() => this.animateToPage(i)} key={`bullet${i}`}>
          <View
            style={i === this.state.currentPage ?
              [styles.chosenBullet, this.props.chosenBulletStyle] :
              [styles.bullet, this.props.bulletStyle]}
          />
        </TouchableWithoutFeedback>);
    }
    return (
      <View style={styles.bullets} pointerEvents="box-none">
        <View style={[styles.bulletsContainer, this.props.bulletsContainerStyle]} pointerEvents="box-none">
          {bullets}
        </View>
      </View>
    );
  }

  _renderArrows = () => {
    let { currentPage } = this.state;
    const { childrenLength } = this.state;
    if (currentPage < 1) {
      currentPage = childrenLength;
    }
    return (
      <View style={styles.arrows} pointerEvents="box-none">
        <View style={[styles.arrowsContainer, this.props.arrowsContainerStyle]} pointerEvents="box-none">
          <TouchableOpacity onPress={() => this.animateToPage(this._normalizePageNumber(currentPage - 1))} style={this.props.arrowStyle}><Text>{this.props.leftArrowText ? this.props.leftArrowText : 'Left'}</Text></TouchableOpacity>
          <TouchableOpacity onPress={() => this.animateToPage(this._normalizePageNumber(currentPage + 1))} style={this.props.arrowStyle}><Text>{this.props.rightArrowText ? this.props.rightArrowText : 'Right'}</Text></TouchableOpacity>
        </View>
      </View>
    );
  }

  render() {
    const { contents } = this.state;

    const containerProps = {
      ref: (c) => { this.container = c; },
      onLayout: this._onLayout,
      style: [this.props.style],
    };

    const { size } = this.state;
    const childrenLength = React.Children.count(this.props.children);

    return (
      <View {...containerProps}>
        <ScrollView
          ref={(c) => { this.scrollView = c; }}
          onScrollBeginDrag={this._onScrollBegin}
          onMomentumScrollEnd={this._onScrollEnd}
          alwaysBounceHorizontal={false}
          alwaysBounceVertical={false}
          contentInset={{ top: 0 }}
          automaticallyAdjustContentInsets={false}
          showsHorizontalScrollIndicator={false}
          horizontal
          pagingEnabled
          bounces={false}
          scrollEnabled={this.props.swipe}
          contentContainerStyle={[
            styles.horizontalScroll,
            this.props.contentContainerStyle,
            {
              width: size.width * (childrenLength + (childrenLength > 1 ? 2 : 0)),
              height: size.height,
            },
          ]}
        >
          {contents}
        </ScrollView>
        {this.props.arrows && this._renderArrows(this.state.childrenLength)}
        {this.props.bullets && this._renderBullets(this.state.childrenLength)}
        {this.props.pageInfo && this._renderPageInfo(this.state.childrenLength)}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  horizontalScroll: {
    position: 'absolute',
  },
  pageInfoBottomContainer: {
    position: 'absolute',
    bottom: 20,
    left: 0,
    right: 0,
    backgroundColor: 'transparent',
  },
  pageInfoContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'transparent',
  },
  pageInfoPill: {
    width: 80,
    height: 20,
    borderRadius: 10,
    alignItems: 'center',
    justifyContent: 'center',
  },
  pageInfoText: {
    textAlign: 'center',
  },
  bullets: {
    position: 'absolute',
    left: 0,
    right: 0,
    bottom: 10,
    height: 30,
    backgroundColor: 'transparent',
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'row',
  },
  arrows: {
    position: 'absolute',
    left: 0,
    right: 0,
    bottom: 0,
    top: 0,
    backgroundColor: 'transparent',
  },
  arrowsContainer: {
    flex: 1,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  bulletsContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'row',
  },
  chosenBullet: {
    margin: 10,
    width: 10,
    height: 10,
    borderRadius: 20,
    backgroundColor: 'white',
  },
  bullet: {
    margin: 10,
    width: 10,
    height: 10,
    borderRadius: 20,
    backgroundColor: 'transparent',
    borderColor: 'white',
    borderWidth: 1,
  },
});
<|endoftext|>