<commit_before>/**
 * Copyright 2015-present 650 Industries. All rights reserved.
 *
 * @providesModule BrowserScreen
 */
'use strict';

import React, { PropTypes } from 'react';
import {
  ActivityIndicator,
  Image,
  StatusBar,
  StyleSheet,
  Text,
  TouchableWithoutFeedback,
  View,
  Platform,
} from 'react-native';
import FadeIn from '@expo/react-native-fade-in-image';

import autobind from 'autobind-decorator';
import Browser from 'Browser';
import BrowserActions from 'BrowserActions';
import BrowserErrorView from 'BrowserErrorView';
import ExColors from 'ExColors';
import ExManifests from 'ExManifests';
import ExponentKernel from 'ExponentKernel';
import Frame from 'Frame';
import { connect } from 'react-redux';

class BrowserScreen extends React.Component {
  static propTypes = {
    url: PropTypes.string.isRequired,
  };

  static getDataProps(data, props) {
    let { url } = props;
    let {
      foregroundTaskUrl,
      isShell,
      shellManifestUrl,
      shellInitialUrl,
      isHomeVisible,
      isMenuVisible,
      isNuxFinished,
    } = data.browser;
    let isForegrounded = url === foregroundTaskUrl && !isHomeVisible;
    let shellTask = shellManifestUrl
      ? data.browser.tasks.get(shellManifestUrl)
      : null;

    return {
      url,
      isForegrounded,
      isNuxFinished,
      isMenuVisible,
      isShell: isShell && url === shellManifestUrl,
      isLetterboxed: isShell && url !== shellManifestUrl,
      task: data.browser.tasks.get(url),
      shellManifestUrl,
      shellInitialUrl,
      shellTask,
    };
  }

  constructor(props, context) {
    super(props, context);

    this.state = {
      loadingStatus: null,

      // we pass manifest as a mutable JS object down native code via Frame,
      // but within JS we take advantage of immutable equals on props.manifest
      manifestJS:
        props.task && props.task.manifest ? props.task.manifest.toJS() : null,
      initialPropsJS:
        props.task && props.task.initialProps
          ? props.task.initialProps.toJS()
          : null,
    };
  }

  render() {
    let content, loadingIndicator, errorView;
    let { task } = this.props;
    if (task) {
      if (task.loadingError) {
        errorView = (
          <BrowserErrorView
            error={task.loadingError}
            isShell={this.props.isShell}
            shellManifestUrl={this.props.shellManifestUrl}
            onRefresh={this._refresh}
            style={styles.errorView}
          />
        );
      }
      if (task.bundleUrl) {
        content = this._renderFrame();
        if (task.isLoading) {
          loadingIndicator = this._renderLoadingIndicator();
        }
      }
    }

    if (!content) {
      content = this._renderPlaceholder();
    }

    let everything = (
      <View style={{ flex: 1 }}>
        {content}
        {loadingIndicator}
        {errorView}
      </View>
    );
    return (
      <View style={styles.container}>
        {this.props.isLetterboxed
          ? this._renderLetterboxWithContent(everything)
          : everything}
      </View>
    );
  }

  _renderFrame() {
    let {
      source,
      appKey,
      debuggerHostname,
      debuggerPort,
    } = ExManifests.getFramePropsFromManifest(
      this.state.manifestJS,
      this.props.task.bundleUrl
    );

    const initialProps = this._getInitialProps(this.state.initialPropsJS);
    return (
      <Frame
        key="frame"
        ref={this._setFrame}
        source={source}
        applicationKey={appKey}
        debuggerHostname={debuggerHostname}
        debuggerPort={debuggerPort}
        initialProps={initialProps}
        initialUri={initialProps.initialUri}
        manifest={this.state.manifestJS}
        style={styles.frame}
        onLoadingStart={this._handleFrameLoadingStart}
        onLoadingProgress={this._handleLoadingProgress}
        onLoadingFinish={this._handleFrameLoadingFinish}
        onLoadingError={this._handleFrameLoadingError}
        onError={this._handleUncaughtError}
      />
    );
  }

  _renderLetterboxWithContent(content) {
    let backButtonText;
    if (
      this.props.shellTask &&
      this.props.shellTask.manifest &&
      this.props.shellTask.manifest.get('name')
    ) {
      backButtonText = `Back to ${this.props.shellTask.manifest.get('name')}`;
    } else {
      backButtonText = 'Back';
    }
    return (
      <View style={styles.letterbox}>
        <StatusBar barStyle="light-content" hidden={false} animated />
        <View style={styles.letterboxTopBar}>
          <TouchableWithoutFeedback onPress={this._onPressLetterbox}>
            <View>
              <Text style={styles.letterboxText}>
                {backButtonText}
              </Text>
            </View>
          </TouchableWithoutFeedback>
        </View>
        {content}
      </View>
    );
  }

  _renderPlaceholder() {
    let content;
    if (this.props.isShell) {
      // possibly null
      content = this._renderManifestLoadingIcon();
    } else {
      content = <Text style={styles.placeholderText}>EXPO</Text>;
    }
    return (
      <View style={styles.placeholder}>
        {content}
      </View>
    );
  }

  _getLoadingBackgroundColor() {
    let { task } = this.props;
    let { manifest } = task;

    let loadingBackgroundColor = 'white';
    if (manifest && manifest.getIn(['loading', 'backgroundColor'])) {
      loadingBackgroundColor = manifest.getIn(['loading', 'backgroundColor']);
    }

    return loadingBackgroundColor;
  }

  _getLoadingIndicatorStyle() {
    let { task } = this.props;
    let { manifest } = task;

    let loadingIndicatorStyle = 'default';
    if (
      manifest &&
      manifest.getIn(['loading', 'loadingIndicatorStyleExperimental'])
    ) {
      loadingIndicatorStyle = manifest.getIn([
        'loading',
        'loadingIndicatorStyleExperimental',
      ]);
    }

    return loadingIndicatorStyle;
  }

  _renderLoadingIndicator() {
    let loadingBackgroundColor = this._getLoadingBackgroundColor();
    let loadingIndicatorStyle = this._getLoadingIndicatorStyle();
    let loadingIcon = this._renderManifestLoadingIcon();
    let loadingBackgroundImage = this._renderManifestLoadingBackgroundImage();
    let { loadingStatus } = this.state;

    return (
      <View
        pointerEvents="none"
        style={[
          styles.loadingIndicatorContainer,
          { backgroundColor: loadingBackgroundColor },
        ]}>
        {loadingBackgroundImage}
        <View>
          {loadingIcon}
          <ActivityIndicator
            size="large"
            color={loadingIndicatorStyle === 'light' ? '#ffffff' : '#333333'}
            style={styles.loadingIndicator}
          />
        </View>
        {loadingStatus !== null &&
          <View style={styles.loadingStatusBar}>
            <Text style={styles.loadingStatusText}>
              {loadingStatus.status}
            </Text>
            {loadingStatus.total > 0 &&
              <Text style={styles.loadingPercentageText}>
                {(loadingStatus.done / loadingStatus.total * 100).toFixed(2)}%
              </Text>}
          </View>}
      </View>
    );
  }

  _renderManifestLoadingIcon() {
    let { task } = this.props;
    if (task) {
      let { manifest } = task;
      if (manifest) {
        // Don't use loading icon if new manifest.loading.splash.image.ios.backgroundImageUrl is set
        if (this._isNewSplashScreenStyle(manifest)) {
          return (
            // This view is empty, but positions the loading indicator corrextly
            <View style={{ width: 200, height: 200, marginVertical: 16 }} />
          );
        }

        let iconUrl = manifest.getIn(['loading', 'iconUrl']);
        let loadingBackgroundColor = this._getLoadingBackgroundColor();
        let backgroundImageUrl = manifest.getIn([
          'loading',
          'backgroundImageUrl',
        ]);

        let placeholderBackgroundColor = loadingBackgroundColor;
        if (backgroundImageUrl) {
          placeholderBackgroundColor = 'transparent';
        }

        if (iconUrl) {
          return (
            <FadeIn
              placeholderStyle={{
                backgroundColor: placeholderBackgroundColor,
              }}>
              <Image
                source={{ uri: iconUrl }}
                resizeMode="center"
                style={{ width: 200, height: 200, marginVertical: 16 }}
              />
            </FadeIn>
          );
        }
      }
    }
    return null;
  }

  _renderManifestLoadingBackgroundImage() {
    let { task } = this.props;
    if (task) {
      let { manifest } = task;
      var backgroundImageUrl;
      if (this._isNewSplashScreenStyle(manifest)) {
        backgroundImageUrl = manifest.getIn([
          'loading',
          'splash',
          'image',
          'ios',
          'backgroundImageUrl',
        ]);
      }
      let resizeMode = this._isNewSplashScreenStyle(manifest)
        ? Image.resizeMode.cover
        : Image.resizeMode.contain;
      if (!backgroundImageUrl) {
        backgroundImageUrl = manifest.getIn(['loading', 'backgroundImageUrl']);
      }
      if (manifest && backgroundImageUrl) {
        return (
          <Image
            source={{ uri: backgroundImageUrl }}
            resizeMode={resizeMode}
            style={styles.loadingBackgroundImage}
          />
        );
      }
    }
    return null;
  }

  componentWillReceiveProps(nextProps) {
    this._recomputePropsJS(nextProps);
  }

  _getInitialProps(baseInitialProps) {
    // start with whatever came from the browser task's initial props (e.g. a push notification payload)
    let baseProps = {};
    if (baseInitialProps) {
      for (let key in baseInitialProps) {
        if (baseInitialProps.hasOwnProperty(key)) {
          baseProps[key] = baseInitialProps[key];
        }
      }
    }

    // add a few more things
    baseProps.manifest = this.state.manifestJS;
    baseProps.shell = this.props.isShell;
    if (this.props.isShell && this.props.shellInitialUrl) {
      baseProps.initialUri = this.props.shellInitialUrl;
    } else {
      baseProps.initialUri = this.props.url;
    }

    let appOwnership;
    if (this.props.isShell) {
      appOwnership = 'standalone';
    } else if (this.props.isLetterboxed) {
      appOwnership = 'guest';
    } else {
      appOwnership = 'expo';
    }
    baseProps.appOwnership = appOwnership;

    return baseProps;
  }

  _recomputePropsJS(props) {
    let { task } = props;
    if (task) {
      // immutable equals
      if (!this.props.task || task.manifest !== this.props.task.manifest) {
        if (task.manifest) {
          this.setState({ manifestJS: task.manifest.toJS() });
        } else {
          this.setState({ manifestJS: null });
        }
      }
      if (
        !this.props.task ||
        task.initialProps !== this.props.task.initialProps
      ) {
        if (task.initialProps) {
          this.setState({ initialPropsJS: task.initialProps.toJS() });
        } else {
          this.setState({ initialPropsJS: null });
        }
      }
    } else {
      this.setState({
        manifestJS: null,
        initialPropsJS: null,
      });
    }
  }

  _isNewSplashScreenStyle(manifest) {
    if (Platform.OS !== 'ios') {
      return false;
    }

    if (
      manifest.getIn([
        'loading',
        'splash',
        'image',
        'ios',
        'backgroundImageUrl',
      ])
    ) {
      return true;
    }

    return false;
  }

