<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
    View,
    Image,
    NativeModules,
    requireNativeComponent,
    ViewPropTypes,
    StyleSheet,
} from 'react-native'

const FastImageViewNativeModule = NativeModules.FastImageView

class FastImage extends Component {
    setNativeProps(nativeProps) {
        this._root.setNativeProps(nativeProps)
    }

    captureRef = e => (this._root = e)

    render() {
        const {
            source,
            onLoadStart,
            onProgress,
            onLoad,
            onError,
            onLoadEnd,
            style,
            children,
            fallback,
            ...props
        } = this.props

        const resolvedSource = Image.resolveAssetSource(source)

        if (fallback) {
            return (
                <View
                    style={[styles.imageContainer, style]}
                    ref={this.captureRef}
                >
                    <Image
                        {...props}
                        style={StyleSheet.absoluteFill}
                        source={resolvedSource}
                        onLoadStart={onLoadStart}
                        onProgress={onProgress}
                        onLoad={onLoad}
                        onError={onError}
                        onLoadEnd={onLoadEnd}
                    />
                    {children}
                </View>
            )
        }

        return (
            <View style={[styles.imageContainer, style]} ref={this.captureRef}>
                <FastImageView
                    {...props}
                    style={StyleSheet.absoluteFill}
                    source={resolvedSource}
                    onFastImageLoadStart={onLoadStart}
                    onFastImageProgress={onProgress}
                    onFastImageLoad={onLoad}
                    onFastImageError={onError}
                    onFastImageLoadEnd={onLoadEnd}
                />
                {children}
            </View>
        )
    }
}

const styles = StyleSheet.create({
    imageContainer: {
        overflow: 'hidden',
    },
})

FastImage.resizeMode = {
    contain: 'contain',
    cover: 'cover',
    stretch: 'stretch',
    center: 'center',
}

FastImage.priority = {
    // lower than usual.
    low: 'low',
    // normal, the default.
    normal: 'normal',
    // higher than usual.
    high: 'high',
}

FastImage.cacheControl = {
    // Ignore headers, use uri as cache key, fetch only if not in cache.
    immutable: 'immutable',
    // Respect http headers, no aggressive caching.
    web: 'web',
    // Only load from cache.
    cacheOnly: 'cacheOnly',
}

FastImage.preload = sources => {
    FastImageViewNativeModule.preload(sources)
}

FastImage.defaultProps = {
    resizeMode: FastImage.resizeMode.cover,
}

const FastImageSourcePropType = PropTypes.shape({
    uri: PropTypes.string,
    headers: PropTypes.objectOf(PropTypes.string),
    priority: PropTypes.oneOf(Object.keys(FastImage.priority)),
    cache: PropTypes.oneOf(Object.keys(FastImage.cacheControl)),
})

FastImage.propTypes = {
    ...ViewPropTypes,
    source: PropTypes.oneOfType([FastImageSourcePropType, PropTypes.number]),
    onLoadStart: PropTypes.func,
    onProgress: PropTypes.func,
    onLoad: PropTypes.func,
    onError: PropTypes.func,
    onLoadEnd: PropTypes.func,
    fallback: PropTypes.bool,
}

const FastImageView = requireNativeComponent('FastImageView', FastImage, {
    nativeOnly: {
        onFastImageLoadStart: true,
        onFastImageProgress: true,
        onFastImageLoad: true,
        onFastImageError: true,
        onFastImageLoadEnd: true,
    },
})

