<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,
} from 'react-native';
import TextInputState from 'react-native/Libraries/Components/TextInput/TextInputState';

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 }) {
            cancelAnimationFrame(id);
            count = wait;
            action.call(this, event);
        };
    };
} 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 }) {
            clearTimeout(id);
            count = wait;
            action.call(this, event);
        };
    };
}

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

    static defaultProps = {
        keyboardOffset: 40,
        multilineInputStyle: { fontSize: 17 },
    };

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

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

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

    componentWillUnmount() {
        this._removeListener();
    }

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

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

        const newChildren = this._cloneDeepComponents(children);

        return (
            <KeyboardAvoidingView behavior={isIOS ? 'padding' : null}>
                <View style={styles.wrap}>
                    <ScrollView ref={this._onRef}
                                onMomentumScrollEnd={this._onMomentumScrollEnd}
                                onFocusCapture={this._onFocus} {...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>
                    </ScrollView>
                </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 }) => {
            if (isIOS) {
                // 确保处理代码在 onChange 之后执行
                // release 版本必须使用 requestAnimationFrame
                requestAnimationFrame(() => this._onSelectionChange(event));
            } else {
                setTimeout(() => this._onSelectionChange(event));
            }
            onSelectionChange &&
            onSelectionChange(event);
        };

        Component.props.onContentSizeChange = ({ ...event }) => {
            this._onContentSizeChange(event);
            onContentSizeChange &&
            onContentSizeChange(event);
        };

        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(({nativeEvent:event}) => {
        if (!this._measureCallback) return;
        this._measureCallback(event.contentSize.height);
        this._measureCallback = null;
        this.setState({ measureInputVisible: false });
    }, 3);

    _onRef = root => {
        if (!root) return;
        this._root = root;

        setTimeout(() => {
            root._innerViewRef.measureInWindow((x, y, width, height) => {
                this._topOffset = y;
            });
        });
    };

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

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

        const curFocusTarget = TextInputState.currentlyFocusedField();
        if (!curFocusTarget) 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 === TextInputState.currentlyFocusedField()) 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() 来收起键盘
        // TextInputState.currentlyFocusedField() 也无法获取当前焦点ID
        // 原因可能是系统并未判定 TextInput 获取焦点，这可能是一个 bug
        // 通常需要在 onStartShouldSetResponderCapture 返回 false 的情况下再点击一次 TextInput 才能恢复正常
        // 所以这里手动再设置一次焦点
        const target = event.target || event.currentTarget;
        TextInputState.focusTextInput(target);

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

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

            if (!isIOS) return;

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

    // 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();
        }
    };

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

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>Animated.ScrollView support.<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
} from 'react-native';
import TextInputState from 'react-native/Libraries/Components/TextInput/TextInputState';

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 }) {
            cancelAnimationFrame(id);
            count = wait;
            action.call(this, event);
        };
    };
} 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 }) {
            clearTimeout(id);
            count = wait;
            action.call(this, event);
        };
    };
}

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: { fontSize: 17 },
        useAnimatedScrollView: false,
    };

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

    componentWillMount() {
        this._root = 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}
                                onFocusCapture={this._onFocus} {...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 }) => {
            if (isIOS) {
                // 确保处理代码在 onChange 之后执行
                // release 版本必须使用 requestAnimationFrame
                requestAnimationFrame(() => this._onSelectionChange(event));
            } else {
                setTimeout(() => this._onSelectionChange(event));
            }
            onSelectionChange &&
            onSelectionChange(event);
        };

        Component.props.onContentSizeChange = ({ ...event }) => {
            this._onContentSizeChange(event);
            onContentSizeChange &&
            onContentSizeChange(event);
        };

        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(({nativeEvent:event}) => {
        if (!this._measureCallback) return;
        this._measureCallback(event.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.measureInWindow((x, y, width, height) => {
                this._topOffset = y;
            });
        });
    };

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

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

        const curFocusTarget = TextInputState.currentlyFocusedField();
        if (!curFocusTarget) 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 === TextInputState.currentlyFocusedField()) 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() 来收起键盘
        // TextInputState.currentlyFocusedField() 也无法获取当前焦点ID
        // 原因可能是系统并未判定 TextInput 获取焦点，这可能是一个 bug
        // 通常需要在 onStartShouldSetResponderCapture 返回 false 的情况下再点击一次 TextInput 才能恢复正常
        // 所以这里手动再设置一次焦点
        const target = event.target || event.currentTarget;
        TextInputState.focusTextInput(target);

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

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

            if (!isIOS) return;

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

    // 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();
        }
    };

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

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 tic = require('tic')();
var createAtlas = require('atlaspack');

function Texture(opts) {
  if (!(this instanceof Texture)) return new Texture(opts || {});
  this.game = opts.game;
  this.THREE = this.game.THREE;
  this.materials = [];
  this.texturePath = opts.texturePath || '/textures/';
  this.loading = false;

  this.options = defaults(opts || {}, {
    crossOrigin: 'Anonymous',
    materialParams: defaults(opts.materialParams || {}, {
      ambient: 0xbbbbbb
    }),
    materialType: this.THREE.MeshLambertMaterial,
    applyTextureParams: function(map) {
      map.magFilter = this.THREE.NearestFilter;
      map.minFilter = this.THREE.LinearMipMapLinearFilter;
    }.bind(this)
  });

  this._animations = [];

  // create a canvas for the texture atlas
  this.canvas = document.createElement('canvas');
  this.canvas.width = opts.atlasWidth || 512;
  this.canvas.height = opts.atlasHeight || 512;

  // create core atlas and texture
  this.atlas = createAtlas(this.canvas);
  this._atlasuv = false;
  this._atlaskey = false;
  this.texture = new this.THREE.Texture(this.canvas);
  this.options.applyTextureParams(this.texture);

  // load a first material for easy application to meshes
  this.material = new this.options.materialType(this.options.materialParams);
  this.material.map = this.texture;
  this.material.transparent = true;

  // a place for meshes to wait while textures are loading
  this._meshQueue = [];
}
module.exports = Texture;

Texture.prototype.load = function(names, done) {
  var self = this;
  if (!Array.isArray(names)) names = [names];
  done = done || function() {};
  this.loading = true;

  var materialSlice = names.map(self._expandName);
  self.materials = self.materials.concat(materialSlice);

  // load onto the texture atlas
  var load = Object.create(null);
  materialSlice.forEach(function(mats) {
    mats.forEach(function(mat) {
      // todo: check if texture already exists
      load[mat] = true;
    });
  });
  each(Object.keys(load), self.pack.bind(self), function() {
    self._afterLoading();
    done(materialSlice);
  });
  return materialSlice;
};

Texture.prototype.pack = function(name, done) {
  var self = this;
  function pack(img) {
    var node = self.atlas.pack(img);
    if (node === false) {
      self.atlas =  self.atlas.expand(img);
    }
    done();
  }
  if (typeof name === 'string') {
    var img = new Image();
    img.src = self.texturePath + ext(name);
    img.id = name;
    img.crossOrigin = self.options.crossOrigin;
    img.onload = function() {
      pack(img);
    };
    img.onerror = function() {
      console.error('Couldn\'t load URL [' + img.src + ']');
      done();
    };
  } else {
    pack(name);
  }
  return self;
};