  _setFrame = frame => {
    this._frame = frame;
  };

  _handleFrameLoadingStart = event => {
    this.props.dispatch(BrowserActions.setLoadingState(this.props.url, true));
  };

  _handleLoadingProgress = event => {
    this.setState({ loadingStatus: event.nativeEvent });
  };

  _handleFrameLoadingFinish = event => {
    this.props.dispatch(BrowserActions.setLoadingState(this.props.url, false));
    if (
      !this.props.isNuxFinished &&
      !this.props.isLetterboxed &&
      !this.props.isShell
    ) {
      setTimeout(() => {
        this.props.dispatch(BrowserActions.showMenuAsync(true));
      }, 200);
    } else if (this.props.isMenuVisible) {
      this.props.dispatch(BrowserActions.showMenuAsync(false));
    }

    this.setState({ loadingStatus: null });
  };

  _handleFrameLoadingError = event => {
    this.props.dispatch(BrowserActions.setLoadingState(this.props.url, false));
    let { nativeEvent } = event;

    this.props.dispatch(
      BrowserActions.showLoadingError(
        nativeEvent.code,
        nativeEvent.description,
        this.props.url,
        this.props.task.manifest
      )
    );

    this.setState({ loadingStatus: null });
  };

  _handleUncaughtError = event => {
    let { id, message, stack, fatal } = event.nativeEvent;
    if (fatal) {
      let isDeveloper = false;
      if (this.props.task && this.props.task.manifest) {
        isDeveloper = this.props.task.manifest.get('developer');
      }
      if (!isDeveloper) {
        // if developer == true, RCTRedBox will show up instead
        this._maybeRecoverFromErrorAsync(event);
      }
    }
  };

  _maybeRecoverFromErrorAsync = async event => {
    let { id, message, stack, fatal } = event.nativeEvent;
    let shouldReload = await ExponentKernel.shouldCurrentTaskAutoReload();
    if (shouldReload) {
      // call this._refresh rather than Browser.refresh because it works
      // in both browser and shell apps.
      this._refresh();
    } else {
      // show error screen with manual reload button
      this.props.dispatch(
        BrowserActions.showLoadingError(
          id,
          message,
          this.props.url,
          this.props.task.manifest
        )
      );
    }
  };

  _refresh = () => {
    if (this.props.task.loadingError) {
      let urlToRefresh = this.props.task.loadingError.originalUrl;
      this.props.dispatch(BrowserActions.clearTaskWithError(urlToRefresh));
      if (this.props.isShell) {
        this.props.dispatch(
          BrowserActions.navigateToUrlAsync(this.props.shellManifestUrl)
        );
      } else {
        ExponentKernel.openURL(urlToRefresh);
      }
      return;
    }
    this.setState(state => {
      if (!this._frame) {
        console.error('Trying to refresh but there is no frame to refresh');
        return;
      }
      this._frame.reload();
    });
  };

  _onPressLetterbox = () => {
    if (this.props.shellManifestUrl) {
      this.props.dispatch(
        BrowserActions.foregroundUrlAsync(this.props.shellManifestUrl)
      );
    }
  };
}

export default connect((data, props) =>
  BrowserScreen.getDataProps(data, props)
)(BrowserScreen);