export default FastImage
<commit_msg>feat: Use forwardRef to allow access to ref.measure and others. (#419)<commit_after>import React, { forwardRef } from 'react'
import PropTypes from 'prop-types'
import {
    View,
    Image,
    NativeModules,
    requireNativeComponent,
    ViewPropTypes,
    StyleSheet,
} from 'react-native'

const FastImageViewNativeModule = NativeModules.FastImageView

const FastImage = forwardRef(
    (
        {
            source,
            onLoadStart,
            onProgress,
            onLoad,
            onError,
            onLoadEnd,
            style,
            children,
            fallback,
            ...props
        },
        ref,
    ) => {
        const resolvedSource = Image.resolveAssetSource(source)

        if (fallback) {
            return (
                <View style={[styles.imageContainer, style]} ref={ref}>
                    <Image
                        {...props}
                        style={StyleSheet.absoluteFill}
                        source={resolvedSource}
                        onLoadStart={onLoadStart}
                        onProgress={onProgress}
                        onLoad={onLoad}
                        onError={onError}
                        onLoadEnd={onLoadEnd}
                    />
                    {children}
                </View>
            )
        }

        return (
            <View style={[styles.imageContainer, style]} ref={ref}>
                <FastImageView
                    {...props}
                    style={StyleSheet.absoluteFill}
                    source={resolvedSource}
                    onFastImageLoadStart={onLoadStart}
                    onFastImageProgress={onProgress}
                    onFastImageLoad={onLoad}
                    onFastImageError={onError}
                    onFastImageLoadEnd={onLoadEnd}
                />
                {children}
            </View>
        )
    },
)

FastImage.displayName = 'FastImage'

const styles = StyleSheet.create({
    imageContainer: {
        overflow: 'hidden',
    },
})

FastImage.resizeMode = {
    contain: 'contain',
    cover: 'cover',
    stretch: 'stretch',
    center: 'center',
}

FastImage.priority = {
    // lower than usual.
    low: 'low',
    // normal, the default.
    normal: 'normal',
    // higher than usual.
    high: 'high',
}

FastImage.cacheControl = {
    // Ignore headers, use uri as cache key, fetch only if not in cache.
    immutable: 'immutable',
    // Respect http headers, no aggressive caching.
    web: 'web',
    // Only load from cache.
    cacheOnly: 'cacheOnly',
}

FastImage.preload = sources => {
    FastImageViewNativeModule.preload(sources)
}

FastImage.defaultProps = {
    resizeMode: FastImage.resizeMode.cover,
}

const FastImageSourcePropType = PropTypes.shape({
    uri: PropTypes.string,
    headers: PropTypes.objectOf(PropTypes.string),
    priority: PropTypes.oneOf(Object.keys(FastImage.priority)),
    cache: PropTypes.oneOf(Object.keys(FastImage.cacheControl)),
})

FastImage.propTypes = {
    ...ViewPropTypes,
    source: PropTypes.oneOfType([FastImageSourcePropType, PropTypes.number]),
    onLoadStart: PropTypes.func,
    onProgress: PropTypes.func,
    onLoad: PropTypes.func,
    onError: PropTypes.func,
    onLoadEnd: PropTypes.func,
    fallback: PropTypes.bool,
}

const FastImageView = requireNativeComponent('FastImageView', FastImage, {
    nativeOnly: {
        onFastImageLoadStart: true,
        onFastImageProgress: true,
        onFastImageLoad: true,
        onFastImageError: true,
        onFastImageLoadEnd: true,
    },
})

export default FastImage
<|endoftext|>


<commit_before>import React, { Component } from "react";
import {
  Dimensions,
  Modal,
  DeviceEventEmitter,
  TouchableWithoutFeedback,
  KeyboardAvoidingView,
  Platform,
  PanResponder,
  Animated
} from "react-native";
import PropTypes from "prop-types";
import {
  View,
  initializeRegistryWithDefinitions,
  registerAnimation,
  createAnimation
} from "react-native-animatable";
import * as ANIMATION_DEFINITIONS from "./animations";

import styles from "./index.style.js";

// Override default animations
initializeRegistryWithDefinitions(ANIMATION_DEFINITIONS);

// Utility for creating custom animations
const makeAnimation = (name, obj) => {
  registerAnimation(name, createAnimation(obj));
};

const isObject = obj => {
  return obj !== null && typeof obj === "object";
};

class ReactNativeModal extends Component {
  static propTypes = {
    animationIn: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
    animationInTiming: PropTypes.number,
    animationOut: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
    animationOutTiming: PropTypes.number,
    avoidKeyboard: PropTypes.bool,
    backdropColor: PropTypes.string,
    backdropOpacity: PropTypes.number,
    backdropTransitionInTiming: PropTypes.number,
    backdropTransitionOutTiming: PropTypes.number,
    children: PropTypes.node.isRequired,
    deviceHeight: PropTypes.number,
    deviceWidth: PropTypes.number,
    isVisible: PropTypes.bool.isRequired,
    hideModalContentWhileAnimating: PropTypes.bool,
    onModalShow: PropTypes.func,
    onModalHide: PropTypes.func,
    onBackButtonPress: PropTypes.func,
    onBackdropPress: PropTypes.func,
    onSwipe: PropTypes.func,
    swipeThreshold: PropTypes.number,
    swipeDirection: PropTypes.oneOf(["up", "down", "left", "right"]),
    useNativeDriver: PropTypes.bool,
    style: PropTypes.any,
    scrollTo: PropTypes.func,
    scrollOffset: PropTypes.number,
    scrollOffsetMax: PropTypes.number,
    supportedOrientations: PropTypes.arrayOf(
      PropTypes.oneOf([
        "portrait",
        "portrait-upside-down",
        "landscape",
        "landscape-left",
        "landscape-right"
      ])
    )
  };

  static defaultProps = {
    animationIn: "slideInUp",
    animationInTiming: 300,
    animationOut: "slideOutDown",
    animationOutTiming: 300,
    avoidKeyboard: false,
    backdropColor: "black",
    backdropOpacity: 0.7,
    backdropTransitionInTiming: 300,
    backdropTransitionOutTiming: 300,
    onModalShow: () => null,
    deviceHeight: null,
    deviceWidth: null,
    onModalHide: () => null,
    isVisible: false,
    hideModalContentWhileAnimating: false,
    onBackdropPress: () => null,
    onBackButtonPress: () => null,
    swipeThreshold: 100,
    useNativeDriver: false,
    scrollTo: null,
    scrollOffset: 0,
    scrollOffsetMax: 0,
    supportedOrientations: ["portrait", "landscape"]
  };

  // We use an internal state for keeping track of the modal visibility: this allows us to keep
  // the modal visibile during the exit animation, even if the user has already change the
  // isVisible prop to false.
  // We store in the state the device width and height so that we can update the modal on
  // device rotation.
  state = {
    showContent: true,
    isVisible: false,
    deviceWidth: Dimensions.get("window").width,
    deviceHeight: Dimensions.get("window").height,
    isSwipeable: this.props.swipeDirection ? true : false,
    pan: null
  };

  transitionLock = null;
  inSwipeClosingState = false;

  constructor(props) {
    super(props);
    this.buildAnimations(props);
    if (this.state.isSwipeable) {
      this.state = { ...this.state, pan: new Animated.ValueXY() };
      this.buildPanResponder();
    }
    if (this.props.isVisible) {
      this.state = {
        ...this.state,
        isVisible: true,
        showContent: true
      };
    }
  }

  UNSAFE_componentWillReceiveProps(nextProps) {
    if (!this.state.isVisible && nextProps.isVisible) {
      this.setState({ isVisible: true, showContent: true });
    }
    if (
      this.props.animationIn !== nextProps.animationIn ||
      this.props.animationOut !== nextProps.animationOut
    ) {
      this.buildAnimations(nextProps);
    }
    if (
      this.props.backdropOpacity !== nextProps.backdropOpacity &&
      this.backdropRef
    ) {
      this.backdropRef.transitionTo(
        { opacity: nextProps.backdropOpacity },
        this.props.backdropTransitionInTiming
      );
    }
  }

  componentDidMount() {
    if (this.state.isVisible) {
      this.open();
    }
    DeviceEventEmitter.addListener(
      "didUpdateDimensions",
      this.handleDimensionsUpdate
    );
  }

  componentWillUnmount() {
    DeviceEventEmitter.removeListener(
      "didUpdateDimensions",
      this.handleDimensionsUpdate
    );
  }

  componentDidUpdate(prevProps, prevState) {
    // On modal open request, we slide the view up and fade in the backdrop
    if (this.props.isVisible && !prevProps.isVisible) {
      this.open();
    } else if (!this.props.isVisible && prevProps.isVisible) {
      // On modal close request, we slide the view down and fade out the backdrop
      this.close();
    }
  }

  buildPanResponder = () => {
    let animEvt = null;

    if (
      this.props.swipeDirection === "right" ||
      this.props.swipeDirection === "left"
    ) {
      animEvt = Animated.event([null, { dx: this.state.pan.x }]);
    } else {
      animEvt = Animated.event([null, { dy: this.state.pan.y }]);
    }

    this.panResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        // The number "4" is just a good tradeoff to make the panResponder
        // work correctly even when the modal has touchable buttons.
        // For reference:
        // https://github.com/react-native-community/react-native-modal/pull/197

        // Ghetto temporary fix: https://github.com/react-native-community/react-native-modal/issues/236#issuecomment-453480635
        // return Math.abs(gestureState.dx) >= 4 || Math.abs(gestureState.dy) >= 4;
      },
      onStartShouldSetPanResponder: () => {
        if (this.props.scrollTo) {
          if (this.props.scrollOffset > 0) {
            return false; // user needs to be able to scroll content back up
          }
        }
        return true;
      },
      onPanResponderMove: (evt, gestureState) => {
        // Dim the background while swiping the modal
        const deviceWidth = this.props.deviceWidth || this.state.deviceWidth;
        const accDistance = this.getAccDistancePerDirection(gestureState);
        const newOpacityFactor = 1 - accDistance / deviceWidth;
        if (this.isSwipeDirectionAllowed(gestureState)) {
          this.backdropRef &&
            this.backdropRef.transitionTo({
              opacity: this.props.backdropOpacity * newOpacityFactor
            });
          animEvt(evt, gestureState);
        } else {
          if (this.props.scrollTo) {
            let offsetY = -gestureState.dy;
            if (offsetY > this.props.scrollOffsetMax) {
              offsetY -= (offsetY - this.props.scrollOffsetMax) / 2;
            }
            this.props.scrollTo({ y: offsetY, animated: false });
          }
        }
      },
      onPanResponderRelease: (evt, gestureState) => {
        // Call the onSwipe prop if the threshold has been exceeded
        const accDistance = this.getAccDistancePerDirection(gestureState);
        if (accDistance > this.props.swipeThreshold) {
          if (this.props.onSwipe) {
            this.inSwipeClosingState = true;
            this.props.onSwipe();
            return;
          }
        }
        //Reset backdrop opacity and modal position
        if (this.backdropRef) {
          this.backdropRef.transitionTo(
            { opacity: this.props.backdropOpacity },
            this.props.backdropTransitionInTiming
          );
        }
        Animated.spring(this.state.pan, {
          toValue: { x: 0, y: 0 },
          bounciness: 0
        }).start();
        if (this.props.scrollOffset > this.props.scrollOffsetMax) {
          this.props.scrollTo({
            y: this.props.scrollOffsetMax,
            animated: true
          });
        }
      }
    });
  };

  getAccDistancePerDirection = gestureState => {
    switch (this.props.swipeDirection) {
      case "up":
        return -gestureState.dy;
      case "down":
        return gestureState.dy;
      case "right":
        return gestureState.dx;
      case "left":
        return -gestureState.dx;
      default:
        return 0;
    }
  };

  isSwipeDirectionAllowed = ({ dy, dx }) => {
    const draggedDown = dy > 0;
    const draggedUp = dy < 0;
    const draggedLeft = dx < 0;
    const draggedRight = dx > 0;

    if (this.props.swipeDirection === "up" && draggedUp) {
      return true;
    } else if (this.props.swipeDirection === "down" && draggedDown) {
      return true;
    } else if (this.props.swipeDirection === "right" && draggedRight) {
      return true;
    } else if (this.props.swipeDirection === "left" && draggedLeft) {
      return true;
    }
    return false;
  };

  // User can define custom react-native-animatable animations, see PR #72
  buildAnimations = props => {
    let animationIn = props.animationIn;
    let animationOut = props.animationOut;

    if (isObject(animationIn)) {
      const animationName = JSON.stringify(animationIn);
      makeAnimation(animationName, animationIn);
      animationIn = animationName;
    }

    if (isObject(animationOut)) {
      const animationName = JSON.stringify(animationOut);
      makeAnimation(animationName, animationOut);
      animationOut = animationName;
    }

    this.animationIn = animationIn;
    this.animationOut = animationOut;
  };

  handleDimensionsUpdate = dimensionsUpdate => {
    if (!this.props.deviceHeight && !this.props.deviceWidth) {
      // Here we update the device dimensions in the state if the layout changed
      // (triggering a render)
      const deviceWidth = Dimensions.get("window").width;
      const deviceHeight = Dimensions.get("window").height;
      if (
        deviceWidth !== this.state.deviceWidth ||
        deviceHeight !== this.state.deviceHeight
      ) {
        this.setState({ deviceWidth, deviceHeight });
      }
    }
  };

  open = () => {
    if (this.transitionLock) return;
    this.transitionLock = true;
    if (this.backdropRef) {
      this.backdropRef.transitionTo(
        { opacity: this.props.backdropOpacity },
        this.props.backdropTransitionInTiming
      );
    }

    // This is for reset the pan position, if not modal get stuck
    // at the last release position when you try to open it.
    // Could certainly be improve - no idea for the moment.
    if (this.state.isSwipeable) {
      this.state.pan.setValue({ x: 0, y: 0 });
    }

    if (this.contentRef) {
      this.contentRef[this.animationIn](this.props.animationInTiming).then(
        () => {
          this.transitionLock = false;
          if (!this.props.isVisible) {
            this.close();
          } else {
            this.props.onModalShow();
          }
        }
      );
    }
  };

  close = () => {
    if (this.transitionLock) return;
    this.transitionLock = true;
    if (this.backdropRef) {
      this.backdropRef.transitionTo(
        { opacity: 0 },
        this.props.backdropTransitionOutTiming
      );
    }

    let animationOut = this.animationOut;

    if (this.inSwipeClosingState) {
      this.inSwipeClosingState = false;
      if (this.props.swipeDirection === "up") {
        animationOut = "slideOutUp";
      } else if (this.props.swipeDirection === "down") {
        animationOut = "slideOutDown";
      } else if (this.props.swipeDirection === "right") {
        animationOut = "slideOutRight";
      } else if (this.props.swipeDirection === "left") {
        animationOut = "slideOutLeft";
      }
    }

    if (this.contentRef) {
      this.contentRef[animationOut](this.props.animationOutTiming).then(() => {
        this.transitionLock = false;
        if (this.props.isVisible) {
          this.open();
        } else {
          this.setState(
            {
              showContent: false
            },
            () => {
              this.setState({
                isVisible: false
              });
            }
          );
          this.props.onModalHide();
        }
      });
    }
  };

  render() {
    const {
      animationIn,
      animationInTiming,
      animationOut,
      animationOutTiming,
      avoidKeyboard,
      backdropColor,
      backdropOpacity,
      backdropTransitionInTiming,
      backdropTransitionOutTiming,
      children,
      deviceHeight: deviceHeightProp,
      deviceWidth: deviceWidthProp,
      isVisible,
      onModalShow,
      onBackdropPress,
      onBackButtonPress,
      useNativeDriver,
      style,
      ...otherProps
    } = this.props;
    const deviceWidth = deviceWidthProp || this.state.deviceWidth;
    const deviceHeight = deviceHeightProp || this.state.deviceHeight;

    const computedStyle = [
      { margin: deviceWidth * 0.05, transform: [{ translateY: 0 }] },
      styles.content,
      style
    ];

    let panHandlers = {};
    let panPosition = {};
    if (this.state.isSwipeable) {
      panHandlers = { ...this.panResponder.panHandlers };
      panPosition = this.state.pan.getLayout();
    }

    const _children =
      this.props.hideModalContentWhileAnimating &&
      this.props.useNativeDriver &&
      !this.state.showContent ? (
        <View />
      ) : (
        children
      );
    const containerView = (
      <View
        {...panHandlers}
        ref={ref => (this.contentRef = ref)}
        style={[panPosition, computedStyle]}
        pointerEvents="box-none"
        useNativeDriver={useNativeDriver}
        {...otherProps}
      >
        {_children}
      </View>
    );

    return (
      <Modal
        transparent={true}
        animationType={"none"}
        visible={this.state.isVisible}
        onRequestClose={onBackButtonPress}
        {...otherProps}
      >
        <TouchableWithoutFeedback onPress={onBackdropPress}>
          <View
            ref={ref => (this.backdropRef = ref)}
            useNativeDriver={useNativeDriver}
            style={[
              styles.backdrop,
              {
                backgroundColor: this.state.showContent
                  ? backdropColor
                  : "transparent",
                width: deviceWidth,
                height: deviceHeight
              }
            ]}
          />
        </TouchableWithoutFeedback>

        {avoidKeyboard && (
          <KeyboardAvoidingView
            behavior={Platform.OS === "ios" ? "padding" : null}
            pointerEvents="box-none"
            style={computedStyle.concat([{ margin: 0 }])}
          >
            {containerView}
          </KeyboardAvoidingView>
        )}

        {!avoidKeyboard && containerView}
      </Modal>
    );
  }
}

export default ReactNativeModal;
export { ReactNativeModal };
<commit_msg>add `propagateSwipe` prop<commit_after>import React, { Component } from "react";
import {
  Dimensions,
  Modal,
  DeviceEventEmitter,
  TouchableWithoutFeedback,
  KeyboardAvoidingView,
  Platform,
  PanResponder,
  Animated
} from "react-native";
import PropTypes from "prop-types";
import {
  View,
  initializeRegistryWithDefinitions,
  registerAnimation,
  createAnimation
} from "react-native-animatable";
import * as ANIMATION_DEFINITIONS from "./animations";

import styles from "./index.style.js";

// Override default animations
initializeRegistryWithDefinitions(ANIMATION_DEFINITIONS);

// Utility for creating custom animations
const makeAnimation = (name, obj) => {
  registerAnimation(name, createAnimation(obj));
};

const isObject = obj => {
  return obj !== null && typeof obj === "object";
};

class ReactNativeModal extends Component {
  static propTypes = {
    animationIn: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
    animationInTiming: PropTypes.number,
    animationOut: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
    animationOutTiming: PropTypes.number,
    avoidKeyboard: PropTypes.bool,
    backdropColor: PropTypes.string,
    backdropOpacity: PropTypes.number,
    backdropTransitionInTiming: PropTypes.number,
    backdropTransitionOutTiming: PropTypes.number,
    children: PropTypes.node.isRequired,
    deviceHeight: PropTypes.number,
    deviceWidth: PropTypes.number,
    isVisible: PropTypes.bool.isRequired,
    hideModalContentWhileAnimating: PropTypes.bool,
    propagateSwipe: PropTypes.bool,
    onModalShow: PropTypes.func,
    onModalHide: PropTypes.func,
    onBackButtonPress: PropTypes.func,
    onBackdropPress: PropTypes.func,
    onSwipe: PropTypes.func,
    swipeThreshold: PropTypes.number,
    swipeDirection: PropTypes.oneOf(["up", "down", "left", "right"]),
    useNativeDriver: PropTypes.bool,
    style: PropTypes.any,
    scrollTo: PropTypes.func,
    scrollOffset: PropTypes.number,
    scrollOffsetMax: PropTypes.number,
    supportedOrientations: PropTypes.arrayOf(
      PropTypes.oneOf([
        "portrait",
        "portrait-upside-down",
        "landscape",
        "landscape-left",
        "landscape-right"
      ])
    )
  };