Texture.prototype.find = function(name) {
  var self = this;
  var type = 0;
  self.materials.forEach(function(mats, i) {
    mats.forEach(function(mat) {
      if (mat === name) {
        type = i + 1;
        return false;
      }
    });
    if (type !== 0) return false;
  });
  return type;
};

Texture.prototype._expandName = function(name) {
  if (name.top) return [name.back, name.front, name.top, name.bottom, name.left, name.right];
  if (!Array.isArray(name)) name = [name];
  // load the 0 texture to all
  if (name.length === 1) name = [name[0],name[0],name[0],name[0],name[0],name[0]];
  // 0 is top/bottom, 1 is sides
  if (name.length === 2) name = [name[1],name[1],name[0],name[0],name[1],name[1]];
  // 0 is top, 1 is bottom, 2 is sides
  if (name.length === 3) name = [name[2],name[2],name[0],name[1],name[2],name[2]];
  // 0 is top, 1 is bottom, 2 is front/back, 3 is left/right
  if (name.length === 4) name = [name[2],name[2],name[0],name[1],name[3],name[3]];
  return name;
};

Texture.prototype._afterLoading = function() {
  var self = this;
  self.loading = false;
  self._atlasuv = self.atlas.uv();
  self._atlaskey = Object.create(null);
  self.atlas.index().forEach(function(key) {
    self._atlaskey[key.name] = key;
  });
  self.texture.needsUpdate = true;
  self.material.needsUpdate = true;
  self.material.map.needsUpdate = true;
  //window.open(self.canvas.toDataURL());
  if (self._meshQueue.length > 0) {
    self._meshQueue.forEach(function(queue) {
      self.paint.apply(self, queue);
    });
    self._meshQueue = [];
  }
};

Texture.prototype.paint = function(mesh, materials) {
  var self = this;

  // if were loading put into queue
  if (self.loading) {
    self._meshQueue.push(arguments);
    return false;
  }

  var isVoxelMesh = (materials) ? false : true;
  if (!isVoxelMesh) materials = self._expandName(materials);

  mesh.geometry.faces.forEach(function(face, i) {
    if (mesh.geometry.faceVertexUvs[0].length < 1) return;

    if (isVoxelMesh) {
      var index = Math.floor(face.color.b*255 + face.color.g*255*255 + face.color.r*255*255*255);
      materials = self.materials[index - 1];
      if (!materials) materials = self.materials[0];
    }

    // BACK, FRONT, TOP, BOTTOM, LEFT, RIGHT
    var name = materials[0] || '';
    if      (face.normal.z === 1)  name = materials[1] || '';
    else if (face.normal.y === 1)  name = materials[2] || '';
    else if (face.normal.y === -1) name = materials[3] || '';
    else if (face.normal.x === -1) name = materials[4] || '';
    else if (face.normal.x === 1)  name = materials[5] || '';

    var atlasuv = self._atlasuv[name];
    if (!atlasuv) return;

    // 0 -- 1
    // |    |
    // 3 -- 2
    // faces on these meshes are flipped vertically, so we map in reverse
    // TODO: tops need rotate
    if (isVoxelMesh) {
      if (face.normal.z === -1 || face.normal.x === 1) {
        atlasuv = uvrot(atlasuv, 90);
      }
      atlasuv = uvinvert(atlasuv);
    } else {
      atlasuv = uvrot(atlasuv, -90);
    }
    for (var j = 0; j < 4; j++) {
      mesh.geometry.faceVertexUvs[0][i][j].set(atlasuv[j][0], 1 - atlasuv[j][1]);
    }
  });

  mesh.geometry.uvsNeedUpdate = true;
};

Texture.prototype.sprite = function(name, w, h, cb) {
  var self = this;
  if (typeof w === 'function') { cb = w; w = null; }
  if (typeof h === 'function') { cb = h; h = null; }
  w = w || 16; h = h || w;
  self.loading = true;
  var img = new Image();
  img.src = self.texturePath + ext(name);
  img.onerror = cb;
  img.onload = function() {
    var canvases = [];
    for (var x = 0; x < img.width; x += w) {
      for (var y = 0; y < img.height; y += h) {
        var canvas = document.createElement('canvas');
        canvas.width = w; canvas.height = h;
        canvas.name = name + '_' + x + '_' + y;
        canvas.getContext('2d').drawImage(img, x, y, w, h, 0, 0, w, h);
        canvases.push(canvas);
      }
    }
    var textures = [];
    each(canvases, function(canvas, next) {
      var tex = new Image();
      tex.name = canvas.name;
      tex.src = canvas.toDataURL();
      tex.onload = function() {
        self.pack(tex, next);
      };
      tex.onerror = next;
      textures.push([
        tex.name, tex.name, tex.name,
        tex.name, tex.name, tex.name
      ]);
    }, function() {
      self._afterLoading();
      delete canvases;
      self.materials = self.materials.concat(textures);
      cb(textures);
    });
  };
  return self;
};

Texture.prototype.animate = function(mesh, names, delay) {
  var self = this;
  delay = delay || 1000;
  if (!Array.isArray(names) || names.length < 2) return false;
  var i = 0;
  var mat = new this.options.materialType(this.options.materialParams);
  mat.map = this.texture;
  mat.transparent = true;
  mat.needsUpdate = true;
  tic.interval(function() {
    self.paint(mesh, names[i % names.length]);
    i++;
  }, delay);
  return mat;
};

Texture.prototype.tick = function(dt) {
  tic.tick(dt);
};

function uvrot(coords, deg) {
  if (deg === 0) return coords;
  var c = [];
  var i = (4 - Math.ceil(deg / 90)) % 4;
  for (var j = 0; j < 4; j++) {
    c.push(coords[i]);
    if (i === 3) i = 0; else i++;
  }
  return c;
}

function uvinvert(coords) {
  var c = coords.slice(0);
  return [c[3], c[2], c[1], c[0]];
}

function ext(name) {
  return (String(name).indexOf('.') !== -1) ? name : name + '.png';
}

function defaults(obj) {
  [].slice.call(arguments, 1).forEach(function(from) {
    if (from) for (var k in from) if (obj[k] == null) obj[k] = from[k];
  });
  return obj;
}

function each(arr, it, done) {
  var count = 0;
  arr.forEach(function(a) {
    it(a, function() {
      count++;
      if (count >= arr.length) done();
    });
  });
}
<commit_msg>Attempt to handle painting meshes while loading better<commit_after>var tic = require('tic')();
var createAtlas = require('atlaspack');

function Texture(opts) {
  if (!(this instanceof Texture)) return new Texture(opts || {});
  this.game = opts.game;
  this.THREE = this.game.THREE;
  this.materials = [];
  this.texturePath = opts.texturePath || '/textures/';
  this.loading = 0;

  this.options = defaults(opts || {}, {
    crossOrigin: 'Anonymous',
    materialParams: defaults(opts.materialParams || {}, {
      ambient: 0xbbbbbb
    }),
    materialType: this.THREE.MeshLambertMaterial,
    applyTextureParams: function(map) {
      map.magFilter = this.THREE.NearestFilter;
      map.minFilter = this.THREE.LinearMipMapLinearFilter;
    }.bind(this)
  });

  this._animations = [];

  // create a canvas for the texture atlas
  this.canvas = document.createElement('canvas');
  this.canvas.width = opts.atlasWidth || 512;
  this.canvas.height = opts.atlasHeight || 512;

  // create core atlas and texture
  this.atlas = createAtlas(this.canvas);
  this._atlasuv = false;
  this._atlaskey = false;
  this.texture = new this.THREE.Texture(this.canvas);
  this.options.applyTextureParams(this.texture);

  // load a first material for easy application to meshes
  this.material = new this.options.materialType(this.options.materialParams);
  this.material.map = this.texture;
  this.material.transparent = true;

  // a place for meshes to wait while textures are loading
  this._meshQueue = [];
}
module.exports = Texture;