let styles = StyleSheet.create({
  container: {
    backgroundColor: '#fff',
    flex: 1,
  },
  frame: {
    flex: 1,
  },
  letterbox: {
    flex: 1,
    backgroundColor: 'black',
  },
  letterboxTopBar: {
    height: 42,
    alignItems: 'center',
  },
  letterboxText: {
    color: 'white',
    paddingTop: 20,
  },
  loadingIndicatorContainer: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loadingBackgroundImage: {
    position: 'absolute',
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
  },
  loadingStatusBar: {
    position: 'absolute',
    left: 0,
    bottom: 0,
    right: 0,
    flexDirection: 'row',
    alignItems: 'center',
    padding: 10,
    backgroundColor: '#fafafa',
    borderTopWidth: StyleSheet.hairlineWidth,
    borderTopColor: '#f3f3f3',
  },
  loadingStatusText: {
    color: '#a7a7a7',
    fontSize: 12,
    flex: 1,
  },
  loadingPercentageText: {
    color: '#a7a7a7',
    fontSize: 12,
    fontVariant: ['tabular-nums'],
  },
  errorView: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
  },
  placeholder: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  placeholderText: {
    color: ExColors.navy,
    fontSize: 32,
    fontWeight: '100',
    letterSpacing: 9,
    marginTop: -200,
  },
});
<commit_msg>Respect the new splash style background color (#827)<commit_after>/**
 * Copyright 2015-present 650 Industries. All rights reserved.
 *
 * @providesModule BrowserScreen
 */
'use strict';

import React, { PropTypes } from 'react';
import {
  ActivityIndicator,
  Image,
  StatusBar,
  StyleSheet,
  Text,
  TouchableWithoutFeedback,
  View,
  Platform,
} from 'react-native';
import FadeIn from '@expo/react-native-fade-in-image';

import autobind from 'autobind-decorator';
import Browser from 'Browser';
import BrowserActions from 'BrowserActions';
import BrowserErrorView from 'BrowserErrorView';
import ExColors from 'ExColors';
import ExManifests from 'ExManifests';
import ExponentKernel from 'ExponentKernel';
import Frame from 'Frame';
import { connect } from 'react-redux';

class BrowserScreen extends React.Component {
  static propTypes = {
    url: PropTypes.string.isRequired,
  };

  static getDataProps(data, props) {
    let { url } = props;
    let {
      foregroundTaskUrl,
      isShell,
      shellManifestUrl,
      shellInitialUrl,
      isHomeVisible,
      isMenuVisible,
      isNuxFinished,
    } = data.browser;
    let isForegrounded = url === foregroundTaskUrl && !isHomeVisible;
    let shellTask = shellManifestUrl
      ? data.browser.tasks.get(shellManifestUrl)
      : null;

    return {
      url,
      isForegrounded,
      isNuxFinished,
      isMenuVisible,
      isShell: isShell && url === shellManifestUrl,
      isLetterboxed: isShell && url !== shellManifestUrl,
      task: data.browser.tasks.get(url),
      shellManifestUrl,
      shellInitialUrl,
      shellTask,
    };
  }

  constructor(props, context) {
    super(props, context);

    this.state = {
      loadingStatus: null,

      // we pass manifest as a mutable JS object down native code via Frame,
      // but within JS we take advantage of immutable equals on props.manifest
      manifestJS:
        props.task && props.task.manifest ? props.task.manifest.toJS() : null,
      initialPropsJS:
        props.task && props.task.initialProps
          ? props.task.initialProps.toJS()
          : null,
    };
  }

  render() {
    let content, loadingIndicator, errorView;
    let { task } = this.props;
    if (task) {
      if (task.loadingError) {
        errorView = (
          <BrowserErrorView
            error={task.loadingError}
            isShell={this.props.isShell}
            shellManifestUrl={this.props.shellManifestUrl}
            onRefresh={this._refresh}
            style={styles.errorView}
          />
        );
      }
      if (task.bundleUrl) {
        content = this._renderFrame();
        if (task.isLoading) {
          loadingIndicator = this._renderLoadingIndicator();
        }
      }
    }

    if (!content) {
      content = this._renderPlaceholder();
    }

    let everything = (
      <View style={{ flex: 1 }}>
        {content}
        {loadingIndicator}
        {errorView}
      </View>
    );
    return (
      <View style={styles.container}>
        {this.props.isLetterboxed
          ? this._renderLetterboxWithContent(everything)
          : everything}
      </View>
    );
  }

  _renderFrame() {
    let {
      source,
      appKey,
      debuggerHostname,
      debuggerPort,
    } = ExManifests.getFramePropsFromManifest(
      this.state.manifestJS,
      this.props.task.bundleUrl
    );

    const initialProps = this._getInitialProps(this.state.initialPropsJS);
    return (
      <Frame
        key="frame"
        ref={this._setFrame}
        source={source}
        applicationKey={appKey}
        debuggerHostname={debuggerHostname}
        debuggerPort={debuggerPort}
        initialProps={initialProps}
        initialUri={initialProps.initialUri}
        manifest={this.state.manifestJS}
        style={styles.frame}
        onLoadingStart={this._handleFrameLoadingStart}
        onLoadingProgress={this._handleLoadingProgress}
        onLoadingFinish={this._handleFrameLoadingFinish}
        onLoadingError={this._handleFrameLoadingError}
        onError={this._handleUncaughtError}
      />
    );
  }

  _renderLetterboxWithContent(content) {
    let backButtonText;
    if (
      this.props.shellTask &&
      this.props.shellTask.manifest &&
      this.props.shellTask.manifest.get('name')
    ) {
      backButtonText = `Back to ${this.props.shellTask.manifest.get('name')}`;
    } else {
      backButtonText = 'Back';
    }
    return (
      <View style={styles.letterbox}>
        <StatusBar barStyle="light-content" hidden={false} animated />
        <View style={styles.letterboxTopBar}>
          <TouchableWithoutFeedback onPress={this._onPressLetterbox}>
            <View>
              <Text style={styles.letterboxText}>
                {backButtonText}
              </Text>
            </View>
          </TouchableWithoutFeedback>
        </View>
        {content}
      </View>
    );
  }

  _renderPlaceholder() {
    let content;
    if (this.props.isShell) {
      // possibly null
      content = this._renderManifestLoadingIcon();
    } else {
      content = <Text style={styles.placeholderText}>EXPO</Text>;
    }
    return (
      <View style={styles.placeholder}>
        {content}
      </View>
    );
  }

  _getLoadingBackgroundColor() {
    let { task } = this.props;
    let { manifest } = task;

    let loadingBackgroundColor;
    if (this._isNewSplashScreenStyle(manifest)) {
      // Use new manifest location for background color
      if (
        manifest &&
        manifest.getIn(['loading', 'splash', 'backgroundColor'])
      ) {
        loadingBackgroundColor = manifest.getIn([
          'loading',
          'splash',
          'backgroundColor',
        ]);
      }
    }

    if (!loadingBackgroundColor) {
      loadingBackgroundColor = 'white';
      if (manifest && manifest.getIn(['loading', 'backgroundColor'])) {
        loadingBackgroundColor = manifest.getIn(['loading', 'backgroundColor']);
      }
    }

    return loadingBackgroundColor;
  }

  _getLoadingIndicatorStyle() {
    let { task } = this.props;
    let { manifest } = task;

    let loadingIndicatorStyle = 'default';
    if (
      manifest &&
      manifest.getIn(['loading', 'loadingIndicatorStyleExperimental'])
    ) {
      loadingIndicatorStyle = manifest.getIn([
        'loading',
        'loadingIndicatorStyleExperimental',
      ]);
    }

    return loadingIndicatorStyle;
  }

  _renderLoadingIndicator() {
    let loadingBackgroundColor = this._getLoadingBackgroundColor();
    let loadingIndicatorStyle = this._getLoadingIndicatorStyle();
    let loadingIcon = this._renderManifestLoadingIcon();
    let loadingBackgroundImage = this._renderManifestLoadingBackgroundImage();
    let { loadingStatus } = this.state;

    return (
      <View
        pointerEvents="none"
        style={[
          styles.loadingIndicatorContainer,
          { backgroundColor: loadingBackgroundColor },
        ]}>
        {loadingBackgroundImage}
        <View>
          {loadingIcon}
          <ActivityIndicator
            size="large"
            color={loadingIndicatorStyle === 'light' ? '#ffffff' : '#333333'}
            style={styles.loadingIndicator}
          />
        </View>
        {loadingStatus !== null &&
          <View style={styles.loadingStatusBar}>
            <Text style={styles.loadingStatusText}>
              {loadingStatus.status}
            </Text>
            {loadingStatus.total > 0 &&
              <Text style={styles.loadingPercentageText}>
                {(loadingStatus.done / loadingStatus.total * 100).toFixed(2)}%
              </Text>}
          </View>}
      </View>
    );
  }

  _renderManifestLoadingIcon() {
    let { task } = this.props;
    if (task) {
      let { manifest } = task;
      if (manifest) {
        // Don't use loading icon if new manifest.loading.splash.image.ios.backgroundImageUrl is set
        if (this._isNewSplashScreenStyle(manifest)) {
          return (
            // This view is empty, but positions the loading indicator corrextly
            <View style={{ width: 200, height: 200, marginVertical: 16 }} />
          );
        }

        let iconUrl = manifest.getIn(['loading', 'iconUrl']);
        let loadingBackgroundColor = this._getLoadingBackgroundColor();
        let backgroundImageUrl = manifest.getIn([
          'loading',
          'backgroundImageUrl',
        ]);

        let placeholderBackgroundColor = loadingBackgroundColor;
        if (backgroundImageUrl) {
          placeholderBackgroundColor = 'transparent';
        }

        if (iconUrl) {
          return (
            <FadeIn
              placeholderStyle={{
                backgroundColor: placeholderBackgroundColor,
              }}>
              <Image
                source={{ uri: iconUrl }}
                resizeMode="center"
                style={{ width: 200, height: 200, marginVertical: 16 }}
              />
            </FadeIn>
          );
        }
      }
    }
    return null;
  }

  _renderManifestLoadingBackgroundImage() {
    let { task } = this.props;
    if (task) {
      let { manifest } = task;
      var backgroundImageUrl;
      if (this._isNewSplashScreenStyle(manifest)) {
        backgroundImageUrl = manifest.getIn([
          'loading',
          'splash',
          'image',
          'ios',
          'backgroundImageUrl',
        ]);
      }
      let resizeMode = this._isNewSplashScreenStyle(manifest)
        ? Image.resizeMode.cover
        : Image.resizeMode.contain;
      if (!backgroundImageUrl) {
        backgroundImageUrl = manifest.getIn(['loading', 'backgroundImageUrl']);
      }
      if (manifest && backgroundImageUrl) {
        return (
          <Image
            source={{ uri: backgroundImageUrl }}
            resizeMode={resizeMode}
            style={styles.loadingBackgroundImage}
          />
        );
      }
    }
    return null;
  }

  componentWillReceiveProps(nextProps) {
    this._recomputePropsJS(nextProps);
  }

  _getInitialProps(baseInitialProps) {
    // start with whatever came from the browser task's initial props (e.g. a push notification payload)
    let baseProps = {};
    if (baseInitialProps) {
      for (let key in baseInitialProps) {
        if (baseInitialProps.hasOwnProperty(key)) {
          baseProps[key] = baseInitialProps[key];
        }
      }
    }

    // add a few more things
    baseProps.manifest = this.state.manifestJS;
    baseProps.shell = this.props.isShell;
    if (this.props.isShell && this.props.shellInitialUrl) {
      baseProps.initialUri = this.props.shellInitialUrl;
    } else {
      baseProps.initialUri = this.props.url;
    }

    let appOwnership;
    if (this.props.isShell) {
      appOwnership = 'standalone';
    } else if (this.props.isLetterboxed) {
      appOwnership = 'guest';
    } else {
      appOwnership = 'expo';
    }
    baseProps.appOwnership = appOwnership;

    return baseProps;
  }

  _recomputePropsJS(props) {
    let { task } = props;
    if (task) {
      // immutable equals
      if (!this.props.task || task.manifest !== this.props.task.manifest) {
        if (task.manifest) {
          this.setState({ manifestJS: task.manifest.toJS() });
        } else {
          this.setState({ manifestJS: null });
        }
      }
      if (
        !this.props.task ||
        task.initialProps !== this.props.task.initialProps
      ) {
        if (task.initialProps) {
          this.setState({ initialPropsJS: task.initialProps.toJS() });
        } else {
          this.setState({ initialPropsJS: null });
        }
      }
    } else {
      this.setState({
        manifestJS: null,
        initialPropsJS: null,
      });
    }
  }

  _isNewSplashScreenStyle(manifest) {
    if (Platform.OS !== 'ios') {
      return false;
    }

    if (manifest.getIn(['loading', 'splash'])) {
      return true;
    }

    return false;
  }

  _setFrame = frame => {
    this._frame = frame;
  };

  _handleFrameLoadingStart = event => {
    this.props.dispatch(BrowserActions.setLoadingState(this.props.url, true));
  };

  _handleLoadingProgress = event => {
    this.setState({ loadingStatus: event.nativeEvent });
  };

  _handleFrameLoadingFinish = event => {
    this.props.dispatch(BrowserActions.setLoadingState(this.props.url, false));
    if (
      !this.props.isNuxFinished &&
      !this.props.isLetterboxed &&
      !this.props.isShell
    ) {
      setTimeout(() => {
        this.props.dispatch(BrowserActions.showMenuAsync(true));
      }, 200);
    } else if (this.props.isMenuVisible) {
      this.props.dispatch(BrowserActions.showMenuAsync(false));
    }

    this.setState({ loadingStatus: null });
  };

  _handleFrameLoadingError = event => {
    this.props.dispatch(BrowserActions.setLoadingState(this.props.url, false));
    let { nativeEvent } = event;

    this.props.dispatch(
      BrowserActions.showLoadingError(
        nativeEvent.code,
        nativeEvent.description,
        this.props.url,
        this.props.task.manifest
      )
    );

    this.setState({ loadingStatus: null });
  };

  _handleUncaughtError = event => {
    let { id, message, stack, fatal } = event.nativeEvent;
    if (fatal) {
      let isDeveloper = false;
      if (this.props.task && this.props.task.manifest) {
        isDeveloper = this.props.task.manifest.get('developer');
      }
      if (!isDeveloper) {
        // if developer == true, RCTRedBox will show up instead
        this._maybeRecoverFromErrorAsync(event);
      }
    }
  };

  _maybeRecoverFromErrorAsync = async event => {
    let { id, message, stack, fatal } = event.nativeEvent;
    let shouldReload = await ExponentKernel.shouldCurrentTaskAutoReload();
    if (shouldReload) {
      // call this._refresh rather than Browser.refresh because it works
      // in both browser and shell apps.
      this._refresh();
    } else {
      // show error screen with manual reload button
      this.props.dispatch(
        BrowserActions.showLoadingError(
          id,
          message,
          this.props.url,
          this.props.task.manifest
        )
      );
    }
  };

  _refresh = () => {
    if (this.props.task.loadingError) {
      let urlToRefresh = this.props.task.loadingError.originalUrl;
      this.props.dispatch(BrowserActions.clearTaskWithError(urlToRefresh));
      if (this.props.isShell) {
        this.props.dispatch(
          BrowserActions.navigateToUrlAsync(this.props.shellManifestUrl)
        );
      } else {
        ExponentKernel.openURL(urlToRefresh);
      }
      return;
    }
    this.setState(state => {
      if (!this._frame) {
        console.error('Trying to refresh but there is no frame to refresh');
        return;
      }
      this._frame.reload();
    });
  };

  _onPressLetterbox = () => {
    if (this.props.shellManifestUrl) {
      this.props.dispatch(
        BrowserActions.foregroundUrlAsync(this.props.shellManifestUrl)
      );
    }
  };
}

export default connect((data, props) =>
  BrowserScreen.getDataProps(data, props)
)(BrowserScreen);

let styles = StyleSheet.create({
  container: {
    backgroundColor: '#fff',
    flex: 1,
  },
  frame: {
    flex: 1,
  },
  letterbox: {
    flex: 1,
    backgroundColor: 'black',
  },
  letterboxTopBar: {
    height: 42,
    alignItems: 'center',
  },
  letterboxText: {
    color: 'white',
    paddingTop: 20,
  },
  loadingIndicatorContainer: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loadingBackgroundImage: {
    position: 'absolute',
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
  },
  loadingStatusBar: {
    position: 'absolute',
    left: 0,
    bottom: 0,
    right: 0,
    flexDirection: 'row',
    alignItems: 'center',
    padding: 10,
    backgroundColor: '#fafafa',
    borderTopWidth: StyleSheet.hairlineWidth,
    borderTopColor: '#f3f3f3',
  },
  loadingStatusText: {
    color: '#a7a7a7',
    fontSize: 12,
    flex: 1,
  },
  loadingPercentageText: {
    color: '#a7a7a7',
    fontSize: 12,
    fontVariant: ['tabular-nums'],
  },
  errorView: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
  },
  placeholder: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  placeholderText: {
    color: ExColors.navy,
    fontSize: 32,
    fontWeight: '100',
    letterSpacing: 9,
    marginTop: -200,
  },
});
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react'
import { Link } from 'react-router'
import Images from 'javascripts/images'
import 'stylesheets/modules/showcase'
import 'stylesheets/utilities/clearfix'