  static defaultProps = {
    animationIn: "slideInUp",
    animationInTiming: 300,
    animationOut: "slideOutDown",
    animationOutTiming: 300,
    avoidKeyboard: false,
    backdropColor: "black",
    backdropOpacity: 0.7,
    backdropTransitionInTiming: 300,
    backdropTransitionOutTiming: 300,
    onModalShow: () => null,
    deviceHeight: null,
    deviceWidth: null,
    onModalHide: () => null,
    isVisible: false,
    hideModalContentWhileAnimating: false,
    propagateSwipe: PropTypes.false,
    onBackdropPress: () => null,
    onBackButtonPress: () => null,
    swipeThreshold: 100,
    useNativeDriver: false,
    scrollTo: null,
    scrollOffset: 0,
    scrollOffsetMax: 0,
    supportedOrientations: ["portrait", "landscape"]
  };

  // We use an internal state for keeping track of the modal visibility: this allows us to keep
  // the modal visibile during the exit animation, even if the user has already change the
  // isVisible prop to false.
  // We store in the state the device width and height so that we can update the modal on
  // device rotation.
  state = {
    showContent: true,
    isVisible: false,
    deviceWidth: Dimensions.get("window").width,
    deviceHeight: Dimensions.get("window").height,
    isSwipeable: this.props.swipeDirection ? true : false,
    pan: null
  };

  transitionLock = null;
  inSwipeClosingState = false;

  constructor(props) {
    super(props);
    this.buildAnimations(props);
    if (this.state.isSwipeable) {
      this.state = { ...this.state, pan: new Animated.ValueXY() };
      this.buildPanResponder();
    }
    if (this.props.isVisible) {
      this.state = {
        ...this.state,
        isVisible: true,
        showContent: true
      };
    }
  }

  UNSAFE_componentWillReceiveProps(nextProps) {
    if (!this.state.isVisible && nextProps.isVisible) {
      this.setState({ isVisible: true, showContent: true });
    }
    if (
      this.props.animationIn !== nextProps.animationIn ||
      this.props.animationOut !== nextProps.animationOut
    ) {
      this.buildAnimations(nextProps);
    }
    if (
      this.props.backdropOpacity !== nextProps.backdropOpacity &&
      this.backdropRef
    ) {
      this.backdropRef.transitionTo(
        { opacity: nextProps.backdropOpacity },
        this.props.backdropTransitionInTiming
      );
    }
  }

  componentDidMount() {
    if (this.state.isVisible) {
      this.open();
    }
    DeviceEventEmitter.addListener(
      "didUpdateDimensions",
      this.handleDimensionsUpdate
    );
  }

  componentWillUnmount() {
    DeviceEventEmitter.removeListener(
      "didUpdateDimensions",
      this.handleDimensionsUpdate
    );
  }

  componentDidUpdate(prevProps, prevState) {
    // On modal open request, we slide the view up and fade in the backdrop
    if (this.props.isVisible && !prevProps.isVisible) {
      this.open();
    } else if (!this.props.isVisible && prevProps.isVisible) {
      // On modal close request, we slide the view down and fade out the backdrop
      this.close();
    }
  }

  buildPanResponder = () => {
    let animEvt = null;

    if (
      this.props.swipeDirection === "right" ||
      this.props.swipeDirection === "left"
    ) {
      animEvt = Animated.event([null, { dx: this.state.pan.x }]);
    } else {
      animEvt = Animated.event([null, { dy: this.state.pan.y }]);
    }

    this.panResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        // The number "4" is just a good tradeoff to make the panResponder
        // work correctly even when the modal has touchable buttons.
        // For reference:
        // https://github.com/react-native-community/react-native-modal/pull/197
        if (!this.props.propagateSwipe) {
          return Math.abs(gestureState.dx) >= 4 || Math.abs(gestureState.dy) >= 4;
        }
      },
      onStartShouldSetPanResponder: () => {
        if (this.props.scrollTo) {
          if (this.props.scrollOffset > 0) {
            return false; // user needs to be able to scroll content back up
          }
        }
        return true;
      },
      onPanResponderMove: (evt, gestureState) => {
        // Dim the background while swiping the modal
        const deviceWidth = this.props.deviceWidth || this.state.deviceWidth;
        const accDistance = this.getAccDistancePerDirection(gestureState);
        const newOpacityFactor = 1 - accDistance / deviceWidth;
        if (this.isSwipeDirectionAllowed(gestureState)) {
          this.backdropRef &&
            this.backdropRef.transitionTo({
              opacity: this.props.backdropOpacity * newOpacityFactor
            });
          animEvt(evt, gestureState);
        } else {
          if (this.props.scrollTo) {
            let offsetY = -gestureState.dy;
            if (offsetY > this.props.scrollOffsetMax) {
              offsetY -= (offsetY - this.props.scrollOffsetMax) / 2;
            }
            this.props.scrollTo({ y: offsetY, animated: false });
          }
        }
      },
      onPanResponderRelease: (evt, gestureState) => {
        // Call the onSwipe prop if the threshold has been exceeded
        const accDistance = this.getAccDistancePerDirection(gestureState);
        if (accDistance > this.props.swipeThreshold) {
          if (this.props.onSwipe) {
            this.inSwipeClosingState = true;
            this.props.onSwipe();
            return;
          }
        }
        //Reset backdrop opacity and modal position
        if (this.backdropRef) {
          this.backdropRef.transitionTo(
            { opacity: this.props.backdropOpacity },
            this.props.backdropTransitionInTiming
          );
        }
        Animated.spring(this.state.pan, {
          toValue: { x: 0, y: 0 },
          bounciness: 0
        }).start();
        if (this.props.scrollOffset > this.props.scrollOffsetMax) {
          this.props.scrollTo({
            y: this.props.scrollOffsetMax,
            animated: true
          });
        }
      }
    });
  };

  getAccDistancePerDirection = gestureState => {
    switch (this.props.swipeDirection) {
      case "up":
        return -gestureState.dy;
      case "down":
        return gestureState.dy;
      case "right":
        return gestureState.dx;
      case "left":
        return -gestureState.dx;
      default:
        return 0;
    }
  };

  isSwipeDirectionAllowed = ({ dy, dx }) => {
    const draggedDown = dy > 0;
    const draggedUp = dy < 0;
    const draggedLeft = dx < 0;
    const draggedRight = dx > 0;

    if (this.props.swipeDirection === "up" && draggedUp) {
      return true;
    } else if (this.props.swipeDirection === "down" && draggedDown) {
      return true;
    } else if (this.props.swipeDirection === "right" && draggedRight) {
      return true;
    } else if (this.props.swipeDirection === "left" && draggedLeft) {
      return true;
    }
    return false;
  };

  // User can define custom react-native-animatable animations, see PR #72
  buildAnimations = props => {
    let animationIn = props.animationIn;
    let animationOut = props.animationOut;

    if (isObject(animationIn)) {
      const animationName = JSON.stringify(animationIn);
      makeAnimation(animationName, animationIn);
      animationIn = animationName;
    }

    if (isObject(animationOut)) {
      const animationName = JSON.stringify(animationOut);
      makeAnimation(animationName, animationOut);
      animationOut = animationName;
    }

    this.animationIn = animationIn;
    this.animationOut = animationOut;
  };

  handleDimensionsUpdate = dimensionsUpdate => {
    if (!this.props.deviceHeight && !this.props.deviceWidth) {
      // Here we update the device dimensions in the state if the layout changed
      // (triggering a render)
      const deviceWidth = Dimensions.get("window").width;
      const deviceHeight = Dimensions.get("window").height;
      if (
        deviceWidth !== this.state.deviceWidth ||
        deviceHeight !== this.state.deviceHeight
      ) {
        this.setState({ deviceWidth, deviceHeight });
      }
    }
  };

  open = () => {
    if (this.transitionLock) return;
    this.transitionLock = true;
    if (this.backdropRef) {
      this.backdropRef.transitionTo(
        { opacity: this.props.backdropOpacity },
        this.props.backdropTransitionInTiming
      );
    }

    // This is for reset the pan position, if not modal get stuck
    // at the last release position when you try to open it.
    // Could certainly be improve - no idea for the moment.
    if (this.state.isSwipeable) {
      this.state.pan.setValue({ x: 0, y: 0 });
    }

    if (this.contentRef) {
      this.contentRef[this.animationIn](this.props.animationInTiming).then(
        () => {
          this.transitionLock = false;
          if (!this.props.isVisible) {
            this.close();
          } else {
            this.props.onModalShow();
          }
        }
      );
    }
  };

  close = () => {
    if (this.transitionLock) return;
    this.transitionLock = true;
    if (this.backdropRef) {
      this.backdropRef.transitionTo(
        { opacity: 0 },
        this.props.backdropTransitionOutTiming
      );
    }

    let animationOut = this.animationOut;

    if (this.inSwipeClosingState) {
      this.inSwipeClosingState = false;
      if (this.props.swipeDirection === "up") {
        animationOut = "slideOutUp";
      } else if (this.props.swipeDirection === "down") {
        animationOut = "slideOutDown";
      } else if (this.props.swipeDirection === "right") {
        animationOut = "slideOutRight";
      } else if (this.props.swipeDirection === "left") {
        animationOut = "slideOutLeft";
      }
    }

    if (this.contentRef) {
      this.contentRef[animationOut](this.props.animationOutTiming).then(() => {
        this.transitionLock = false;
        if (this.props.isVisible) {
          this.open();
        } else {
          this.setState(
            {
              showContent: false
            },
            () => {
              this.setState({
                isVisible: false
              });
            }
          );
          this.props.onModalHide();
        }
      });
    }
  };

  render() {
    const {
      animationIn,
      animationInTiming,
      animationOut,
      animationOutTiming,
      avoidKeyboard,
      backdropColor,
      backdropOpacity,
      backdropTransitionInTiming,
      backdropTransitionOutTiming,
      children,
      deviceHeight: deviceHeightProp,
      deviceWidth: deviceWidthProp,
      isVisible,
      onModalShow,
      onBackdropPress,
      onBackButtonPress,
      useNativeDriver,
      style,
      ...otherProps
    } = this.props;
    const deviceWidth = deviceWidthProp || this.state.deviceWidth;
    const deviceHeight = deviceHeightProp || this.state.deviceHeight;

    const computedStyle = [
      { margin: deviceWidth * 0.05, transform: [{ translateY: 0 }] },
      styles.content,
      style
    ];

    let panHandlers = {};
    let panPosition = {};
    if (this.state.isSwipeable) {
      panHandlers = { ...this.panResponder.panHandlers };
      panPosition = this.state.pan.getLayout();
    }

    const _children =
      this.props.hideModalContentWhileAnimating &&
      this.props.useNativeDriver &&
      !this.state.showContent ? (
        <View />
      ) : (
        children
      );
    const containerView = (
      <View
        {...panHandlers}
        ref={ref => (this.contentRef = ref)}
        style={[panPosition, computedStyle]}
        pointerEvents="box-none"
        useNativeDriver={useNativeDriver}
        {...otherProps}
      >
        {_children}
      </View>
    );

    return (
      <Modal
        transparent={true}
        animationType={"none"}
        visible={this.state.isVisible}
        onRequestClose={onBackButtonPress}
        {...otherProps}
      >
        <TouchableWithoutFeedback onPress={onBackdropPress}>
          <View
            ref={ref => (this.backdropRef = ref)}
            useNativeDriver={useNativeDriver}
            style={[
              styles.backdrop,
              {
                backgroundColor: this.state.showContent
                  ? backdropColor
                  : "transparent",
                width: deviceWidth,
                height: deviceHeight
              }
            ]}
          />
        </TouchableWithoutFeedback>

        {avoidKeyboard && (
          <KeyboardAvoidingView
            behavior={Platform.OS === "ios" ? "padding" : null}
            pointerEvents="box-none"
            style={computedStyle.concat([{ margin: 0 }])}
          >
            {containerView}
          </KeyboardAvoidingView>
        )}

        {!avoidKeyboard && containerView}
      </Modal>
    );
  }
}

export default ReactNativeModal;
export { ReactNativeModal };
<|endoftext|>


