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

import React, { PropTypes } from 'react';
import {
  Animated,
  Dimensions,
  Easing,
  Image,
  NativeModules,
  PixelRatio,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  TouchableHighlight,
  TouchableOpacity,
  View,
} from 'react-native';

let {
  ExponentKernel,
} = NativeModules;

import Expo from 'expo';
import { Ionicons } from '@expo/vector-icons';
import ResponsiveImage from '@expo/react-native-responsive-image';

import Browser from 'Browser';
import BrowserActions from 'BrowserActions';
import DevIndicator from '../Home/components/DevIndicator';
import ExStore from 'ExStore';
import FriendlyUrls from 'FriendlyUrls';

let SCREEN_WIDTH = Dimensions.get('window').width;
let MENU_NARROW_SCREEN = SCREEN_WIDTH < 375;

const AnimatedBlurView = Animated.createAnimatedComponent(Expo.BlurView);

export default class MenuView extends React.Component {
  static propTypes = {
    task: PropTypes.object.isRequired,
    shouldFadeIn: PropTypes.bool,
    isNuxFinished: PropTypes.bool,
  };

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

    this.state = {
      transitionIn: new Animated.Value(props.shouldFadeIn ? 0 : 1),
      enableDevMenuTools: false,
      devMenuItems: {},
    };
  }

  async componentDidMount() {
    this._mounted = true;
    if (this.props.shouldFadeIn) {
      Animated.timing(this.state.transitionIn, {
        easing: Easing.inOut(Easing.quad),
        toValue: 1,
        duration: 100,
      }).start();
    }
    this.forceStatusBarUpdateAsync();
    let enableDevMenuTools = await ExponentKernel.doesCurrentTaskEnableDevtools();
    let devMenuItems = await ExponentKernel.getDevMenuItemsToShow();
    if (this._mounted) {
      this.setState({ enableDevMenuTools, devMenuItems });
    }
  }

  componentWillUnmount() {
    this.restoreStatusBar();
    this._mounted = false;
  }

  forceStatusBarUpdateAsync = async () => {
    if (NativeModules.StatusBarManager._captureProperties) {
      this._statusBarValuesToRestore = await NativeModules.StatusBarManager._captureProperties();
      // HACK: StatusBar only updates changed props.
      // because MenuView typically lives under a different RN bridge, its stack of StatusBar
      // props does not necessarily reflect what the user is seeing.
      // so we force StatusBar to clear its state and update all props when we mount.
      StatusBar._currentValues = null;
    }
  };
  restoreStatusBar = () => {
    if (
      NativeModules.StatusBarManager._applyPropertiesAndForget &&
      this._statusBarValuesToRestore
    ) {
      NativeModules.StatusBarManager._applyPropertiesAndForget(
        this._statusBarValuesToRestore
      );
    }
  };
  render() {
    let scale = this.state.transitionIn.interpolate({
      inputRange: [0, 1],
      outputRange: [1.05, 1],
    });
    let intensity = this.state.transitionIn.interpolate({
      inputRange: [0, 1],
      outputRange: [0, 85],
    });

    return (
      <AnimatedBlurView
        style={styles.container}
        tint="light"
        intensity={intensity}>
        <StatusBar barStyle="default" />
        <Animated.View
          style={[
            styles.overlay,
            { opacity: this.state.transitionIn, transform: [{ scale }] },
          ]}>
          <ScrollView>
            {this.props.isNuxFinished
              ? this._renderTaskInfoRow()
              : this._renderNUXRow()}
            <View style={styles.separator} />
            <View style={styles.buttonContainer}>
              {this._renderButton(
                'refresh',
                'Refresh',
                Browser.refresh,
                require('../Assets/ios-menu-refresh.png')
              )}
              {this._renderButton(
                'home',
                'Go to Expo Home',
                this._goToHome,
                require('../Assets/ios-menu-home.png')
              )}
            </View>
            {this._maybeRenderDevMenuTools()}
            <TouchableHighlight
              style={styles.closeButton}
              onPress={this._onPressClose}
              underlayColor="#eee"
              hitSlop={{ top: 4, bottom: 8, left: 8, right: 4 }}>
              <Ionicons
                name="md-close"
                size={20}
                style={styles.closeButtonIcon}
              />
            </TouchableHighlight>
          </ScrollView>
        </Animated.View>
      </AnimatedBlurView>
    );
  }

  _renderNUXRow() {
    let tooltipMessage;
    if (Expo.Constants.isDevice) {
      tooltipMessage = 'Shake your device to show this menu.';
    } else {
      tooltipMessage = 'In iPhone Simulator, press Cmd+D to show this menu.';
    }
    let headingStyles = MENU_NARROW_SCREEN
      ? [styles.nuxHeading, styles.nuxHeadingNarrow]
      : styles.nuxHeading;
    return (
      <View style={styles.nuxRow}>
        <View style={styles.nuxHeadingRow}>
          <ResponsiveImage
            sources={{
              2: {
                uri: 'https://s3.amazonaws.com/exp-us-standard/exponent-icon@2x.png',
              },
              3: {
                uri: 'https://s3.amazonaws.com/exp-us-standard/exponent-icon@3x.png',
              },
            }}
            style={styles.nuxLogo}
          />
          <Text style={headingStyles}>
            Welcome to Expo!
          </Text>
        </View>
        <Text style={styles.nuxTooltip}>
          {tooltipMessage}
        </Text>
        <TouchableOpacity
          style={styles.nuxButton}
          onPress={this._onPressFinishNux}>
          <Text style={styles.nuxButtonLabel}>
            Got it
          </Text>
        </TouchableOpacity>
      </View>
    );
  }

  _renderTaskInfoRow() {
    let { task } = this.props;
    let taskUrl;
    if (task.loadingError) {
      taskUrl = task.loadingError.originalUrl;
    } else {
      taskUrl = task.manifestUrl
        ? FriendlyUrls.toFriendlyString(task.manifestUrl)
        : '';
    }
    let iconUrl = task.manifest && task.manifest.get('iconUrl');
    let taskName = task.manifest && task.manifest.get('name');

    let icon = iconUrl
      ? <Image source={{ uri: iconUrl }} style={styles.taskIcon} />
      : <ResponsiveImage
          resizeMode="contain"
          sources={{
            2: {
              uri: 'https://s3.amazonaws.com/exp-us-standard/exponent-icon@2x.png',
            },
            3: {
              uri: 'https://s3.amazonaws.com/exp-us-standard/exponent-icon@3x.png',
            },
          }}
          style={styles.taskIcon}
        />;
    let taskNameStyles = taskName
      ? styles.taskName
      : [styles.taskName, { color: '#c5c6c7' }];
    return (
      <View style={styles.taskMetaRow}>
        <View style={styles.taskIconColumn}>
          {icon}
        </View>
        <View style={styles.taskInfoColumn}>
          <Text style={taskNameStyles}>
            {taskName ? taskName : 'Untitled Experience'}
          </Text>
          <Text style={[styles.taskUrl]}>{taskUrl}</Text>
          {this._maybeRenderDevServerName()}
        </View>
      </View>
    );
  }

  _maybeRenderDevServerName() {
    let { task } = this.props;
    let devServerName = task.manifest.getIn(['developer', 'tool']);
    if (devServerName) {
      // XDE is upper
      if (devServerName === 'xde') {
        devServerName = devServerName.toUpperCase();
      }
      return (
        <View style={{ flexDirection: 'row' }}>
          <DevIndicator style={{ marginTop: 4.5, marginRight: 7 }} />
          <Text style={styles.taskDevServerName}>{devServerName}</Text>
        </View>
      );
    }
    return null;
  }

  _maybeRenderDevMenuTools() {
    if (this.state.enableDevMenuTools && this.state.devMenuItems) {
      return (
        <View>
          <View style={styles.separator} />
          <View style={styles.buttonContainer}>
            {Object.keys(this.state.devMenuItems).map((key, idx) => {
              return this._renderDevMenuItem(key, this.state.devMenuItems[key]);
            })}
          </View>
        </View>
      );
    }
    return null;
  }

  _renderDevMenuItem(key, item) {
    let { label, isEnabled } = item;
    if (isEnabled) {
      return this._renderButton(
        key,
        label,
        () => {
          this._onPressDevMenuButton(key);
        },
        null,
        true
      );
    } else {
      return (
        <View style={[styles.button, styles.buttonWithSeparator]} key={key}>
          <View style={styles.buttonIcon} />
          <Text style={[styles.buttonText, { color: '#9ca0a6' }]}>
            {label}
          </Text>
        </View>
      );
    }
  }

  _renderButton(key, text, onPress, iconSource, withSeparator) {
    let icon, buttonStyles;
    if (iconSource) {
      icon = <Image style={styles.buttonIcon} source={iconSource} />;
    } else {
      icon = <View style={styles.buttonIcon} />;
    }
    if (withSeparator) {
      buttonStyles = [styles.button, styles.buttonWithSeparator];
    } else {
      buttonStyles = styles.button;
    }
    return (
      <TouchableOpacity key={key} style={buttonStyles} onPress={onPress}>
        {icon}
        <Text style={styles.buttonText}>
          {text}
        </Text>
      </TouchableOpacity>
    );
  }

  _onPressFinishNux = () => {
    ExStore.dispatch(BrowserActions.setIsNuxFinishedAsync(true));
    ExStore.dispatch(BrowserActions.showMenuAsync(false));
  };
  _onPressClose = () => {
    if (this.props.isNuxFinished) {
      ExStore.dispatch(BrowserActions.showMenuAsync(false));
    }
  };
  _goToHome = () => {
    ExStore.dispatch(BrowserActions.foregroundHomeAsync());
  };
  _onPressDevMenuButton = key => {
    ExponentKernel.selectDevMenuItemWithKey(key);
    ExStore.dispatch(BrowserActions.showMenuAsync(false));
  };
}