class Showcase extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      dragXStart: null,
      dragXNext: null,
      direction: null,
      xOffset: -200,
      showcaseIndex: 0,
      sliding: false,
      transition: '0.5s',
      length: this.props.images[this.props.showcaseItem].slideshow.length
    };
    this.hideDrag = this.hideDrag.bind(this);
    this.drag = this.drag.bind(this);
    this.setDrag = this.setDrag.bind(this);
    this.setIndex = this.setIndex.bind(this);
    this.setFocus = this.setFocus.bind(this);
    this.incrementIndex = this.incrementIndex.bind(this);
    this.decrementIndex = this.decrementIndex.bind(this);
  }

  render() {
    const showcaseItem = Images[this.props.params.piece];
     const showcaseIndex = this.state.showcaseIndex;
     const slideshow = showcaseItem.slideshow;
     const slideShowLength = slideshow.length;
     const setIndex = this.setIndex;
     const setFocus = this.setFocus;
       return (

{showcaseItem.name}

          {slideShowLength < 5 && slideShowLength > 1 &&

hello

            {slideshow.map(function(item, index){
              return (
                <Link to={ {pathname: '/work', query: {piece: item.name+'_'+(index+1)}} }>
                   setFocus(index)} className={(index === showcaseIndex) ? 'focus' : 'non-focus'} src={item} />
                </Link>
              )
            })
            }

 }
          {slideShowLength > 4 &&

             this.setIndex((slideShowLength - 4), showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[slideShowLength - 4]} />
             this.setIndex((slideShowLength - 3), showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[slideShowLength - 3]} />
             this.setIndex((slideShowLength - 2), showcaseIndex, slideShowLength)} className={((slideShowLength - 2) === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[slideShowLength - 2]} />
             this.setIndex((slideShowLength - 1), showcaseIndex, slideShowLength)} className={((slideShowLength - 1) === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[slideShowLength - 1]} />

          {slideshow.map(function(item, sliderIndex){
             return (
                setIndex(sliderIndex, showcaseIndex, slideShowLength)} className={(sliderIndex === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={item} />
              );
             }
          )}
            this.setIndex(0, showcaseIndex, slideShowLength)} className={(0 === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[0]} />
            this.setIndex(1, showcaseIndex, slideShowLength)} className={(1 === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[1]} />
            this.setIndex(2, showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[2]} />
            this.setIndex(3, showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[3]} />

}

        )
  }

  componentDidUpdate() {
    if(this.props.showcaseItem !== false){
    document.getElementsByClassName('showcase-image')[0].addEventListener('dragstart', this.hideDrag);
    }
   }

  componentWillUnmount() {
    document.getElementsByClassName('showcase-image')[0].removeEventListener('dragstart', this.hideDrag);
  }

  setFocus(newIndex) {
    this.setState({showcaseIndex: newIndex});
  }

  setIndex(newIndex, oldIndex) {
    const length = this.state.length;
    if (!this.state.sliding){
      this.setState({sliding: true});
      if((newIndex - oldIndex) > 3){
        const tx = this.state.xOffset + ((oldIndex + (length - newIndex))*100);
        this.setState({xOffset: tx, showcaseIndex: newIndex});
        if (this.state.xOffset > -300){
          setTimeout(
            function(){
              this.setState({transition: '0s', xOffset: this.state.xOffset - (length * 100)});
            }.bind(this), 550);
          setTimeout(
            function(){
              this.setState({transition: '0.5s', sliding: false});
            }.bind(this), 590);
       }
      } else if ((oldIndex - newIndex) > 3) {
         const tx = this.state.xOffset - ((newIndex + (length - oldIndex))*100);
         this.setState({xOffset: tx, showcaseIndex: newIndex});
         if (this.state.xOffset < -200){
           setTimeout(
             function(){
               this.setState({transition: '0s', xOffset: this.state.xOffset + (length * 100)});
             }.bind(this), 550);
           setTimeout(
             function(){
               this.setState({transition: '0.5s', sliding: false});
             }.bind(this), 590);
         }
      } else {
        const tx = this.state.xOffset + ((oldIndex  - newIndex)*100);
        this.setState({xOffset: tx, showcaseIndex: newIndex, sliding: false});
      }
    }
  }

  incrementIndex() {
    const newIndex = (this.state.showcaseIndex === this.state.length - 1) ? 0 : (this.state.showcaseIndex + 1)
    const oldIndex = this.state.showcaseIndex;
    this.setIndex(newIndex, oldIndex);
  }

  decrementIndex() {
    const newIndex = (this.state.showcaseIndex === 0) ? (this.state.length - 1) : (this.state.showcaseIndex - 1);
    const oldIndex = this.state.showcaseIndex;
    this.setIndex(newIndex, oldIndex);
  }

  setDrag(){
    this.setState({direction: null, dragXStart: null, dragXNext: null});
  }

  drag(event) {
    if(event.type === 'drag'){
    } else if (event.type === 'touch'){
    }
    if(this.state.dragXStart === null){
      if(event.type === 'drag'){
      this.setState({dragXStart: event.clientX});
      } else if (event.type === 'touchmove'){
        this.setState({dragXStart: event.touches[0].pageX});
      }
    } else if((this.state.dragXNext === null) && (this.state.dragXStart!==null)) {
      if (event.type === 'drag' && (this.state.dragXStart !== event.clientX)){
      this.setState({dragXNext: event.clientX}, function(){
        if(this.state.dragXStart < this.state.dragXNext){
          this.setState({direction: 'right'}, this.decrementIndex);
          } else if(this.state.dragXStart > this.state.dragXNext){
          this.setState({direction: 'left'}, this.incrementIndex);
        }
      });
      } else if (event.type === 'touchmove' && (this.state.dragXStart !== event.touches[0].clientX)){
      this.setState({dragXNext: event.touches[0].clientX}, function(){
        if(this.state.dragXStart < this.state.dragXNext){
          this.setState({direction: 'right'}, this.decrementIndex);
        } else if (this.state.dragXStart > this.state.dragXNext) {
          this.setState({direction: 'left'}, this.incrementIndex);
        }
        });
      }
    }
  }

  hideDrag(event) {
    if(this.props.showcaseItem !== false){
      event.dataTransfer.effectAllowed = 'none';
      const image = this.props.images[this.props.showcaseItem].slideshow[this.props.showcaseIndex]
      event.dataTransfer.setData("text/plain", event.target.id);
      var img = new Image();
      img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
      event.dataTransfer.setDragImage(img, 100, 100);
    }
  }

}

export default Showcase
<commit_msg>assign showcase length based on params<commit_after>import React, {Component, PropTypes} from 'react'
import { Link } from 'react-router'
import 'stylesheets/modules/showcase'
import 'stylesheets/utilities/clearfix'

class Showcase extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      dragXStart: null,
      dragXNext: null,
      direction: null,
      xOffset: -200,
      showcaseIndex: 0,
      sliding: false,
      transition: '0.5s',
      length: this.props.images[this.props.params.piece].slideshow.length
    };
    this.hideDrag = this.hideDrag.bind(this);
    this.drag = this.drag.bind(this);
    this.setDrag = this.setDrag.bind(this);
    this.setIndex = this.setIndex.bind(this);
    this.setFocus = this.setFocus.bind(this);
    this.incrementIndex = this.incrementIndex.bind(this);
    this.decrementIndex = this.decrementIndex.bind(this);
  }

  render() {
    const showcaseItem = this.props.images[this.props.params.piece];
     const showcaseIndex = this.state.showcaseIndex;
     const slideshow = showcaseItem.slideshow;
     const slideShowLength = slideshow.length;
     const setIndex = this.setIndex;
     const setFocus = this.setFocus;
       return (

{showcaseItem.name}

          {slideShowLength < 5 && slideShowLength > 1 &&

hello

            {slideshow.map(function(item, index){
              return (
                <Link to={ {pathname: '/work', query: {piece: item.name+'_'+(index+1)}} }>
                   setFocus(index)} className={(index === showcaseIndex) ? 'focus' : 'non-focus'} src={item} />
                </Link>
              )
            })
            }

 }
          {slideShowLength > 4 &&

             this.setIndex((slideShowLength - 4), showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[slideShowLength - 4]} />
             this.setIndex((slideShowLength - 3), showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[slideShowLength - 3]} />
             this.setIndex((slideShowLength - 2), showcaseIndex, slideShowLength)} className={((slideShowLength - 2) === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[slideShowLength - 2]} />
             this.setIndex((slideShowLength - 1), showcaseIndex, slideShowLength)} className={((slideShowLength - 1) === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[slideShowLength - 1]} />

          {slideshow.map(function(item, sliderIndex){
             return (
                setIndex(sliderIndex, showcaseIndex, slideShowLength)} className={(sliderIndex === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={item} />
              );
             }
          )}
            this.setIndex(0, showcaseIndex, slideShowLength)} className={(0 === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[0]} />
            this.setIndex(1, showcaseIndex, slideShowLength)} className={(1 === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[1]} />
            this.setIndex(2, showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[2]} />
            this.setIndex(3, showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[3]} />

}

        )
  }

  componentDidUpdate() {
    if(this.props.showcaseItem !== false){
    document.getElementsByClassName('showcase-image')[0].addEventListener('dragstart', this.hideDrag);
    }
   }

  componentWillUnmount() {
    document.getElementsByClassName('showcase-image')[0].removeEventListener('dragstart', this.hideDrag);
  }

  setFocus(newIndex) {
    this.setState({showcaseIndex: newIndex});
  }

  setIndex(newIndex, oldIndex) {
    const length = this.state.length;
    if (!this.state.sliding){
      this.setState({sliding: true});
      if((newIndex - oldIndex) > 3){
        const tx = this.state.xOffset + ((oldIndex + (length - newIndex))*100);
        this.setState({xOffset: tx, showcaseIndex: newIndex});
        if (this.state.xOffset > -300){
          setTimeout(
            function(){
              this.setState({transition: '0s', xOffset: this.state.xOffset - (length * 100)});
            }.bind(this), 550);
          setTimeout(
            function(){
              this.setState({transition: '0.5s', sliding: false});
            }.bind(this), 590);
       }
      } else if ((oldIndex - newIndex) > 3) {
         const tx = this.state.xOffset - ((newIndex + (length - oldIndex))*100);
         this.setState({xOffset: tx, showcaseIndex: newIndex});
         if (this.state.xOffset < -200){
           setTimeout(
             function(){
               this.setState({transition: '0s', xOffset: this.state.xOffset + (length * 100)});
             }.bind(this), 550);
           setTimeout(
             function(){
               this.setState({transition: '0.5s', sliding: false});
             }.bind(this), 590);
         }
      } else {
        const tx = this.state.xOffset + ((oldIndex  - newIndex)*100);
        this.setState({xOffset: tx, showcaseIndex: newIndex, sliding: false});
      }
    }
  }

  incrementIndex() {
    const newIndex = (this.state.showcaseIndex === this.state.length - 1) ? 0 : (this.state.showcaseIndex + 1)
    const oldIndex = this.state.showcaseIndex;
    this.setIndex(newIndex, oldIndex);
  }

  decrementIndex() {
    const newIndex = (this.state.showcaseIndex === 0) ? (this.state.length - 1) : (this.state.showcaseIndex - 1);
    const oldIndex = this.state.showcaseIndex;
    this.setIndex(newIndex, oldIndex);
  }

  setDrag(){
    this.setState({direction: null, dragXStart: null, dragXNext: null});
  }

  drag(event) {
    if(event.type === 'drag'){
    } else if (event.type === 'touch'){
    }
    if(this.state.dragXStart === null){
      if(event.type === 'drag'){
      this.setState({dragXStart: event.clientX});
      } else if (event.type === 'touchmove'){
        this.setState({dragXStart: event.touches[0].pageX});
      }
    } else if((this.state.dragXNext === null) && (this.state.dragXStart!==null)) {
      if (event.type === 'drag' && (this.state.dragXStart !== event.clientX)){
      this.setState({dragXNext: event.clientX}, function(){
        if(this.state.dragXStart < this.state.dragXNext){
          this.setState({direction: 'right'}, this.decrementIndex);
          } else if(this.state.dragXStart > this.state.dragXNext){
          this.setState({direction: 'left'}, this.incrementIndex);
        }
      });
      } else if (event.type === 'touchmove' && (this.state.dragXStart !== event.touches[0].clientX)){
      this.setState({dragXNext: event.touches[0].clientX}, function(){
        if(this.state.dragXStart < this.state.dragXNext){
          this.setState({direction: 'right'}, this.decrementIndex);
        } else if (this.state.dragXStart > this.state.dragXNext) {
          this.setState({direction: 'left'}, this.incrementIndex);
        }
        });
      }
    }
  }

  hideDrag(event) {
    if(this.props.showcaseItem !== false){
      event.dataTransfer.effectAllowed = 'none';
      const image = this.props.images[this.props.showcaseItem].slideshow[this.props.showcaseIndex]
      event.dataTransfer.setData("text/plain", event.target.id);
      var img = new Image();
      img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
      event.dataTransfer.setDragImage(img, 100, 100);
    }
  }

}