<commit_before>/* @flow */
import path from 'path';
import React from 'react';
import ReactDOM from 'react-dom/server';
import evaluate from 'eval';
import { match, RouterContext } from 'react-router';
import async from 'async';
import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin';
import Promise from 'bluebird';

import { name as packageName } from '../package.json';
import { getAllPaths, log } from './utils.js';
import { render } from './Html.js';

/**
 * All source will be compiled with babel so ES6 goes
 *
 * Usage:
 *
 *   new StaticSitePlugin({ src: 'client/routes.js', ...options }),
 *
 */

type Options = {
  routes: string,
  bundle?: string,
  stylesheet?: string,
  favicon?: string,
  template?: string,

  src?: string, // Deprecated. Use routes instead
}

const validateOptions = (options) => {
  if (!options.routes) {
    throw new Error('No routes param provided');
  }
};

function StaticSitePlugin(options: Options) {
  validateOptions(options);
  this.options = options;
}

// Purely for debugging
const dir = (obj, params) => console.dir(obj, { colors: true, depth: 4, ...params });

const CompileAsset = (a: string, b: Object, c: string) => Promise;
const compileRoutes: CompileAsset = (routes, compilation, context) => {

  const compilerName = `react-static-webpack compiling "${routes}"`;
  const outputFilename = 'routes.js';
  const outputOptions = {
    filename: outputFilename,
    publicPath: compilation.outputOptions.publicPath,
  };

  const childCompiler = compilation.createChildCompiler(compilerName, outputOptions);
  childCompiler.apply(
    new SingleEntryPlugin(context, routes)
  );

  // Run the compilation async and return a promise
  return new Promise((resolve, reject) => {
    childCompiler.runAsChild(function(err, entries, childCompilation) {
      // Resolve / reject the promise
      if (childCompilation.errors && childCompilation.errors.length) {
        const errorDetails = childCompilation.errors.map((err) => {
          return err.message + (err.error ? ':\n' + err.error : '');
        }).join('\n');

        reject('Child compilation failed:\n' + errorDetails);
      } else {
        resolve(compilation.assets[outputFilename]);
      }
    });
  });
};

/**
 * compiler seems to be an instance of the Compiler
 * https://github.com/webpack/webpack/blob/master/lib/Compiler.js#L143
 *
 * NOTE: renderProps.routes is always passed as an array of route elements. For
 * deeply nested routes the last element in the array is the route we want to
 * get the props of, so we grab it out of the array and use it. This lets us do
 * things like:
 *
 *   <Route title='Blah blah blah...' {...moreProps} />
 *
 * Then set the document title to the title defined on that route
 *
 * NOTE: Sometimes when matching routes we do not get an error but nore do we
 * get renderProps. In my experience this usually means we hit an IndexRedirect
 * or some form of Route that doesn't actually have a component to render. In
 * these cases we simply keep on moving and don't render anything.
 *
 * TODO:
 * - Allow passing a function for title?
 *
 */
StaticSitePlugin.prototype.apply = function(compiler) {

  // compiler.plugin('compilation', (compilation) => {
  //   if (compilation.compiler.isChild()) return;
  //   console.log('Compilation');
  // });

  // compiler.plugin("normal-module-factory", function(nmf) {
  //   console.log('NMF', nmf);
  //     nmf.plugin("after-resolve", function(data) {
  //       console.log('DATA', data);
  //         data.loaders.unshift(path.join(__dirname, "postloader.js"));
  //     });
  // });

  // Can add loaders here as well since we have access to
  // params.normalModuleFactory
  // compiler.plugin('compile', (params) => {
  //   console.log('PARAMS');
  //   dir(params);
  // });

  let compilationPromise;

  // Compile Routes and or entry point
  compiler.plugin('make', (compilation, cb) => {
    const { routes } = this.options;
    compilationPromise = compileRoutes(routes, compilation, this.context)
    .catch(err => new Error(err))
    .finally(cb);
  });

  compiler.plugin('emit', (compilation, cb) => {
    compilationPromise
    .then((asset) => {
      dir(asset);
    })
    .then((...args) => {
      cb(...args);
    });
    // .then(() => {
    //   const asset = findAsset(this.options.src, compilation);
    //
    //   if (!asset) {
    //     throw new Error(`Output file not found: ${this.options.src}`);
    //   }
    //
    //   const source = evaluate(asset.source(), true);
    //   const Component = source.routes || source;
    //   log('src evaluated to Component:', Component);
    //
    //   // NOTE: If Symbol(react.element) was removed this would no longer work
    //   if (!isValidComponent(Component)) {
    //     log('Component was invalid. Throwing error.');
    //     throw new Error(`${packageName} -- options.src entry point must export a valid React Component.`);
    //   }
    //
    //   if (!isRoute(Component)) {
    //     log('Entrypoint or chunk name did not return a Route component. Rendering as individual component instead.');
    //     compilation.assets['index.html'] = renderSingleComponent(Component, this.options, this.render);
    //     return cb();
    //   }
    //
    //   const paths = getAllPaths(Component);
    //   log('Parsed routes:', paths);
    //
    //   async.forEach(paths,
    //     (location, callback) => {
    //       match({ routes: Component, location }, (err, redirectLocation, renderProps) => {
    //         // Skip if something goes wrong. See NOTE above.
    //         if (err || !renderProps) {
    //           log('Error matching route', err, renderProps);
    //           return callback();
    //         }
    //
    //         const route = renderProps.routes[renderProps.routes.length - 1]; // See NOTE
    //         const body = ReactDOM.renderToString(<RouterContext {...renderProps} />);
    //         const { stylesheet, favicon, bundle } = this.options;
    //         const assetKey = getAssetKey(location);
    //         const doc = this.render({
    //           title: route.title,
    //           body,
    //           stylesheet,
    //           favicon,
    //           bundle,
    //         });
    //
    //         compilation.assets[assetKey] = {
    //           source() { return doc; },
    //           size() { return doc.length; },
    //         };
    //
    //         callback();
    //       });
    //     },
    //     err => {
    //       if (err) throw err;
    //       cb();
    //     }
    //   );
    // })

  });
};

/**
 * @param {string} src
 * @param {Compilation} compilation
 */
function findAsset(src, compilation) {
  const asset = compilation.assets[src];

  // Found it. It was a key within assets
  if (asset) return asset;

  // Didn't find it in assets, it must be a chunk

  const webpackStatsJson = compilation.getStats().toJson();
  let chunkValue = webpackStatsJson.assetsByChunkName[src];

  // Uh oh, couldn't find it as a chunk value either. This indicates a failure
  // to find the asset. The caller should handle a falsey value as it sees fit.
  if (!chunkValue) return null;

  // Webpack outputs an array for each chunk when using sourcemaps
  if (chunkValue instanceof Array) {
    chunkValue = chunkValue[0]; // Is the main bundle always the first element?
  }

  return compilation.assets[chunkValue];
}

/**
 * Given a string location (i.e. path) return a relevant HTML filename.
 * Ex: '/' -> 'index.html'
 * Ex: '/about' -> 'about.html'
 * Ex: '/about/' -> 'about/index.html'
 * Ex: '/about/team' -> 'about/team.html'
 *
 * NOTE: Don't want leading slash
 * i.e. 'path/to/file.html' instead of '/path/to/file.html'
 *
 * NOTE: There is a lone case where the users specifices a not found route that
 * results in a '/*' location. In this case we output 404.html, since it's
 * assumed that this is a 404 route. See the RR changelong for details:
 * https://github.com/rackt/react-router/blob/1.0.x/CHANGES.md#notfound-route
 */
function getAssetKey(location: string): string {
  const basename = path.basename(location);
  const dirname = path.dirname(location).slice(1); // See NOTE above
  let filename;

  if (!basename || location.slice(-1) === '/') {
    filename = 'index.html';
  } else if (basename === '*') {
    filename = '404.html';
  } else {
    filename = basename + '.html';
  }

  return dirname ? (dirname + path.sep + filename) : filename;
}

/**
 * Test if a React Element is a React Router Route or not. Note that this tests
 * the actual object (i.e. React Element), not a constructor. As such we
 * immediately deconstruct out the type property as that is what we want to
 * test.
 *
 * NOTE: Testing whether Component.type was an instanceof Route did not work.
 *
 * NOTE: This is a fragile test. The React Router API is notorious for
 * introducing breaking changes, so of the RR team changed the manditory path
 * and component props this would fail.
 */
function isRoute({ type: component }): boolean {
  return component && component.propTypes.path && component.propTypes.component;
}

/**
 * Test if a component is a valid React component.
 *
 * NOTE: This is a pretty wonky test. React.createElement wasn't doing it for
 * me. It seemed to be giving false positives.
 *
 * @param {any} component
 * @return {boolean}
 */
function isValidComponent(Component): boolean {
  const { type } = React.createElement(Component);
  return typeof type === 'object' || typeof type === 'function';
}

/**
 * If not provided with any React Router Routes we try to render whatever asset
 * was passed to the plugin as a React component. The use case would be anyone
 * who doesn't need/want to add RR as a dependency to their app and instead only
 * wants a single HTML file output.
 *
 * NOTE: In the case of a single component we use the static prop 'title' to get
 * the page title. If this is not provided then the title will default to
 * whatever is provided in the template.
 */
function renderSingleComponent(Component, options, render) { // eslint-disable-line no-shadow
  const body = ReactDOM.renderToString(<Component />);
  const { stylesheet, favicon, bundle } = options;
  const doc = render({
    title: Component.title, // See NOTE
    body,
    stylesheet,
    favicon,
    bundle,
  });

  return {
    source() { return doc; },
    size() { return doc.length; },
  };
}

module.exports = StaticSitePlugin;
<commit_msg>Start re-adding route parsing logic<commit_after>/* @flow */
import path from 'path';
import vm from 'vm';
import React from 'react';
import ReactDOM from 'react-dom/server';
import evaluate from 'eval';
import { match, RouterContext } from 'react-router';
import async from 'async';
import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin';
import Promise from 'bluebird';

/**
 * TODO: There is currnetly an issue where it seems the compiled bundle is not
 * UMD. Meaning when it getes evaled there is nothing there so the component is
 * not recognized.
 *
 * Maybe try using the library target plugin...
 */

import { name as packageName } from '../package.json';
import { getAllPaths, log } from './utils.js';
import { render } from './Html.js';

/**
 * All source will be compiled with babel so ES6 goes
 *
 * Usage:
 *
 *   new StaticSitePlugin({ src: 'client/routes.js', ...options }),
 *
 */

type Options = {
  routes: string,
  bundle?: string,
  stylesheet?: string,
  favicon?: string,
  template?: string,

  src?: string, // Deprecated. Use routes instead
}

const validateOptions = (options) => {
  if (!options.routes) {
    throw new Error('No routes param provided');
  }
};

// Purely for debugging
const dir = (obj, params) => console.dir(obj, { colors: true, depth: 4, ...params });

const CompileAsset = (a: string, b: Object, c: string) => Promise;
const compileRoutes: CompileAsset = (routes, compilation, context) => {

  const compilerName = `react-static-webpack compiling "${routes}"`;
  const outputFilename = 'routes.js';
  const outputOptions = {
    filename: outputFilename,
    publicPath: compilation.outputOptions.publicPath,
  };

  const childCompiler = compilation.createChildCompiler(compilerName, outputOptions);
  childCompiler.apply(
    new SingleEntryPlugin(context, routes)
  );

  // Run the compilation async and return a promise
  return new Promise((resolve, reject) => {
    childCompiler.runAsChild(function(err, entries, childCompilation) {
      // Resolve / reject the promise
      if (childCompilation.errors && childCompilation.errors.length) {
        const errorDetails = childCompilation.errors.map((err) => {
          return err.message + (err.error ? ':\n' + err.error : '');
        }).join('\n');

        reject('Child compilation failed:\n' + errorDetails);
      } else {
        resolve(compilation.assets[outputFilename]);
      }
    });
  });
};