Texture.prototype.load = function(names, done) {
  var self = this;
  if (!Array.isArray(names)) names = [names];
  done = done || function() {};
  this.loading = true;

  var materialSlice = names.map(self._expandName);
  self.materials = self.materials.concat(materialSlice);

  // load onto the texture atlas
  var load = Object.create(null);
  materialSlice.forEach(function(mats) {
    mats.forEach(function(mat) {
      // todo: check if texture already exists
      load[mat] = true;
    });
  });
  each(Object.keys(load), self.pack.bind(self), function() {
    self._afterLoading();
    self.needsUpdate = true;
    done(materialSlice);
  });
  return materialSlice;
};

Texture.prototype.pack = function(name, done) {
  var self = this;
  function pack(img) {
    var node = self.atlas.pack(img);
    if (node === false) {
      self.atlas =  self.atlas.expand(img);
    }
    done();
  }
  if (typeof name === 'string') {
    var img = new Image();
    img.src = self.texturePath + ext(name);
    img.id = name;
    img.crossOrigin = self.options.crossOrigin;
    img.onload = function() {
      pack(img);
    };
    img.onerror = function() {
      console.error('Couldn\'t load URL [' + img.src + ']');
      done();
    };
  } else {
    pack(name);
  }
  return self;
};

Texture.prototype.find = function(name) {
  var self = this;
  var type = 0;
  self.materials.forEach(function(mats, i) {
    mats.forEach(function(mat) {
      if (mat === name) {
        type = i + 1;
        return false;
      }
    });
    if (type !== 0) return false;
  });
  return type;
};

Texture.prototype._expandName = function(name) {
  if (name.top) return [name.back, name.front, name.top, name.bottom, name.left, name.right];
  if (!Array.isArray(name)) name = [name];
  // load the 0 texture to all
  if (name.length === 1) name = [name[0],name[0],name[0],name[0],name[0],name[0]];
  // 0 is top/bottom, 1 is sides
  if (name.length === 2) name = [name[1],name[1],name[0],name[0],name[1],name[1]];
  // 0 is top, 1 is bottom, 2 is sides
  if (name.length === 3) name = [name[2],name[2],name[0],name[1],name[2],name[2]];
  // 0 is top, 1 is bottom, 2 is front/back, 3 is left/right
  if (name.length === 4) name = [name[2],name[2],name[0],name[1],name[3],name[3]];
  return name;
};

Texture.prototype._afterLoading = function() {
  var self = this;
  setTimeout(function() {
    self.loading--;
    self._atlasuv = self.atlas.uv();
    self._atlaskey = Object.create(null);
    self.atlas.index().forEach(function(key) {
      self._atlaskey[key.name] = key;
    });
    self.texture.needsUpdate = true;
    self.material.needsUpdate = true;
    //window.open(self.canvas.toDataURL());
    if (self._meshQueue.length > 0) {
      self._meshQueue.forEach(function(queue, i) {
        self.paint.apply(queue.self, queue.args);
        delete self._meshQueue[i];
      });
    }
  }, 100);
};

Texture.prototype.paint = function(mesh, materials) {
  var self = this;

  // if were loading put into queue
  if (self.loading > 0) {
    self._meshQueue.push({self: self, args: arguments});
    return false;
  }

  var isVoxelMesh = (materials) ? false : true;
  if (!isVoxelMesh) materials = self._expandName(materials);

  mesh.geometry.faces.forEach(function(face, i) {
    if (mesh.geometry.faceVertexUvs[0].length < 1) return;

    if (isVoxelMesh) {
      var index = Math.floor(face.color.b*255 + face.color.g*255*255 + face.color.r*255*255*255);
      materials = self.materials[index - 1];
      if (!materials) materials = self.materials[0];
    }

    // BACK, FRONT, TOP, BOTTOM, LEFT, RIGHT
    var name = materials[0] || '';
    if      (face.normal.z === 1)  name = materials[1] || '';
    else if (face.normal.y === 1)  name = materials[2] || '';
    else if (face.normal.y === -1) name = materials[3] || '';
    else if (face.normal.x === -1) name = materials[4] || '';
    else if (face.normal.x === 1)  name = materials[5] || '';

    var atlasuv = self._atlasuv[name];
    if (!atlasuv) return;

    // 0 -- 1
    // |    |
    // 3 -- 2
    // faces on these meshes are flipped vertically, so we map in reverse
    // TODO: tops need rotate
    if (isVoxelMesh) {
      if (face.normal.z === -1 || face.normal.x === 1) {
        atlasuv = uvrot(atlasuv, 90);
      }
      atlasuv = uvinvert(atlasuv);
    } else {
      atlasuv = uvrot(atlasuv, -90);
    }
    for (var j = 0; j < 4; j++) {
      mesh.geometry.faceVertexUvs[0][i][j].set(atlasuv[j][0], 1 - atlasuv[j][1]);
    }
  });

  mesh.geometry.uvsNeedUpdate = true;
};

Texture.prototype.sprite = function(name, w, h, cb) {
  var self = this;
  if (typeof w === 'function') { cb = w; w = null; }
  if (typeof h === 'function') { cb = h; h = null; }
  w = w || 16; h = h || w;
  self.loading++;
  var img = new Image();
  img.src = self.texturePath + ext(name);
  img.onerror = cb;
  img.onload = function() {
    var canvases = [];
    for (var x = 0; x < img.width; x += w) {
      for (var y = 0; y < img.height; y += h) {
        var canvas = document.createElement('canvas');
        canvas.width = w; canvas.height = h;
        canvas.name = name + '_' + x + '_' + y;
        canvas.getContext('2d').drawImage(img, x, y, w, h, 0, 0, w, h);
        canvases.push(canvas);
      }
    }
    var textures = [];
    each(canvases, function(canvas, next) {
      var tex = new Image();
      tex.name = canvas.name;
      tex.src = canvas.toDataURL();
      tex.onload = function() {
        self.pack(tex, next);
      };
      tex.onerror = next;
      textures.push([
        tex.name, tex.name, tex.name,
        tex.name, tex.name, tex.name
      ]);
    }, function() {
      self._afterLoading();
      delete canvases;
      self.materials = self.materials.concat(textures);
      cb(textures);
    });
  };
  return self;
};

Texture.prototype.animate = function(mesh, names, delay) {
  var self = this;
  delay = delay || 1000;
  if (!Array.isArray(names) || names.length < 2) return false;
  var i = 0;
  var mat = new this.options.materialType(this.options.materialParams);
  mat.map = this.texture;
  mat.transparent = true;
  mat.needsUpdate = true;
  tic.interval(function() {
    self.paint(mesh, names[i % names.length]);
    i++;
  }, delay);
  return mat;
};