let styles = StyleSheet.create({
  container: {
    position: 'absolute',
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
    justifyContent: 'center',
    flexDirection: 'row',
  },
  overlay: {
    flex: 1,
    backgroundColor: 'transparent',
    marginTop: 32,
  },
  closeButtonIcon: {
    color: '#49a7e8',
  },
  closeButton: {
    position: 'absolute',
    right: 10,
    paddingVertical: 2,
    paddingHorizontal: 6,
    borderRadius: 2,
  },
  taskMetaRow: {
    flexDirection: 'row',
    paddingHorizontal: 24,
    paddingBottom: 16,
  },
  taskInfoColumn: {
    flex: 4,
  },
  taskIconColumn: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  taskName: {
    color: '#595c68',
    backgroundColor: 'transparent',
    fontWeight: '700',
    fontSize: 16,
    marginTop: 16,
    marginBottom: 2,
  },
  taskUrl: {
    color: '#9ca0a6',
    backgroundColor: 'transparent',
    marginRight: 16,
    marginVertical: 4,
    fontSize: 12,
  },
  taskIcon: {
    width: 52,
    height: 52,
    marginTop: 12,
    marginRight: 16,
    alignSelf: 'center',
    backgroundColor: 'transparent',
  },
  taskDevServerName: {
    fontSize: 12,
    color: '#9ca0a6',
    fontWeight: '700',
  },
  separator: {
    borderColor: '#d5d6d7',
    borderTopWidth: 1 / PixelRatio.get(),
    backgroundColor: '#f0f0f1',
    height: 12,
    marginVertical: 4,
    marginHorizontal: -1,
  },
  buttonContainer: {
    backgroundColor: 'transparent',
  },
  button: {
    backgroundColor: 'transparent',
    flexDirection: 'row',
  },
  buttonWithSeparator: {
    borderBottomWidth: 1 / PixelRatio.get(),
    borderBottomColor: '#dadada',
  },
  buttonIcon: {
    width: 16,
    height: 16,
    marginVertical: 12,
    marginLeft: 20,
    alignSelf: 'flex-start',
  },
  buttonText: {
    color: '#595c68',
    fontSize: 14,
    textAlign: 'left',
    marginVertical: 12,
    paddingHorizontal: 12,
    fontWeight: '700',
  },
  nuxRow: {
    paddingHorizontal: 12,
  },
  nuxHeadingRow: {
    flexDirection: 'row',
    marginTop: 16,
    marginRight: 16,
    marginBottom: 8,
  },
  nuxLogo: {
    width: 47 * 0.7,
    height: 40 * 0.7,
    marginRight: 12,
    marginLeft: 8,
    alignSelf: 'flex-start',
  },
  nuxHeading: {
    flex: 1,
    color: '#595c68',
    fontWeight: '700',
    fontSize: 22,
  },
  nuxHeadingNarrow: {
    fontSize: 18,
    marginTop: 2,
  },
  nuxTooltip: {
    color: '#595c68',
    marginRight: 16,
    marginVertical: 4,
    fontSize: 16,
  },
  nuxButton: {
    alignItems: 'center',
    marginVertical: 12,
    paddingVertical: 10,
    backgroundColor: '#056ecf',
    borderRadius: 3,
  },
  nuxButtonLabel: {
    color: '#ffffff',
    fontSize: 16,
  },
});
<commit_msg>Small tweaks to menu style<commit_after>/**
 * Copyright 2015-present 650 Industries. All rights reserved.
 *
 * @providesModule MenuView
 */
'use strict';

import React, { PropTypes } from 'react';
import {
  Animated,
  Dimensions,
  Easing,
  Image,
  NativeModules,
  PixelRatio,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  TouchableHighlight,
  TouchableOpacity,
  View,
} from 'react-native';

let {
  ExponentKernel,
} = NativeModules;

import Expo, { Constants } from 'expo';
import { Ionicons } from '@expo/vector-icons';
import ResponsiveImage from '@expo/react-native-responsive-image';

import Browser from 'Browser';
import BrowserActions from 'BrowserActions';
import DevIndicator from '../Home/components/DevIndicator';
import ExStore from 'ExStore';
import FriendlyUrls from 'FriendlyUrls';

let SCREEN_WIDTH = Dimensions.get('window').width;
let MENU_NARROW_SCREEN = SCREEN_WIDTH < 375;

const AnimatedBlurView = Animated.createAnimatedComponent(Expo.BlurView);

export default class MenuView extends React.Component {
  static propTypes = {
    task: PropTypes.object.isRequired,
    shouldFadeIn: PropTypes.bool,
    isNuxFinished: PropTypes.bool,
  };

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

    this.state = {
      transitionIn: new Animated.Value(props.shouldFadeIn ? 0 : 1),
      enableDevMenuTools: false,
      devMenuItems: {},
    };
  }

  async componentDidMount() {
    this._mounted = true;
    if (this.props.shouldFadeIn) {
      requestAnimationFrame(() => {
        this._mounted && Animated.timing(this.state.transitionIn, {
          easing: Easing.inOut(Easing.quad),
          toValue: 1,
          duration: 100,
        }).start();
      });
    }
    this.forceStatusBarUpdateAsync();
    let enableDevMenuTools = await ExponentKernel.doesCurrentTaskEnableDevtools();
    let devMenuItems = await ExponentKernel.getDevMenuItemsToShow();
    if (this._mounted) {
      this.setState({ enableDevMenuTools, devMenuItems });
    }
  }

  componentWillUnmount() {
    this.restoreStatusBar();
    this._mounted = false;
  }

  forceStatusBarUpdateAsync = async () => {
    if (NativeModules.StatusBarManager._captureProperties) {
      this._statusBarValuesToRestore = await NativeModules.StatusBarManager._captureProperties();
      // HACK: StatusBar only updates changed props.
      // because MenuView typically lives under a different RN bridge, its stack of StatusBar
      // props does not necessarily reflect what the user is seeing.
      // so we force StatusBar to clear its state and update all props when we mount.
      StatusBar._currentValues = null;
    }
  };
  restoreStatusBar = () => {
    if (
      NativeModules.StatusBarManager._applyPropertiesAndForget &&
      this._statusBarValuesToRestore
    ) {
      NativeModules.StatusBarManager._applyPropertiesAndForget(
        this._statusBarValuesToRestore
      );
    }
  };
  render() {
    let scale = this.state.transitionIn.interpolate({
      inputRange: [0, 1],
      outputRange: [1.05, 1],
    });
    let intensity = this.state.transitionIn.interpolate({
      inputRange: [0, 1],
      outputRange: [0, 85],
    });

    return (
      <AnimatedBlurView
        style={styles.container}
        tint="light"
        intensity={intensity}>
        <StatusBar barStyle="default" />
        <Animated.View
          style={[
            styles.overlay,
            { opacity: this.state.transitionIn, transform: [{ scale }] },
          ]}>
          <ScrollView>
            {this.props.isNuxFinished
              ? this._renderTaskInfoRow()
              : this._renderNUXRow()}
            <View style={styles.separator} />
            <View style={styles.buttonContainer}>
              {this._renderButton(
                'refresh',
                'Refresh',
                Browser.refresh,
                require('../Assets/ios-menu-refresh.png')
              )}
              {this._renderButton(
                'home',
                'Go to Expo Home',
                this._goToHome,
                require('../Assets/ios-menu-home.png')
              )}
            </View>
            {this._maybeRenderDevMenuTools()}
            <TouchableHighlight
              style={styles.closeButton}
              onPress={this._onPressClose}
              underlayColor="#eee"
              hitSlop={{ top: 4, bottom: 8, left: 8, right: 4 }}>
              <Ionicons
                name="md-close"
                size={20}
                style={styles.closeButtonIcon}
              />
            </TouchableHighlight>
          </ScrollView>
        </Animated.View>
      </AnimatedBlurView>
    );
  }

  _renderNUXRow() {
    let tooltipMessage;
    if (Expo.Constants.isDevice) {
      tooltipMessage = 'Shake your device to show this menu.';
    } else {
      tooltipMessage = 'In iPhone Simulator, press Cmd+D to show this menu.';
    }
    let headingStyles = MENU_NARROW_SCREEN
      ? [styles.nuxHeading, styles.nuxHeadingNarrow]
      : styles.nuxHeading;
    return (
      <View style={styles.nuxRow}>
        <View style={styles.nuxHeadingRow}>
          <ResponsiveImage
            sources={{
              2: {
                uri: 'https://s3.amazonaws.com/exp-us-standard/exponent-icon@2x.png',
              },
              3: {
                uri: 'https://s3.amazonaws.com/exp-us-standard/exponent-icon@3x.png',
              },
            }}
            style={styles.nuxLogo}
          />
          <Text style={headingStyles}>
            Welcome to Expo!
          </Text>
        </View>
        <Text style={styles.nuxTooltip}>
          {tooltipMessage}
        </Text>
        <TouchableOpacity
          style={styles.nuxButton}
          onPress={this._onPressFinishNux}>
          <Text style={styles.nuxButtonLabel}>
            Got it
          </Text>
        </TouchableOpacity>
      </View>
    );
  }

  _renderTaskInfoRow() {
    let { task } = this.props;
    let taskUrl;
    if (task.loadingError) {
      taskUrl = task.loadingError.originalUrl;
    } else {
      taskUrl = task.manifestUrl
        ? FriendlyUrls.toFriendlyString(task.manifestUrl)
        : '';
    }
    let iconUrl = task.manifest && task.manifest.get('iconUrl');
    let taskName = task.manifest && task.manifest.get('name');

    let icon = iconUrl
      ? <Image source={{ uri: iconUrl }} style={styles.taskIcon} />
      : <ResponsiveImage
          resizeMode="contain"
          sources={{
            2: {
              uri: 'https://s3.amazonaws.com/exp-us-standard/exponent-icon@2x.png',
            },
            3: {
              uri: 'https://s3.amazonaws.com/exp-us-standard/exponent-icon@3x.png',
            },
          }}
          style={styles.taskIcon}
        />;
    let taskNameStyles = taskName
      ? styles.taskName
      : [styles.taskName, { color: '#c5c6c7' }];
    return (
      <View style={styles.taskMetaRow}>
        <View style={styles.taskIconColumn}>
          {icon}
        </View>
        <View style={styles.taskInfoColumn}>
          <Text style={taskNameStyles} numberOfLines={1}>
            {taskName ? taskName : 'Untitled Experience'}
          </Text>
          <Text style={[styles.taskUrl]} numberOfLines={1}>
            {taskUrl}
          </Text>
          {this._maybeRenderDevServerName()}
        </View>
      </View>
    );
  }

  _maybeRenderDevServerName() {
    let { task } = this.props;
    let devServerName = task.manifest.getIn(['developer', 'tool']);
    if (devServerName) {
      // XDE is upper
      if (devServerName === 'xde') {
        devServerName = devServerName.toUpperCase();
      }
      return (
        <View style={{ flexDirection: 'row' }}>
          <DevIndicator style={{ marginTop: 4.5, marginRight: 7 }} />
          <Text style={styles.taskDevServerName}>{devServerName}</Text>
        </View>
      );
    }
    return null;
  }

  _maybeRenderDevMenuTools() {
    if (this.state.enableDevMenuTools && this.state.devMenuItems) {
      return (
        <View>
          <View style={styles.separator} />
          <View style={styles.buttonContainer}>
            {Object.keys(this.state.devMenuItems).map((key, idx) => {
              return this._renderDevMenuItem(key, this.state.devMenuItems[key]);
            })}
          </View>
        </View>
      );
    }
    return null;
  }

  _renderDevMenuItem(key, item) {
    let { label, isEnabled } = item;
    if (isEnabled) {
      return this._renderButton(
        key,
        label,
        () => {
          this._onPressDevMenuButton(key);
        },
        null,
        true
      );
    } else {
      return (
        <View style={[styles.button, styles.buttonWithSeparator]} key={key}>
          <View style={styles.buttonIcon} />
          <Text style={[styles.buttonText, { color: '#9ca0a6' }]}>
            {label}
          </Text>
        </View>
      );
    }
  }

  _renderButton(key, text, onPress, iconSource, withSeparator) {
    let icon, buttonStyles;
    if (iconSource) {
      icon = <Image style={styles.buttonIcon} source={iconSource} />;
    } else {
      icon = <View style={styles.buttonIcon} />;
    }
    if (withSeparator) {
      buttonStyles = [styles.button, styles.buttonWithSeparator];
    } else {
      buttonStyles = styles.button;
    }
    return (
      <TouchableOpacity key={key} style={buttonStyles} onPress={onPress}>
        {icon}
        <Text style={styles.buttonText}>
          {text}
        </Text>
      </TouchableOpacity>
    );
  }

  _onPressFinishNux = () => {
    ExStore.dispatch(BrowserActions.setIsNuxFinishedAsync(true));
    ExStore.dispatch(BrowserActions.showMenuAsync(false));
  };
  _onPressClose = () => {
    if (this.props.isNuxFinished) {
      ExStore.dispatch(BrowserActions.showMenuAsync(false));
    }
  };
  _goToHome = () => {
    ExStore.dispatch(BrowserActions.foregroundHomeAsync());
  };
  _onPressDevMenuButton = key => {
    ExponentKernel.selectDevMenuItemWithKey(key);
    ExStore.dispatch(BrowserActions.showMenuAsync(false));
  };
}