function StaticSitePlugin(options: Options) {
  validateOptions(options);
  this.options = options;
  this.render = this.options.template
    ? require(path.resolve(this.options.template))
    : render;
}

/**
 * compiler seems to be an instance of the Compiler
 * https://github.com/webpack/webpack/blob/master/lib/Compiler.js#L143
 *
 * NOTE: renderProps.routes is always passed as an array of route elements. For
 * deeply nested routes the last element in the array is the route we want to
 * get the props of, so we grab it out of the array and use it. This lets us do
 * things like:
 *
 *   <Route title='Blah blah blah...' {...moreProps} />
 *
 * Then set the document title to the title defined on that route
 *
 * NOTE: Sometimes when matching routes we do not get an error but nore do we
 * get renderProps. In my experience this usually means we hit an IndexRedirect
 * or some form of Route that doesn't actually have a component to render. In
 * these cases we simply keep on moving and don't render anything.
 *
 * TODO:
 * - Allow passing a function for title?
 *
 */
StaticSitePlugin.prototype.apply = function(compiler) {

  // compiler.plugin('compilation', (compilation) => {
  //   if (compilation.compiler.isChild()) return;
  //   console.log('Compilation');
  // });

  // compiler.plugin("normal-module-factory", function(nmf) {
  //   console.log('NMF', nmf);
  //     nmf.plugin("after-resolve", function(data) {
  //       console.log('DATA', data);
  //         data.loaders.unshift(path.join(__dirname, "postloader.js"));
  //     });
  // });

  // Can add loaders here as well since we have access to
  // params.normalModuleFactory
  // compiler.plugin('compile', (params) => {
  //   console.log('PARAMS');
  //   dir(params);
  // });

  let compilationPromise;

  // Compile Routes and or entry point
  compiler.plugin('make', (compilation, cb) => {
    const { routes } = this.options;
    compilationPromise = compileRoutes(routes, compilation, this.context)
    .catch(err => new Error(err))
    .finally(cb);
  });

  /**
   * [1]: We want to allow the user the option of eithe export default routes or
   * export routes.
   */
  compiler.plugin('emit', (compilation, cb) => {
    compilationPromise
    .then((asset) => {
      return vm.runInThisContext(source);
      // console.log(evaluate(asset.source(), true));
      // return evaluate(asset.source(), true);
    })
    .catch(cb) // TODO: Eval failed, likely a syntax error in build
    .then((routes) => {
      if (!routes) {
        throw new Error(`File compiled with empty source: ${this.options.routes}`);
      }

      const Component = routes.routes || routes; // [1]

      dir(Component);

      if (!isRoute(Component)) {
        log('Entrypoint or chunk name did not return a Route component. Rendering as individual component instead.');
        compilation.assets['index.html'] = renderSingleComponent(Component, this.options, this.render);
        return cb();
      }

      const paths = getAllPaths(Component);
      log('Parsed routes:', paths);

      cb();
    })
    // .then(() => {
    //   const asset = findAsset(this.options.src, compilation);
    //
    //   if (!asset) {
    //     throw new Error(`Output file not found: ${this.options.src}`);
    //   }
    //
    //   const source = evaluate(asset.source(), true);
    //   const Component = source.routes || source;
    //   log('src evaluated to Component:', Component);
    //
    //   // NOTE: If Symbol(react.element) was removed this would no longer work
    //   if (!isValidComponent(Component)) {
    //     log('Component was invalid. Throwing error.');
    //     throw new Error(`${packageName} -- options.src entry point must export a valid React Component.`);
    //   }
    //
    //   if (!isRoute(Component)) {
    //     log('Entrypoint or chunk name did not return a Route component. Rendering as individual component instead.');
    //     compilation.assets['index.html'] = renderSingleComponent(Component, this.options, this.render);
    //     return cb();
    //   }
    //
    //   const paths = getAllPaths(Component);
    //   log('Parsed routes:', paths);
    //
    //   async.forEach(paths,
    //     (location, callback) => {
    //       match({ routes: Component, location }, (err, redirectLocation, renderProps) => {
    //         // Skip if something goes wrong. See NOTE above.
    //         if (err || !renderProps) {
    //           log('Error matching route', err, renderProps);
    //           return callback();
    //         }
    //
    //         const route = renderProps.routes[renderProps.routes.length - 1]; // See NOTE
    //         const body = ReactDOM.renderToString(<RouterContext {...renderProps} />);
    //         const { stylesheet, favicon, bundle } = this.options;
    //         const assetKey = getAssetKey(location);
    //         const doc = this.render({
    //           title: route.title,
    //           body,
    //           stylesheet,
    //           favicon,
    //           bundle,
    //         });
    //
    //         compilation.assets[assetKey] = {
    //           source() { return doc; },
    //           size() { return doc.length; },
    //         };
    //
    //         callback();
    //       });
    //     },
    //     err => {
    //       if (err) throw err;
    //       cb();
    //     }
    //   );
    // })

  });
};

/**
 * @param {string} src
 * @param {Compilation} compilation
 */
function findAsset(src, compilation) {
  const asset = compilation.assets[src];

  // Found it. It was a key within assets
  if (asset) return asset;

  // Didn't find it in assets, it must be a chunk

  const webpackStatsJson = compilation.getStats().toJson();
  let chunkValue = webpackStatsJson.assetsByChunkName[src];

  // Uh oh, couldn't find it as a chunk value either. This indicates a failure
  // to find the asset. The caller should handle a falsey value as it sees fit.
  if (!chunkValue) return null;

  // Webpack outputs an array for each chunk when using sourcemaps
  if (chunkValue instanceof Array) {
    chunkValue = chunkValue[0]; // Is the main bundle always the first element?
  }

  return compilation.assets[chunkValue];
}

/**
 * Given a string location (i.e. path) return a relevant HTML filename.
 * Ex: '/' -> 'index.html'
 * Ex: '/about' -> 'about.html'
 * Ex: '/about/' -> 'about/index.html'
 * Ex: '/about/team' -> 'about/team.html'
 *
 * NOTE: Don't want leading slash
 * i.e. 'path/to/file.html' instead of '/path/to/file.html'
 *
 * NOTE: There is a lone case where the users specifices a not found route that
 * results in a '/*' location. In this case we output 404.html, since it's
 * assumed that this is a 404 route. See the RR changelong for details:
 * https://github.com/rackt/react-router/blob/1.0.x/CHANGES.md#notfound-route
 */
function getAssetKey(location: string): string {
  const basename = path.basename(location);
  const dirname = path.dirname(location).slice(1); // See NOTE above
  let filename;

  if (!basename || location.slice(-1) === '/') {
    filename = 'index.html';
  } else if (basename === '*') {
    filename = '404.html';
  } else {
    filename = basename + '.html';
  }

  return dirname ? (dirname + path.sep + filename) : filename;
}

/**
 * Test if a React Element is a React Router Route or not. Note that this tests
 * the actual object (i.e. React Element), not a constructor. As such we
 * immediately deconstruct out the type property as that is what we want to
 * test.
 *
 * NOTE: Testing whether Component.type was an instanceof Route did not work.
 *
 * NOTE: This is a fragile test. The React Router API is notorious for
 * introducing breaking changes, so of the RR team changed the manditory path
 * and component props this would fail.
 */
function isRoute({ type: component }): boolean {
  return component && component.propTypes.path && component.propTypes.component;
}

/**
 * Test if a component is a valid React component.
 *
 * NOTE: This is a pretty wonky test. React.createElement wasn't doing it for
 * me. It seemed to be giving false positives.
 *
 * @param {any} component
 * @return {boolean}
 */
function isValidComponent(Component): boolean {
  const { type } = React.createElement(Component);
  return typeof type === 'object' || typeof type === 'function';
}

/**
 * If not provided with any React Router Routes we try to render whatever asset
 * was passed to the plugin as a React component. The use case would be anyone
 * who doesn't need/want to add RR as a dependency to their app and instead only
 * wants a single HTML file output.
 *
 * NOTE: In the case of a single component we use the static prop 'title' to get
 * the page title. If this is not provided then the title will default to
 * whatever is provided in the template.
 */
function renderSingleComponent(Component, options, render) { // eslint-disable-line no-shadow
  dir(Component)
  const body = ReactDOM.renderToString(<Component />);
  const { stylesheet, favicon, bundle } = options;
  const doc = render({
    title: Component.title, // See NOTE
    body,
    stylesheet,
    favicon,
    bundle,
  });

  return {
    source() { return doc; },
    size() { return doc.length; },
  };
}

module.exports = StaticSitePlugin;
<|endoftext|>


<commit_before>// Set an empty reference to _ because it's not known yet if it will be
// underscore or lodash
var _;

// UUID
// https://gist.github.com/jed/982883
/* jshint ignore:start */
function b(
  a                  // placeholder
){
  return a           // if the placeholder was passed, return
    ? (              // a random number from 0 to 15
      a ^            // unless b is 8,
      Math.random()  // in which case
      * 16           // a random number from
      >> a/4         // 8 to 11
      ).toString(16) // in hexadecimal
    : (              // or otherwise a concatenated string:
      [1e7] +        // 10000000 +
      -1e3 +         // -1000 +
      -4e3 +         // -4000 +
      -8e3 +         // -80000000 +
      -1e11          // -100000000000,
      ).replace(     // replacing
        /[018]/g,    // zeroes, ones, and eights with
        b            // random hex digits
      )
}
/* jshint ignore:end */

// Copies properties from an docect to another
function __update(dest, src) {
  _.each(src, function(value, key) {
    dest[key] = value;
  });
}

// Removes an item from an array
function __remove(array, item) {
  var index = _.indexOf(array, item);
  if (index != -1) array.splice(index, 1);
}

function get(collection, id) {
  return _.find(collection, function (doc) {
    return doc.id === id;
  });
}

function createId(collection, doc) {
  return b();
}

function insert(collection, doc) {
  var clone = _.clone(doc);

  if (!clone.hasOwnProperty('id')) clone.id = _.createId(collection, doc);

  collection.push(clone);

  return clone;
}

function update(collection, id, attrs) {
  var doc = get(collection, id);

  if (doc) __update(doc, attrs);

  return doc;
}

function updateWhere(collection, whereAttrs, attrs) {
  var docs = _.where(collection, whereAttrs);

  docs.forEach(function(doc) {
    __update(doc, attrs);
  });

  return docs;
}

function remove(collection, id) {
  var doc = get(collection, id);

  __remove(collection, doc);

  return doc;
}

function removeWhere(collection, attrs) {
  var docs = _.where(collection, attrs);

  docs.forEach(function(doc) {
    __remove(collection, doc);
  });

  return docs;
}

function set_(lib) {
  _ = lib;
}

function get_() {
  return _;
}

function mixWith(lib) {
  set_(lib);
  lib.mixin(this);
}

module.exports = {
  get_: get_,
  set_: set_,
  mixWith: mixWith,
  get: get,
  createId: createId,
  insert: insert,
  update: update,
  updateWhere: updateWhere,
  remove: remove,
  removeWhere: removeWhere
};
<commit_msg>Change UUID<commit_after>// Set an empty reference to _ because it's not known yet if it will be
// underscore or lodash
var _;