Texture.prototype.tick = function(dt) {
  tic.tick(dt);
};

function uvrot(coords, deg) {
  if (deg === 0) return coords;
  var c = [];
  var i = (4 - Math.ceil(deg / 90)) % 4;
  for (var j = 0; j < 4; j++) {
    c.push(coords[i]);
    if (i === 3) i = 0; else i++;
  }
  return c;
}

function uvinvert(coords) {
  var c = coords.slice(0);
  return [c[3], c[2], c[1], c[0]];
}

function ext(name) {
  return (String(name).indexOf('.') !== -1) ? name : name + '.png';
}

function defaults(obj) {
  [].slice.call(arguments, 1).forEach(function(from) {
    if (from) for (var k in from) if (obj[k] == null) obj[k] = from[k];
  });
  return obj;
}

function each(arr, it, done) {
  var count = 0;
  arr.forEach(function(a) {
    it(a, function() {
      count++;
      if (count >= arr.length) done();
    });
  });
}
<|endoftext|>


<commit_before>'use strict';

const express = require('express');
const bodyParser = require('body-parser');

const restService = express();
restService.use(bodyParser.json());

restService.post('/hook', function (req, res) {

    console.log('hook request');

    try {
        var speech = 'empty speech';

        if (req.body) {
            var requestBody = req.body;

            if (requestBody.result) {
                speech = '';

                if (requestBody.result.fulfillment) {
                    //speech += requestBody.result.fulfillment.speech;
                    speech += 'Tata 3';
                }

                /*if (requestBody.result.action) {
                    speech += 'action: ' + requestBody.result.action;
					//speech += requestBody.result;
                }*/
				if (requestBody.result.action){
					speech += 'Tata2';
				}
            }
        }

        console.log('result: ', speech);

        return res.json({
            speech: speech,
            displayText: speech,
            source: 'apiai-webhook-sample'
        });
    } catch (err) {
        console.error("Can't process request", err);

        return res.status(400).json({
            status: {
                code: 400,
                errorType: err.message
            }
        });
    }
});

restService.listen((process.env.PORT || 5000), function () {
    console.log("Server listening");
});<commit_msg>modified<commit_after>'use strict';

const express = require('express');
const bodyParser = require('body-parser');

const restService = express();
restService.use(bodyParser.json());

restService.post('/hook', function (req, res) {

    console.log('hook request');

    try {
        var speech = 'empty speech';

        if (req.body) {
            var requestBody = req.body;

            if (requestBody.result) {
                speech = '';

                if (requestBody.result.fulfillment) {
                    speech += requestBody.result.fulfillment.speech;
                    speech += 'Tata 3';
                }

                /*if (requestBody.result.action) {
                    speech += 'action: ' + requestBody.result.action;
					//speech += requestBody.result;
                }*/
				if (requestBody.result.action){
					speech += 'Tata2';
				}
            }
        }

        console.log('result: ', speech);

        return res.json({
            speech: speech,
            displayText: speech,
            source: 'apiai-webhook-sample'
        });
    } catch (err) {
        console.error("Can't process request", err);

        return res.status(400).json({
            status: {
                code: 400,
                errorType: err.message
            }
        });
    }
});

restService.listen((process.env.PORT || 5000), function () {
    console.log("Server listening");
});<|endoftext|>


<commit_before>'use strict';

module.exports = function () {
    var result = '';
    var idx = 0;
    var lastIdx = -1;

    for (var current = 1; current < arguments.length && idx !== -1; current++) {
        idx = arguments[0].indexOf('%', lastIdx);
        if (arguments[0][idx + 1] === 's' || arguments[0][idx + 1] === 'd') {
            result += arguments[0].substring(lastIdx, idx);
            result += arguments[current];
            lastIdx = idx + 2;
        } else {
            current--;
        }
    }

    result += arguments[0].substring(lastIdx);

    return result;
};
<commit_msg>Honest %d specifier<commit_after>'use strict';

module.exports = function () {
    var result = '';
    var idx = 0;
    var lastIdx = -1;

    for (var current = 1; current < arguments.length && idx !== -1; current++) {
        idx = arguments[0].indexOf('%', lastIdx);
        if (arguments[0][idx + 1] === 's' || arguments[0][idx + 1] === 'd') {
            result += arguments[0].substring(lastIdx, idx);
            result += (arguments[0][idx + 1] === 'd') ? Math.floor(arguments[current]) : arguments[current];
            lastIdx = idx + 2;
        } else {
            current--;
        }
    }

    result += arguments[0].substring(lastIdx);

    return result;
};
<|endoftext|>


<commit_before>'use strict';

class Schema_Alchemia{

    constructor(schema_rules){
        this.current_source;
        this.current_target;
        this.current_data;

        if(Object.getOwnPropertyNames(schema_rules).length >= 1){
            for(var prop in schema_rules){
                if(typeof schema_rules[prop].group_by === 'undefined'){
                    this[prop] = {is_source: false, is_target: false, group_by: false, schema: schema_rules[prop], data:{}, translated:{}};
                }else{
                    this[prop] = {is_source: false, is_target: false, group_by: schema_rules[prop].group_by ,schema: schema_rules[prop].model, data:{}, translated:{}};
                }
            }
        }else{
            throw {"msg": "At least 1 schema must be parsed to initialize this class, Example: { shema1:{}, schema2:{}, schemaN... }"}
        }
    }

    //define el origen de los datos a ser manipulados
    set_source_schema(schema_name, data){
        if(typeof this[schema_name] !== 'undefined'){
            this[schema_name].is_source = true;
            this.current_source = schema_name;
            if(data){
               this[schema_name].data = data;
            }
        }
    }

    set_target_schema(schema_name){
        if(typeof this[schema_name] !== 'undefined'){
            this[schema_name].is_target = true;
            this.current_target = schema_name;
        }
    }

    //guarda los datos obtenidos desde la base de datos
    set_data(data, schema_name){
        schema_name = !schema_name? this.current_schema: schema_name;

        if(typeof this[schema_name] !== 'undefined'){
            this[schema_name].data = data;
        }else{
            throw {"msg": "Schema not initialized"}
        }
    }

    transform(){
        if(this[this.current_source].group_by){
            console.log('multiples documentos');
            //es un arreglo de datos con con el mismo schema
            for(var data_indx in this[this.current_source].data){
                this.current_data = this[this.current_source].data[data_indx];
                //console.log(this.current_data);
                for(var prop in this[this.current_source].schema){
                    //read source
                    let value = this.__read_write(this.current_data, prop);
                    //write translation
                    //console.log('targeting:', this[this.current_target]);
                    if(typeof this[this.current_target].translated[this.current_data[this[this.current_source].group_by]] === 'undefined'){
                        this[this.current_target].translated[this.current_data[this[this.current_source].group_by]] = {};
                    }
                    //console.log(this[this.current_target].translated[this.current_data[this[this.current_source].group_by]]);
                    this.__read_write(this[this.current_target].translated[this.current_data[this[this.current_source].group_by]], this[this.current_source].schema[prop], value);
                }

            }
            return this[this.current_target].translated;
        }else{
            //es solo un documento con el schema definido
            console.log('un solo documento');
            this.current_data = this[this.current_source].data;
            for(var prop in this[this.current_source].schema){
                //read source
                let value = this.__read_write(this[this.current_source].data, prop);

                //write translation
                //console.log('targeting:', this[this.current_target]);
                this.__read_write(this[this.current_target].translated, this[this.current_source].schema[prop], value);
            }
            return this[this.current_target].translated;
        }
    }