export default Showcase
<|endoftext|>


<commit_before>// Tests for coapack plugin register
const { expect } = require("chai");
const path = require("path");
const Register = require("../packages/coapack-register");
let register;

describe("coapack-register tests", () => {
  describe("registerPlguin()", () => {
    it("should init the object", () => {
      register = new Register();
      expect(register).to.be.an("object");
    });

    it("should test registering a plugin with just a name", (done) => {
      register.registerPlguin("chai");
      expect(register.plugins).to.have.property("chai");
      expect(register.plugins.chai).to.deep.equal({
        name: "chai",
        path: path.join(process.cwd(), "node_modules", "chai"),
        pkgJSON: require(path.join(process.cwd(), "node_modules", "chai", "package.json"))
      });
      done();
    });

    it("should test registering a plugin with a plugin object", (done) => {
      const pluginsToTest = {
        name: "chai",
        path: "node_modules/chai",
      };
      register.registerPlguin(pluginsToTest);
      expect(register.plugins).to.have.property("chai");
      expect(register.plugins.chai).to.deep.equal(pluginsToTest);
      done();
    });

    it("should check path property is added if not there", (done) => {
      const pluginsToTest = {
        name: "chai",
      };
      register.registerPlguin(pluginsToTest);
      expect(register.plugins).to.have.property("chai");
      expect(register.plugins.chai).to.have.property("path");
      done();
    });

    it("should check register errors if name is not specified", (done) => {
      expect(() => register.registerPlguin({})).to.throw("ENONAME");
      done();
    });

    it("should resolve the path specified", (done) => {
      register.registerPlguin({
        name: "chai",
        path: "node_modules/chai"
      });
      expect(register.plugins.chai.path).to.equal(path.resolve("node_modules/chai"));
      done();
    });
  });
});
<commit_msg>:white_check_mark: Fix some testing issues<commit_after>// Tests for coapack plugin register
const { expect } = require("chai");
const path = require("path");
const Register = require("../packages/coapack-register");
let register;

describe("coapack-register tests", () => {
  describe("registerPlguin()", () => {
    it("should init the object", () => {
      register = new Register();
      expect(register).to.be.an("object");
    });

    it("should test registering a plugin with just a name", (done) => {
      register.registerPlugin("chai");
      expect(register.plugins).to.have.property("chai");
      expect(register.plugins.chai).to.deep.equal({
        name: "chai",
        path: path.join(process.cwd(), "node_modules", "chai"),
        pkgJSON: require(path.join(process.cwd(), "node_modules", "chai", "package.json"))
      });
      done();
    });

    it("should test registering a plugin with a plugin object", (done) => {
      const pluginsToTest = {
        name: "chai",
        path: "node_modules/chai",
      };
      register.registerPlugin(pluginsToTest);
      expect(register.plugins).to.have.property("chai");
      expect(register.plugins.chai).to.deep.equal(pluginsToTest);
      done();
    });

    it("should check path property is added if not there", (done) => {
      const pluginsToTest = {
        name: "chai",
      };
      register.registerPlugin(pluginsToTest);
      expect(register.plugins).to.have.property("chai");
      expect(register.plugins.chai).to.have.property("path");
      done();
    });

    it("should check register errors if name is not specified", (done) => {
      expect(() => register.registerPlugin({})).to.throw("ENONAME");
      done();
    });

    it("should resolve the path specified", (done) => {
      register.registerPlugin({
        name: "chai",
        path: "node_modules/chai"
      });
      expect(register.plugins.chai.path).to.equal(path.resolve("node_modules/chai"));
      done();
    });
  });
});
<|endoftext|>