// UUID
// https://gist.github.com/LeverOne/1308368
/* jshint ignore:start */
function uuid(a,b){for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');return b}
/* jshint ignore:end */

// Copies properties from an docect to another
function __update(dest, src) {
  _.each(src, function(value, key) {
    dest[key] = value;
  });
}

// Removes an item from an array
function __remove(array, item) {
  var index = _.indexOf(array, item);
  if (index != -1) array.splice(index, 1);
}

function get(collection, id) {
  return _.find(collection, function (doc) {
    return doc.id === id;
  });
}

function createId(collection, doc) {
  return uuid();
}

function insert(collection, doc) {
  var clone = _.clone(doc);

  if (!clone.hasOwnProperty('id')) clone.id = _.createId(collection, doc);

  collection.push(clone);

  return clone;
}

function update(collection, id, attrs) {
  var doc = get(collection, id);

  if (doc) __update(doc, attrs);

  return doc;
}

function updateWhere(collection, whereAttrs, attrs) {
  var docs = _.where(collection, whereAttrs);

  docs.forEach(function(doc) {
    __update(doc, attrs);
  });

  return docs;
}

function remove(collection, id) {
  var doc = get(collection, id);

  __remove(collection, doc);

  return doc;
}

function removeWhere(collection, attrs) {
  var docs = _.where(collection, attrs);

  docs.forEach(function(doc) {
    __remove(collection, doc);
  });

  return docs;
}

function set_(lib) {
  _ = lib;
}

function get_() {
  return _;
}

function mixWith(lib) {
  set_(lib);
  lib.mixin(this);
}

module.exports = {
  get_: get_,
  set_: set_,
  mixWith: mixWith,
  get: get,
  createId: createId,
  insert: insert,
  update: update,
  updateWhere: updateWhere,
  remove: remove,
  removeWhere: removeWhere
};
<|endoftext|>


<commit_before>import './app.scss' // eslint-disable-line no-unused-vars
import './index.html' // eslint-disable-line no-unused-vars

document.write('Hello World')

const [a, , c] = [1, 2, 3]

console.log(a, c)
<commit_msg>Add app index<commit_after>import '../public/app.scss'
import '../public/index.html'
import Arkanoid from './Arkanoid'

document.addEventListener('DOMContentLoaded', () => {
  window.requestAnimFrame = window.requestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.oRequestAnimationFrame ||
      window.msRequestAnimationFrame ||
      function (callback) { window.setTimeout(callback, 1000 / 60) }

  const game = new Arkanoid()
  function startGameLoop() {
    game.update()
    window.requestAnimFrame(startGameLoop)
  }
  startGameLoop()
})
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';

ReactDOM.render(<App />, document.getElementById('root'));
<commit_msg>Updating index<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './components/App';

ReactDOM.render(<App />, document.getElementById('root'));
<|endoftext|>


<commit_before>import React, { Component } from "react";
import {Motion, spring} from "react-motion";
import TourButton from "./tour-button";
import TourButtonContainer from "./tour-button-container";
import Arrow from "./arrow";
import positions from "./helpers/position-helpers";
import * as viewBoxHelpers from "./helpers/viewbox-helpers";
import scrollToPosition from "./helpers/scroll-to-position";

export default class ReactUserTour extends Component {

	constructor(props) {
		super(props);
		this.prevPos = {
			top: 0,
			left: 0
		};
		this.getStepPosition = this.getStepPosition.bind(this);
	}

	shouldComponentUpdate(nextProps) {
		return this.props.step !== nextProps.step || this.props.active !== nextProps.active;
	}

	getStepPosition(
		selector,
		tourElWidth,
		tourElHeight,
		overridePos,
		margin = 25,
		horizontalOffset = 0,
		verticalOffset = 0
	) {
		const windowHeight = window.innerHeight;
		const windowWidth = window.innerWidth;
		const el = document.querySelector(selector);
		if (el) {
			let position = el ? el.getBoundingClientRect() : {};
			const isElementBelowViewBox = viewBoxHelpers.isElementBelowViewBox(windowHeight, position.top);
			const isElementAboveViewBox = viewBoxHelpers.isElementBelowViewBox(position.bottom);
			if (isElementBelowViewBox) {
				position = scrollToPosition(el, position.bottom);
			}
			else if (isElementAboveViewBox) {
				position = scrollToPosition(el, window.pageYOffset + position.top);
			}
			const shouldPositionLeft = viewBoxHelpers.shouldPositionLeft(windowWidth, position.left);
			const shouldPositionAbove = viewBoxHelpers.shouldPositionAbove(windowHeight, position.bottom);
			const shouldPositionBelow = viewBoxHelpers.shouldPositionBelow(position.top);
			let elPos;
			if (overridePos && positions[overridePos]) {
				elPos = positions[overridePos]({
					position,
					tourElWidth,
					tourElHeight,
					arrowSize: this.props.arrowSize,
					offsetHeight: el.offsetHeight,
					margin
				});
			}
			else if (shouldPositionLeft && !shouldPositionAbove && !shouldPositionBelow) {
				elPos = positions.left({
					position,
					tourElWidth,
					margin
				});
			}
			else if (shouldPositionAbove) {
				elPos = shouldPositionLeft ? positions.topLeft({
					position,
					tourElWidth,
					tourElHeight,
					arrowSize: this.props.arrowSize,
					margin
				}) :
				positions.top({
					position,
					tourElHeight,
					arrowSize: this.props.arrowSize,
					margin
				});
			}
			else if (shouldPositionBelow) {
				elPos = shouldPositionLeft ? positions.bottomLeft({
					position,
					tourElWidth,
					arrowSize: this.props.arrowSize,
					offsetHeight: el.offsetHeight,
					margin
				}) :
				positions.bottom({
					position,
					arrowSize: this.props.arrowSize,
					offsetHeight: el.offsetHeight,
					margin
				});
			}
			else {
				elPos = positions.right({
					position,
					margin
				});
			}

			elPos.left += horizontalOffset;
			elPos.top += verticalOffset;

			this.prevPos = elPos;
			return elPos;
		}
		else {
			return this.prevPos;
		}
	}

	getCustomArrow(position) {
		return (
				typeof this.props.arrow === "function"
				?
				this.props.arrow({
					position: position.positioned,
					width: this.props.style.width,
					height: this.props.style.height,
					size: this.props.arrowSize,
					color: this.props.arrowColor
				})
				:
				this.props.arrow
			);
	}

	render() {
		const currentTourStep = this.props.steps.filter(step => step.step === this.props.step)[0];
		if (!this.props.active || !currentTourStep) {
			return ;
		}
		const position = this.getStepPosition(
			currentTourStep.selector,
			this.props.style.width,
			this.props.style.height,
			currentTourStep.position,
			currentTourStep.margin,
			currentTourStep.horizontalOffset,
			currentTourStep.verticalOffset
		);
		const style = {...this.props.style};
		const arrow = (
			this.props.arrow
			?
			this.getCustomArrow(position)
			:
			<Arrow
				position={position.positioned}
				width={this.props.style.width}
				height={this.props.style.height}
				size={this.props.arrowSize}
				color={this.props.arrowColor}
			/>
		);

		const extraButtonProps = this.props.buttonStyle ? {style: this.props.buttonStyle} : {};

		const nextButton = (
			this.props.step !== this.props.steps.length ?
				<TourButton
					onClick={() => this.props.onNext(this.props.step + 1)}
					onTouchTap={() => this.props.onNext(this.props.step + 1)}
					{...extraButtonProps}
					className="react-user-tour-next-button">
						{this.props.nextButtonText}
				</TourButton> : ""
		);

		const backButton = (
			this.props.step !== 1 ?
				<TourButton
					onClick={() => this.props.onBack(this.props.step - 1)}
					onTouchTap={() => this.props.onBack(this.props.step - 1)}
					{...extraButtonProps}
					className="react-user-tour-back-button">
						{this.props.backButtonText}
				</TourButton> : ""
		);

		const doneButton = (
			this.props.step === this.props.steps.length ?
				<TourButton
					onClick={() => this.props.onCancel()}
					onTouchTap={() => this.props.onCancel}
					{...extraButtonProps}
					className="react-user-tour-done-button">
						{this.props.doneButtonText}
				</TourButton> : ""
		);

		const tourButtonContainer = (
			!this.props.hideButtons ?
				<TourButtonContainer style={this.props.buttonContainerStyle}>
					{nextButton}
					{doneButton}
					{backButton}
				</TourButtonContainer> : ""
		);

		const xStyle = {
			"float": "right",
			"cursor": "pointer",
			"paddingRight": 10,
			"paddingTop": 10
		};

		const closeButton = (
			!this.props.hideClose ?

						{this.props.closeButtonText}
				 : ""
		);

		return (

				<Motion style={{x: spring(position.left), y: spring(position.top)}}>
					{({x, y}) =>

						<div style={{...style, transform: `translate3d(${x}px, ${y}px, 0)`}}>
							{arrow}
							{closeButton}
							{currentTourStep.title}
							{currentTourStep.body}
							{tourButtonContainer}

					}
				</Motion>

		);
	}
}

ReactUserTour.defaultProps = {
	style: {
		height: 150,
		width: 350,
		position: "absolute",
		zIndex: 9999,
		backgroundColor: "#fff",
		color: "#494949",
		boxShadow: "0 6px 8px 0 rgba(0, 0, 0, 0.24)"
	},
	containerStyle: {},
	onCancel: () => {},
	onNext: () => {},
	onBack: () => {},
	nextButtonText: "Next",
	backButtonText: "Back",
	doneButtonText: "Done",
	closeButtonText: "Close",
	buttonContainerStyle: {
		position: "absolute",
		bottom: 10,
		right: 0
	},
	hideButtons: false,
	hideClose: false,
	arrowColor: "#fff",
	arrowSize: 15
};
<commit_msg>fix on touch tap event for done button<commit_after>import React, { Component } from "react";
import {Motion, spring} from "react-motion";
import TourButton from "./tour-button";
import TourButtonContainer from "./tour-button-container";
import Arrow from "./arrow";
import positions from "./helpers/position-helpers";
import * as viewBoxHelpers from "./helpers/viewbox-helpers";
import scrollToPosition from "./helpers/scroll-to-position";

export default class ReactUserTour extends Component {

	constructor(props) {
		super(props);
		this.prevPos = {
			top: 0,
			left: 0
		};
		this.getStepPosition = this.getStepPosition.bind(this);
	}

	shouldComponentUpdate(nextProps) {
		return this.props.step !== nextProps.step || this.props.active !== nextProps.active;
	}

	getStepPosition(
		selector,
		tourElWidth,
		tourElHeight,
		overridePos,
		margin = 25,
		horizontalOffset = 0,
		verticalOffset = 0
	) {
		const windowHeight = window.innerHeight;
		const windowWidth = window.innerWidth;
		const el = document.querySelector(selector);
		if (el) {
			let position = el ? el.getBoundingClientRect() : {};
			const isElementBelowViewBox = viewBoxHelpers.isElementBelowViewBox(windowHeight, position.top);
			const isElementAboveViewBox = viewBoxHelpers.isElementBelowViewBox(position.bottom);
			if (isElementBelowViewBox) {
				position = scrollToPosition(el, position.bottom);
			}
			else if (isElementAboveViewBox) {
				position = scrollToPosition(el, window.pageYOffset + position.top);
			}
			const shouldPositionLeft = viewBoxHelpers.shouldPositionLeft(windowWidth, position.left);
			const shouldPositionAbove = viewBoxHelpers.shouldPositionAbove(windowHeight, position.bottom);
			const shouldPositionBelow = viewBoxHelpers.shouldPositionBelow(position.top);
			let elPos;
			if (overridePos && positions[overridePos]) {
				elPos = positions[overridePos]({
					position,
					tourElWidth,
					tourElHeight,
					arrowSize: this.props.arrowSize,
					offsetHeight: el.offsetHeight,
					margin
				});
			}
			else if (shouldPositionLeft && !shouldPositionAbove && !shouldPositionBelow) {
				elPos = positions.left({
					position,
					tourElWidth,
					margin
				});
			}
			else if (shouldPositionAbove) {
				elPos = shouldPositionLeft ? positions.topLeft({
					position,
					tourElWidth,
					tourElHeight,
					arrowSize: this.props.arrowSize,
					margin
				}) :
				positions.top({
					position,
					tourElHeight,
					arrowSize: this.props.arrowSize,
					margin
				});
			}
			else if (shouldPositionBelow) {
				elPos = shouldPositionLeft ? positions.bottomLeft({
					position,
					tourElWidth,
					arrowSize: this.props.arrowSize,
					offsetHeight: el.offsetHeight,
					margin
				}) :
				positions.bottom({
					position,
					arrowSize: this.props.arrowSize,
					offsetHeight: el.offsetHeight,
					margin
				});
			}
			else {
				elPos = positions.right({
					position,
					margin
				});
			}

			elPos.left += horizontalOffset;
			elPos.top += verticalOffset;

			this.prevPos = elPos;
			return elPos;
		}
		else {
			return this.prevPos;
		}
	}

	getCustomArrow(position) {
		return (
				typeof this.props.arrow === "function"
				?
				this.props.arrow({
					position: position.positioned,
					width: this.props.style.width,
					height: this.props.style.height,
					size: this.props.arrowSize,
					color: this.props.arrowColor
				})
				:
				this.props.arrow
			);
	}

	render() {
		const currentTourStep = this.props.steps.filter(step => step.step === this.props.step)[0];
		if (!this.props.active || !currentTourStep) {
			return ;
		}
		const position = this.getStepPosition(
			currentTourStep.selector,
			this.props.style.width,
			this.props.style.height,
			currentTourStep.position,
			currentTourStep.margin,
			currentTourStep.horizontalOffset,
			currentTourStep.verticalOffset
		);
		const style = {...this.props.style};
		const arrow = (
			this.props.arrow
			?
			this.getCustomArrow(position)
			:
			<Arrow
				position={position.positioned}
				width={this.props.style.width}
				height={this.props.style.height}
				size={this.props.arrowSize}
				color={this.props.arrowColor}
			/>
		);

		const extraButtonProps = this.props.buttonStyle ? {style: this.props.buttonStyle} : {};

		const nextButton = (
			this.props.step !== this.props.steps.length ?
				<TourButton
					onClick={() => this.props.onNext(this.props.step + 1)}
					onTouchTap={() => this.props.onNext(this.props.step + 1)}
					{...extraButtonProps}
					className="react-user-tour-next-button">
						{this.props.nextButtonText}
				</TourButton> : ""
		);

		const backButton = (
			this.props.step !== 1 ?
				<TourButton
					onClick={() => this.props.onBack(this.props.step - 1)}
					onTouchTap={() => this.props.onBack(this.props.step - 1)}
					{...extraButtonProps}
					className="react-user-tour-back-button">
						{this.props.backButtonText}
				</TourButton> : ""
		);

		const doneButton = (
			this.props.step === this.props.steps.length ?
				<TourButton
					onClick={this.props.onCancel}
					onTouchTap={this.props.onCancel}
					{...extraButtonProps}
					className="react-user-tour-done-button">
						{this.props.doneButtonText}
				</TourButton> : ""
		);

		const tourButtonContainer = (
			!this.props.hideButtons ?
				<TourButtonContainer style={this.props.buttonContainerStyle}>
					{nextButton}
					{doneButton}
					{backButton}
				</TourButtonContainer> : ""
		);

		const xStyle = {
			"float": "right",
			"cursor": "pointer",
			"paddingRight": 10,
			"paddingTop": 10
		};

		const closeButton = (
			!this.props.hideClose ?

						{this.props.closeButtonText}
				 : ""
		);

		return (

				<Motion style={{x: spring(position.left), y: spring(position.top)}}>
					{({x, y}) =>

						<div style={{...style, transform: `translate3d(${x}px, ${y}px, 0)`}}>
							{arrow}
							{closeButton}
							{currentTourStep.title}
							{currentTourStep.body}
							{tourButtonContainer}

					}
				</Motion>

		);
	}
}

ReactUserTour.defaultProps = {
	style: {
		height: 150,
		width: 350,
		position: "absolute",
		zIndex: 9999,
		backgroundColor: "#fff",
		color: "#494949",
		boxShadow: "0 6px 8px 0 rgba(0, 0, 0, 0.24)"
	},
	containerStyle: {},
	onCancel: () => {},
	onNext: () => {},
	onBack: () => {},
	nextButtonText: "Next",
	backButtonText: "Back",
	doneButtonText: "Done",
	closeButtonText: "Close",
	buttonContainerStyle: {
		position: "absolute",
		bottom: 10,
		right: 0
	},
	hideButtons: false,
	hideClose: false,
	arrowColor: "#fff",
	arrowSize: 15
};
<|endoftext|>


<commit_before>'use strict';

var assert = require('assert');
var _ = require('lodash');
var URI = require('URIjs');
var mimeDB = require('mime-db');
var jsonCompat = require('json-schema-compatibility');
var jp = require('jsonpath');
var traverse = require('traverse');

exports.checkFormat = function (data) {
  return !_.isUndefined(data.discoveryVersion);
};

exports.getVersion = function (data) {
  return data.discoveryVersion;
};

exports.convert = function (data) {
  assert(exports.checkFormat(data));
  assert(exports.getVersion(data) === 'v1');
  assert.equal(data.protocol, 'rest');

  //fields that doesn't map to anything:
  //	id
  //	revision
  //	icons
  //	batchPath
  //	labels
  //	features

  //deprecated:
  //	baseUrl
  //	basePath

  var rootUrl = URI(data.rootUrl);
  var srGlobalRefParameters = [];
  var srGlobalParameters = processGlobalParameters(data.parameters, srGlobalRefParameters);

  var swagger = {
    swagger: '2.0',
    info: {
      title: data.title,
      'x-serviceName': data.name,
      description: data.description,
      contact: {
        name: data.ownerName,
        url: 'https://' + data.ownerDomain,
      },
      version: data.version,
    },
    host: rootUrl.host(),
    basePath: '/' + data.servicePath.replace(/^\/|\/$/, ''),
    schemes: [rootUrl.scheme()],
    paths: processResource(data, srGlobalRefParameters),
    definitions: processDefinitions(data.schemas),
    parameters: srGlobalParameters,
    securityDefinitions: processAuth(data.auth)
  };

  if (data.documentationLink)
    swagger.externalDocs = { url: data.documentationLink };

  removeUndefined(swagger);
  return swagger;
};

function processAuth(auth) {
  if (auth === undefined)
    return undefined;

  //For now Google use only Oauth2.0
  assert(Object.keys(auth).length === 1);
  assert(Object.keys(auth)[0] === 'oauth2');

  return {
    Oauth2: {
      type: 'oauth2',
      description: 'Oauth 2.0 authentication',
      flow: 'implicit',
      authorizationUrl: 'https://accounts.google.com/o/oauth2/auth',
      scopes: _.mapValues(auth.oauth2.scopes, 'description')
    }
  };
}

function processGlobalParameters(parameters, srGlobalRefParameters) {
  var srGlobalParameters = {};
  _.each(parameters, function (param, name) {
    srGlobalParameters[name] = processParameter(name, param);
    srGlobalRefParameters.push({$ref: '#/parameters/' + name});
  });
  return srGlobalParameters;
}

function fixRef(ref) {
  return '#/definitions/' + ref;
}

function applyOnProperty(schema, name, type, cb) {
  var path = '$..*["' + name + '"]';
  jp.apply(schema, path , function (value) {
    if (typeof value !== type)
      return value;
    return cb(value);
  });
}

function processDefinitions(schemas) {
  if (schemas === undefined)
    return undefined;

  schemas = jsonCompat.v4(schemas);
  applyOnProperty(schemas, '$ref', 'string', fixRef);

  //HACK: Swagger doesn't support full JSON Schema
  applyOnProperty(schemas, 'id', 'string', _.noop);
  applyOnProperty(schemas, 'enumDescriptions', 'object', function (value) {
    if (_.isArray(value))
      return undefined;
    return value;
  });
  applyOnProperty(schemas, 'annotations', 'object', function (value) {
    var keys = _.keys(value);
    if (_.isEqual(keys, ['required']) && _.isArray(value.required))
      return undefined;
    return value;
  });

  //TODO: delete, after PR will be acepted
  applyOnProperty(schemas, 'type', 'string', function (value) {
    if (value === 'any')
      return undefined;
    return value;
  });

  //Google for some reason code minimum/maximum as strings
  function convertInt(value) {
    if (typeof value === 'string')
      return parseInt(value);
    return value;
  }
  jp.apply(schemas, '$..*.minimum' , convertInt);
  jp.apply(schemas, '$..*.maximum' , convertInt);

  return schemas;
}

function processResource(data, srGlobalRefParameters) {
  var srPaths = processMethodList(data);

  if ('resources' in data) {
    _.each(data.resources, function (subResource, name) {
      var srSubPaths = processSubResource(data.resources[name]);

      //Add top-level resource name as tag to all sub-methods.
      _.each(srSubPaths, function (srPath) {
        _.each(srPath, function (srOperation) {
          srOperation.tags = [name];
        });
      });

      srPaths = _.merge(srPaths, srSubPaths);
    });
  }

  //Add reference to global parameters
  _.each(srPaths, function (srPath) {
    srPath.parameters = srGlobalRefParameters;
  });
  return srPaths;
}

function processMethodList(data) {
  if (!('methods' in data))
    return {};

  var srPaths = {};
  for (var key in data.methods) {
    var method = data.methods[key];
    var httpMethod = method.httpMethod.toLowerCase();
    var path = method.path;
    if (path[0] !== '/')
      path = '/' + path;

    if (!(path in srPaths))
      srPaths[path] = { };
    srPaths[path][httpMethod] = processMethod(method);
  }
  return srPaths;
}

function processSubResource(data) {
  var srPaths = processMethodList(data);

  if (!('resources' in data))
    return srPaths;

  _.each(data.resources, function (resource, name) {
    var srSubPaths = processSubResource(resource);
    srPaths = _.merge(srPaths, srSubPaths);
  });
  return srPaths;
}

function globMime(pattern) {
  if (pattern === '*/*')
    return ['application/octet-stream'];

  var slashIdx = pattern.indexOf('/');
  if (slashIdx === -1 || pattern.slice(slashIdx + 1) !== '*')
    return [pattern];

  var prefix = pattern.slice(0,slashIdx+1);
  var result = [];
  _.each(mimeDB, function (dummy, name) {
    if (name.slice(0, slashIdx+1) === prefix)
      result.push(name);
  })
  return result;
}

function convertMime(list) {
  var result = [];
  _.each(list, function (pattern) {
    _.each(globMime(pattern), function (name) {
      //skip duplicates
      if (result.indexOf(name) !== -1)
        return;
      result.push(name);
    });
  });
  return result;
}

function processMethod(method) {
  var srResponse = {
    description: 'Successful response',
  };

  var srMethod = {
    description: method.description,
    operationId: method.id,
    responses: {
      200 : srResponse
    },
  };

  //TODO: implement file upload/download
  //  * rest of fields in 'mediaUpload'
  //  * 'supportsMediaDownload' https://code.google.com/p/google-api-go-client/issues/detail?id=16
  if (method.supportsMediaUpload)
    srMethod.consumes = convertMime(method.mediaUpload.accept);

  //TODO: convert data.supportsSubscription

  var srParameters = processParameterList(method);

  if ('request' in method) {
    var request = method.request;
    srParameters.push({
      name: request.parameterName || 'body',
      in: 'body',
      schema: processSchemaRef(request)
    });
  }

  if (!_.isEmpty(srParameters))
    srMethod.parameters = srParameters;

  if ('response' in method)
    srResponse.schema = processSchemaRef(method.response);

  if ('scopes' in method)
    srMethod.security = [{ Oauth2: method.scopes}];

  return srMethod;
}

function processSchemaRef(data) {
  assert('$ref' in data);
  return {
    $ref: fixRef(data.$ref)
  };
}

function processParameterList(method) {
  var parameters = method.parameters || [];
  var paramOrder = method.parameterOrder || [];

  //First push parameters based on 'paramOreder' field
  var srParameters = _.map(paramOrder, function (name) {
    return processParameter(name, parameters[name]);
  });

  //When process all parameters that doesn't have order
  _(parameters).omit(paramOrder).each(function (param, name) {
    var srParam = processParameter(name, param);
    srParameters.push(srParam);
  }).value();

  return srParameters;
}

function processParameter(name, param) {
  assert(!('$ref' in param));
  assert(param.location === 'query' || param.location === 'path');
  assert(['string', 'number', 'integer', 'boolean'].indexOf(param.type) >= 0);
  assert(!('properties' in param));
  assert(!('additionalProperties' in param));
  assert(!('annotations' in param));

  fixDefault(param);

  var srParam = {
    name: name,
    in: param.location,
    description: param.description,
    required: param.required,
    default: param.default
  };

  if (param.repeated) {
    _.extend(srParam, {
      type: 'array',
      items: processType(param),
      collectionFormat: ((srParam.in === 'path') ? 'csv' : 'multi')
    });
  }
  else
    _.extend(srParam, processType(param));

  return srParam;
}

function processType(type) {
  var srType = {
    type: type.type,
    enum: type.enum,
    minimum: (type.minimum ? parseInt(type.minimum) : undefined),
    maximum: (type.maximum ? parseInt(type.maximum) : undefined)
  };

  //TODO: convert format.
  //if ('format' in type) {

  //TODO: use strings from type.enumDescriptions
  return srType;
}

function fixDefault(param) {
  //Google for some reason encode default values for enums like that
  //SOME_PREFIX_VALUE
  //That mean we need convert to lower case and strip prefix.
  if ('enum' in param && typeof param.default === 'string' &&
     param.enum.indexOf(param.default) === -1)
  {
    var lower = param.default.toLowerCase();
    var candidate;
    _.each(param.enum, function (value) {
      if (lower.slice(-value.length) === value) {
         assert(candidate === undefined);
         candidate = value;
      }
    });
    //If we can't fix default when remove it.
    delete param.default;
    if (candidate !== undefined)
      param.default = candidate;
  }
}

function removeUndefined(obj) {
  traverse(obj).forEach(function (value) {
    if (value === undefined)
      this.remove();
  });
}
<commit_msg>Fixed #3(Convert defaults to proper type)<commit_after>'use strict';

var assert = require('assert');
var _ = require('lodash');
var URI = require('URIjs');
var mimeDB = require('mime-db');
var jsonCompat = require('json-schema-compatibility');
var jp = require('jsonpath');
var traverse = require('traverse');

exports.checkFormat = function (data) {
  return !_.isUndefined(data.discoveryVersion);
};

exports.getVersion = function (data) {
  return data.discoveryVersion;
};

exports.convert = function (data) {
  assert(exports.checkFormat(data));
  assert(exports.getVersion(data) === 'v1');
  assert.equal(data.protocol, 'rest');

  //fields that doesn't map to anything:
  //	id
  //	revision
  //	icons
  //	batchPath
  //	labels
  //	features

  //deprecated:
  //	baseUrl
  //	basePath

  var rootUrl = URI(data.rootUrl);
  var srGlobalRefParameters = [];
  var srGlobalParameters = processGlobalParameters(data.parameters, srGlobalRefParameters);

  var swagger = {
    swagger: '2.0',
    info: {
      title: data.title,
      'x-serviceName': data.name,
      description: data.description,
      contact: {
        name: data.ownerName,
        url: 'https://' + data.ownerDomain,
      },
      version: data.version,
    },
    host: rootUrl.host(),
    basePath: '/' + data.servicePath.replace(/^\/|\/$/, ''),
    schemes: [rootUrl.scheme()],
    paths: processResource(data, srGlobalRefParameters),
    definitions: processDefinitions(data.schemas),
    parameters: srGlobalParameters,
    securityDefinitions: processAuth(data.auth)
  };

  if (data.documentationLink)
    swagger.externalDocs = { url: data.documentationLink };

  removeUndefined(swagger);
  return swagger;
};

function processAuth(auth) {
  if (auth === undefined)
    return undefined;

  //For now Google use only Oauth2.0
  assert(Object.keys(auth).length === 1);
  assert(Object.keys(auth)[0] === 'oauth2');

  return {
    Oauth2: {
      type: 'oauth2',
      description: 'Oauth 2.0 authentication',
      flow: 'implicit',
      authorizationUrl: 'https://accounts.google.com/o/oauth2/auth',
      scopes: _.mapValues(auth.oauth2.scopes, 'description')
    }
  };
}

function processGlobalParameters(parameters, srGlobalRefParameters) {
  var srGlobalParameters = {};
  _.each(parameters, function (param, name) {
    srGlobalParameters[name] = processParameter(name, param);
    srGlobalRefParameters.push({$ref: '#/parameters/' + name});
  });
  return srGlobalParameters;
}

function fixRef(ref) {
  return '#/definitions/' + ref;
}

function applyOnProperty(schema, name, type, cb) {
  var path = '$..*["' + name + '"]';
  jp.apply(schema, path , function (value) {
    if (typeof value !== type)
      return value;
    return cb(value);
  });
}

function processDefinitions(schemas) {
  if (schemas === undefined)
    return undefined;

  schemas = jsonCompat.v4(schemas);
  applyOnProperty(schemas, '$ref', 'string', fixRef);

  //HACK: Swagger doesn't support full JSON Schema
  applyOnProperty(schemas, 'id', 'string', _.noop);
  applyOnProperty(schemas, 'enumDescriptions', 'object', function (value) {
    if (_.isArray(value))
      return undefined;
    return value;
  });
  applyOnProperty(schemas, 'annotations', 'object', function (value) {
    var keys = _.keys(value);
    if (_.isEqual(keys, ['required']) && _.isArray(value.required))
      return undefined;
    return value;
  });

  //TODO: delete, after PR will be acepted
  applyOnProperty(schemas, 'type', 'string', function (value) {
    if (value === 'any')
      return undefined;
    return value;
  });

  //Google for some reason code minimum/maximum as strings
  function convertInt(value) {
    if (typeof value === 'string')
      return parseInt(value);
    return value;
  }
  jp.apply(schemas, '$..*.minimum' , convertInt);
  jp.apply(schemas, '$..*.maximum' , convertInt);

  return schemas;
}

function processResource(data, srGlobalRefParameters) {
  var srPaths = processMethodList(data);

  if ('resources' in data) {
    _.each(data.resources, function (subResource, name) {
      var srSubPaths = processSubResource(data.resources[name]);

      //Add top-level resource name as tag to all sub-methods.
      _.each(srSubPaths, function (srPath) {
        _.each(srPath, function (srOperation) {
          srOperation.tags = [name];
        });
      });

      srPaths = _.merge(srPaths, srSubPaths);
    });
  }

  //Add reference to global parameters
  _.each(srPaths, function (srPath) {
    srPath.parameters = srGlobalRefParameters;
  });
  return srPaths;
}

function processMethodList(data) {
  if (!('methods' in data))
    return {};

  var srPaths = {};
  for (var key in data.methods) {
    var method = data.methods[key];
    var httpMethod = method.httpMethod.toLowerCase();
    var path = method.path;
    if (path[0] !== '/')
      path = '/' + path;

    if (!(path in srPaths))
      srPaths[path] = { };
    srPaths[path][httpMethod] = processMethod(method);
  }
  return srPaths;
}

function processSubResource(data) {
  var srPaths = processMethodList(data);

  if (!('resources' in data))
    return srPaths;

  _.each(data.resources, function (resource, name) {
    var srSubPaths = processSubResource(resource);
    srPaths = _.merge(srPaths, srSubPaths);
  });
  return srPaths;
}

function globMime(pattern) {
  if (pattern === '*/*')
    return ['application/octet-stream'];

  var slashIdx = pattern.indexOf('/');
  if (slashIdx === -1 || pattern.slice(slashIdx + 1) !== '*')
    return [pattern];

  var prefix = pattern.slice(0,slashIdx+1);
  var result = [];
  _.each(mimeDB, function (dummy, name) {
    if (name.slice(0, slashIdx+1) === prefix)
      result.push(name);
  })
  return result;
}

function convertMime(list) {
  var result = [];
  _.each(list, function (pattern) {
    _.each(globMime(pattern), function (name) {
      //skip duplicates
      if (result.indexOf(name) !== -1)
        return;
      result.push(name);
    });
  });
  return result;
}

function processMethod(method) {
  var srResponse = {
    description: 'Successful response',
  };

  var srMethod = {
    description: method.description,
    operationId: method.id,
    responses: {
      200 : srResponse
    },
  };

  //TODO: implement file upload/download
  //  * rest of fields in 'mediaUpload'
  //  * 'supportsMediaDownload' https://code.google.com/p/google-api-go-client/issues/detail?id=16
  if (method.supportsMediaUpload)
    srMethod.consumes = convertMime(method.mediaUpload.accept);

  //TODO: convert data.supportsSubscription

  var srParameters = processParameterList(method);

  if ('request' in method) {
    var request = method.request;
    srParameters.push({
      name: request.parameterName || 'body',
      in: 'body',
      schema: processSchemaRef(request)
    });
  }

  if (!_.isEmpty(srParameters))
    srMethod.parameters = srParameters;

  if ('response' in method)
    srResponse.schema = processSchemaRef(method.response);

  if ('scopes' in method)
    srMethod.security = [{ Oauth2: method.scopes}];

  return srMethod;
}

function processSchemaRef(data) {
  assert('$ref' in data);
  return {
    $ref: fixRef(data.$ref)
  };
}

function processParameterList(method) {
  var parameters = method.parameters || [];
  var paramOrder = method.parameterOrder || [];

  //First push parameters based on 'paramOreder' field
  var srParameters = _.map(paramOrder, function (name) {
    return processParameter(name, parameters[name]);
  });

  //When process all parameters that doesn't have order
  _(parameters).omit(paramOrder).each(function (param, name) {
    var srParam = processParameter(name, param);
    srParameters.push(srParam);
  }).value();

  return srParameters;
}

function processParameter(name, param) {
  assert(!('$ref' in param));
  assert(param.location === 'query' || param.location === 'path');
  assert(['string', 'number', 'integer', 'boolean'].indexOf(param.type) >= 0);
  assert(!('properties' in param));
  assert(!('additionalProperties' in param));
  assert(!('annotations' in param));

  var srParam = {
    name: name,
    in: param.location,
    description: param.description,
    required: param.required,
    default: processDefault(param)
  };

  if (param.repeated) {
    _.extend(srParam, {
      type: 'array',
      items: processType(param),
      collectionFormat: ((srParam.in === 'path') ? 'csv' : 'multi')
    });
  }
  else
    _.extend(srParam, processType(param));

  return srParam;
}

function processType(type) {
  var srType = {
    type: type.type,
    enum: type.enum,
    minimum: (type.minimum ? parseInt(type.minimum) : undefined),
    maximum: (type.maximum ? parseInt(type.maximum) : undefined)
  };

  //TODO: convert format.
  //if ('format' in type) {

  //TODO: use strings from type.enumDescriptions
  return srType;
}

function processDefault(param) {
  if (!('default' in param))
    return undefined;

  assert(_.isString(param.default));
  if (param.type !== 'string')
    param.default = JSON.parse(param.default);

  assert.equal({
    number: 'number',
    integer: 'number',
    boolean: 'boolean',
    string: 'string'
  }[param.type], typeof param.default);

  //Google for some reason encode default values for enums like that
  //SOME_PREFIX_VALUE
  //That mean we need convert to lower case and strip prefix.
  if ('enum' in param && param.enum.indexOf(param.default) === -1)
  {
    var lower = param.default.toLowerCase();
    var candidate;
    _.each(param.enum, function (value) {
      if (lower.slice(-value.length) === value) {
         assert(candidate === undefined);
         candidate = value;
      }
    });
    //If we can't fix default when return undefined and remove it.
    return candidate;
  }
  return param.default;
}

function removeUndefined(obj) {
  traverse(obj).forEach(function (value) {
    if (value === undefined)
      this.remove();
  });
}
<|endoftext|>