    __read_write(obj,field_addr, value){
        if (typeof field_addr == 'string') {
            return this.__read_write(obj,field_addr.split('.'), value);
        }else if(!Array.isArray(field_addr) && field_addr.toString() === '[object Object]'){
            if(typeof field_addr.target == 'undefined'){ throw {"msg": "Target not defined for this value: " + value}}
            if(typeof field_addr.script == 'undefined'){ throw {"msg": "Script not defined for [" + field_addr.target + "]"}}
            var data_source = this.current_data;
            let script = eval(field_addr.script);
            var result = null;
            try{ result = script();
            }catch(e){
                console.error('script_failed: target:', field_addr.target);
                console.error('script_failed: value:', value);
                console.error('script_failed: script:', field_addr.script);
                result = '';
            }
            this.__read_write(obj, field_addr.target, result);
        }else if (field_addr.length==1 && value!==undefined){
            //console.log(obj);
            obj[field_addr[0]] = value;
            return obj[field_addr[0]];
        }else if (field_addr.length==0){
            return obj;
        }else{
            if(obj == undefined && value == undefined){
                console.log('field', field_addr[0]);
                console.log('obj', obj);
                console.log('value', value);
                return;
            }
            if(typeof obj[field_addr[0]] === 'undefined' && value!==undefined){
                obj[field_addr[0]] = {};
            }
            return this.__read_write(obj[field_addr[0]],field_addr.slice(1), value);
        }
    }
}

module.exports = Schema_Alchemia;<commit_msg>Support for multiple documents with no group key<commit_after>'use strict';

class Schema_Alchemia{

    constructor(schema_rules){
        this.current_source;
        this.current_target;
        this.current_data;

        if(Object.getOwnPropertyNames(schema_rules).length >= 1){
            for(var prop in schema_rules){
                if(typeof schema_rules[prop].group_by === 'undefined'){
                    this[prop] = {is_source: false, is_target: false, group_by: false, schema: schema_rules[prop], data:{}, translated:{}};
                }else{
                    this[prop] = {is_source: false, is_target: false, group_by: schema_rules[prop].group_by ,schema: schema_rules[prop].model, data:{}, translated:{}};
                }
            }
        }else{
            throw {"msg": "At least 1 schema must be parsed to initialize this class, Example: { shema1:{}, schema2:{}, schemaN... }"}
        }
    }

    //define el origen de los datos a ser manipulados
    set_source_schema(schema_name, data){
        if(typeof this[schema_name] !== 'undefined'){
            this[schema_name].is_source = true;
            this.current_source = schema_name;
            if(data){
               this[schema_name].data = data;
            }
        }
    }

    set_target_schema(schema_name){
        if(typeof this[schema_name] !== 'undefined'){
            this[schema_name].is_target = true;
            this.current_target = schema_name;
        }
    }

    //guarda los datos obtenidos desde la base de datos
    set_data(data, schema_name){
        schema_name = !schema_name? this.current_schema: schema_name;

        if(typeof this[schema_name] !== 'undefined'){
            this[schema_name].data = data;
        }else{
            throw {"msg": "Schema not initialized"}
        }
    }

    transform(){
        if(this[this.current_source].group_by){
            console.log('Multiple documents -> group_by');
            //es un arreglo de datos con con el mismo schema
            for(let data_indx in this[this.current_source].data){
                this.current_data = this[this.current_source].data[data_indx];
                //console.log(this.current_data);
                for(let prop in this[this.current_source].schema){
                    //read source
                    let value = this.__read_write(this.current_data, prop);
                    //write translation
                    //console.log('targeting:', this[this.current_target]);
                    if(typeof this[this.current_target].translated[this.current_data[this[this.current_source].group_by]] === 'undefined'){
                        this[this.current_target].translated[this.current_data[this[this.current_source].group_by]] = {};
                    }
                    //console.log(this[this.current_target].translated[this.current_data[this[this.current_source].group_by]]);
                    this.__read_write(this[this.current_target].translated[this.current_data[this[this.current_source].group_by]], this[this.current_source].schema[prop], value);
                }

            }
            return this[this.current_target].translated;
        }else if(Array.isArray(this[this.current_source].data)){
            console.log('Multiple documents -> no group_by');
            //es un arreglo de datos con con el mismo schema
            var doc = 0;
            for(let data_indx in this[this.current_source].data){
                this.current_data = this[this.current_source].data[data_indx];
                //console.log(this.current_data);
                for(let prop in this[this.current_source].schema){
                    //read source
                    let value = this.__read_write(this.current_data, prop);
                    //write translation
                    //console.log('targeting:', this[this.current_target]);
                    if(!Array.isArray(this[this.current_target].translated)){
                        this[this.current_target].translated = [];
                    }
                    if(typeof this[this.current_target].translated[doc] === 'undefined'){
                        this[this.current_target].translated[doc] = {};
                    }
                    //console.log(this[this.current_target].translated[this.current_data[this[this.current_source].group_by]]);
                    this.__read_write(this[this.current_target].translated[doc], this[this.current_source].schema[prop], value);
                }
                doc++;
            }
            return this[this.current_target].translated;
        }else{
            //es solo un documento con el schema definido
            console.log('Single document');
            this.current_data = this[this.current_source].data;
            for(var prop in this[this.current_source].schema){
                //read source
                let value = this.__read_write(this[this.current_source].data, prop);

                //write translation
                //console.log('targeting:', this[this.current_target]);
                this.__read_write(this[this.current_target].translated, this[this.current_source].schema[prop], value);
            }
            return this[this.current_target].translated;
        }
    }

    __read_write(obj,field_addr, value){
        if (typeof field_addr == 'string') {
            return this.__read_write(obj,field_addr.split('.'), value);
        }else if(!Array.isArray(field_addr) && field_addr.toString() === '[object Object]'){
            if(typeof field_addr.target == 'undefined'){ throw {"msg": "Target not defined for this value: " + value}}
            if(typeof field_addr.script == 'undefined'){ throw {"msg": "Script not defined for [" + field_addr.target + "]"}}
            var data_source = this.current_data;
            let script = eval(field_addr.script);
            var result = null;
            try{ result = script();
            }catch(e){
                console.error('script_failed: target:', field_addr.target);
                console.error('script_failed: value:', value);
                console.error('script_failed: script:', field_addr.script);
                result = '';
                throw e;
            }
            this.__read_write(obj, field_addr.target, result);
        }else if (field_addr.length==1 && value!==undefined){
            //console.log(obj);
            obj[field_addr[0]] = value;
            return obj[field_addr[0]];
        }else if (field_addr.length==0){
            return obj;
        }else{
            if(obj == undefined && value == undefined){
                console.log('field', field_addr[0]);
                console.log('obj', obj);
                console.log('value', value);
                return;
            }
            if(typeof obj[field_addr[0]] === 'undefined' && value!==undefined){
                obj[field_addr[0]] = {};
            }
            return this.__read_write(obj[field_addr[0]],field_addr.slice(1), value);
        }
    }
}