let styles = StyleSheet.create({
  container: {
    position: 'absolute',
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
    justifyContent: 'center',
    flexDirection: 'row',
  },
  overlay: {
    flex: 1,
    backgroundColor: 'transparent',
    marginTop: Constants.statusBarHeight,
  },
  closeButtonIcon: {
    color: '#49a7e8',
  },
  closeButton: {
    position: 'absolute',
    right: 10,
    top: 10,
    paddingVertical: 2,
    paddingHorizontal: 6,
    borderRadius: 2,
  },
  taskMetaRow: {
    flexDirection: 'row',
    paddingHorizontal: 14,
    paddingBottom: 12,
  },
  taskInfoColumn: {
    flex: 4,
  },
  taskIconColumn: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  taskName: {
    color: '#595c68',
    backgroundColor: 'transparent',
    fontWeight: '700',
    fontSize: 16,
    marginTop: 16,
    marginBottom: 2,
  },
  taskUrl: {
    color: '#9ca0a6',
    backgroundColor: 'transparent',
    marginRight: 16,
    marginBottom: 2,
    marginTop: 1,
    fontSize: 12,
  },
  taskIcon: {
    width: 52,
    height: 52,
    marginTop: 12,
    marginRight: 10,
    alignSelf: 'center',
    backgroundColor: 'transparent',
  },
  taskDevServerName: {
    fontSize: 12,
    color: '#9ca0a6',
    fontWeight: '700',
  },
  separator: {
    borderColor: '#d5d6d7',
    borderTopWidth: 1 / PixelRatio.get(),
    backgroundColor: '#f0f0f1',
    height: 12,
    marginVertical: 4,
    marginHorizontal: -1,
  },
  buttonContainer: {
    backgroundColor: 'transparent',
  },
  button: {
    backgroundColor: 'transparent',
    flexDirection: 'row',
  },
  buttonWithSeparator: {
    borderBottomWidth: StyleSheet.hairlineWidth * 2,
    borderBottomColor: '#f4f4f5',
  },
  buttonIcon: {
    width: 16,
    height: 16,
    marginVertical: 12,
    marginLeft: 20,
    alignSelf: 'flex-start',
  },
  buttonText: {
    color: '#595c68',
    fontSize: 14,
    textAlign: 'left',
    marginVertical: 12,
    marginRight: 5,
    paddingHorizontal: 12,
    fontWeight: '700',
  },
  nuxRow: {
    paddingHorizontal: 12,
  },
  nuxHeadingRow: {
    flexDirection: 'row',
    marginTop: 16,
    marginRight: 16,
    marginBottom: 8,
  },
  nuxLogo: {
    width: 47 * 0.7,
    height: 40 * 0.7,
    marginRight: 12,
    marginLeft: 8,
    alignSelf: 'flex-start',
  },
  nuxHeading: {
    flex: 1,
    color: '#595c68',
    fontWeight: '700',
    fontSize: 22,
  },
  nuxHeadingNarrow: {
    fontSize: 18,
    marginTop: 2,
  },
  nuxTooltip: {
    color: '#595c68',
    marginRight: 16,
    marginVertical: 4,
    fontSize: 16,
  },
  nuxButton: {
    alignItems: 'center',
    marginVertical: 12,
    paddingVertical: 10,
    backgroundColor: '#056ecf',
    borderRadius: 3,
  },
  nuxButtonLabel: {
    color: '#ffffff',
    fontSize: 16,
  },
});
<|endoftext|>


<commit_before>'use strict';

var Async = require('async');
var Browserify = require('browserify');
var Chokidar = require('chokidar');
var Express = require('express');
var Fs = require('fs');
var Livereload = require('livereload');
var assign = require('lodash.assign');
var clone = require('lodash.clone');
var debounce = require('lodash.debounce');
var last = require('lodash.last');
var Mkdirp = require('mkdirp');
var Ncp = require('ncp').ncp;
var Path = require('path');
var Rimraf = require('rimraf');
var Watchify = require('watchify');

var buildModule = require('./../build-pipeline/build-module');
var Helpers = require('./../build-pipeline/helpers/helpers');

var BLANK = '';
var DOUBLE_DOT = '..';
// Avoid these for cross-OS compatibility: . , [ ] { } ( ) ! : ; " ' * ? < > | ¢ ™ $ ® \ /
var SAFE_NAMESPACE_DELIMITER = '~';
var PROJECT_DIR = Path.join(__dirname, '..', '..');
var CORE_COMPONENTS_FOLDER = Path.join(__dirname, '..', '..', 'lib', 'core-components', 'famous');

function LocalAssistant(options) {
    this.setOptions(options);
    this.cachedFrameworkBundle = null;
    this.cachedFrameworkBundleTimeStamp = Date.now();
    this.cacheTTL = 1000; // milliseconds
}