<commit_before>(function (enyo, scope) {
	/**
	* A light-weight panels implementation that has basic support for CSS transitions between child
	* components.
	*
	* @class enyo.LightPanels
	* @extends enyo.Control
	* @ui
	* @public
	*/
	enyo.kind(
		/** @lends enyo.LightPanels.prototype */ {

		/**
		* @private
		*/
		name: 'enyo.LightPanels',

		/**
		* @private
		*/
		kind: 'enyo.Control',

		/**
		* @private
		*/
		classes: 'enyo-light-panels',

		/**
		* @private
		* @lends enyo.LightPanels.prototype
		*/
		published: {

			/**
			* The index of the active panel.
			*
			* @type {Number}
			* @default 0
			* @public
			*/
			index: 0,

			/**
			* Indicates whether the panels animate when transitioning.
			*
			* @type {Boolean}
			* @default true
			* @public
			*/
			animate: true,

			/**
			* Indicates whether panels "wrap around" when moving past the end.
			* The actual effect depends upon the arranger in use.
			*
			* @type {Boolean}
			* @default false
			* @public
			*/
			wrap: false,

			/**
			* When `true`, panels are automatically popped when the user moves back.
			*
			* @type {Boolean}
			* @default true
			* @public
			*/
			popOnBack: true,

			/**
			* The amount of time, in milliseconds, to run the transition animation between panels.
			*
			* @type {Number}
			* @default 250
			* @public
			*/
			duration: 250,

			/**
			* The timing function to be applied to the transition animation between panels.
			*
			* @type {String}
			* @default 'ease-out'
			* @public
			*/
			timingFunction: 'ease-out',

			/**
			* The orientation of the panels. Possible values are 'vertical' and 'horizontal'.
			*
			* @type {String}
			* @default 'horizontal'
			* @public
			*/
			orientation: 'horizontal',

			/**
			* The direction of the panel movement. Possible values are 'forwards' and 'backwards'.
			*
			* @type {String}
			* @default 'forwards'
			* @public
			*/
			direction: 'forwards',

			cachePanels: true
		},

		/**
		* @private
		*/
		handlers: {
			ontransitionend: 'transitionFinished'
		},

		/**
		* @method
		* @private
		*/
		create: enyo.inherit(function (sup) {
			return function () {
				sup.apply(this, arguments);
				this.init();
				this.indexChanged();
			};
		}),

		/**
		* @private
		*/
		init: function () {
			this.addClass(this.orientation);
			this.addClass(this.direction);

			if (this.cachePanels) {
				this.createChrome([
					{name: 'panelCache', kind: 'enyo.Control', canGenerate: false}
				]);
				this.removeChild(this.$.panelCache);
				this.cachedPanels = {};
				this.queuedPanels = [];
			}
		},

		/**
		* @private
		*/
		indexChanged: function (previousIndex) {
			var panels = this.getPanels(),
				nextPanel = panels[this.index],
				trans, wTrans, axis, direction;

			this._indexDirection = (this.index - previousIndex < 0 ? -1 : 1);

			if (nextPanel) {
				// only animate transition if there is more than one panel and/or we're animating
				if (!this.shouldAnimate()) {
					nextPanel.applyStyle('-webkit-transition-duration', '0s');
					nextPanel.applyStyle('transition-duration', '0s');
				} else {
					trans = enyo.format('transform %.ms %.', this.duration, this.timingFunction);
					wTrans = '-webkit-' + trans;
					nextPanel.applyStyle('-webkit-transition', wTrans);
					nextPanel.applyStyle('transition', trans);
					nextPanel.addClass('transitioning');
					if (this._currentPanel) {
						this._currentPanel.applyStyle('-webkit-transition', wTrans);
						this._currentPanel.applyStyle('transition', trans);
						this._currentPanel.addClass('transitioning');
					}
				}

				axis = this.orientation == 'horizontal' ? 'X' : 'Y';
				direction = this.direction == 'forwards' ? 1 : -1;
				setTimeout(this.bindSafely(function () {
					var nextTransition = {};
					nextTransition['translate' + axis] = -100 * direction + '%';
					enyo.dom.transform(nextPanel, nextTransition);
					if (this._currentPanel) {
						var currentTransition = {};
						currentTransition['translate' + axis] = this._indexDirection > 0 ? -200 * direction + '%' : '0%';
						enyo.dom.transform(this._currentPanel, currentTransition);
					}

					this._previousPanel = this._currentPanel;
					this._currentPanel = nextPanel;
					if (!this.shouldAnimate()) {
						this.transitionFinished(this._currentPanel, {originator: this._currentPanel});
					}

				}), this.shouldAnimate() ? 16 : 0);
			}
		},

		/**
		* Determines whether or not we should animate the panel transition.
		*
		* @return {Boolean} If `true`, the panels should animate.
		* @public
		*/
		shouldAnimate: function () {
			return this.getPanels().length > 1 && this.animate;
		},

		/**
		* Retrieves the currently displayed panel.
		*
		* @return {Object} The currently displayed panel.
		* @public
		*/
		getActivePanel: function () {
			return this._currentPanel;
		},

		/**
		* Transitions to the previous panel--i.e., the panel whose index value is one
		* less than that of the current active panel.
		*
		* @public
		*/
		previous: function () {
			var prevIndex = this.index - 1;
			if (this.wrap && prevIndex < 0) {
				prevIndex = this.getPanels().length - 1;
			}
			if (prevIndex >= 0) {
				this.set('index', prevIndex);
			}
		},

		/**
		* Transitions to the next panel--i.e., the panel whose index value is one
		* greater than that of the current active panel.
		*
		* @public
		*/
		next: function () {
			var nextIndex = this.index + 1;
			if (this.wrap && nextIndex >= this.getPanels().length) {
				nextIndex = 0;
			}
			if (nextIndex < this.getPanels().length) {
				this.set('index', nextIndex);
			}
		},

		/**
		* Creates a panel on top of the stack and increments index to select that component.
		*
		* @param {Object} info - The declarative {@glossary kind} definition.
		* @param {Object} moreInfo - Additional properties to be applied (defaults).
		* @param {Object} opts - Additional options to be used during panel pushing.
		* @return {Object} The instance of the panel that was created on top of the stack.
		* @public
		*/
		pushPanel: function (info, moreInfo, opts) {
			if (opts && opts.purge) {
				this.purge();
			}

			var lastIndex = this.getPanels().length - 1,
				nextPanel = (this.cachePanels && this.restorePanel(info.kind)) || this.createComponent(info, moreInfo);
			nextPanel.render();
			this.set('index', lastIndex + 1, true);

			// TODO: When pushing panels after we have gone back (but have not popped), we need to
			// adjust the position of the panels after the previous index before our push.
			return nextPanel;
		},

		/**
		* Creates multiple panels on top of the stack and updates index to select the last one
		* created. Supports an optional `options` object as the third parameter.
		*
		* @param {Object[]} info - The declarative {@glossary kind} definitions.
		* @param {Object} commonInfo - Additional properties to be applied (defaults).
		* @param {Object} options - Additional options for pushPanels.
		* @return {null|Object[]} Array of the panels that were created on top of the stack, or
		*	`null` if panels could not be created.
		* @public
		*/
		pushPanels: function (info, commonInfo, options) {
			var lastIndex = this.getPanels().length,
				newPanels = this.createComponents(info, commonInfo),
				idx;

			for (idx = 0; idx < newPanels.length; ++idx) {
				newPanels[idx].render();
			}

			this.set('index', lastIndex, true);

			return newPanels;
		},

		/**
		* @private
		*/
		getPanels: function () {
			/*jshint -W093 */
			return (this._panels = this._panels || (this.controlParent || this).children);
		},

		/**
		* Destroys panels whose index is greater than or equal to a specified value.
		*
		* @param {Number} index - Index at which to start destroying panels.
		* @public
		*/
		popPanels: function (index) {
			var panels = this.getPanels(),
				panel;

			index = index || panels.length - 1;

			while (panels.length > index && index >= 0) {
				panel = panels[panels.length - 1];
				if (this.cachePanels) {
					this.cachePanel(panel);
				}
				else {
					panel.destroy();
				}
			}
		},

		/**
		* Destroys all panels. These will be queued for destruction after the next panel has loaded.
		*
		* @private
		*/
		purge: function () {
			var panels = this.getPanels();
			this._garbagePanels = panels.slice();
			panels.length = 0;
		},

		/**
		* Clean-up any panels queued for destruction.
		*
		* @private
		*/
		finalizePurge: function () {
			var panels = this._garbagePanels,
				panel;
			while (panels.length) {
				panel = panels.pop();
				if (this.cachePanels) {
					this.cachePanel(panel);
				}
				else {
					panel.destroy();
				}
			}
		},

		/**
		* Caches a given panel so that it can be quickly instantiated and utilized at a later point.
		* This is typically performed on a panel which has already been rendered and which no longer
		* needs to remain in view, but may be revisited at a later time.
		*
		* @param {Object} panel - The panel to cache for later use.
		* @public
		*/
		cachePanel: function (panel) {
			// TODO: This works for Settings use case,
			// but we need to support an alternative
			// identifier for panel-caching purposes
			var pid = panel.kind;

			panel.node.remove();
			panel.teardownRender();

			this.removeControl(panel);
			this.$.panelCache.addControl(panel);

			this.cachedPanels[pid] = panel;
		},

		/**
		* Restores the specified panel that was previously cached.
		*
		* @param {String} pid - The unique identifier for the cached panel that is being restored.
		* @return {Object} The restored panel.
		* @public
		*/
		restorePanel: function (pid) {
			var cp = this.cachedPanels,
				panel = cp[pid];

			if (panel) {
				this.$.panelCache.removeControl(panel);
				this.addControl(panel);

				this.cachedPanels[pid] = null;
			}

			return panel;
		},

		/**
		* Pre-caches a set of panels by creating and caching them, even if they have not yet been
		* rendered into view. This is helpful for reducing the instantiation time for panels which
		* have yet to be shown, but can and eventually will be shown to the user.
		*
		* @param {Object} info - The declarative {@glossary kind} definition.
		* @param {Object} commonInfo - Additional properties to be applied (defaults).
		* @param {Boolean} runPostTransition - If `true`, the {@link enyo.LightPanel#postTransition}
		*	method will be run.
		* @public
		*/
		preCachePanels: function(info, commonInfo, runPostTransition) {
			var pc, panels, i, panel;

			if (this.cachePanels) {
				pc = this.$.panelCache;
				commonInfo = commonInfo || {};
				commonInfo.owner = this;
				panels = pc.createComponents(info, commonInfo);
				for (i = 0; i < panels.length; i++) {
					panel = panels[i];
					this.cachedPanels[panel.kind] = panel;
					if (runPostTransition) {
						panel.postTransition();
					}
				}
			}
		},

		/**
		* @private
		*/
		preCacheQueuedPanels: function () {
			this.preCachePanels(this.queuedPanels, {}, true);
			this.queuedPanels.length = 0;
		},

		/**
		* Enqueues a view that will eventually be pre-cached at an opportunistic time.
		*
		* @param {String} viewName - The name of the view to be enqueued.
		* @public
		*/
		enqueueView: function (viewProps) {
			this.queuedPanels.push(viewProps);
		},

		/**
		* Enqueues a set of views that will eventually be pre-cached at an opportunistic time.
		*
		* @param {Array} viewNames - A set of views to be enqueued.
		* @public
		*/
		enqueueViews: function (viewPropsArray) {
			this.queuedPanels = this.queuedPanels.concat(viewPropsArray);
		},

		/**
		* @private
		*/
		transitionFinished: function (sender, ev) {
			if (ev.originator === this._currentPanel) {
				this._currentPanel.removeClass('transitioning');
				if (this._previousPanel) {
					this._previousPanel.removeClass('transitioning');
				}
				if (this.popOnBack && this._indexDirection < 0 && this.index < this.getPanels().length - 1) {
					this.popPanels(this.index + 1);
				}
				if (this._currentPanel.shouldSkipPostTransition && !this._currentPanel.shouldSkipPostTransition()) {
					enyo.asyncMethod(this, function () {
						this._currentPanel.postTransition();
					});
				}
				if (this._garbagePanels && this._garbagePanels.length) {
					this.finalizePurge();
				}
				if (this.cachePanels && this.queuedPanels.length) {
					this.startJob('preCacheQueuedPanels', function() {
						this.preCacheQueuedPanels();
					}, 750);
				}
			}
		}

	});

})(enyo, this);
<commit_msg>Updating doc.<commit_after>(function (enyo, scope) {
	/**
	* A light-weight panels implementation that has basic support for CSS transitions between child
	* components.
	*
	* @class enyo.LightPanels
	* @extends enyo.Control
	* @ui
	* @public
	*/
	enyo.kind(
		/** @lends enyo.LightPanels.prototype */ {

		/**
		* @private
		*/
		name: 'enyo.LightPanels',

		/**
		* @private
		*/
		kind: 'enyo.Control',

		/**
		* @private
		*/
		classes: 'enyo-light-panels',

		/**
		* @private
		* @lends enyo.LightPanels.prototype
		*/
		published: {

			/**
			* The index of the active panel.
			*
			* @type {Number}
			* @default 0
			* @public
			*/
			index: 0,

			/**
			* Indicates whether the panels animate when transitioning.
			*
			* @type {Boolean}
			* @default true
			* @public
			*/
			animate: true,

			/**
			* Indicates whether panels "wrap around" when moving past the end.
			* The actual effect depends upon the arranger in use.
			*
			* @type {Boolean}
			* @default false
			* @public
			*/
			wrap: false,

			/**
			* When `true`, panels are automatically popped when the user moves back.
			*
			* @type {Boolean}
			* @default true
			* @public
			*/
			popOnBack: true,

			/**
			* The amount of time, in milliseconds, to run the transition animation between panels.
			*
			* @type {Number}
			* @default 250
			* @public
			*/
			duration: 250,

			/**
			* The timing function to be applied to the transition animation between panels.
			*
			* @type {String}
			* @default 'ease-out'
			* @public
			*/
			timingFunction: 'ease-out',

			/**
			* The orientation of the panels. Possible values are 'vertical' and 'horizontal'.
			*
			* @type {String}
			* @default 'horizontal'
			* @public
			*/
			orientation: 'horizontal',

			/**
			* The direction of the panel movement. Possible values are 'forwards' and 'backwards'.
			*
			* @type {String}
			* @default 'forwards'
			* @public
			*/
			direction: 'forwards',

			cachePanels: true
		},

		/**
		* @private
		*/
		handlers: {
			ontransitionend: 'transitionFinished'
		},

		/**
		* @method
		* @private
		*/
		create: enyo.inherit(function (sup) {
			return function () {
				sup.apply(this, arguments);
				this.init();
				this.indexChanged();
			};
		}),

		/**
		* @private
		*/
		init: function () {
			this.addClass(this.orientation);
			this.addClass(this.direction);

			if (this.cachePanels) {
				this.createChrome([
					{name: 'panelCache', kind: 'enyo.Control', canGenerate: false}
				]);
				this.removeChild(this.$.panelCache);
				this.cachedPanels = {};
				this.queuedPanels = [];
			}
		},

		/**
		* @private
		*/
		indexChanged: function (previousIndex) {
			var panels = this.getPanels(),
				nextPanel = panels[this.index],
				trans, wTrans, axis, direction;

			this._indexDirection = (this.index - previousIndex < 0 ? -1 : 1);

			if (nextPanel) {
				// only animate transition if there is more than one panel and/or we're animating
				if (!this.shouldAnimate()) {
					nextPanel.applyStyle('-webkit-transition-duration', '0s');
					nextPanel.applyStyle('transition-duration', '0s');
				} else {
					trans = enyo.format('transform %.ms %.', this.duration, this.timingFunction);
					wTrans = '-webkit-' + trans;
					nextPanel.applyStyle('-webkit-transition', wTrans);
					nextPanel.applyStyle('transition', trans);
					nextPanel.addClass('transitioning');
					if (this._currentPanel) {
						this._currentPanel.applyStyle('-webkit-transition', wTrans);
						this._currentPanel.applyStyle('transition', trans);
						this._currentPanel.addClass('transitioning');
					}
				}

				axis = this.orientation == 'horizontal' ? 'X' : 'Y';
				direction = this.direction == 'forwards' ? 1 : -1;
				setTimeout(this.bindSafely(function () {
					var nextTransition = {};
					nextTransition['translate' + axis] = -100 * direction + '%';
					enyo.dom.transform(nextPanel, nextTransition);
					if (this._currentPanel) {
						var currentTransition = {};
						currentTransition['translate' + axis] = this._indexDirection > 0 ? -200 * direction + '%' : '0%';
						enyo.dom.transform(this._currentPanel, currentTransition);
					}

					this._previousPanel = this._currentPanel;
					this._currentPanel = nextPanel;
					if (!this.shouldAnimate()) {
						this.transitionFinished(this._currentPanel, {originator: this._currentPanel});
					}

				}), this.shouldAnimate() ? 16 : 0);
			}
		},

		/**
		* Determines whether or not we should animate the panel transition.
		*
		* @return {Boolean} If `true`, the panels should animate.
		* @public
		*/
		shouldAnimate: function () {
			return this.getPanels().length > 1 && this.animate;
		},

		/**
		* Retrieves the currently displayed panel.
		*
		* @return {Object} The currently displayed panel.
		* @public
		*/
		getActivePanel: function () {
			return this._currentPanel;
		},

		/**
		* Transitions to the previous panel--i.e., the panel whose index value is one
		* less than that of the current active panel.
		*
		* @public
		*/
		previous: function () {
			var prevIndex = this.index - 1;
			if (this.wrap && prevIndex < 0) {
				prevIndex = this.getPanels().length - 1;
			}
			if (prevIndex >= 0) {
				this.set('index', prevIndex);
			}
		},

		/**
		* Transitions to the next panel--i.e., the panel whose index value is one
		* greater than that of the current active panel.
		*
		* @public
		*/
		next: function () {
			var nextIndex = this.index + 1;
			if (this.wrap && nextIndex >= this.getPanels().length) {
				nextIndex = 0;
			}
			if (nextIndex < this.getPanels().length) {
				this.set('index', nextIndex);
			}
		},

		/**
		* Creates a panel on top of the stack and increments index to select that component.
		*
		* @param {Object} info - The declarative {@glossary kind} definition.
		* @param {Object} moreInfo - Additional properties to be applied (defaults).
		* @param {Object} opts - Additional options to be used during panel pushing.
		* @return {Object} The instance of the panel that was created on top of the stack.
		* @public
		*/
		pushPanel: function (info, moreInfo, opts) {
			if (opts && opts.purge) {
				this.purge();
			}

			var lastIndex = this.getPanels().length - 1,
				nextPanel = (this.cachePanels && this.restorePanel(info.kind)) || this.createComponent(info, moreInfo);
			nextPanel.render();
			this.set('index', lastIndex + 1, true);

			// TODO: When pushing panels after we have gone back (but have not popped), we need to
			// adjust the position of the panels after the previous index before our push.
			return nextPanel;
		},

		/**
		* Creates multiple panels on top of the stack and updates index to select the last one
		* created. Supports an optional `options` object as the third parameter.
		*
		* @param {Object[]} info - The declarative {@glossary kind} definitions.
		* @param {Object} commonInfo - Additional properties to be applied (defaults).
		* @param {Object} options - Additional options for pushPanels.
		* @return {null|Object[]} Array of the panels that were created on top of the stack, or
		*	`null` if panels could not be created.
		* @public
		*/
		pushPanels: function (info, commonInfo, options) {
			var lastIndex = this.getPanels().length,
				newPanels = this.createComponents(info, commonInfo),
				idx;

			for (idx = 0; idx < newPanels.length; ++idx) {
				newPanels[idx].render();
			}

			this.set('index', lastIndex, true);

			return newPanels;
		},

		/**
		* @private
		*/
		getPanels: function () {
			/*jshint -W093 */
			return (this._panels = this._panels || (this.controlParent || this).children);
		},

		/**
		* Destroys panels whose index is greater than or equal to a specified value.
		*
		* @param {Number} index - Index at which to start destroying panels.
		* @public
		*/
		popPanels: function (index) {
			var panels = this.getPanels(),
				panel;

			index = index || panels.length - 1;

			while (panels.length > index && index >= 0) {
				panel = panels[panels.length - 1];
				if (this.cachePanels) {
					this.cachePanel(panel);
				}
				else {
					panel.destroy();
				}
			}
		},

		/**
		* Destroys all panels. These will be queued for destruction after the next panel has loaded.
		*
		* @private
		*/
		purge: function () {
			var panels = this.getPanels();
			this._garbagePanels = panels.slice();
			panels.length = 0;
		},

		/**
		* Clean-up any panels queued for destruction.
		*
		* @private
		*/
		finalizePurge: function () {
			var panels = this._garbagePanels,
				panel;
			while (panels.length) {
				panel = panels.pop();
				if (this.cachePanels) {
					this.cachePanel(panel);
				}
				else {
					panel.destroy();
				}
			}
		},

		/**
		* Caches a given panel so that it can be quickly instantiated and utilized at a later point.
		* This is typically performed on a panel which has already been rendered and which no longer
		* needs to remain in view, but may be revisited at a later time.
		*
		* @param {Object} panel - The panel to cache for later use.
		* @public
		*/
		cachePanel: function (panel) {
			// TODO: This works for Settings use case,
			// but we need to support an alternative
			// identifier for panel-caching purposes
			var pid = panel.kind;

			panel.node.remove();
			panel.teardownRender();

			this.removeControl(panel);
			this.$.panelCache.addControl(panel);

			this.cachedPanels[pid] = panel;
		},

		/**
		* Restores the specified panel that was previously cached.
		*
		* @param {String} pid - The unique identifier for the cached panel that is being restored.
		* @return {Object} The restored panel.
		* @public
		*/
		restorePanel: function (pid) {
			var cp = this.cachedPanels,
				panel = cp[pid];

			if (panel) {
				this.$.panelCache.removeControl(panel);
				this.addControl(panel);

				this.cachedPanels[pid] = null;
			}

			return panel;
		},

		/**
		* Pre-caches a set of panels by creating and caching them, even if they have not yet been
		* rendered into view. This is helpful for reducing the instantiation time for panels which
		* have yet to be shown, but can and eventually will be shown to the user.
		*
		* @param {Object} info - The declarative {@glossary kind} definition.
		* @param {Object} commonInfo - Additional properties to be applied (defaults).
		* @param {Boolean} runPostTransition - If `true`, the {@link enyo.LightPanel#postTransition}
		*	method will be run.
		* @public
		*/
		preCachePanels: function(info, commonInfo, runPostTransition) {
			var pc, panels, i, panel;

			if (this.cachePanels) {
				pc = this.$.panelCache;
				commonInfo = commonInfo || {};
				commonInfo.owner = this;
				panels = pc.createComponents(info, commonInfo);
				for (i = 0; i < panels.length; i++) {
					panel = panels[i];
					this.cachedPanels[panel.kind] = panel;
					if (runPostTransition) {
						panel.postTransition();
					}
				}
			}
		},

		/**
		* @private
		*/
		preCacheQueuedPanels: function () {
			this.preCachePanels(this.queuedPanels, {}, true);
			this.queuedPanels.length = 0;
		},

		/**
		* Enqueues a view that will eventually be pre-cached at an opportunistic time.
		*
		* @param {String} viewProps - The properties of the view to be enqueued.
		* @public
		*/
		enqueueView: function (viewProps) {
			this.queuedPanels.push(viewProps);
		},

		/**
		* Enqueues a set of views that will eventually be pre-cached at an opportunistic time.
		*
		* @param {Array} viewPropsArray - A set of views to be enqueued.
		* @public
		*/
		enqueueViews: function (viewPropsArray) {
			this.queuedPanels = this.queuedPanels.concat(viewPropsArray);
		},

		/**
		* @private
		*/
		transitionFinished: function (sender, ev) {
			if (ev.originator === this._currentPanel) {
				this._currentPanel.removeClass('transitioning');
				if (this._previousPanel) {
					this._previousPanel.removeClass('transitioning');
				}
				if (this.popOnBack && this._indexDirection < 0 && this.index < this.getPanels().length - 1) {
					this.popPanels(this.index + 1);
				}
				if (this._currentPanel.shouldSkipPostTransition && !this._currentPanel.shouldSkipPostTransition()) {
					enyo.asyncMethod(this, function () {
						this._currentPanel.postTransition();
					});
				}
				if (this._garbagePanels && this._garbagePanels.length) {
					this.finalizePurge();
				}
				if (this.cachePanels && this.queuedPanels.length) {
					this.startJob('preCacheQueuedPanels', function() {
						this.preCacheQueuedPanels();
					}, 750);
				}
			}
		}

	});

})(enyo, this);
<|endoftext|>