module.exports = Schema_Alchemia;<|endoftext|>


<commit_before>'use strict';
const app = require('app');
const BrowserWindow = require('browser-window');
const GlobalShortcut = require('global-shortcut');
const Menu = require('menu');

// report crashes to the Electron project
require('crash-reporter').start();

// prevent window being GC'd
let mainWindow = null;

app.on('window-all-closed', function () {
  app.quit();
});

app.on('ready', function () {
  initMenu();

  const isDevMode = process.argv.indexOf('--dev') >= 0;
  const width = 1280;
  const height = 800;

  var opts = {
    width: width,
    height: height,
    resizable: true,
    preload: __dirname + '/preload.js'
  };

  if (isDevMode) {
    var atomScreen = require('screen');
    var displays = atomScreen.getAllDisplays();
    var d2 = displays.length > 1 ? displays[1] : null;

    if (d2) {
      opts.x = d2.bounds.x + (d2.size.width - width) / 2;
      opts.y = d2.bounds.y + (d2.size.height - height) / 2;
    }
  }

  mainWindow = new BrowserWindow(opts);

  mainWindow.loadUrl('https://play.google.com/music');
  mainWindow.on('closed', function () {
    // deref the window
    // for multiple windows store them in an array
    mainWindow = null;
    GlobalShortcut.unregisterAll();
  });

  initPlayerButtons();
});

function initMenu() {
  var template;
  if (process.platform == 'darwin') {
    template = [{
      label: 'GPMP',
      submenu: [{
        label: 'About GPMP',
        selector: 'orderFrontStandardAboutPanel:'
      }, {
        type: 'separator'
      }, {
        label: 'Services',
        submenu: []
      }, {
        type: 'separator'
      }, {
        label: 'Hide Electron',
        accelerator: 'Command+H',
        selector: 'hide:'
      }, {
        label: 'Hide Others',
        accelerator: 'Command+Shift+H',
        selector: 'hideOtherApplications:'
      }, {
        label: 'Show All',
        selector: 'unhideAllApplications:'
      }, {
        type: 'separator'
      }, {
        label: 'Quit',
        accelerator: 'Command+Q',
        click: function () {
          app.quit();
        }
      }]
    }, {
      label: 'View',
      submenu: [{
        label: 'Reload',
        accelerator: 'Command+R',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.reload();
        }
      }, {
        label: 'Toggle Full Screen',
        accelerator: 'Ctrl+Command+F',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
        }
      }, {
        label: 'Toggle Developer Tools',
        accelerator: 'Alt+Command+I',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.toggleDevTools();
        }
      }]
    }, {
      label: 'Window',
      submenu: [{
        label: 'Minimize',
        accelerator: 'Command+M',
        selector: 'performMiniaturize:'
      }, {
        label: 'Close',
        accelerator: 'Command+W',
        selector: 'performClose:'
      }, {
        type: 'separator'
      }, {
        label: 'Bring All to Front',
        selector: 'arrangeInFront:'
      }]
    }, {
      label: 'Help',
      submenu: [{
        label: 'Learn More',
        click: function () {
          require('shell').openExternal('https://github.com/importre/gpmp')
        }
      }, {
        label: 'Documentation',
        click: function () {
          require('shell').openExternal('https://github.com/importre/gpmp')
        }
      }, {
        label: 'Search Issues',
        click: function () {
          require('shell').openExternal('https://github.com/importre/gpmp/issues')
        }
      }]
    }];
  } else {
    template = [{
      label: '&File',
      submenu: [{
        label: '&Close',
        accelerator: 'Ctrl+W',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.close();
        }
      }]
    }, {
      label: '&View',
      submenu: [{
        label: '&Reload',
        accelerator: 'Ctrl+R',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.reload();
        }
      }, {
        label: 'Toggle &Full Screen',
        accelerator: 'F11',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
        }
      }, {
        label: 'Toggle &Developer Tools',
        accelerator: 'Alt+Ctrl+I',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.toggleDevTools();
        }
      }]
    }, {
      label: 'Help',
      submenu: [{
        label: 'Learn More',
        click: function () {
          require('shell').openExternal('https://github.com/importre/gpmp')
        }
      }, {
        label: 'Documentation',
        click: function () {
          require('shell').openExternal('https://github.com/importre/gpmp')
        }
      }, {
        label: 'Search Issues',
        click: function () {
          require('shell').openExternal('https://github.com/importre/gpmp/issues')
        }
      }]
    }];
  }

  var menu = Menu.buildFromTemplate(template);
  Menu.setApplicationMenu(menu);
}

function initPlayerButtons() {
  GlobalShortcut.register('MediaPlayPause', function () {
    var code = 'triggerKeyCode(document.body, 32);';
    mainWindow.webContents.executeJavaScript(code);
  });

  GlobalShortcut.register('MediaNextTrack', function () {
    var code = 'triggerKeyCode(document.body, 39);';
    mainWindow.webContents.executeJavaScript(code);
  });

  GlobalShortcut.register('MediaPreviousTrack', function () {
    var code = 'triggerKeyCode(document.body, 37);';
    mainWindow.webContents.executeJavaScript(code);
  });
}
<commit_msg>cleanup<commit_after>'use strict';
const app = require('app');
const BrowserWindow = require('browser-window');
const GlobalShortcut = require('global-shortcut');
const Menu = require('menu');
const Sh = require('shell');

const repo = 'https://github.com/importre/gpmp';
const issues = repo + '/issues';

// report crashes to the Electron project
require('crash-reporter').start();

// prevent window being GC'd
let mainWindow = null;

app.on('window-all-closed', function () {
  app.quit();
});

app.on('ready', function () {
  initMenu();

  const isDevMode = process.argv.indexOf('--dev') >= 0;
  const width = 1280;
  const height = 800;

  var opts = {
    width: width,
    height: height,
    resizable: true,
    preload: __dirname + '/preload.js'
  };

  if (isDevMode) {
    var atomScreen = require('screen');
    var displays = atomScreen.getAllDisplays();
    var d2 = displays.length > 1 ? displays[1] : null;

    if (d2) {
      opts.x = d2.bounds.x + (d2.size.width - width) / 2;
      opts.y = d2.bounds.y + (d2.size.height - height) / 2;
    }
  }

  mainWindow = new BrowserWindow(opts);

  mainWindow.loadUrl('https://play.google.com/music');
  mainWindow.on('closed', function () {
    // deref the window
    // for multiple windows store them in an array
    mainWindow = null;
    GlobalShortcut.unregisterAll();
  });

  initPlayerButtons();
});