LocalAssistant.DEFAULTS = {
    // These get passed into the build pipeline
    sourceFolder: null,
    destinationFolder: null,
    componentDelimiter: ':',
    entrypointExtnames: { '.js': true },

    // These are only used by the project assistant itself
    chokidarIgnored: /[\/\\]\./,
    fileOptions: { encoding: 'utf8' },
    folderBlacklist: { 'node_modules': true, '.git': true },
    livereloadOptions: {
        //port: 35729,
        port: 35729,
        exts: ['html','css','js','png','gif','jpg','coffee','less','json'],
        applyJSLive: false,
        applyCSSLive: false,
        exclusions: [/\\node_modules\//,/\\.git\//,/\\.svn\//,/\\.hg\//],
        interval: 5000
    },
    moduleNameRegexp: /FamousFramework.(scene|module|register|component)\(\'(\S+)\'/gi
};

LocalAssistant.prototype.setOptions = function(options) {
    this.options = assign(clone(LocalAssistant.DEFAULTS), clone(options || {}));
};

LocalAssistant.prototype.buildFramework = function(cb) {
    var inputFile = Path.join(PROJECT_DIR, 'lib', 'index.js');

    var browserify = Browserify(inputFile, {
        standalone: 'FamousFramework'
    });

    if (this.cachedFrameworkBundle && (this.cachedFrameworkBundleTimeStamp + this.cacheTTL > Date.now())) {
        return cb(null, this.cachedFrameworkBundle);
    }
    else {
        browserify.bundle(function(browserifyErr, buf) {
            if (browserifyErr) {
                return cb(browserifyErr);
            }

            this.cachedFrameworkBundle = buf.toString();
            this.cachedFrameworkBundleTimeStamp = Date.now();

            return cb(null, this.cachedFrameworkBundle);
        }.bind(this));
    }
};

LocalAssistant.prototype.buildModule = function(moduleName, files, finish) {
    var destinationFolder = Path.join(this.options.destinationFolder, moduleName.split(this.options.componentDelimiter).join(SAFE_NAMESPACE_DELIMITER));

    return buildModule(moduleName, files, {
        localSourceFolder: this.options.sourceFolder,
        componentDelimiter: this.options.componentDelimiter,
        entrypointExtnames: this.options.entrypointExtnames
    }, function(buildModuleErr, nameOut, filesOut, dataOut) {
        if (buildModuleErr) {
            return finish(buildModuleErr);
        }

        this.buildFramework(function(buildFrameworkErr, frameworkString) {
            if (buildFrameworkErr) {
                return finish(buildFrameworkErr);
            }

            dataOut.flatProjectFiles.push({
                path: 'famous-framework.bundle.js',
                content: frameworkString
            });

            Async.map(dataOut.flatProjectFiles, function(file, cb) {
                var filePathFull = Path.join(destinationFolder, file.path);
                var filePathBase = Path.dirname(filePathFull);

                Mkdirp(filePathBase, function(mkdirErr) {
                    if (mkdirErr) {
                        return cb(mkdirErr);
                    }

                    var fileData;
                    if (Helpers.doesFileLookLikeBinary(file) && typeof file.content === 'string') {
                        // Ensure that we convert assets like images to binary
                        // buffers instead of mangling them by writing the plain strings
                        fileData = new Buffer(file.content, 'binary');
                    }
                    else {
                        fileData = file.content;
                    }

                    Fs.writeFile(filePathFull, fileData, { flags: 'w' }, function(writeFileErr) {
                        if (writeFileErr) {
                            return cb(writeFileErr);
                        }

                        return cb(null);
                    });
                });
            }, function(writeAllErr) {
                if (writeAllErr) {
                    return finish(writeAllErr);
                }

                return finish(null, nameOut, filesOut, dataOut);
            });
        });
    }.bind(this));
};

LocalAssistant.prototype.buildAll = function(baseDir, subDir, cb) {
    this.cachedFrameworkBundle = null;

    this.buildRecursive(baseDir, subDir, function(err, result) {
        this.cachedFrameworkBundle = null;

        if (err) {
            return cb(err);
        }

        return cb(null, result);
    });
};

LocalAssistant.prototype.buildSingle = function(baseDir, subDir, cb) {
    var moduleName;
    if (subDir.length > 0) {
         moduleName = subDir.split(Path.sep).join(this.options.componentDelimiter);
    }
    else {
        // If the subdir string was empty, we should assume that the baseDir
        // is the home for the single component. This means we need to do some
        // additional work to find the module name. We'll look for an entry point
        // file, and then try to locate the module name. This is a bit brittle
        // and warrants some re-thinking. TODO
        var dirParts = baseDir.split(Path.sep);
        var lastPart = dirParts[dirParts.length - 1];
        var entrypointNameToLookFor = lastPart + '.js';
        var entrypointPath = Path.join(baseDir, entrypointNameToLookFor);
        var entrypointData = Fs.readFileSync(entrypointPath, this.options.fileOptions);
        var regexpMatches = this.options.moduleNameRegexp.exec(entrypointData);

        // We need to reset the lastindex so this regex will run properly
        // on subsequent runs.
        this.options.moduleNameRegexp.lastIndex = 0;

        var moduleNameMatch = regexpMatches[2];

        if (!moduleNameMatch || moduleNameMatch.indexOf(this.options.componentDelimiter) === -1) {
            return cb(new Error('Unable to find the name of the module in the given directory; is your syntax correct?'));
        }
        else {
            moduleName = moduleNameMatch;
        }
    }

    var files = [];
    this.pushFilesToArray(files, baseDir, subDir, BLANK);
    this.buildModule(moduleName, files, cb);
};

LocalAssistant.prototype.tuplesRecursive = function(tuples, baseDir, subDir, cb) {
    var mainPath = Path.join(baseDir, subDir);
    var entries = Fs.readdirSync(mainPath);

    entries.forEach(function(entryPath) {
        var fullEntryPath = Path.join(mainPath, entryPath);
        var entryStat = Fs.lstatSync(fullEntryPath);
        var entryExtname = Path.extname(fullEntryPath);
        var entryBasename = Path.basename(fullEntryPath, entryExtname);

        if (entryStat.isDirectory()) {
            var partialPath = Path.join(subDir, entryPath);

            if (this.isModuleDir(fullEntryPath)) {
                tuples.push([baseDir, partialPath]);
            }

            this.tuplesRecursive(tuples, baseDir, partialPath);
        }
        else {
            var subDirSegment = last(subDir.split(Path.sep));
            if (subDirSegment === entryBasename && (entryExtname in this.options.entrypointExtnames)) {
                tuples.push([baseDir, subDir]);
            }
        }
    }.bind(this));

    if (cb) {
        cb(null, tuples);
    }
};

LocalAssistant.prototype.buildRecursive = function(baseDir, subDir, finish) {
    var alreadyBuilt = [];
    this.tuplesRecursive([], baseDir, subDir, function(err, tuples) {
        if (err) {
            console.error(err);
        }
        Async.mapSeries(tuples, function(tuple, cb) {
            // Don't rebuild one that we just built during this run
            var stringified = JSON.stringify([tuple[0], tuple[1]]);
            if (alreadyBuilt.indexOf(stringified) === -1) {
                this.buildSingle(tuple[0], tuple[1], cb);
                alreadyBuilt.push(stringified);
            }
            else {
                cb();
            }
        }.bind(this), finish);
    }.bind(this));
};

LocalAssistant.prototype.isPushableDir = function(dir) {
    var isPushable = true;
    var dirParts = dir.split(Path.sep);

    for (var i = 0; i < dirParts.length; i++) {
        var dirPart = dirParts[i];

        if (dirPart in this.options.folderBlacklist) {
            isPushable = false;
        }
    }
    return isPushable;
};

LocalAssistant.prototype.pushFilesToArray = function(files, baseDir, subDir, prefix) {
    if (this.isPushableDir(subDir)) {
        var fullPath = Path.join(baseDir, subDir);
        var entries = Fs.readdirSync(fullPath);

        entries.forEach(function(entryPath) {
            var entryPartialPath = Path.join(subDir, entryPath);
            var entryFullPath = Path.join(fullPath, entryPath);
            var entryStat = Fs.lstatSync(entryFullPath);

            var finalPath = Path.join(prefix, entryPath);

            if (!entryStat.isDirectory()) {
                var entryContent = Fs.readFileSync(entryFullPath);

                files.push({ path: finalPath, content: entryContent });
            }
            else {
                this.pushFilesToArray(files, baseDir, entryPartialPath, finalPath);
            }
        }.bind(this));
    }
};

LocalAssistant.prototype.isModuleDir = function(dir) {
    var isMod = false;
    var folderBasename = Path.basename(dir);

    try {
        var entries = Fs.readdirSync(dir);
        entries.forEach(function(entryPath) {
            var entryFullPath = Path.join(dir, entryPath);
            var entryStat = Fs.lstatSync(entryFullPath);

            if (!entryStat.isDirectory()) {
                var entryExtname = Path.extname(entryFullPath);
                var entryBasename = Path.basename(entryFullPath, entryExtname);

                if (entryBasename === folderBasename) {
                   if (entryExtname in this.options.entrypointExtnames) {
                       isMod = true;
                   }
                }
            }
        }.bind(this));
    }
    catch (err) {
        console.error(err);
        return false;
    }
    return isMod;
};

LocalAssistant.prototype.getModuleDir = function(bottomDir, dir) {
    if (this.isModuleDir(dir)) {
        return dir;
    }
    else {
        if (dir === bottomDir || (dir + Path.sep) === bottomDir) {
            return false;
        }
        else {
            return this.getModuleDir(bottomDir, Path.join(dir, DOUBLE_DOT));
        }
    }
};

LocalAssistant.prototype.watchDirectory = function(baseDir, subDir, doRebuildEverythingOnChange, cb) {
    var watcher = Chokidar.watch(Path.join(baseDir, subDir), {
        ignored: this.options.chokidarIgnored,
        ignoreInitial: true
    });

    var handler = debounce(function(event, filename) {
        if (doRebuildEverythingOnChange) {
            this.buildAll(baseDir, '', cb);
        }
        else {
            this.buildSingle(baseDir, subDir, cb);
        }
    }.bind(this), 1000);

    watcher.on('all', handler);
};

LocalAssistant.prototype.watchDirectoryRecursive = function(baseDir, subDir, options, cb) {
    var pathToWatch;
    if (options.triggerEntrypointBuildOnAnyChange) {
        pathToWatch = baseDir;
    }
    else {
        pathToWatch = Path.join(baseDir, subDir);
    }

    var watcher = Chokidar.watch(pathToWatch, {
        ignored: this.options.chokidarIgnored,
        ignoreInitial: true
    });

    var handler = debounce(function(event, filename) {

        if (options.triggerEntrypointBuildOnAnyChange) {
            this.buildSingle(baseDir, subDir, cb);
        }
        else {
            var fileChangedDir = Path.dirname(filename);
            var moduleFullDir = this.getModuleDir(baseDir, fileChangedDir);
            if (moduleFullDir) {
                if (options.doRebuildEverythingOnChange) {
                    this.buildAll(baseDir, '', cb);
                }
                else {
                    var moduleRelativeDir = moduleFullDir.replace(baseDir, BLANK).replace(/^\//, '').replace(/^\\/, '');
                    this.buildSingle(baseDir, moduleRelativeDir, cb);
                }
            }
        }
    }.bind(this), 1000);

    watcher.on('all', handler);
};

LocalAssistant.prototype.localOnlyBootstrap = function(info) {
    console.log('famous framework: Bootstrapping local components...');

    this.setOptions({
        sourceFolder: info.sourceFolder,
        destinationFolder: info.destinationFolder
    });

    Rimraf(info.destinationFolder, function(rmErr) {
        var subDir = '';

        if (info.entrypointModuleName) {
            subDir = info.entrypointModuleName.split(this.options.componentDelimiter).join(Path.sep);
        }

        this.buildAll(info.sourceFolder, subDir, function(buildAllErr) {
            if (buildAllErr) {
                return console.log(buildAllErr);
            }

            if (info.watchAfterBuild !== 'no') {
                var livereloadServer = Livereload.createServer(this.options.livereloadOptions);
                var server = Express();

                var doRebuildEverythingOnChange = false;
                if (info.entrypointModuleName) {
                    doRebuildEverythingOnChange = true;
                }
                if (info.rebuildEverythingOnChange === 'yes') {
                    doRebuildEverythingOnChange = true;
                }
                else if (info.rebuildEverythingOnChange === 'no') {
                    doRebuildEverythingOnChange = false;
                }

                var options = {
                    doRebuildEverythingOnChange: doRebuildEverythingOnChange,
                    entrypointModuleName: info.entrypointModuleName,
                    triggerEntrypointBuildOnAnyChange: !!info.entrypointModuleName
                };

                this.watchDirectoryRecursive(info.sourceFolder, subDir, options, function(err, result) {
                    if (err) {
                        return console.log(err);
                    }

                    livereloadServer.refresh(Path.join(info.servedFolder, 'index.html'));
                    return console.log('famous framework: Finished!');
                });

                server.use(Express.static(info.servedFolder));
                server.listen(info.port);

                return console.log('famous framework: Finished! Serving at http://localhost:' + info.port);
            }
            else {
                return console.log('famous framework: Finished!');
            }
        }.bind(this));
    }.bind(this));
};

LocalAssistant.prototype.watchRuntime = function(info) {
    console.log('famous framework: Building the browser runtime...');

    var b = Browserify(info.inputFile, { cache: {}, packageCache: {} });
    var w = Watchify(b);

    function bundle() {
        w.bundle().pipe(Fs.createWriteStream(info.outputFile));
    }

    w.on('update', function() {
        bundle();
    });

    w.on('bytes', function(bytes) {
        console.log('famous framework: Wrote ' + bytes + ' bytes to ' + info.outputFile);
    });

    bundle();
};

LocalAssistant.prototype.copyCoreComponents = function(info) {
    Rimraf(info.destinationFolder, function(rmErr) {
        Ncp(CORE_COMPONENTS_FOLDER, info.destinationFolder, function(copyErr) {
            if (copyErr) {
                return console.error('famous framework: Couldn\'t copy core components!');
            }
        });
    });
};

module.exports = LocalAssistant;
<commit_msg>add files<commit_after>'use strict';

var Async = require('async');
var Browserify = require('browserify');
var Chokidar = require('chokidar');
var Express = require('express');
var Fs = require('fs');
var Livereload = require('livereload');
var assign = require('lodash.assign');
var clone = require('lodash.clone');
var debounce = require('lodash.debounce');
var last = require('lodash.last');
var Mkdirp = require('mkdirp');
var Ncp = require('ncp').ncp;
var Path = require('path');
var Rimraf = require('rimraf');
var Watchify = require('watchify');

var buildModule = require('./../build-pipeline/build-module');
var Helpers = require('./../build-pipeline/helpers/helpers');

var BLANK = '';
var DOUBLE_DOT = '..';
// Avoid these for cross-OS compatibility: . , [ ] { } ( ) ! : ; " ' * ? < > | ¢ ™ $ ® \ /
var SAFE_NAMESPACE_DELIMITER = '~';
var PROJECT_DIR = Path.join(__dirname, '..', '..');
var CORE_COMPONENTS_FOLDER = Path.join(__dirname, '..', '..', 'lib', 'core-components', 'famous');

function LocalAssistant(options) {
    this.setOptions(options);
    this.cachedFrameworkBundle = null;
    this.cachedFrameworkBundleTimeStamp = Date.now();
    this.cacheTTL = 1000; // milliseconds
}

LocalAssistant.DEFAULTS = {
    // These get passed into the build pipeline
    sourceFolder: null,
    destinationFolder: null,
    componentDelimiter: ':',
    entrypointExtnames: { '.js': true },

    // These are only used by the project assistant itself
    chokidarIgnored: /[\/\\]\./,
    fileOptions: { encoding: 'utf8' },
    folderBlacklist: { 'node_modules': true, '.git': true },
    livereloadOptions: {
        //port: 35729,
        //port: 35729,
        exts: ['html','css','js','png','gif','jpg','coffee','less','json'],
        applyJSLive: false,
        applyCSSLive: false,
        exclusions: [/\\node_modules\//,/\\.git\//,/\\.svn\//,/\\.hg\//],
        interval: 5000
    },
    moduleNameRegexp: /FamousFramework.(scene|module|register|component)\(\'(\S+)\'/gi
};

LocalAssistant.prototype.setOptions = function(options) {
    this.options = assign(clone(LocalAssistant.DEFAULTS), clone(options || {}));
};

LocalAssistant.prototype.buildFramework = function(cb) {
    var inputFile = Path.join(PROJECT_DIR, 'lib', 'index.js');

    var browserify = Browserify(inputFile, {
        standalone: 'FamousFramework'
    });

    if (this.cachedFrameworkBundle && (this.cachedFrameworkBundleTimeStamp + this.cacheTTL > Date.now())) {
        return cb(null, this.cachedFrameworkBundle);
    }
    else {
        browserify.bundle(function(browserifyErr, buf) {
            if (browserifyErr) {
                return cb(browserifyErr);
            }

            this.cachedFrameworkBundle = buf.toString();
            this.cachedFrameworkBundleTimeStamp = Date.now();

            return cb(null, this.cachedFrameworkBundle);
        }.bind(this));
    }
};

LocalAssistant.prototype.buildModule = function(moduleName, files, finish) {
    var destinationFolder = Path.join(this.options.destinationFolder, moduleName.split(this.options.componentDelimiter).join(SAFE_NAMESPACE_DELIMITER));

    return buildModule(moduleName, files, {
        localSourceFolder: this.options.sourceFolder,
        componentDelimiter: this.options.componentDelimiter,
        entrypointExtnames: this.options.entrypointExtnames
    }, function(buildModuleErr, nameOut, filesOut, dataOut) {
        if (buildModuleErr) {
            return finish(buildModuleErr);
        }

        this.buildFramework(function(buildFrameworkErr, frameworkString) {
            if (buildFrameworkErr) {
                return finish(buildFrameworkErr);
            }

            dataOut.flatProjectFiles.push({
                path: 'famous-framework.bundle.js',
                content: frameworkString
            });

            Async.map(dataOut.flatProjectFiles, function(file, cb) {
                var filePathFull = Path.join(destinationFolder, file.path);
                var filePathBase = Path.dirname(filePathFull);

                Mkdirp(filePathBase, function(mkdirErr) {
                    if (mkdirErr) {
                        return cb(mkdirErr);
                    }

                    var fileData;
                    if (Helpers.doesFileLookLikeBinary(file) && typeof file.content === 'string') {
                        // Ensure that we convert assets like images to binary
                        // buffers instead of mangling them by writing the plain strings
                        fileData = new Buffer(file.content, 'binary');
                    }
                    else {
                        fileData = file.content;
                    }

                    Fs.writeFile(filePathFull, fileData, { flags: 'w' }, function(writeFileErr) {
                        if (writeFileErr) {
                            return cb(writeFileErr);
                        }

                        return cb(null);
                    });
                });
            }, function(writeAllErr) {
                if (writeAllErr) {
                    return finish(writeAllErr);
                }

                return finish(null, nameOut, filesOut, dataOut);
            });
        });
    }.bind(this));
};

LocalAssistant.prototype.buildAll = function(baseDir, subDir, cb) {
    this.cachedFrameworkBundle = null;

    this.buildRecursive(baseDir, subDir, function(err, result) {
        this.cachedFrameworkBundle = null;

        if (err) {
            return cb(err);
        }

        return cb(null, result);
    });
};

LocalAssistant.prototype.buildSingle = function(baseDir, subDir, cb) {
    var moduleName;
    if (subDir.length > 0) {
         moduleName = subDir.split(Path.sep).join(this.options.componentDelimiter);
    }
    else {
        // If the subdir string was empty, we should assume that the baseDir
        // is the home for the single component. This means we need to do some
        // additional work to find the module name. We'll look for an entry point
        // file, and then try to locate the module name. This is a bit brittle
        // and warrants some re-thinking. TODO
        var dirParts = baseDir.split(Path.sep);
        var lastPart = dirParts[dirParts.length - 1];
        var entrypointNameToLookFor = lastPart + '.js';
        var entrypointPath = Path.join(baseDir, entrypointNameToLookFor);
        var entrypointData = Fs.readFileSync(entrypointPath, this.options.fileOptions);
        var regexpMatches = this.options.moduleNameRegexp.exec(entrypointData);

        // We need to reset the lastindex so this regex will run properly
        // on subsequent runs.
        this.options.moduleNameRegexp.lastIndex = 0;

        var moduleNameMatch = regexpMatches[2];

        if (!moduleNameMatch || moduleNameMatch.indexOf(this.options.componentDelimiter) === -1) {
            return cb(new Error('Unable to find the name of the module in the given directory; is your syntax correct?'));
        }
        else {
            moduleName = moduleNameMatch;
        }
    }

    var files = [];
    this.pushFilesToArray(files, baseDir, subDir, BLANK);
    this.buildModule(moduleName, files, cb);
};

LocalAssistant.prototype.tuplesRecursive = function(tuples, baseDir, subDir, cb) {
    var mainPath = Path.join(baseDir, subDir);
    var entries = Fs.readdirSync(mainPath);

    entries.forEach(function(entryPath) {
        var fullEntryPath = Path.join(mainPath, entryPath);
        var entryStat = Fs.lstatSync(fullEntryPath);
        var entryExtname = Path.extname(fullEntryPath);
        var entryBasename = Path.basename(fullEntryPath, entryExtname);

        if (entryStat.isDirectory()) {
            var partialPath = Path.join(subDir, entryPath);

            if (this.isModuleDir(fullEntryPath)) {
                tuples.push([baseDir, partialPath]);
            }

            this.tuplesRecursive(tuples, baseDir, partialPath);
        }
        else {
            var subDirSegment = last(subDir.split(Path.sep));
            if (subDirSegment === entryBasename && (entryExtname in this.options.entrypointExtnames)) {
                tuples.push([baseDir, subDir]);
            }
        }
    }.bind(this));

    if (cb) {
        cb(null, tuples);
    }
};

LocalAssistant.prototype.buildRecursive = function(baseDir, subDir, finish) {
    var alreadyBuilt = [];
    this.tuplesRecursive([], baseDir, subDir, function(err, tuples) {
        if (err) {
            console.error(err);
        }
        Async.mapSeries(tuples, function(tuple, cb) {
            // Don't rebuild one that we just built during this run
            var stringified = JSON.stringify([tuple[0], tuple[1]]);
            if (alreadyBuilt.indexOf(stringified) === -1) {
                this.buildSingle(tuple[0], tuple[1], cb);
                alreadyBuilt.push(stringified);
            }
            else {
                cb();
            }
        }.bind(this), finish);
    }.bind(this));
};

LocalAssistant.prototype.isPushableDir = function(dir) {
    var isPushable = true;
    var dirParts = dir.split(Path.sep);

    for (var i = 0; i < dirParts.length; i++) {
        var dirPart = dirParts[i];

        if (dirPart in this.options.folderBlacklist) {
            isPushable = false;
        }
    }
    return isPushable;
};

LocalAssistant.prototype.pushFilesToArray = function(files, baseDir, subDir, prefix) {
    if (this.isPushableDir(subDir)) {
        var fullPath = Path.join(baseDir, subDir);
        var entries = Fs.readdirSync(fullPath);

        entries.forEach(function(entryPath) {
            var entryPartialPath = Path.join(subDir, entryPath);
            var entryFullPath = Path.join(fullPath, entryPath);
            var entryStat = Fs.lstatSync(entryFullPath);

            var finalPath = Path.join(prefix, entryPath);

            if (!entryStat.isDirectory()) {
                var entryContent = Fs.readFileSync(entryFullPath);

                files.push({ path: finalPath, content: entryContent });
            }
            else {
                this.pushFilesToArray(files, baseDir, entryPartialPath, finalPath);
            }
        }.bind(this));
    }
};

LocalAssistant.prototype.isModuleDir = function(dir) {
    var isMod = false;
    var folderBasename = Path.basename(dir);

    try {
        var entries = Fs.readdirSync(dir);
        entries.forEach(function(entryPath) {
            var entryFullPath = Path.join(dir, entryPath);
            var entryStat = Fs.lstatSync(entryFullPath);

            if (!entryStat.isDirectory()) {
                var entryExtname = Path.extname(entryFullPath);
                var entryBasename = Path.basename(entryFullPath, entryExtname);

                if (entryBasename === folderBasename) {
                   if (entryExtname in this.options.entrypointExtnames) {
                       isMod = true;
                   }
                }
            }
        }.bind(this));
    }
    catch (err) {
        console.error(err);
        return false;
    }
    return isMod;
};

LocalAssistant.prototype.getModuleDir = function(bottomDir, dir) {
    if (this.isModuleDir(dir)) {
        return dir;
    }
    else {
        if (dir === bottomDir || (dir + Path.sep) === bottomDir) {
            return false;
        }
        else {
            return this.getModuleDir(bottomDir, Path.join(dir, DOUBLE_DOT));
        }
    }
};

LocalAssistant.prototype.watchDirectory = function(baseDir, subDir, doRebuildEverythingOnChange, cb) {
    var watcher = Chokidar.watch(Path.join(baseDir, subDir), {
        ignored: this.options.chokidarIgnored,
        ignoreInitial: true
    });

    var handler = debounce(function(event, filename) {
        if (doRebuildEverythingOnChange) {
            this.buildAll(baseDir, '', cb);
        }
        else {
            this.buildSingle(baseDir, subDir, cb);
        }
    }.bind(this), 1000);

    watcher.on('all', handler);
};

LocalAssistant.prototype.watchDirectoryRecursive = function(baseDir, subDir, options, cb) {
    var pathToWatch;
    if (options.triggerEntrypointBuildOnAnyChange) {
        pathToWatch = baseDir;
    }
    else {
        pathToWatch = Path.join(baseDir, subDir);
    }

    var watcher = Chokidar.watch(pathToWatch, {
        ignored: this.options.chokidarIgnored,
        ignoreInitial: true
    });

    var handler = debounce(function(event, filename) {

        if (options.triggerEntrypointBuildOnAnyChange) {
            this.buildSingle(baseDir, subDir, cb);
        }
        else {
            var fileChangedDir = Path.dirname(filename);
            var moduleFullDir = this.getModuleDir(baseDir, fileChangedDir);
            if (moduleFullDir) {
                if (options.doRebuildEverythingOnChange) {
                    this.buildAll(baseDir, '', cb);
                }
                else {
                    var moduleRelativeDir = moduleFullDir.replace(baseDir, BLANK).replace(/^\//, '').replace(/^\\/, '');
                    this.buildSingle(baseDir, moduleRelativeDir, cb);
                }
            }
        }
    }.bind(this), 1000);

    watcher.on('all', handler);
};

LocalAssistant.prototype.localOnlyBootstrap = function(info) {
    console.log('famous framework: Bootstrapping local components...');

    this.setOptions({
        sourceFolder: info.sourceFolder,
        destinationFolder: info.destinationFolder
    });

    Rimraf(info.destinationFolder, function(rmErr) {
        var subDir = '';

        if (info.entrypointModuleName) {
            subDir = info.entrypointModuleName.split(this.options.componentDelimiter).join(Path.sep);
        }

        this.buildAll(info.sourceFolder, subDir, function(buildAllErr) {
            if (buildAllErr) {
                return console.log(buildAllErr);
            }

            if (info.watchAfterBuild !== 'no') {
                var livereloadServer = Livereload.createServer(this.options.livereloadOptions);
                var server = Express();

                var doRebuildEverythingOnChange = false;
                if (info.entrypointModuleName) {
                    doRebuildEverythingOnChange = true;
                }
                if (info.rebuildEverythingOnChange === 'yes') {
                    doRebuildEverythingOnChange = true;
                }
                else if (info.rebuildEverythingOnChange === 'no') {
                    doRebuildEverythingOnChange = false;
                }

                var options = {
                    doRebuildEverythingOnChange: doRebuildEverythingOnChange,
                    entrypointModuleName: info.entrypointModuleName,
                    triggerEntrypointBuildOnAnyChange: !!info.entrypointModuleName
                };

                this.watchDirectoryRecursive(info.sourceFolder, subDir, options, function(err, result) {
                    if (err) {
                        return console.log(err);
                    }

                    livereloadServer.refresh(Path.join(info.servedFolder, 'index.html'));
                    return console.log('famous framework: Finished!');
                });

                server.use(Express.static(info.servedFolder));
                server.listen(info.port);

                return console.log('famous framework: Finished! Serving at http://localhost:' + info.port);
            }
            else {
                return console.log('famous framework: Finished!');
            }
        }.bind(this));
    }.bind(this));
};

LocalAssistant.prototype.watchRuntime = function(info) {
    console.log('famous framework: Building the browser runtime...');

    var b = Browserify(info.inputFile, { cache: {}, packageCache: {} });
    var w = Watchify(b);

    function bundle() {
        w.bundle().pipe(Fs.createWriteStream(info.outputFile));
    }

    w.on('update', function() {
        bundle();
    });

    w.on('bytes', function(bytes) {
        console.log('famous framework: Wrote ' + bytes + ' bytes to ' + info.outputFile);
    });

    bundle();
};

LocalAssistant.prototype.copyCoreComponents = function(info) {
    Rimraf(info.destinationFolder, function(rmErr) {
        Ncp(CORE_COMPONENTS_FOLDER, info.destinationFolder, function(copyErr) {
            if (copyErr) {
                return console.error('famous framework: Couldn\'t copy core components!');
            }
        });
    });
};

module.exports = LocalAssistant;
<|endoftext|>


<commit_before>// ==UserScript==
// @name        bloater
// @namespace   http://tampermonkey.net/
// @version     0.2
// @updateURL   https://raw.githubusercontent.com/chuntr/pagebloat/master/bloater.js
// @description Tampermonkey script that resizes the default column widths for some popular sites to take advantage of wider monitors
// @match       http://*.readthedocs.org/*
// @match       http://stackexchange.com/*
// @match       http://*.stackexchange.com/*
// @match       http://stackoverflow.com/*
// @match       http://serverfault.com/*
// @grant       none
// ==/UserScript==
/* jshint -W097 */
'use strict';
function jQsetWidthByClassName(width,className){
    jQuery(className).css('width', width);
}
function jQsetMaxWidthByClassName(pxWidth,className){
    jQuery(className).css('max-width', pxWidth);
}
function setWidthByClassName(width,className){
    var elements = document.getElementsByClassName(className);
    for (var i=0; i<elements.length; i++){
        elements[i].setAttribute("style","width:"+width+";");
        elements[i].style.width = width;
    }
}
function setWidthById(width,id){
    var element = document.getElementById(id);
    element.setAttribute("style","width:"+width+";");
    element.style.width = width;
}

// readthedocs.org
jQsetWidthByClassName(1024,"div.rst-content");
jQsetMaxWidthByClassName("1128px","div.wy-nav-content");

// stackexchange
setWidthById("1400px","content");
setWidthById("1100px","mainbar");
setWidthById("1100px","question");
setWidthByClassName("1000px","post-text");
setWidthById("1000px","answers");
<commit_msg>updated namespace<commit_after>// ==UserScript==
// @name        bloater
// @namespace   http://github.com/chuntr/
// @version     0.2.1
// @updateURL   https://raw.githubusercontent.com/chuntr/pagebloat/master/bloater.js
// @description Tampermonkey script that resizes the default column widths for some popular sites to take advantage of wider monitors
// @match       http://*.readthedocs.org/*
// @match       http://stackexchange.com/*
// @match       http://*.stackexchange.com/*
// @match       http://stackoverflow.com/*
// @match       http://serverfault.com/*
// @grant       none
// ==/UserScript==
/* jshint -W097 */
'use strict';
function jQsetWidthByClassName(width,className){
    jQuery(className).css('width', width);
}
function jQsetMaxWidthByClassName(pxWidth,className){
    jQuery(className).css('max-width', pxWidth);
}
function setWidthByClassName(width,className){
    var elements = document.getElementsByClassName(className);
    for (var i=0; i<elements.length; i++){
        elements[i].setAttribute("style","width:"+width+";");
        elements[i].style.width = width;
    }
}
function setWidthById(width,id){
    var element = document.getElementById(id);
    element.setAttribute("style","width:"+width+";");
    element.style.width = width;
}

// readthedocs.org
jQsetWidthByClassName(1024,"div.rst-content");
jQsetMaxWidthByClassName("1128px","div.wy-nav-content");

// stackexchange
setWidthById("1400px","content");
setWidthById("1100px","mainbar");
setWidthById("1100px","question");
setWidthByClassName("1000px","post-text");
setWidthById("1000px","answers");
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Control for changing a Property of type {number}.
 * Consists of a labeled value, slider and arrow buttons.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var Color = require( 'SCENERY/util/Color' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var HSlider = require( 'SUN/HSlider' );
  var inherit = require( 'PHET_CORE/inherit' );
  var NumberDisplay = require( 'SCENERY_PHET/NumberDisplay' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Tandem = require( 'TANDEM/Tandem' );
  var TandemText = require( 'TANDEM/scenery/nodes/TandemText' );
  var Text = require( 'SCENERY/nodes/Text' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var Util = require( 'DOT/Util' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // strings
  var numberControlPattern0Value1UnitsString = require( 'string!SCENERY_PHET/NumberControl.pattern_0value_1units' );

  /**
   * @param {string} title
   * @param {Property.<number>} numberProperty
   * @param {Range} numberRange
   * @param {Object} [options]
   * @constructor
   */
  function NumberControl( title, numberProperty, numberRange, options ) {

    options = _.extend( {

      startCallback: function() {}, // called when interaction begins
      endCallback: function() {}, // called when interaction ends
      enabledProperty: new Property( true ), // {Property.<boolean>} is this control enabled?
      disabledOpacity: 0.5, // {number} opacity used to make the control look disabled

      // title
      titleFont: new PhetFont( 12 ),
      titleMaxWidth: null, // {null|string} maxWidth to use for title, to constrain width for i18n

      // value
      valueFont: new PhetFont( 12 ),
      valueMaxWidth: null, // {null|string} maxWidth to use for value display, to constrain width for i18n
      decimalPlaces: 0,
      units: null,

      // arrow buttons
      delta: 1,

      // slider
      majorTicks: [], // array of objects with these fields: { value: {number}, label: {Node} }
      minorTickSpacing: 0, // zero indicates no minor ticks
      trackSize: new Dimension2( 180, 3 ),
      thumbSize: new Dimension2( 17, 34 ),
      majorTickLength: 20,
      minorTickStroke: 'rgba( 0, 0, 0, 0.3 )',
      thumbFillEnabled: 'green',

      tandem: null

    }, options );

    // highlight color for thumb defaults to a brighter version of the thumb color
    options.thumbFillHighlighted = options.thumbFillHighlighted || Color.toColor( options.thumbFillEnabled ).brighterColor();

    // validate options
    assert && assert( options.disabledOpacity > 0 && options.disabledOpacity < 1, 'invalid disabledOpacity: ' + options.disabledOpacity );
    Tandem.validateOptions( options ); // The tandem is required when brand==='phet-io'

    var thisNode = this;

    var delta = options.delta; // to improve readability

    var titleNode = new TandemText( title, {
      font: options.titleFont,
      maxWidth: options.titleMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'titleNode' )
    } );

    // if units were provided, fill in the {1} units, but leave the {0} value alone.
    var valuePattern = options.units ? StringUtils.format( numberControlPattern0Value1UnitsString, '{0}', options.units ) : '{0}';

    var numberDisplay = new NumberDisplay( numberProperty, numberRange, {
      valuePattern: valuePattern,
      font: options.valueFont,
      decimalPlaces: options.decimalPlaces,
      maxWidth: options.valueMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'numberDisplay' )
    } );

    var arrowButtonOptions = {
      delta: options.delta,
      startCallback: options.startCallback,
      endCallback: options.endCallback,
      scale: 0.85
    };

    var leftArrowButton = new ArrowButton( 'left', function() {
      var value = numberProperty.get() - delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.max( value, numberRange.min ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'leftArrowButton' )
    }, arrowButtonOptions ) );

    var rightArrowButton = new ArrowButton( 'right', function() {
      var value = numberProperty.get() + delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.min( value, numberRange.max ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'rightArrowButton' )
    }, arrowButtonOptions ) );

    var arrowEnabledListener = function( value ) {
      leftArrowButton.enabled = ( value > numberRange.min );
      rightArrowButton.enabled = ( value < numberRange.max );
    };
    numberProperty.link( arrowEnabledListener );

    var slider = new HSlider( numberProperty, numberRange, _.extend( {
      startDrag: options.startCallback,
      endDrag: options.endCallback,
      constrainValue: function( value ) {
        // constrain to delta
        value = Util.roundSymmetric( value / options.delta ) * options.delta;
        // constrain to range
        return numberRange.constrainValue( value );
      }
    }, options, {

      // This uses a 3-arg extend so that the tandem is overriden properly
      tandem: options.tandem && options.tandem.createTandem( 'slider' )
    } ) );

    // major ticks
    var majorTicks = options.majorTicks;
    for ( var i = 0; i < majorTicks.length; i++ ) {
      slider.addMajorTick( majorTicks[ i ].value, majorTicks[ i ].label );
    }

    // minor ticks, exclude values where we already have major ticks
    if ( options.minorTickSpacing > 0 ) {
      for ( var minorTickValue = numberRange.min; minorTickValue <= numberRange.max; ) {
        if ( !_.find( majorTicks, function( majorTick ) { return majorTick.value === minorTickValue; } ) ) {
          slider.addMinorTick( minorTickValue );
        }
        minorTickValue += options.minorTickSpacing;
      }
    }

    options.spacing = 5;
    options.resize = false; // workaround for slider
    options.children = [
      new HBox( {
        spacing: 5,
        children: [ titleNode, numberDisplay ]
      } ),
      new HBox( {
        spacing: 15,
        resize: false,
        children: [ leftArrowButton, slider, rightArrowButton ]
      } )
    ];
    VBox.call( this, options );

    // enabled/disable this control
    this.enabledProperty = options.enabledProperty; // @public
    var enabledObserver = function( enabled ) {
      thisNode.pickable = enabled;
      thisNode.opacity = enabled ? 1.0 : options.disabledOpacity;
      //TODO if !enabled, cancel any interaction that is in progress, see scenery#218
    };
    this.enabledProperty.link( enabledObserver );

    // @private
    this.disposeNumberControl = function() {
      numberDisplay.dispose();
      numberProperty.unlink( arrowEnabledListener );
      thisNode.enabledProperty.unlink( enabledObserver );
      slider.dispose();
      options.tandem && options.tandem.removeInstance( this );
    };

    options.tandem && options.tandem.addInstance( this );
  }

  sceneryPhet.register( 'NumberControl', NumberControl );

  return inherit( VBox, NumberControl, {

    // @public
    dispose: function() {
      this.disposeNumberControl();
      VBox.prototype.dispose.call( this );
    },

    // @public
    setEnabled: function( enabled ) { this.enabledProperty.set( enabled ); },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled: function() { return this.enabledProperty.get(); },
    get enabled() { return this.getEnabled(); }

  }, {

    /**
     * Creates a NumberControl with default tick marks for min and max values.
     * @param {string} label
     * @param {Property.<number>} property
     * @param {Range} range
     * @param {Object} [options]
     * @returns {NumberControl}
     * @static
     * @public
     */
    withMinMaxTicks: function( label, property, range, options ) {

      options = _.extend( {
        tickLabelFont: new PhetFont( 12 )
      }, options );

      options.majorTicks = [
        { value: range.min, label: new Text( range.min, { font: options.tickLabelFont } ) },
        { value: range.max, label: new Text( range.max, { font: options.tickLabelFont } ) }
      ];

      return new NumberControl( label, property, range, options );
    }
  } );
} );
<commit_msg>move default value for options.constrainValue closer to _.extends call, #252<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Control for changing a Property of type {number}.
 * Consists of a labeled value, slider and arrow buttons.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var Color = require( 'SCENERY/util/Color' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var HSlider = require( 'SUN/HSlider' );
  var inherit = require( 'PHET_CORE/inherit' );
  var NumberDisplay = require( 'SCENERY_PHET/NumberDisplay' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Tandem = require( 'TANDEM/Tandem' );
  var TandemText = require( 'TANDEM/scenery/nodes/TandemText' );
  var Text = require( 'SCENERY/nodes/Text' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var Util = require( 'DOT/Util' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // strings
  var numberControlPattern0Value1UnitsString = require( 'string!SCENERY_PHET/NumberControl.pattern_0value_1units' );

  /**
   * @param {string} title
   * @param {Property.<number>} numberProperty
   * @param {Range} numberRange
   * @param {Object} [options]
   * @constructor
   */
  function NumberControl( title, numberProperty, numberRange, options ) {

    options = _.extend( {

      startCallback: function() {}, // called when interaction begins
      endCallback: function() {}, // called when interaction ends
      enabledProperty: new Property( true ), // {Property.<boolean>} is this control enabled?
      disabledOpacity: 0.5, // {number} opacity used to make the control look disabled

      // title
      titleFont: new PhetFont( 12 ),
      titleMaxWidth: null, // {null|string} maxWidth to use for title, to constrain width for i18n

      // value
      valueFont: new PhetFont( 12 ),
      valueMaxWidth: null, // {null|string} maxWidth to use for value display, to constrain width for i18n
      decimalPlaces: 0,
      units: null,

      // arrow buttons
      delta: 1,

      // slider
      majorTicks: [], // array of objects with these fields: { value: {number}, label: {Node} }
      minorTickSpacing: 0, // zero indicates no minor ticks
      trackSize: new Dimension2( 180, 3 ),
      thumbSize: new Dimension2( 17, 34 ),
      majorTickLength: 20,
      minorTickStroke: 'rgba( 0, 0, 0, 0.3 )',
      thumbFillEnabled: 'green',

      tandem: null

    }, options );

    // highlight color for thumb defaults to a brighter version of the thumb color
    options.thumbFillHighlighted = options.thumbFillHighlighted || Color.toColor( options.thumbFillEnabled ).brighterColor();

    // constrain the slider value to the provided range and the same delta as the arrow buttons
    options.constrainValue = options.constrainValue || function( value ) {
        var newValue = Util.roundSymmetric( value / options.delta ) * options.delta;
        return numberRange.constrainValue( newValue );
      };

    // validate options
    assert && assert( options.disabledOpacity > 0 && options.disabledOpacity < 1, 'invalid disabledOpacity: ' + options.disabledOpacity );
    Tandem.validateOptions( options ); // The tandem is required when brand==='phet-io'

    var thisNode = this;

    var delta = options.delta; // to improve readability

    var titleNode = new TandemText( title, {
      font: options.titleFont,
      maxWidth: options.titleMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'titleNode' )
    } );

    // if units were provided, fill in the {1} units, but leave the {0} value alone.
    var valuePattern = options.units ? StringUtils.format( numberControlPattern0Value1UnitsString, '{0}', options.units ) : '{0}';

    var numberDisplay = new NumberDisplay( numberProperty, numberRange, {
      valuePattern: valuePattern,
      font: options.valueFont,
      decimalPlaces: options.decimalPlaces,
      maxWidth: options.valueMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'numberDisplay' )
    } );

    var arrowButtonOptions = {
      delta: options.delta,
      startCallback: options.startCallback,
      endCallback: options.endCallback,
      scale: 0.85
    };

    var leftArrowButton = new ArrowButton( 'left', function() {
      var value = numberProperty.get() - delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.max( value, numberRange.min ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'leftArrowButton' )
    }, arrowButtonOptions ) );

    var rightArrowButton = new ArrowButton( 'right', function() {
      var value = numberProperty.get() + delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.min( value, numberRange.max ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'rightArrowButton' )
    }, arrowButtonOptions ) );

    var arrowEnabledListener = function( value ) {
      leftArrowButton.enabled = ( value > numberRange.min );
      rightArrowButton.enabled = ( value < numberRange.max );
    };
    numberProperty.link( arrowEnabledListener );

    var slider = new HSlider( numberProperty, numberRange, _.extend( {
      startDrag: options.startCallback,
      endDrag: options.endCallback,
      constrainValue: options.constrainValue
    }, options, {

      // This uses a 3-arg extend so that the tandem is overriden properly
      tandem: options.tandem && options.tandem.createTandem( 'slider' )
    } ) );

    // major ticks
    var majorTicks = options.majorTicks;
    for ( var i = 0; i < majorTicks.length; i++ ) {
      slider.addMajorTick( majorTicks[ i ].value, majorTicks[ i ].label );
    }

    // minor ticks, exclude values where we already have major ticks
    if ( options.minorTickSpacing > 0 ) {
      for ( var minorTickValue = numberRange.min; minorTickValue <= numberRange.max; ) {
        if ( !_.find( majorTicks, function( majorTick ) { return majorTick.value === minorTickValue; } ) ) {
          slider.addMinorTick( minorTickValue );
        }
        minorTickValue += options.minorTickSpacing;
      }
    }

    options.spacing = 5;
    options.resize = false; // workaround for slider
    options.children = [
      new HBox( {
        spacing: 5,
        children: [ titleNode, numberDisplay ]
      } ),
      new HBox( {
        spacing: 15,
        resize: false,
        children: [ leftArrowButton, slider, rightArrowButton ]
      } )
    ];
    VBox.call( this, options );

    // enabled/disable this control
    this.enabledProperty = options.enabledProperty; // @public
    var enabledObserver = function( enabled ) {
      thisNode.pickable = enabled;
      thisNode.opacity = enabled ? 1.0 : options.disabledOpacity;
      //TODO if !enabled, cancel any interaction that is in progress, see scenery#218
    };
    this.enabledProperty.link( enabledObserver );

    // @private
    this.disposeNumberControl = function() {
      numberDisplay.dispose();
      numberProperty.unlink( arrowEnabledListener );
      thisNode.enabledProperty.unlink( enabledObserver );
      slider.dispose();
      options.tandem && options.tandem.removeInstance( this );
    };

    options.tandem && options.tandem.addInstance( this );
  }

  sceneryPhet.register( 'NumberControl', NumberControl );

  return inherit( VBox, NumberControl, {

    // @public
    dispose: function() {
      this.disposeNumberControl();
      VBox.prototype.dispose.call( this );
    },

    // @public
    setEnabled: function( enabled ) { this.enabledProperty.set( enabled ); },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled: function() { return this.enabledProperty.get(); },
    get enabled() { return this.getEnabled(); }

  }, {

    /**
     * Creates a NumberControl with default tick marks for min and max values.
     * @param {string} label
     * @param {Property.<number>} property
     * @param {Range} range
     * @param {Object} [options]
     * @returns {NumberControl}
     * @static
     * @public
     */
    withMinMaxTicks: function( label, property, range, options ) {

      options = _.extend( {
        tickLabelFont: new PhetFont( 12 )
      }, options );

      options.majorTicks = [
        { value: range.min, label: new Text( range.min, { font: options.tickLabelFont } ) },
        { value: range.max, label: new Text( range.max, { font: options.tickLabelFont } ) }
      ];

      return new NumberControl( label, property, range, options );
    }
  } );
} );
<|endoftext|>


<commit_before>var BlueboxNocApp = angular.module("BlueboxNocApp", ["ng-admin"]);
// https://github.com/marmelab/ng-admin
BlueboxNocApp.config(function (NgAdminConfigurationProvider, Application, Entity, Field, Reference, ReferencedList, ReferenceMany) {
    // set the main API endpoint for this admin
    var rootUrl = (location.protocol + '//' + location.hostname +
    (location.port ? ':' + location.port : ''));
    var app = new Application('Menu'). // application main title
        baseApiUrl(rootUrl + "/api/"); // main API endpoint
    // define all entities at the top to allow references between them
    var vpn = new Entity('vpn')
        .label("VPNs")
        .identifier(new Field('title'));
    var bbx = new Entity('bbx')
        .label("Blue Boxes")
        .identifier(new Field('title'));
    var vnc = new Entity('vnc')
        .label("VNCs")
        .identifier(new Field('title'));
    var user = new Entity('user')
        .label("Users")
        .identifier(new Field('username'));
    // set the application entities
    app
        .addEntity(vpn)
        .addEntity(bbx)
        .addEntity(vnc)
        .addEntity(user);
    // set the application menu entries
    var menuCnt = 0;
    vpn.menuView()
        .order(menuCnt++)
        .icon('');
    bbx.menuView()
        .order(menuCnt++)
        .icon('');
    vnc.menuView()
        .order(menuCnt++)
        .icon('');
    user.menuView()
        .order(menuCnt++)
        .icon('');

    // Common fields
    function descField() { return new Field('desc').label('Description') };

    // VPNs
    vpn.dashboardView()
        .title("VPNs List")
        .order(1) // display the post panel first in the dashboard
        .limit(5) // limit the panel to the 5 latest posts
        .fields([
            new Field("title").isDetailLink(true).label("VPN"),
            descField()
        ]);
    vpn.editionView()
        .title("Edit VPN : {{entry.values.title}}")
        .actions(["list", "show", "delete", "bluebox"])
        .fields([
            new Field("title").editable(false).isDetailLink(true),
            descField(),
            new ReferenceMany('bbxs') // a Reference is a particular type of field that references another entity
                .label('Blue Boxes')
                .targetEntity(bbx) // the tag entity is defined later in this file
                .targetField(new Field('title')) // the field to be displayed in this list
        ]);
    vpn.listView()
        .title("All VPNs")
        .fields([
            vpn.editionView().fields(),
            // duplicated ReferenceMany field to get the class only on list view
            new ReferenceMany('bbxs') // a Reference is a particular type of field that references another entity
                .label('Blue Boxes')
                .targetEntity(bbx) // the tag entity is defined later in this file
                .targetField(new Field('title')) // the field to be displayed in this list
                .cssClasses('bboxes_tag')
        ]);
    var vpnTitleAtCreationTime = new Field("title").validation({validator: !{VPNTitleValidator}});
    vpn.creationView().fields([
        vpnTitleAtCreationTime,
        descField()]);
    vpn.showView().fields([
        new Field("title").editable(false).isDetailLink(true),
        descField(),
        new Field("vpnBoxes").type("template").template('
Hello, {{user}}.
')
    ]);
    // BlueBoxes
    bbx.dashboardView()
        .title("BlueBoxes List")
        .order(2) // display the post panel first in the dashboard
        .limit(10) // limit the panel to the 5 latest posts
        .fields([
            new Field("title").isDetailLink(true).label("BBX"),
            descField(), new Field("vpn")
        ]);
    bbx.editionView().title("Blue Box : {{entry.values.title}}")
        .actions(["list", "show", "delete"])
        .fields([
            new Field("title").editable(false).isDetailLink(true),
            new Field("vpn").editable(false),
            descField()
        ]);
    bbx.listView()
        .title("All Blue Boxes")
        .fields(bbx.editionView().fields());
    var bbxTitleAtCreationTime = new Field("title").validation({validator: !{BBXTitleValidator}});
    bbx.creationView().fields([
        bbxTitleAtCreationTime,
        descField()]);
    bbx.showView().fields([
        new Field("title").editable(false).isDetailLink(true),
        descField(),
        new Field("vpn"),
        new Field("BBxVpn").type("template").template('
Hello, {{user}}.
')
    ]);
    // VNCs
    vnc.dashboardView()
        .title("VNCs List")
        .order(3) // display the post panel first in the dashboard
        .limit(5) // limit the panel to the 5 latest posts
        .fields([
            new Field("title").isDetailLink(true).label("VNC"),
            descField()
        ]);
    vnc.editionView()
        .title("Edit VNC : {{entry.values.title}}")
        .actions(["list", "show", "delete"])
        .fields([
            new Field("title").editable(false).isDetailLink(true),
            descField(),
            new Field("ip"),
            new Field("port"),
            // @todo preselect current vpn in the list
            new Reference('vpn')
                .label('VPN title')
                .targetEntity(vpn) // Select a target Entity
                .targetField(new Field('title')), // Select a label Field
            // @todo see how to add a frame with VNC
            new Field('Open VNC', 'template')
                .type('template')
                .editable(false)
                .template('Open {{entry.values.title}} in a new window')
        ]);
    vnc.listView()
        .title("All VNCs")
        .fields(vnc.editionView().fields());
    var vncTitleAtCreationTime = new Field("title").validation({validator: !{VNCTitleValidator}});
    vnc.creationView().fields([
        vncTitleAtCreationTime,
        descField()]);
    vnc.showView().fields([
        new Field("title").editable(false).isDetailLink(true),
        descField(),
        new Field("vncBoxes").type("template").template('
Connect to {{entry.values.title}}
')
    ]);
    // USERs
    user.dashboardView()
        .title("Users List")
        .order(4)
        .limit(10)
        .fields([
            new Field("username").isDetailLink(true).label("Users").identifier(true),
            new Field("sciper"),
            new Field("email")
        ]);
    user.editionView()
        .title("Edit user : {{entry.values.title}}")
        .actions(["list", "show", "delete"])
        .fields([
            new Field("username").editable(false).isDetailLink(true).identifier(true),
            new Field("sciper").editable(false),
            new Field("email").editable(false),
            new Field("group"),
            /*new ReferencedList('group') // display list of related comments
             .targetEntity(user)
             .targetReferenceField('username')
             .targetFields([
             new Field('group')
             ]),*/
            new Field('View details', 'template')
                .type('template')
                .editable(false)
                .template('Open {{entry.values.username}} details')
        ]);
    user.listView()
        .title("All Users")
        .fields(user.editionView().fields());
    var userTitleAtCreationTime = new Field("title").validation({validator: !{USERTitleValidator}});
    user.creationView().fields([
        userTitleAtCreationTime,
        descField()]);
    user.showView().fields([
        new Field("title").editable(false).isDetailLink(true),
        descField(),
        new Field("vncBoxes").type("template").template('
Hello, {{user}}.
')
    ]);
    NgAdminConfigurationProvider.configure(app);
});
// How to slap additional UI on any of the ng-admin pages,
// e.g. here to access the VNC features
BlueboxNocApp.controller("HelloWorld", function ($scope) {
    $scope.user = "Calvin Hobbes";
});
<commit_msg>Fix syntax error in Chrome<commit_after>var BlueboxNocApp = angular.module("BlueboxNocApp", ["ng-admin"]);
// https://github.com/marmelab/ng-admin
BlueboxNocApp.config(function (NgAdminConfigurationProvider, Application, Entity, Field, Reference, ReferencedList, ReferenceMany) {
    // set the main API endpoint for this admin
    var rootUrl = (location.protocol + '//' + location.hostname +
    (location.port ? ':' + location.port : ''));
    var app = new Application('Menu'). // application main title
        baseApiUrl(rootUrl + "/api/"); // main API endpoint
    // define all entities at the top to allow references between them
    var vpn = new Entity('vpn')
        .label("VPNs")
        .identifier(new Field('title'));
    var bbx = new Entity('bbx')
        .label("Blue Boxes")
        .identifier(new Field('title'));
    var vnc = new Entity('vnc')
        .label("VNCs")
        .identifier(new Field('title'));
    var user = new Entity('user')
        .label("Users")
        .identifier(new Field('username'));
    // set the application entities
    app
        .addEntity(vpn)
        .addEntity(bbx)
        .addEntity(vnc)
        .addEntity(user);
    // set the application menu entries
    var menuCnt = 0;
    vpn.menuView()
        .order(menuCnt++)
        .icon('');
    bbx.menuView()
        .order(menuCnt++)
        .icon('');
    vnc.menuView()
        .order(menuCnt++)
        .icon('');
    user.menuView()
        .order(menuCnt++)
        .icon('');

    // Common fields
    function descField() { return new Field('desc').label('Description') };

    // VPNs
    vpn.dashboardView()
        .title("VPNs List")
        .order(1) // display the post panel first in the dashboard
        .limit(5) // limit the panel to the 5 latest posts
        .fields([
            new Field("title").isDetailLink(true).label("VPN"),
            descField()
        ]);
    vpn.editionView()
        .title("Edit VPN : {{entry.values.title}}")
        .actions(["list", "show", "delete", "bluebox"])
        .fields([
            new Field("title").editable(false).isDetailLink(true),
            descField(),
            new ReferenceMany('bbxs') // a Reference is a particular type of field that references another entity
                .label('Blue Boxes')
                .targetEntity(bbx) // the tag entity is defined later in this file
                .targetField(new Field('title')) // the field to be displayed in this list
        ]);
    vpn.listView()
        .title("All VPNs")
        .fields([
            vpn.editionView().fields(),
            // duplicated ReferenceMany field to get the class only on list view
            new ReferenceMany('bbxs') // a Reference is a particular type of field that references another entity
                .label('Blue Boxes')
                .targetEntity(bbx) // the tag entity is defined later in this file
                .targetField(new Field('title')) // the field to be displayed in this list
                .cssClasses('bboxes_tag')
        ]);
    var vpnTitleAtCreationTime = new Field("title").validation({validator: VPNTitleValidator});
    vpn.creationView().fields([
        vpnTitleAtCreationTime,
        descField()]);
    vpn.showView().fields([
        new Field("title").editable(false).isDetailLink(true),
        descField(),
        new Field("vpnBoxes").type("template").template('
Hello, {{user}}.
')
    ]);
    // BlueBoxes
    bbx.dashboardView()
        .title("BlueBoxes List")
        .order(2) // display the post panel first in the dashboard
        .limit(10) // limit the panel to the 5 latest posts
        .fields([
            new Field("title").isDetailLink(true).label("BBX"),
            descField(), new Field("vpn")
        ]);
    bbx.editionView().title("Blue Box : {{entry.values.title}}")
        .actions(["list", "show", "delete"])
        .fields([
            new Field("title").editable(false).isDetailLink(true),
            new Field("vpn").editable(false),
            descField()
        ]);
    bbx.listView()
        .title("All Blue Boxes")
        .fields(bbx.editionView().fields());
    var bbxTitleAtCreationTime = new Field("title").validation({validator: BBXTitleValidator});
    bbx.creationView().fields([
        bbxTitleAtCreationTime,
        descField()]);
    bbx.showView().fields([
        new Field("title").editable(false).isDetailLink(true),
        descField(),
        new Field("vpn"),
        new Field("BBxVpn").type("template").template('
Hello, {{user}}.
')
    ]);
    // VNCs
    vnc.dashboardView()
        .title("VNCs List")
        .order(3) // display the post panel first in the dashboard
        .limit(5) // limit the panel to the 5 latest posts
        .fields([
            new Field("title").isDetailLink(true).label("VNC"),
            descField()
        ]);
    vnc.editionView()
        .title("Edit VNC : {{entry.values.title}}")
        .actions(["list", "show", "delete"])
        .fields([
            new Field("title").editable(false).isDetailLink(true),
            descField(),
            new Field("ip"),
            new Field("port"),
            // @todo preselect current vpn in the list
            new Reference('vpn')
                .label('VPN title')
                .targetEntity(vpn) // Select a target Entity
                .targetField(new Field('title')), // Select a label Field
            // @todo see how to add a frame with VNC
            new Field('Open VNC', 'template')
                .type('template')
                .editable(false)
                .template('Open {{entry.values.title}} in a new window')
        ]);
    vnc.listView()
        .title("All VNCs")
        .fields(vnc.editionView().fields());
    var vncTitleAtCreationTime = new Field("title").validation({validator: VNCTitleValidator});
    vnc.creationView().fields([
        vncTitleAtCreationTime,
        descField()]);
    vnc.showView().fields([
        new Field("title").editable(false).isDetailLink(true),
        descField(),
        new Field("vncBoxes").type("template").template('
Connect to {{entry.values.title}}
')
    ]);
    // USERs
    user.dashboardView()
        .title("Users List")
        .order(4)
        .limit(10)
        .fields([
            new Field("username").isDetailLink(true).label("Users").identifier(true),
            new Field("sciper"),
            new Field("email")
        ]);
    user.editionView()
        .title("Edit user : {{entry.values.title}}")
        .actions(["list", "show", "delete"])
        .fields([
            new Field("username").editable(false).isDetailLink(true).identifier(true),
            new Field("sciper").editable(false),
            new Field("email").editable(false),
            new Field("group"),
            /*new ReferencedList('group') // display list of related comments
             .targetEntity(user)
             .targetReferenceField('username')
             .targetFields([
             new Field('group')
             ]),*/
            new Field('View details', 'template')
                .type('template')
                .editable(false)
                .template('Open {{entry.values.username}} details')
        ]);
    user.listView()
        .title("All Users")
        .fields(user.editionView().fields());
    var userTitleAtCreationTime = new Field("title").validation({validator: USERTitleValidator});
    user.creationView().fields([
        userTitleAtCreationTime,
        descField()]);
    user.showView().fields([
        new Field("title").editable(false).isDetailLink(true),
        descField(),
        new Field("vncBoxes").type("template").template('
Hello, {{user}}.
')
    ]);
    NgAdminConfigurationProvider.configure(app);
});
// How to slap additional UI on any of the ng-admin pages,
// e.g. here to access the VNC features
BlueboxNocApp.controller("HelloWorld", function ($scope) {
    $scope.user = "Calvin Hobbes";
});
<|endoftext|>