<commit_before>// Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
//
// MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

wn.provide("wn.print");

// opts:
// doctype (parent)
// docname
// tabletype
// fieldname
// show_all = false;

wn.print.Table = Class.extend({
	init: function(opts) {
		$.extend(this, opts);
		if(!this.columns)
			this.columns = this.get_columns();
		this.data = this.get_data();
		this.remove_empty_cols();
		this.set_widths();
		this.make();
	},
	get_columns: function() {
		return ['Sr'].concat($.map(wn.meta.docfield_list[this.tabletype], function(df) {
			return cint(df.print_hide) ? null : df.fieldname;
		}));
	},
	get_data: function() {
		var children = wn.model.get(this.tabletype, {
			parent:this.docname, parenttype:this.doctype, parentfield: this.fieldname})

		var data = []
		for(var i=0; i<children.length; i++) {
			data.push(copy_dict(children[i]));
		}
		return data;
	},

	remove_empty_cols: function() {
		var cols_with_value = [];
		var widths = [];
		var head_labels = [];
		var me = this;

		$.each(this.data, function(i, row) {
			$.each(me.columns, function(ci, fieldname) {
				var value = row[fieldname];
				if((value!==null && value!=="") || ci==0) {
					if(!in_list(cols_with_value, fieldname)) {
						cols_with_value.push(fieldname);

						// also prepare a new list of widths and head labels
						me.widths && widths.push(me.widths[ci]);
						me.head_labels && head_labels.push(me.head_labels[ci]);
					}
				}
			});
		});

		this.columns = cols_with_value;
		if(this.widths) this.widths = widths;
		if(this.head_labels) this.head_labels = head_labels;
	},

	make: function() {
		var me = this;
		this.tables = [];
		var table_data = [];
		$.each(this.data, function(i, d) {
			table_data.push(d);
			if(d.page_break) {
				me.add_table(table_data);
				table_data = [];
			}
		});
		if(table_data)
			me.add_table(table_data);
	},

	add_table: function(data) {
		var me = this;
		var wrapper = $("<div>")
		var table = $("<table>").css(this.table_style).appendTo(wrapper);

		var headrow = $("<tr>").appendTo(table);
		$.each(me.columns, function(ci, fieldname) {
			var df = wn.meta.docfield_map[me.tabletype][fieldname];
			if(me.head_labels) {
				var label = me.head_labels[ci];
			} else {
				var label = df ? df.label : fieldname;
			}
			var td = $("<td>").html(label)
				.css(me.head_cell_style)
				.css({"width": me.widths[ci]})
				.appendTo(headrow)

			if(ci==0) {
				td.css({"min-width": "24pt"});
			} else {
				td.css({"min-width": "5%"});
			}

			if(df && in_list(['Float', 'Currency'], df.fieldtype)) {
				td.css({"text-align": "right"});
			}
		});

		$.each(data, function(ri, row) {
			var allow = true;
			if(me.condition) {
				allow = me.condition(row);
			}
			if(allow) {
				var tr = $("<tr>").appendTo(table);

				$.each(me.columns, function(ci, fieldname) {
					if(ci==0)
						var value = row.idx;
					else
						var value = row[fieldname];

					var df = wn.meta.docfield_map[me.tabletype][fieldname];
					value = wn.form.get_formatter(
						df && df.fieldtype || "Data")(value);

					if(me.modifier && me.modifier[fieldname])
						value = me.modifier[fieldname](row);

					var td = $("<td>").html(value)
						.css(me.cell_style)
						.css({width: me.widths[ci]})
						.appendTo(tr);

					if(ci==0) {
						td.css({"min-width": "24pt"});
					} else {
						td.css({"min-width": "5%"});
					}
				});
			}
		});
		this.tables.push(wrapper)
	},

	set_widths: function() {
		var me = this;
		// if widths not passed (like in standard),
		// get from doctype and redistribute to fit 100%
		if(!this.widths) {
			this.widths = $.map(this.columns, function(fieldname, ci) {
				df = wn.meta.docfield_map[me.tabletype][fieldname];
				return df && df.width || (fieldname=="Sr" ? 30 : 80);
			});

			var sum = 0;
			$.each(this.widths, function(i, w) {
				sum += cint(w);
			});

			this.widths = $.map(this.widths, function(w) {
				return (flt(w) / sum * 100).toFixed(0) + "%";
			});
		}
	},

	get_tables: function() {
		if(this.tables.length > 1) {
			return $.map(this.tables, function(t) {
				return t.get(0);
			});
		} else {
			return this.tables[0].get(0);
		}
	},

	cell_style: {
		border: '1px solid #999',
		padding: '3px',
		'vertical-align': 'top',
		'word-wrap': 'break-word',
	},

	head_cell_style: {
		border: '1px solid #999',
		padding: '3px',
		'vertical-align': 'top',
		'background-color': '#ddd',
		'font-weight': 'bold',
		'word-wrap': 'break-word',
	},

	table_style: {
		width: '100%',
		'border-collapse': 'collapse',
		'margin-bottom': '10px',
		'margin-top': '10px',
		'table-layout': 'fixed'
	},
})