function initMenu() {
  var template;
  if (process.platform == 'darwin') {
    template = [{
      label: 'GPMP',
      submenu: [{
        label: 'About GPMP',
        selector: 'orderFrontStandardAboutPanel:'
      }, {
        type: 'separator'
      }, {
        label: 'Services',
        submenu: []
      }, {
        type: 'separator'
      }, {
        label: 'Hide GPMP',
        accelerator: 'Command+H',
        selector: 'hide:'
      }, {
        label: 'Hide Others',
        accelerator: 'Command+Shift+H',
        selector: 'hideOtherApplications:'
      }, {
        label: 'Show All',
        selector: 'unhideAllApplications:'
      }, {
        type: 'separator'
      }, {
        label: 'Quit',
        accelerator: 'Command+Q',
        click: function () {
          app.quit();
        }
      }]
    }, {
      label: 'View',
      submenu: [{
        label: 'Reload',
        accelerator: 'Command+R',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.reload();
        }
      }, {
        label: 'Toggle Full Screen',
        accelerator: 'Ctrl+Command+F',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
        }
      }, {
        label: 'Toggle Developer Tools',
        accelerator: 'Alt+Command+I',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.toggleDevTools();
        }
      }]
    }, {
      label: 'Window',
      submenu: [{
        label: 'Minimize',
        accelerator: 'Command+M',
        selector: 'performMiniaturize:'
      }, {
        label: 'Close',
        accelerator: 'Command+W',
        selector: 'performClose:'
      }, {
        type: 'separator'
      }, {
        label: 'Bring All to Front',
        selector: 'arrangeInFront:'
      }]
    }, {
      label: 'Help',
      submenu: [{
        label: 'Learn More',
        click: function () {
          Sh.openExternal(repo)
        }
      }, {
        label: 'Documentation',
        click: function () {
          Sh.openExternal(repo)
        }
      }, {
        label: 'Search Issues',
        click: function () {
          Sh.openExternal(issues)
        }
      }]
    }];
  } else {
    template = [{
      label: '&File',
      submenu: [{
        label: '&Close',
        accelerator: 'Ctrl+W',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.close();
        }
      }]
    }, {
      label: '&View',
      submenu: [{
        label: '&Reload',
        accelerator: 'Ctrl+R',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.reload();
        }
      }, {
        label: 'Toggle &Full Screen',
        accelerator: 'F11',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
        }
      }, {
        label: 'Toggle &Developer Tools',
        accelerator: 'Alt+Ctrl+I',
        click: function () {
          var focusedWindow = BrowserWindow.getFocusedWindow();
          if (focusedWindow)
            focusedWindow.toggleDevTools();
        }
      }]
    }, {
      label: 'Help',
      submenu: [{
        label: 'Learn More',
        click: function () {
          Sh.openExternal(repo)
        }
      }, {
        label: 'Documentation',
        click: function () {
          Sh.openExternal(repo)
        }
      }, {
        label: 'Search Issues',
        click: function () {
          Sh.openExternal(issues)
        }
      }]
    }];
  }

  var menu = Menu.buildFromTemplate(template);
  Menu.setApplicationMenu(menu);
}

function initPlayerButtons() {
  GlobalShortcut.register('MediaPlayPause', function () {
    var code = 'triggerKeyCode(document.body, 32);';
    mainWindow.webContents.executeJavaScript(code);
  });

  GlobalShortcut.register('MediaNextTrack', function () {
    var code = 'triggerKeyCode(document.body, 39);';
    mainWindow.webContents.executeJavaScript(code);
  });

  GlobalShortcut.register('MediaPreviousTrack', function () {
    var code = 'triggerKeyCode(document.body, 37);';
    mainWindow.webContents.executeJavaScript(code);
  });
}
<|endoftext|>


<commit_before>'use strict';
var objectAssign = require('object-assign');
var escapeStringRegexp = require('escape-string-regexp');
var platform = process.platform;

var main = {
	tick: '✔',
	cross: '✖',
	star: '★',
	square: '▇',
	squareSmall: '◻',
	squareSmallFilled: '◼',
	play: '▶',
	circle: '◯',
	circleFilled: '◉',
	circleDotted: '◌',
	circleDouble: '◎',
	circleCircle: 'ⓞ',
	circleCross: 'ⓧ',
	circlePipe: 'Ⓘ',
	circleQuestionMark: '?⃝',
	bullet: '●',
	dot: '․',
	line: '─',
	ellipsis: '…',
	pointer: '❯',
	pointerSmall: '›',
	info: 'ℹ',
	warning: '⚠',
	hamburger: '☰',
	smiley: '㋡',
	mustache: '෴',
	heart: '♥',
	arrowUp: '↑',
	arrowDown: '↓',
	arrowLeft: '←',
	arrowRight: '→',
	radioOn: '◉',
	radioOff: '◯',
	checkboxOn: '☒',
	checkboxOff: '☐',
	checkboxCircleOn: 'ⓧ',
	checkboxCircleOff: 'Ⓘ',
	questionMarkPrefix: '?⃝',
	oneHalf: '½',
	oneThird: '⅓',
	oneQuarter: '¼',
	oneFifth: '⅕',
	oneSixth: '⅙',
	oneSeventh: '⅐',
	oneEighth: '⅛',
	oneNinth: '⅑',
	oneTenth: '⅒',
	twoThirds: '⅔',
	twoFifths: '⅖',
	threeQuarters: '¾',
	threeFifths: '⅗',
	threeEighths: '⅜',
	fourFifths: '⅘',
	fiveSixths: '⅚',
	fiveEighths: '⅝',
	sevenEighths: '⅞'
};

var win = {
	tick: '√',
	cross: '×',
	star: '*',
	square: '█',
	squareSmall: '[ ]',
	squareSmallFilled: '[█]',
	play: '►',
	circle: '( )',
	circleFilled: '(*)',
	circleDotted: '( )',
	circleDouble: '( )',
	circleCircle: '(○)',
	circleCross: '(×)',
	circlePipe: '(│)',
	circleQuestionMark: '(?)',
	bullet: '*',
	dot: '.',
	line: '─',
	ellipsis: '...',
	pointer: '>',
	pointerSmall: '»',
	info: 'i',
	warning: '‼',
	hamburger: '≡',
	smiley: '☺',
	mustache: '┌─┐',
	heart: main.heart,
	arrowUp: main.arrowUp,
	arrowDown: main.arrowDown,
	arrowLeft: main.arrowLeft,
	arrowRight: main.arrowRight,
	radioOn: '(*)',
	radioOff: '( )',
	checkboxOn: '[×]',
	checkboxOff: '[ ]',
	checkboxCircleOn: '(×)',
	checkboxCircleOff: '( )',
	questionMarkPrefix: '？',
	oneHalf: '1/2',
	oneThird: '1/3',
	oneQuarter: '1/4',
	oneFifth: '1/5',
	oneSixth: '1/6',
	oneSeventh: '1/7',
	oneEighth: '1/8',
	oneNinth: '1/9',
	oneTenth: '1/10',
	twoThirds: '2/3',
	twoFifths: '2/5',
	threeQuarters: '3/4',
	threeFifths: '3/5',
	threeEighths: '3/8',
	fourFifths: '4/5',
	fiveSixths: '5/6',
	fiveEighths: '5/8',
	sevenEighths: '7/8'
};

if (platform === 'linux') {
	// the main one doesn't look that good on Ubuntu
	main.questionMarkPrefix = '?';
}

var figures = platform === 'win32' ? win : main;

var fn = function (str) {
	if (figures === main) {
		return str;
	}

	Object.keys(main).forEach(function (key) {
		if (main[key] === figures[key]) {
			return;
		}

		str = str.replace(new RegExp(escapeStringRegexp(main[key]), 'g'), figures[key]);
	});

	return str;
};

module.exports = objectAssign(fn, figures);
<commit_msg>fix lint issue<commit_after>'use strict';
var objectAssign = require('object-assign');
var escapeStringRegexp = require('escape-string-regexp');

var platform = process.platform;

var main = {
	tick: '✔',
	cross: '✖',
	star: '★',
	square: '▇',
	squareSmall: '◻',
	squareSmallFilled: '◼',
	play: '▶',
	circle: '◯',
	circleFilled: '◉',
	circleDotted: '◌',
	circleDouble: '◎',
	circleCircle: 'ⓞ',
	circleCross: 'ⓧ',
	circlePipe: 'Ⓘ',
	circleQuestionMark: '?⃝',
	bullet: '●',
	dot: '․',
	line: '─',
	ellipsis: '…',
	pointer: '❯',
	pointerSmall: '›',
	info: 'ℹ',
	warning: '⚠',
	hamburger: '☰',
	smiley: '㋡',
	mustache: '෴',
	heart: '♥',
	arrowUp: '↑',
	arrowDown: '↓',
	arrowLeft: '←',
	arrowRight: '→',
	radioOn: '◉',
	radioOff: '◯',
	checkboxOn: '☒',
	checkboxOff: '☐',
	checkboxCircleOn: 'ⓧ',
	checkboxCircleOff: 'Ⓘ',
	questionMarkPrefix: '?⃝',
	oneHalf: '½',
	oneThird: '⅓',
	oneQuarter: '¼',
	oneFifth: '⅕',
	oneSixth: '⅙',
	oneSeventh: '⅐',
	oneEighth: '⅛',
	oneNinth: '⅑',
	oneTenth: '⅒',
	twoThirds: '⅔',
	twoFifths: '⅖',
	threeQuarters: '¾',
	threeFifths: '⅗',
	threeEighths: '⅜',
	fourFifths: '⅘',
	fiveSixths: '⅚',
	fiveEighths: '⅝',
	sevenEighths: '⅞'
};

var win = {
	tick: '√',
	cross: '×',
	star: '*',
	square: '█',
	squareSmall: '[ ]',
	squareSmallFilled: '[█]',
	play: '►',
	circle: '( )',
	circleFilled: '(*)',
	circleDotted: '( )',
	circleDouble: '( )',
	circleCircle: '(○)',
	circleCross: '(×)',
	circlePipe: '(│)',
	circleQuestionMark: '(?)',
	bullet: '*',
	dot: '.',
	line: '─',
	ellipsis: '...',
	pointer: '>',
	pointerSmall: '»',
	info: 'i',
	warning: '‼',
	hamburger: '≡',
	smiley: '☺',
	mustache: '┌─┐',
	heart: main.heart,
	arrowUp: main.arrowUp,
	arrowDown: main.arrowDown,
	arrowLeft: main.arrowLeft,
	arrowRight: main.arrowRight,
	radioOn: '(*)',
	radioOff: '( )',
	checkboxOn: '[×]',
	checkboxOff: '[ ]',
	checkboxCircleOn: '(×)',
	checkboxCircleOff: '( )',
	questionMarkPrefix: '？',
	oneHalf: '1/2',
	oneThird: '1/3',
	oneQuarter: '1/4',
	oneFifth: '1/5',
	oneSixth: '1/6',
	oneSeventh: '1/7',
	oneEighth: '1/8',
	oneNinth: '1/9',
	oneTenth: '1/10',
	twoThirds: '2/3',
	twoFifths: '2/5',
	threeQuarters: '3/4',
	threeFifths: '3/5',
	threeEighths: '3/8',
	fourFifths: '4/5',
	fiveSixths: '5/6',
	fiveEighths: '5/8',
	sevenEighths: '7/8'
};

if (platform === 'linux') {
	// the main one doesn't look that good on Ubuntu
	main.questionMarkPrefix = '?';
}

var figures = platform === 'win32' ? win : main;

var fn = function (str) {
	if (figures === main) {
		return str;
	}

	Object.keys(main).forEach(function (key) {
		if (main[key] === figures[key]) {
			return;
		}

		str = str.replace(new RegExp(escapeStringRegexp(main[key]), 'g'), figures[key]);
	});

	return str;
};

module.exports = objectAssign(fn, figures);
<|endoftext|>


<commit_before>'use strict';
const electron = require('electron');
const app = electron.app;

// adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();

// prevent window being garbage collected
let mainWindow;

var path = require('path');

process.env.PROJECT_ROOT = extractActionhero();
console.log('project root: ' + process.env.PROJECT_ROOT);

var actionheroPrototype = require('actionhero').actionheroPrototype;
var actionhero = new actionheroPrototype();
actionhero.start({}, function(error, api) {console.log("ACTIONHERO RUNNING!")});

function onClosed() {
	// dereference the window
	// for multiple windows store them in an array
	mainWindow = null;
}

function createMainWindow() {
	const win = new electron.BrowserWindow({
		width: 800,
		height: 600
	});

	win.loadURL(`file://${process.env.PROJECT_ROOT}/public/chat.html`);
	win.on('closed', onClosed);

	return win;
}

app.on('window-all-closed', () => {
	if (process.platform !== 'darwin') {
		app.quit();
	}
});

app.on('activate', () => {
	if (!mainWindow) {
		mainWindow = createMainWindow();
	}
});

app.on('ready', () => {
	mainWindow = createMainWindow();
});

function extractActionhero() {
	var ah = 'actionhero';
	var src = path.join(app.getAppPath(), ah);
	var dst = path.join(app.getPath('userData'), ah);
	console.log('extracting from ' + src + ' to ' + dst);

	var fs = require('fs-extra');
	fs.emptyDirSync(dst);
	fs.copySync(src, dst, {
		filter: function (path) {
			return ! path.endsWith('.gitkeep');
		}
	});
	return dst;
}<commit_msg>minor cleanup<commit_after>'use strict';
const electron = require('electron');
const app = electron.app;

// adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();

// prevent window & actionhero from being garbage collected
let mainWindow, actionhero = startActionhero();

function onClosed() {
	// dereference the window
	// for multiple windows store them in an array
	mainWindow = null;
}

function createMainWindow() {
	const win = new electron.BrowserWindow({
		width: 800,
		height: 600
	});

	win.loadURL(`file://${process.env.PROJECT_ROOT}/public/chat.html`);
	win.on('closed', onClosed);

	return win;
}

app.on('window-all-closed', () => {
	if (process.platform !== 'darwin') {
		app.quit();
	}
});

app.on('activate', () => {
	if (!mainWindow) {
		mainWindow = createMainWindow();
	}
});

app.on('ready', () => {
	mainWindow = createMainWindow();
});

function extractActionhero() {
	var path = require('path');
	var ah = 'actionhero';
	var src = path.join(app.getAppPath(), ah);
	var dst = path.join(app.getPath('userData'), ah);
	console.log('extracting from ' + src + ' to ' + dst);

	var fs = require('fs-extra');
	fs.emptyDirSync(dst);
	fs.copySync(src, dst, {
		filter: function (path) {
			return ! path.endsWith('.gitkeep');
		}
	});
	return dst;
}

function startActionhero() {
	process.env.PROJECT_ROOT = extractActionhero();
	console.log('project root: ' + process.env.PROJECT_ROOT);

	var actionheroPrototype = require('actionhero').actionheroPrototype;
	var actionhero = new actionheroPrototype();
	actionhero.start({}, function (error, api) {
		console.log("ACTIONHERO RUNNING!")
	});
	return actionhero;
}
<|endoftext|>