function print_table(dt, dn, fieldname, tabletype, cols, head_labels, widths, condition, cssClass, modifier) {
	return new wn.print.Table({
		doctype: dt,
		docname: dn,
		fieldname: fieldname,
		tabletype: tabletype,
		columns: cols,
		head_labels: head_labels,
		widths: widths,
		condition: condition,
		cssClass: cssClass,
		modifier: modifier
	}).get_tables();
}
<commit_msg>min width 5%<commit_after>// Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
//
// MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

wn.provide("wn.print");

// opts:
// doctype (parent)
// docname
// tabletype
// fieldname
// show_all = false;

wn.print.Table = Class.extend({
	init: function(opts) {
		$.extend(this, opts);
		if(!this.columns)
			this.columns = this.get_columns();
		this.data = this.get_data();
		this.remove_empty_cols();
		this.set_widths();
		this.make();
	},
	get_columns: function() {
		return ['Sr'].concat($.map(wn.meta.docfield_list[this.tabletype], function(df) {
			return cint(df.print_hide) ? null : df.fieldname;
		}));
	},
	get_data: function() {
		var children = wn.model.get(this.tabletype, {
			parent:this.docname, parenttype:this.doctype, parentfield: this.fieldname})

		var data = []
		for(var i=0; i<children.length; i++) {
			data.push(copy_dict(children[i]));
		}
		return data;
	},

	remove_empty_cols: function() {
		var cols_with_value = [];
		var widths = [];
		var head_labels = [];
		var me = this;

		$.each(this.data, function(i, row) {
			$.each(me.columns, function(ci, fieldname) {
				var value = row[fieldname];
				if((value!==null && value!=="") || ci==0) {
					if(!in_list(cols_with_value, fieldname)) {
						cols_with_value.push(fieldname);

						// also prepare a new list of widths and head labels
						me.widths && widths.push(me.widths[ci]);
						me.head_labels && head_labels.push(me.head_labels[ci]);
					}
				}
			});
		});

		this.columns = cols_with_value;
		if(this.widths) this.widths = widths;
		if(this.head_labels) this.head_labels = head_labels;
	},

	make: function() {
		var me = this;
		this.tables = [];
		var table_data = [];
		$.each(this.data, function(i, d) {
			table_data.push(d);
			if(d.page_break) {
				me.add_table(table_data);
				table_data = [];
			}
		});
		if(table_data)
			me.add_table(table_data);
	},

	add_table: function(data) {
		var me = this;
		var wrapper = $("<div>")
		var table = $("<table>").css(this.table_style).appendTo(wrapper);

		var headrow = $("<tr>").appendTo(table);
		$.each(me.columns, function(ci, fieldname) {
			var df = wn.meta.docfield_map[me.tabletype][fieldname];
			if(me.head_labels) {
				var label = me.head_labels[ci];
			} else {
				var label = df ? df.label : fieldname;
			}
			var td = $("<td>").html(label)
				.css(me.head_cell_style)
				.css({"width": me.widths[ci]})
				.appendTo(headrow)

			if(df && in_list(['Float', 'Currency'], df.fieldtype)) {
				td.css({"text-align": "right"});
			}
		});

		$.each(data, function(ri, row) {
			var allow = true;
			if(me.condition) {
				allow = me.condition(row);
			}
			if(allow) {
				var tr = $("<tr>").appendTo(table);

				$.each(me.columns, function(ci, fieldname) {
					if(ci==0)
						var value = row.idx;
					else
						var value = row[fieldname];

					var df = wn.meta.docfield_map[me.tabletype][fieldname];
					value = wn.form.get_formatter(
						df && df.fieldtype || "Data")(value);

					if(me.modifier && me.modifier[fieldname])
						value = me.modifier[fieldname](row);

					var td = $("<td>").html(value)
						.css(me.cell_style)
						.css({width: me.widths[ci]})
						.appendTo(tr);
				});
			}
		});
		this.tables.push(wrapper)
	},

	set_widths: function() {
		var me = this;
		// if widths not passed (like in standard),
		// get from doctype and redistribute to fit 100%
		if(!this.widths) {
			this.widths = $.map(this.columns, function(fieldname, ci) {
				df = wn.meta.docfield_map[me.tabletype][fieldname];
				return df && df.width || (fieldname=="Sr" ? 30 : 80);
			});

			var sum = 0;
			$.each(this.widths, function(i, w) {
				sum += cint(w);
			});

			this.widths = $.map(this.widths, function(w) {
				w = (flt(w) / sum * 100).toFixed(0);
				return (w < 5 ? 5 : w) + "%";
			});
		}
	},

	get_tables: function() {
		if(this.tables.length > 1) {
			return $.map(this.tables, function(t) {
				return t.get(0);
			});
		} else {
			return this.tables[0].get(0);
		}
	},

	cell_style: {
		border: '1px solid #999',
		padding: '3px',
		'vertical-align': 'top',
		'word-wrap': 'break-word',
	},

	head_cell_style: {
		border: '1px solid #999',
		padding: '3px',
		'vertical-align': 'top',
		'background-color': '#ddd',
		'font-weight': 'bold',
		'word-wrap': 'break-word',
	},

	table_style: {
		width: '100%',
		'border-collapse': 'collapse',
		'margin-bottom': '10px',
		'margin-top': '10px',
		'table-layout': 'fixed'
	},
})

function print_table(dt, dn, fieldname, tabletype, cols, head_labels, widths, condition, cssClass, modifier) {
	return new wn.print.Table({
		doctype: dt,
		docname: dn,
		fieldname: fieldname,
		tabletype: tabletype,
		columns: cols,
		head_labels: head_labels,
		widths: widths,
		condition: condition,
		cssClass: cssClass,
		modifier: modifier
	}).get_tables();
}
<|endoftext|>


<commit_before>'use strict';

var $ = require('jquery'),
    toggles = require('../libs/jquery-toggles/toggles.min');

function MasterSwitchComponent(html) {

    this.$html = html;

};

MasterSwitchComponent.prototype.init = function () {

    var component = this;

    component.$container = this.$html.find('.masterswitch');

    component.$container.each(function() {

        var $this = $(this)

        this.$control = $this.find('.masterswitch-control input');
        this.$content = $this.find('.masterswitch-content');
        this.$toggle = $this.find('.toggle');

        this.$toggle.toggles({
            checkbox: this.$control,
            on: this.$control.prop('checked'),
            text: {
                on: '',
                off: ''
            }
        });

        if (!this.$control.prop('checked')) {
            component.disableElements(component.$container);
        } else {
            component
                .switchOn(this.$control)
                .enableElements(component.$container);
        }

        this.$toggle.on('toggle', function (e, active) {
            if (active) {
                component.switchOn(e.target);
            } else {
                component.switchOff(e.target);
            }
        });
    });

};

MasterSwitchComponent.prototype.switchOn = function (target) {

    var component = this;

    component.$target = $(target).closest('.masterswitch').find('.masterswitch-content');

    component.$target.removeClass('is-disabled');
    component.enableElements(component.$target);
};

MasterSwitchComponent.prototype.switchOff = function (target) {

    var component = this;

    component.$target = $(target).closest('.masterswitch').find('.masterswitch-content');

    component.$target.addClass('is-disabled');
    component.disableElements(component.$target);

};

MasterSwitchComponent.prototype.disableElements = function (target) {

    var component = this,
        CLICKABLES_SELECTOR = 'a, button, input, select';

    $(target)
        .on('click', CLICKABLES_SELECTOR, preventDefault)
        .find(CLICKABLES_SELECTOR)
            .addClass('disabled')
            .attr('disabled', 'disabled');

};

MasterSwitchComponent.prototype.enableElements = function (target) {

    var component = this,
        CLICKABLES_SELECTOR = 'a, button, input, select';

    $(target)
        .off('click', CLICKABLES_SELECTOR, preventDefault)
        .find(CLICKABLES_SELECTOR)
            .removeClass('disabled')
            .removeAttr('disabled')

};

function preventDefault(e) {
    e.preventDefault();
}

module.exports = MasterSwitchComponent;
<commit_msg>Masterswitch no lonver disables links forever<commit_after>'use strict';

var $ = require('jquery'),
    toggles = require('../libs/jquery-toggles/toggles.min');

function MasterSwitchComponent(html) {

    this.$html = html;

};

MasterSwitchComponent.prototype.init = function () {

    var component = this;

    component.$container = this.$html.find('.masterswitch');

    component.$container.each(function() {

        var $this = $(this)

        this.$control = $this.find('.masterswitch-control input');
        this.$content = $this.find('.masterswitch-content');
        this.$toggle = $this.find('.toggle');

        this.$toggle.toggles({
            checkbox: this.$control,
            on: this.$control.prop('checked'),
            text: {
                on: '',
                off: ''
            }
        });

        if (!this.$control.prop('checked')) {
            component.disableElements(this.$content);
        } else {
            component.switchOn(this.$control);
            component.enableElements(this.$content);
        }

        this.$toggle.on('toggle', function (e, active) {
            if (active) {
                component.switchOn(e.target);
            } else {
                component.switchOff(e.target);
            }
        });
    });

};

MasterSwitchComponent.prototype.switchOn = function (target) {

    var component = this;

    component.$target = $(target).closest('.masterswitch').find('.masterswitch-content');

    component.$target.removeClass('is-disabled');
    component.enableElements(component.$target);
};

MasterSwitchComponent.prototype.switchOff = function (target) {

    var component = this;

    component.$target = $(target).closest('.masterswitch').find('.masterswitch-content');

    component.$target.addClass('is-disabled');
    component.disableElements(component.$target);
};

MasterSwitchComponent.prototype.disableElements = function (target) {

    var component = this,
        CLICKABLES_SELECTOR = 'a, button, input, select';

    $(target)
        .on('click', CLICKABLES_SELECTOR, preventDefault)
        .find(CLICKABLES_SELECTOR)
            .addClass('disabled')
            .attr('disabled', 'disabled');

};

MasterSwitchComponent.prototype.enableElements = function (target) {

    var component = this,
        CLICKABLES_SELECTOR = 'a, button, input, select';

    $(target)
        .off('click', CLICKABLES_SELECTOR, preventDefault)
        .find(CLICKABLES_SELECTOR)
            .removeClass('disabled')
            .removeAttr('disabled')

};

function preventDefault(e) {
    e.preventDefault();
}

module.exports = MasterSwitchComponent;
<|endoftext|>