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

import React from 'react';
import {
  Animated,
  DeviceEventEmitter,
  Easing,
  NativeModules,
  StyleSheet,
  View,
} from 'react-native';
import ExNavigator from '@expo/react-native-navigator';
import TimerMixin from 'react-timer-mixin';

import autobind from 'autobind-decorator';
import Browser from 'Browser';
import BrowserActions from 'BrowserActions';
import ConsoleActions from 'ConsoleActions';
import ExButton from 'ExButton';
import ExRouter from 'ExRouter';
import ExponentKernel from 'ExponentKernel';
import LocalStorage from 'LocalStorage';
import MenuView from 'MenuView';
import reactMixin from 'react-mixin';
import { connect } from 'react-redux';

const {
  ExponentConstants,
} = NativeModules;

const KERNEL_ROUTE_HOME = 0;
const KERNEL_ROUTE_BROWSER = 1;
const FORCE_TOUCH_SWITCH_THRESHOLD = 0.995;
const FORCE_TOUCH_CAPTURE_THRESHOLD = 0.85;

const MENU_FADE_IN_TOTAL_MS = 400;
const MENU_FADE_IN_BEGIN_MS = 50; // make this one shorter

class KernelNavigator extends React.Component {
  static getDataProps(data) {
    let {
      isShell,
      shellManifestUrl,
      isHomeVisible,
      isMenuVisible,
      isNuxFinished,
      foregroundTaskUrl,
      tasks,
      history,
    } = data.browser;
    return {
      isShell,
      shellManifestUrl,
      isHomeVisible,
      isMenuVisible,
      isNuxFinished,
      foregroundTaskUrl,
      tasks,
      history,
      consoleHistory: data.console.history,
    };
  }

  constructor(props, context) {
    super(props, context);
    this.state = {
      menuTransition: new Animated.Value(0),
      isShowingOverlay: false,
    };
    this.state.menuTransition.addListener((event) => {
      let isShowingOverlay = (event.value > 0);
      if (isShowingOverlay !== this.state.isShowingOverlay) {
        this.setState({ isShowingOverlay });
      }
    });
  }

  componentWillMount() {
    this._homeRoute = ExRouter.getHomeRoute();
    this._browserRoutes = {};
    if (this.props.isShell) {
      ExponentKernel.routeDidForeground(KERNEL_ROUTE_BROWSER, {
        url: this.props.shellManifestUrl,
      });
    } else {
      ExponentKernel.routeDidForeground(KERNEL_ROUTE_HOME, null);
    }
  }

  componentDidMount() {
    this._refreshSubscription = Browser.addRefreshListener(async () => {
      // bypass validation of the link and just try to open it again
      if (this.props.isShell) {
        // don't clean browser routes: we'll make a new history item and task for
        // the same url, and the existing route's props will update.
        // don't call ExponentKernel.openURL because we don't want to re-route
        // the existing url. we want to force a reload of the manifest.
        this.props.dispatch(BrowserActions.setKernelLoadingState(true));
        this.props.dispatch(BrowserActions.navigateToUrlAsync(this.props.shellManifestUrl));
      } else if (this.props.foregroundTaskUrl) {
        let urlToRefresh = this.props.foregroundTaskUrl;
        await this.props.dispatch(BrowserActions.foregroundHomeAsync(true));
        this._cleanUnusedBrowserRoutes();
        ExponentKernel.openURL(urlToRefresh);
      }
    });
    DeviceEventEmitter.addListener('ExponentKernel.foregroundTask', this._foregroundTask);
    this._loadNuxStateAsync();
  }

  componentWillUnmount() {
    delete this._homeRoute;
    delete this._browserRoutes;
    if (this._refreshSubscription) {
      this._refreshSubscription.remove();
    }
  }

  componentWillReceiveProps(nextProps) {
    this._updateNavigator(nextProps);
    this._updateMenuFromProps(nextProps);
  }

  render() {
    let { isShell, shellManifestUrl, tasks, foregroundTaskUrl, isHomeVisible, isMenuVisible, isNuxFinished } = this.props;
    let initialRouteStack = (isShell) ? [this._findOrCreateBrowserRoute(shellManifestUrl)] : [this._homeRoute];

    let simulatorButton;
    // EXButton appears for simulators on computers with no force touch
    // because all the gestures are too annoying in this circumstance.
    if (!ExponentConstants.isDevice && tasks.size > 0 && !isShell) {
      // don't show it if the menu is currently on screen.
      if (isHomeVisible || !isMenuVisible || !isNuxFinished) {
        simulatorButton = (<ExButton onPress={this._switchTasks} />);
      }
    }

    let menuView, menuOverlay;
    if (this.state.isShowingOverlay) {
      let backgroundColor = this.state.menuTransition.interpolate({
        inputRange: [0, 1],
        outputRange: ['rgba(0, 0, 0, 0.001)', 'rgba(0, 0, 0, 0.5)'],
      });
      menuOverlay = (
        <Animated.View style={[styles.menuOverlay, {backgroundColor}]} />
      );
    }
    if (isMenuVisible) {
      let task = tasks.get(foregroundTaskUrl);
      menuView = (
        <MenuView
          task={task}
          isNuxFinished={isNuxFinished}
          shouldFadeIn
        />
      );
    }

    return (
      <View
        style={styles.container}
        onMoveShouldSetResponderCapture={this._onContainerMoveShouldSetResponderCapture}
        onStartShouldSetResponder={this._onContainerStartShouldSetResponder}
        onResponderGrant={this._onContainerResponderGrant}
        onResponderMove={this._onContainerResponderMove}
        onResponderRelease={this._onContainerResponderRelease}>
        <ExNavigator
          ref={component => { this._navigator = component; }}
          initialRouteStack={initialRouteStack}
          showNavigationBar={false}
          style={styles.navigator}
          sceneStyle={styles.scene}
        />
        {simulatorButton}
        {menuOverlay}
        {menuView}
      </View>
    );
  }

  @autobind
  _onContainerMoveShouldSetResponderCapture(event) {
    // if the force touch gesture is available/meaningful,
    // and user force touches on a subview that would otherwise capture the touch,
    // override that and become the responder.
    if (this._onContainerStartShouldSetResponder()) {
      if (View.forceTouchAvailable) {
        let { force } = event.nativeEvent;
        return (force > FORCE_TOUCH_CAPTURE_THRESHOLD);
      }
    }
    return false;
  }

  @autobind
  _onContainerStartShouldSetResponder() {
    return (!this.props.isShell && this.props.tasks.size > 0 && !this.props.isMenuVisible);
  }

  @autobind
  _onContainerResponderGrant(event) {
    this._hasTouch = true;
    this._handleTouch(event);
  }

  @autobind
  _onContainerResponderMove(event) {
    this._handleTouch(event);
  }

  @autobind
  _onContainerResponderRelease(event) {
    this._hasTouch = false;
    this._hasDoubleTouch = false;
    this._interruptMenuTransition();
  }

  @autobind
  _handleTouch(event) {
    let { force, touches } = event.nativeEvent;
    if (this._hasTouch) {
      if (View.forceTouchAvailable) {
        if (force >= FORCE_TOUCH_SWITCH_THRESHOLD && touches.length === 2) {
          this._switchTasks();
        }
      } else if (touches) {
        if (touches.length === 2 && !this._hasDoubleTouch) {
          this._hasDoubleTouch = true;
          // quickly start fading in the menu to respond to double touch
          this.setTimeout(() => {
            if (this._hasDoubleTouch) {
              this._transitionMenu(0.75, MENU_FADE_IN_TOTAL_MS - MENU_FADE_IN_BEGIN_MS);
            }
          }, MENU_FADE_IN_BEGIN_MS);
          this.setTimeout(() => {
            if (this._hasDoubleTouch) {
              this._switchTasks();
            }
          }, MENU_FADE_IN_TOTAL_MS);
        }
        if (touches.length !== 2 && this._hasDoubleTouch) {
          this._interruptMenuTransition();
          this._hasDoubleTouch = false;
        }
      }
    }
  }

  _updateMenuFromProps(nextProps) {
    if (nextProps.isMenuVisible) {
      this._transitionMenu(1, 200);
    } else {
      this._transitionMenu(0, 200);
    }
  }

  _interruptMenuTransition() {
    if (!this.props.isMenuVisible) {
      this._transitionMenu(0, 100);
    }
  }

  _transitionMenu(toValue, duration) {
    this.state.menuTransition.stopAnimation(() => {
      Animated.timing(this.state.menuTransition, {
        easing: Easing.inOut(Easing.quad),
        toValue,
        duration,
      }).start();
    });
  }

  @autobind
  _switchTasks() {
    this._hasTouch = false;
    this._hasDoubleTouch = false;
    if (this.props.isHomeVisible) {
      // since only one task besides Home is allowed, just pick the first nonnull url
      let urlsToForeground = this.props.tasks.keySeq().filter(taskUrl => (taskUrl !== null));
      if (urlsToForeground) {
        this.props.dispatch(BrowserActions.foregroundUrlAsync(urlsToForeground.first()));
      }
    } else {
      this.props.dispatch(BrowserActions.showMenuAsync(true));
      // this.props.dispatch(BrowserActions.foregroundHomeAsync());
    }
  }

  @autobind
  _foregroundTask(event) {
    let { taskUrl } = event;
    let matchingUrlTasks = this.props.tasks.keySeq().filter(url => (url === taskUrl));
    if (matchingUrlTasks) {
      this.props.dispatch(BrowserActions.foregroundUrlAsync(matchingUrlTasks.first()));
    }
  }

  _updateNavigator(nextProps) {
    // present the console if something happened in it
    let isBrowserChanged = false;
    if (this._shouldPushConsoleFromNextProps(nextProps)) {
        this.pushConsole(!this._isCurrentTaskDev());
    } else if (nextProps.foregroundTaskUrl !== this.props.foregroundTaskUrl) {
      if (nextProps.foregroundTaskUrl) {
        this.props.dispatch(ConsoleActions.clearConsole());
      }
      isBrowserChanged = true;
    } else {
      // same url but possibly new time (i.e. the user opened it again)
      let prevTime = (this.props.history && this.props.history.size) ? this.props.history.get(0).get('time') : 0;
      let nextTime = (nextProps.history && nextProps.history.size) ? nextProps.history.get(0).get('time') : 0;
      if (prevTime !== nextTime && nextTime !== 0) {
        isBrowserChanged = true;
      } else if (nextProps.isHomeVisible !== this.props.isHomeVisible) {
        isBrowserChanged = true;
      }
    }

    if (isBrowserChanged) {
      let prevUrl = this.props.foregroundTaskUrl;
      if (nextProps.isHomeVisible || !nextProps.foregroundTaskUrl) {
        requestAnimationFrame(() => this._foregroundHome(prevUrl));
      } else {
        let newUrl = nextProps.foregroundTaskUrl;
        let urlToBackground;
        if (prevUrl !== newUrl && !this.props.isHomeVisible) {
          urlToBackground = prevUrl;
          requestAnimationFrame(() => {
            this._foregroundHome(prevUrl);
            this._foregroundRouteForUrl(newUrl, null);
          });
        } else {
          requestAnimationFrame(() => this._foregroundRouteForUrl(newUrl, urlToBackground));
        }
      }
    }
  }

  _foregroundRouteForUrl(url, urlToBackground) {
    let routeToForeground = this._findOrCreateBrowserRoute(url);
    let currentRoutes = this._navigator.getCurrentRoutes();
    if (currentRoutes.indexOf(routeToForeground) !== -1) {
      this.props.dispatch(BrowserActions.setKernelLoadingState(false));
      this._navigator.jumpTo(routeToForeground);
    } else {
      this._cleanUnusedBrowserRoutes();
      this._navigator.push(routeToForeground);
    }
    ExponentKernel.routeDidForeground(KERNEL_ROUTE_BROWSER, { url, urlToBackground });
  }

  _foregroundHome(urlToBackground) {
    if (this.props.isShell) {
      // there is no home in this case, go back to the root shell task
      this._foregroundRouteForUrl(this.props.shellManifestUrl, urlToBackground);
    } else {
      if (this.isConsolePresented()) {
        this._navigator.pop();
      }
      this._navigator.jumpTo(this._homeRoute);
      ExponentKernel.routeDidForeground(KERNEL_ROUTE_HOME, { urlToBackground });
    }
  }

  _findOrCreateBrowserRoute(url) {
    if (this._browserRoutes[url]) {
      return this._browserRoutes[url];
    } else {
      let newRoute = ExRouter.getBrowserRoute(url);
      this._browserRoutes[url] = newRoute;
      return newRoute;
    }
  }

  _cleanUnusedBrowserRoutes() {
    let newBrowserRoutes = {};
    for (let key in this._browserRoutes) {
      if (this._browserRoutes.hasOwnProperty(key)) {
        let url = key;
        let matchingTasks = this.props.tasks.keySeq().filter(taskUrl => (taskUrl === url)).toList();
        if (matchingTasks && matchingTasks.size) {
          newBrowserRoutes[url] = this._browserRoutes[url];
        }
      }
    }
    this._browserRoutes = newBrowserRoutes;
  }

  @autobind
  isConsolePresented() {
    let currentRoute = this._navigator.navigationContext.currentRoute;
    let currentRoutes = this._navigator.getCurrentRoutes();
    let currentIndex = currentRoutes.findIndex(route => route === currentRoute);
    let isConsolePresented = currentRoutes
      .slice(0, currentIndex + 1)
      .some(route => route.isConsole);

    return isConsolePresented;
  }

  /**
   *  @param isUserFacing: if true, console should show a end-user-readable "oops" message
   *    overlaying the technical details of the console.
   */
  @autobind
  pushConsole(isUserFacing) {
    if (!this.isConsolePresented()) {
      this._navigator.push(ExRouter.getConsoleRoute(Browser.refresh, isUserFacing));
    }
  }

  _shouldPushConsoleFromNextProps(nextProps) {
    let currentConsoleSize = this.props.consoleHistory ? this.props.consoleHistory.size : 0;
    let newConsoleSize = nextProps.consoleHistory ? nextProps.consoleHistory.size : 0;
    if (newConsoleSize > currentConsoleSize) {
      // current behavior: Pop the console if the error was fatal and we're NOT developing (so no redbox happened).
      // TODO: user accounts: Additionally pop the console if the logged in user owns this experience.
      return this._isLastConsoleItemFatal(nextProps.consoleHistory) && !this._isCurrentTaskDev();
    }
    return false;
  }

  _isCurrentTaskDev() {
    let currentExperience = this.props.tasks.get(this.props.foregroundTaskUrl);
    return currentExperience &&
      currentExperience.manifest &&
      currentExperience.manifest.get('developer');
  }

  _isLastConsoleItemFatal(consoleHistory) {
    consoleHistory = consoleHistory || this.props.consoleHistory;
    if (consoleHistory) {
      let lastConsoleItem = consoleHistory.valueSeq().last();
      return (lastConsoleItem && lastConsoleItem.get('fatal'));
    }
    return false;
  }

  async _loadNuxStateAsync() {
    // load wherever the previous nux left off
    let isNuxFinished = await LocalStorage.getIsNuxFinishedAsync();
    if (isNuxFinished === 'true') {
      this.props.dispatch(BrowserActions.setIsNuxFinishedAsync(true));
    }

    // listen for kernel nux events
    DeviceEventEmitter.addListener('ExponentKernel.resetNuxState', () => {
      this.props.dispatch(BrowserActions.setIsNuxFinishedAsync(false));
    });
  }
}

reactMixin(KernelNavigator.prototype, TimerMixin);

export default connect(
  data => KernelNavigator.getDataProps(data)
)(KernelNavigator);

let styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  navigator: {
    backgroundColor: '#1272b6',
  },
  scene: {
    backgroundColor: 'transparent',
  },
  menuOverlay: {
    position: 'absolute',
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
  },
});
<commit_msg>Put EXButton and two-finger gesture behind a flag<commit_after>/**
 * Copyright 2015-present 650 Industries. All rights reserved.
 *
 * @providesModule KernelNavigator
 * @flow
 */
'use strict';

import React from 'react';
import {
  Animated,
  DeviceEventEmitter,
  Easing,
  NativeModules,
  StyleSheet,
  View,
} from 'react-native';
import ExNavigator from '@expo/react-native-navigator';
import TimerMixin from 'react-timer-mixin';

import autobind from 'autobind-decorator';
import Browser from 'Browser';
import BrowserActions from 'BrowserActions';
import ConsoleActions from 'ConsoleActions';
import ExButton from 'ExButton';
import ExRouter from 'ExRouter';
import ExponentKernel from 'ExponentKernel';
import LocalStorage from 'LocalStorage';
import MenuView from 'MenuView';
import reactMixin from 'react-mixin';
import { connect } from 'react-redux';

const {
  ExponentConstants,
} = NativeModules;

const KERNEL_ROUTE_HOME = 0;
const KERNEL_ROUTE_BROWSER = 1;
const FORCE_TOUCH_SWITCH_THRESHOLD = 0.995;
const FORCE_TOUCH_CAPTURE_THRESHOLD = 0.85;

const ENABLE_LEGACY_MENU_BEHAVIOR = true;

const MENU_FADE_IN_TOTAL_MS = 400;
const MENU_FADE_IN_BEGIN_MS = 50; // make this one shorter

class KernelNavigator extends React.Component {
  static getDataProps(data) {
    let {
      isShell,
      shellManifestUrl,
      isHomeVisible,
      isMenuVisible,
      isNuxFinished,
      foregroundTaskUrl,
      tasks,
      history,
    } = data.browser;
    return {
      isShell,
      shellManifestUrl,
      isHomeVisible,
      isMenuVisible,
      isNuxFinished,
      foregroundTaskUrl,
      tasks,
      history,
      consoleHistory: data.console.history,
    };
  }

  constructor(props, context) {
    super(props, context);
    this.state = {
      menuTransition: new Animated.Value(0),
      isShowingOverlay: false,
    };
    this.state.menuTransition.addListener((event) => {
      let isShowingOverlay = (event.value > 0);
      if (isShowingOverlay !== this.state.isShowingOverlay) {
        this.setState({ isShowingOverlay });
      }
    });
  }

  componentWillMount() {
    this._homeRoute = ExRouter.getHomeRoute();
    this._browserRoutes = {};
    if (this.props.isShell) {
      ExponentKernel.routeDidForeground(KERNEL_ROUTE_BROWSER, {
        url: this.props.shellManifestUrl,
      });
    } else {
      ExponentKernel.routeDidForeground(KERNEL_ROUTE_HOME, null);
    }
  }

  componentDidMount() {
    this._refreshSubscription = Browser.addRefreshListener(async () => {
      // bypass validation of the link and just try to open it again
      if (this.props.isShell) {
        // don't clean browser routes: we'll make a new history item and task for
        // the same url, and the existing route's props will update.
        // don't call ExponentKernel.openURL because we don't want to re-route
        // the existing url. we want to force a reload of the manifest.
        this.props.dispatch(BrowserActions.setKernelLoadingState(true));
        this.props.dispatch(BrowserActions.navigateToUrlAsync(this.props.shellManifestUrl));
      } else if (this.props.foregroundTaskUrl) {
        let urlToRefresh = this.props.foregroundTaskUrl;
        await this.props.dispatch(BrowserActions.foregroundHomeAsync(true));
        this._cleanUnusedBrowserRoutes();
        ExponentKernel.openURL(urlToRefresh);
      }
    });
    DeviceEventEmitter.addListener('ExponentKernel.foregroundTask', this._foregroundTask);
    this._loadNuxStateAsync();
  }

  componentWillUnmount() {
    delete this._homeRoute;
    delete this._browserRoutes;
    if (this._refreshSubscription) {
      this._refreshSubscription.remove();
    }
  }

  componentWillReceiveProps(nextProps) {
    this._updateNavigator(nextProps);
    this._updateMenuFromProps(nextProps);
  }

  render() {
    let { isShell, shellManifestUrl, tasks, foregroundTaskUrl, isHomeVisible, isMenuVisible, isNuxFinished } = this.props;
    let initialRouteStack = (isShell) ? [this._findOrCreateBrowserRoute(shellManifestUrl)] : [this._homeRoute];

    let simulatorButton;
    if (ENABLE_LEGACY_MENU_BEHAVIOR) {
      // EXButton appears for simulators on computers with no force touch
      // because all the gestures are too annoying in this circumstance.
      if (!ExponentConstants.isDevice && tasks.size > 0 && !isShell) {
        // don't show it if the menu is currently on screen.
        if (isHomeVisible || !isMenuVisible || !isNuxFinished) {
          simulatorButton = (<ExButton onPress={this._switchTasks} />);
        }
      }
    }

    let menuView, menuOverlay;
    if (this.state.isShowingOverlay) {
      let backgroundColor = this.state.menuTransition.interpolate({
        inputRange: [0, 1],
        outputRange: ['rgba(0, 0, 0, 0.001)', 'rgba(0, 0, 0, 0.5)'],
      });
      menuOverlay = (
        <Animated.View style={[styles.menuOverlay, {backgroundColor}]} />
      );
    }
    if (isMenuVisible) {
      let task = tasks.get(foregroundTaskUrl);
      menuView = (
        <MenuView
          task={task}
          isNuxFinished={isNuxFinished}
          shouldFadeIn
        />
      );
    }

    let responders = {};
    if (ENABLE_LEGACY_MENU_BEHAVIOR) {
      responders = {
        onMoveShouldSetResponderCapture: this._onContainerMoveShouldSetResponderCapture,
        onStartShouldSetResponder: this._onContainerStartShouldSetResponder,
        onResponderGrant: this._onContainerResponderGrant,
        onResponderMove: this._onContainerResponderMove,
        onResponderRelease: this._onContainerResponderRelease,
      }
    }

    return (
      <View
        style={styles.container}
        {...responders}>
        <ExNavigator
          ref={component => { this._navigator = component; }}
          initialRouteStack={initialRouteStack}
          showNavigationBar={false}
          style={styles.navigator}
          sceneStyle={styles.scene}
        />
        {simulatorButton}
        {menuOverlay}
        {menuView}
      </View>
    );
  }

  @autobind
  _onContainerMoveShouldSetResponderCapture(event) {
    // if the force touch gesture is available/meaningful,
    // and user force touches on a subview that would otherwise capture the touch,
    // override that and become the responder.
    if (this._onContainerStartShouldSetResponder()) {
      if (View.forceTouchAvailable) {
        let { force } = event.nativeEvent;
        return (force > FORCE_TOUCH_CAPTURE_THRESHOLD);
      }
    }
    return false;
  }

  @autobind
  _onContainerStartShouldSetResponder() {
    return (!this.props.isShell && this.props.tasks.size > 0 && !this.props.isMenuVisible);
  }

  @autobind
  _onContainerResponderGrant(event) {
    this._hasTouch = true;
    this._handleTouch(event);
  }

  @autobind
  _onContainerResponderMove(event) {
    this._handleTouch(event);
  }

  @autobind
  _onContainerResponderRelease(event) {
    this._hasTouch = false;
    this._hasDoubleTouch = false;
    this._interruptMenuTransition();
  }

  @autobind
  _handleTouch(event) {
    let { force, touches } = event.nativeEvent;
    if (this._hasTouch) {
      if (View.forceTouchAvailable) {
        if (force >= FORCE_TOUCH_SWITCH_THRESHOLD && touches.length === 2) {
          this._switchTasks();
        }
      } else if (touches) {
        if (touches.length === 2 && !this._hasDoubleTouch) {
          this._hasDoubleTouch = true;
          // quickly start fading in the menu to respond to double touch
          this.setTimeout(() => {
            if (this._hasDoubleTouch) {
              this._transitionMenu(0.75, MENU_FADE_IN_TOTAL_MS - MENU_FADE_IN_BEGIN_MS);
            }
          }, MENU_FADE_IN_BEGIN_MS);
          this.setTimeout(() => {
            if (this._hasDoubleTouch) {
              this._switchTasks();
            }
          }, MENU_FADE_IN_TOTAL_MS);
        }
        if (touches.length !== 2 && this._hasDoubleTouch) {
          this._interruptMenuTransition();
          this._hasDoubleTouch = false;
        }
      }
    }
  }

  _updateMenuFromProps(nextProps) {
    if (nextProps.isMenuVisible) {
      this._transitionMenu(1, 200);
    } else {
      this._transitionMenu(0, 200);
    }
  }

  _interruptMenuTransition() {
    if (!this.props.isMenuVisible) {
      this._transitionMenu(0, 100);
    }
  }

  _transitionMenu(toValue, duration) {
    this.state.menuTransition.stopAnimation(() => {
      Animated.timing(this.state.menuTransition, {
        easing: Easing.inOut(Easing.quad),
        toValue,
        duration,
      }).start();
    });
  }

  @autobind
  _switchTasks() {
    this._hasTouch = false;
    this._hasDoubleTouch = false;
    if (this.props.isHomeVisible) {
      // since only one task besides Home is allowed, just pick the first nonnull url
      let urlsToForeground = this.props.tasks.keySeq().filter(taskUrl => (taskUrl !== null));
      if (urlsToForeground) {
        this.props.dispatch(BrowserActions.foregroundUrlAsync(urlsToForeground.first()));
      }
    } else {
      this.props.dispatch(BrowserActions.showMenuAsync(true));
      // this.props.dispatch(BrowserActions.foregroundHomeAsync());
    }
  }

  @autobind
  _foregroundTask(event) {
    let { taskUrl } = event;
    let matchingUrlTasks = this.props.tasks.keySeq().filter(url => (url === taskUrl));
    if (matchingUrlTasks) {
      this.props.dispatch(BrowserActions.foregroundUrlAsync(matchingUrlTasks.first()));
    }
  }

  _updateNavigator(nextProps) {
    // present the console if something happened in it
    let isBrowserChanged = false;
    if (this._shouldPushConsoleFromNextProps(nextProps)) {
        this.pushConsole(!this._isCurrentTaskDev());
    } else if (nextProps.foregroundTaskUrl !== this.props.foregroundTaskUrl) {
      if (nextProps.foregroundTaskUrl) {
        this.props.dispatch(ConsoleActions.clearConsole());
      }
      isBrowserChanged = true;
    } else {
      // same url but possibly new time (i.e. the user opened it again)
      let prevTime = (this.props.history && this.props.history.size) ? this.props.history.get(0).get('time') : 0;
      let nextTime = (nextProps.history && nextProps.history.size) ? nextProps.history.get(0).get('time') : 0;
      if (prevTime !== nextTime && nextTime !== 0) {
        isBrowserChanged = true;
      } else if (nextProps.isHomeVisible !== this.props.isHomeVisible) {
        isBrowserChanged = true;
      }
    }

    if (isBrowserChanged) {
      let prevUrl = this.props.foregroundTaskUrl;
      if (nextProps.isHomeVisible || !nextProps.foregroundTaskUrl) {
        requestAnimationFrame(() => this._foregroundHome(prevUrl));
      } else {
        let newUrl = nextProps.foregroundTaskUrl;
        let urlToBackground;
        if (prevUrl !== newUrl && !this.props.isHomeVisible) {
          urlToBackground = prevUrl;
          requestAnimationFrame(() => {
            this._foregroundHome(prevUrl);
            this._foregroundRouteForUrl(newUrl, null);
          });
        } else {
          requestAnimationFrame(() => this._foregroundRouteForUrl(newUrl, urlToBackground));
        }
      }
    }
  }

  _foregroundRouteForUrl(url, urlToBackground) {
    let routeToForeground = this._findOrCreateBrowserRoute(url);
    let currentRoutes = this._navigator.getCurrentRoutes();
    if (currentRoutes.indexOf(routeToForeground) !== -1) {
      this.props.dispatch(BrowserActions.setKernelLoadingState(false));
      this._navigator.jumpTo(routeToForeground);
    } else {
      this._cleanUnusedBrowserRoutes();
      this._navigator.push(routeToForeground);
    }
    ExponentKernel.routeDidForeground(KERNEL_ROUTE_BROWSER, { url, urlToBackground });
  }

  _foregroundHome(urlToBackground) {
    if (this.props.isShell) {
      // there is no home in this case, go back to the root shell task
      this._foregroundRouteForUrl(this.props.shellManifestUrl, urlToBackground);
    } else {
      if (this.isConsolePresented()) {
        this._navigator.pop();
      }
      this._navigator.jumpTo(this._homeRoute);
      ExponentKernel.routeDidForeground(KERNEL_ROUTE_HOME, { urlToBackground });
    }
  }

  _findOrCreateBrowserRoute(url) {
    if (this._browserRoutes[url]) {
      return this._browserRoutes[url];
    } else {
      let newRoute = ExRouter.getBrowserRoute(url);
      this._browserRoutes[url] = newRoute;
      return newRoute;
    }
  }

  _cleanUnusedBrowserRoutes() {
    let newBrowserRoutes = {};
    for (let key in this._browserRoutes) {
      if (this._browserRoutes.hasOwnProperty(key)) {
        let url = key;
        let matchingTasks = this.props.tasks.keySeq().filter(taskUrl => (taskUrl === url)).toList();
        if (matchingTasks && matchingTasks.size) {
          newBrowserRoutes[url] = this._browserRoutes[url];
        }
      }
    }
    this._browserRoutes = newBrowserRoutes;
  }

  @autobind
  isConsolePresented() {
    let currentRoute = this._navigator.navigationContext.currentRoute;
    let currentRoutes = this._navigator.getCurrentRoutes();
    let currentIndex = currentRoutes.findIndex(route => route === currentRoute);
    let isConsolePresented = currentRoutes
      .slice(0, currentIndex + 1)
      .some(route => route.isConsole);

    return isConsolePresented;
  }

  /**
   *  @param isUserFacing: if true, console should show a end-user-readable "oops" message
   *    overlaying the technical details of the console.
   */
  @autobind
  pushConsole(isUserFacing) {
    if (!this.isConsolePresented()) {
      this._navigator.push(ExRouter.getConsoleRoute(Browser.refresh, isUserFacing));
    }
  }

  _shouldPushConsoleFromNextProps(nextProps) {
    let currentConsoleSize = this.props.consoleHistory ? this.props.consoleHistory.size : 0;
    let newConsoleSize = nextProps.consoleHistory ? nextProps.consoleHistory.size : 0;
    if (newConsoleSize > currentConsoleSize) {
      // current behavior: Pop the console if the error was fatal and we're NOT developing (so no redbox happened).
      // TODO: user accounts: Additionally pop the console if the logged in user owns this experience.
      return this._isLastConsoleItemFatal(nextProps.consoleHistory) && !this._isCurrentTaskDev();
    }
    return false;
  }

  _isCurrentTaskDev() {
    let currentExperience = this.props.tasks.get(this.props.foregroundTaskUrl);
    return currentExperience &&
      currentExperience.manifest &&
      currentExperience.manifest.get('developer');
  }

  _isLastConsoleItemFatal(consoleHistory) {
    consoleHistory = consoleHistory || this.props.consoleHistory;
    if (consoleHistory) {
      let lastConsoleItem = consoleHistory.valueSeq().last();
      return (lastConsoleItem && lastConsoleItem.get('fatal'));
    }
    return false;
  }

  async _loadNuxStateAsync() {
    // load wherever the previous nux left off
    let isNuxFinished = await LocalStorage.getIsNuxFinishedAsync();
    if (isNuxFinished === 'true') {
      this.props.dispatch(BrowserActions.setIsNuxFinishedAsync(true));
    }

    // listen for kernel nux events
    DeviceEventEmitter.addListener('ExponentKernel.resetNuxState', () => {
      this.props.dispatch(BrowserActions.setIsNuxFinishedAsync(false));
    });
  }
}

reactMixin(KernelNavigator.prototype, TimerMixin);

export default connect(
  data => KernelNavigator.getDataProps(data)
)(KernelNavigator);

let styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  navigator: {
    backgroundColor: '#1272b6',
  },
  scene: {
    backgroundColor: 'transparent',
  },
  menuOverlay: {
    position: 'absolute',
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
  },
});
<|endoftext|>


<commit_before>#!/usr/bin/env node
/*
The MIT License (MIT)

Copyright (c) 2015 Bryan Hughes <bryan@theoreticalideations.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

var fs = require('fs');
var iniBuilder = require('ini-builder');

console.log('Checking if I2C is enabled at boot time');

var config = '';

try {
  config = fs.readFileSync('/boot/config.txt').toString()
} catch (e) {
  if (e.code == 'ENOENT') {
    console.log('A file will be created at /boot/config.txt');
  }
}

config = iniBuilder.parse(config, { commentDelimiter: '#' })

var changes = false;

var i2c_arm = iniBuilder.find(config, ['dtparam', 'i2c_arm']);
if (!i2c_arm) {
  config.push({
    path: ['dtparam', 'i2c_arm'],
    value: 'on'
  });
  changes = true;
} else if (i2c_arm.value != 'on') {
  i2c_arm.value = 'on';
  changes = true;
}

var i2c = iniBuilder.find(config, ['dtparam', 'i2c']);
if (!i2c) {
  config.push({
    path: ['dtparam', 'i2c'],
    value: 'on'
  });
  changes = true;
} else if (i2c.value != 'on') {
  i2c.value = 'on';
  changes = true;
}

var i2c_arm_baudrate = iniBuilder.find(config, ['dtparam', 'i2c_arm_baudrate']);
if (!i2c_arm_baudrate) {
  config.push({
    path: ['dtparam', 'i2c_arm_baudrate'],
    value: '100000'
  });
  changes = true;
}

if (changes) {
  console.log('Enabled I2C at boot time');
  fs.writeFileSync('/boot/config.txt', iniBuilder.serialize(config));
  console.warn('YOU MUST REBOOT YOUR PI BEFORE USING I2C!');
} else {
  console.log('I2C is already enabled at boot time');
}
<commit_msg>Fix linting errors<commit_after>#!/usr/bin/env node
/*
The MIT License (MIT)

Copyright (c) 2015 Bryan Hughes <bryan@theoreticalideations.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

var fs = require('fs');
var iniBuilder = require('ini-builder');

console.log('Checking if I2C is enabled at boot time');

var config = '';

try {
  config = fs.readFileSync('/boot/config.txt').toString();
} catch (e) {
  if (e.code == 'ENOENT') {
    console.log('A file will be created at /boot/config.txt');
  }
}

config = iniBuilder.parse(config, { commentDelimiter: '#' });

var changes = false;

var i2c_arm = iniBuilder.find(config, ['dtparam', 'i2c_arm']);
if (!i2c_arm) {
  config.push({
    path: ['dtparam', 'i2c_arm'],
    value: 'on'
  });
  changes = true;
} else if (i2c_arm.value != 'on') {
  i2c_arm.value = 'on';
  changes = true;
}

var i2c = iniBuilder.find(config, ['dtparam', 'i2c']);
if (!i2c) {
  config.push({
    path: ['dtparam', 'i2c'],
    value: 'on'
  });
  changes = true;
} else if (i2c.value != 'on') {
  i2c.value = 'on';
  changes = true;
}

var i2c_arm_baudrate = iniBuilder.find(config, ['dtparam', 'i2c_arm_baudrate']);
if (!i2c_arm_baudrate) {
  config.push({
    path: ['dtparam', 'i2c_arm_baudrate'],
    value: '100000'
  });
  changes = true;
}

if (changes) {
  console.log('Enabled I2C at boot time');
  fs.writeFileSync('/boot/config.txt', iniBuilder.serialize(config));
  console.warn('YOU MUST REBOOT YOUR PI BEFORE USING I2C!');
} else {
  console.log('I2C is already enabled at boot time');
}
<|endoftext|>


<commit_before>var I18n = function () {
  this.locale = jQuery.cookie('locale') || 'en';
  this.template = {
    error: "Error",
    denied: "Access Denied",
    voting: "Voting",
    vote_saved: "Vote saved",
    'vote.remove': "Remove vote",
    'vote.good': "Good",
    'vote.great': "Great",
    'vote.fav': "Favorite",
    '__default__': "no translations"
  };
  if (this.locale !== 'en') {
    this.fetch(this.locale);
  }
};

I18n.prototype.fetch = function (locale) {
  var th = this;
  try {
    jQuery.ajax('/assets/'+locale+'.json', {
      dataType: 'json'
    }).done(function (data) {
      th.template = data;
    });
  } catch (error) {}
  return false;
};

I18n.prototype.translate = function (label) {
  if (arguments.length > 1) {
    return this.subst(arguments);
  }
  return this.template[label] || this.template['__default__'];
};

I18n.prototype.subst = function () {
  var args = arguments,
      tl = this.template[arguments[0]] || this.template['__default__'];
  function repl(str, p1, off, s) {
    return args[p1];
  }
  return tl.replace(/\$(\d+)/g, repl);
};

var i18n = new I18n(),
    t = function(label) {
      return i18n.translate(label);
    };
<commit_msg>Remove i18n.js<commit_after><|endoftext|>


<commit_before>// Copyright 2013-2019, University of Colorado Boulder

/**
 * Reset All button, typically used to reset everything ('reset all') on a Screen.
 * Extends ResetButton, adding things that are specific to 'reset all'.
 *
 * @author John Blanco
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ActivationUtterance = require( 'SCENERY_PHET/accessibility/ActivationUtterance' );
  var BooleanIO = require( 'TANDEM/types/BooleanIO' );
  var DerivedProperty = require( 'AXON/DerivedProperty' );
  var DerivedPropertyIO = require( 'AXON/DerivedPropertyIO' );
  var inherit = require( 'PHET_CORE/inherit' );
  var PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' );
  var ResetButton = require( 'SCENERY_PHET/buttons/ResetButton' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var SceneryPhetA11yStrings = require( 'SCENERY_PHET/SceneryPhetA11yStrings' );
  var SoundClip = require( 'TAMBO/sound-generators/SoundClip' );
  var soundManager = require( 'TAMBO/soundManager' );
  var Tandem = require( 'TANDEM/Tandem' );
  var utteranceQueue = require( 'SCENERY_PHET/accessibility/utteranceQueue' );

  // constants
  var RESET_ALL_BUTTON_RADIUS = 20.8;

  // sounds
  const resetAllSound = require( 'sound!TAMBO/reset-all.mp3' );

  // a11y strings - not translatable
  var resetAllButtonNameString = SceneryPhetA11yStrings.resetAllLabelString.value;
  var resetAllAlertString = SceneryPhetA11yStrings.resetAllAlertString.value;

  /**
   * @param {Object} [options]
   * @constructor
   */
  function ResetAllButton( options ) {

    options = _.extend( {
      radius: RESET_ALL_BUTTON_RADIUS,

      // Fine tuned in https://github.com/phetsims/tasks/issues/985 and should not be overriden lightly
      touchAreaDilation: 5.2,
      baseColor: PhetColorScheme.RESET_ALL_BUTTON_BASE_COLOR,
      arrowColor: 'white',
      tandem: Tandem.required,

      phetioDocumentation: 'The orange, round button that can be used to restore the initial state',

      // sound, supply an alternative if desired or set to null for no sound
      soundInfo: resetAllSound,

      // a11y
      innerContent: resetAllButtonNameString
    }, options );

    ResetButton.call( this, options );

    // @private - Mirrored property of `buttonModel.isFiringProperty`, but is phet-io instrumented.
    var isFiringProperty = new DerivedProperty( [ this.buttonModel.isFiringProperty ], function( a ) { return a; }, {
      tandem: options.tandem.createTandem( 'isFiringProperty' ),
      phetioDocumentation: 'Temporarily becomes true while the Reset All button is firing.  Commonly used to disable audio effects during reset.',
      phetioType: DerivedPropertyIO( BooleanIO ),
      phetioState: false // this is a transient property based on user interaction, should not be stored in the state
    } );

    // sound generation
    let resetAllSoundClip;
    if ( options.soundInfo ) {
      resetAllSoundClip = new SoundClip( options.soundInfo, { initialOutputLevel: 0.7 } );
      soundManager.addSoundGenerator( resetAllSoundClip );

      // play the sound when fired
      isFiringProperty.lazyLink( function( isFiring ) {
        if ( isFiring ) {
          resetAllSoundClip.play();
        }
      } );
    }

    // a11y - when reset all button is fired, disable alerts so that there isn't an excessive stream of alerts
    // while many Properties are reset. When callbacks are ended for reset all, enable alerts again and announce an
    // alert that everything was reset.
    var resetUtterance = new ActivationUtterance( { alert: resetAllAlertString } );
    isFiringProperty.lazyLink( function( isFiring ) {
      utteranceQueue.enabled = !isFiring;

      if ( isFiring ) {
        utteranceQueue.clear();
      }
      else {
        utteranceQueue.addToBack( resetUtterance );
      }
    } );

    // @private - dispose function
    this.disposeResetAllButton = function() {
      if ( options.soundInfo ) {
        soundManager.removeSoundGenerator( resetAllSoundClip );
        resetAllSoundClip.dispose();
      }
      isFiringProperty.dispose();
    };
  }

  sceneryPhet.register( 'ResetAllButton', ResetAllButton );

  return inherit( ResetButton, ResetAllButton, {

    /**
     * Make eligible for garbage collection.
     * @public
     */
    dispose: function() {
      this.disposeResetAllButton();
      ResetButton.prototype.dispose.call( this );
    }
  } );
} );
<commit_msg>removed common sound implementation (put it on a branch), see https://github.com/phetsims/tambo/issues/73<commit_after>// Copyright 2013-2019, University of Colorado Boulder

/**
 * Reset All button, typically used to reset everything ('reset all') on a Screen.
 * Extends ResetButton, adding things that are specific to 'reset all'.
 *
 * @author John Blanco
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ActivationUtterance = require( 'SCENERY_PHET/accessibility/ActivationUtterance' );
  var BooleanIO = require( 'TANDEM/types/BooleanIO' );
  var DerivedProperty = require( 'AXON/DerivedProperty' );
  var DerivedPropertyIO = require( 'AXON/DerivedPropertyIO' );
  var inherit = require( 'PHET_CORE/inherit' );
  var PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' );
  var ResetButton = require( 'SCENERY_PHET/buttons/ResetButton' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var SceneryPhetA11yStrings = require( 'SCENERY_PHET/SceneryPhetA11yStrings' );
  var Tandem = require( 'TANDEM/Tandem' );
  var utteranceQueue = require( 'SCENERY_PHET/accessibility/utteranceQueue' );

  // constants
  var RESET_ALL_BUTTON_RADIUS = 20.8;

  // a11y strings - not translatable
  var resetAllButtonNameString = SceneryPhetA11yStrings.resetAllLabelString.value;
  var resetAllAlertString = SceneryPhetA11yStrings.resetAllAlertString.value;

  /**
   * @param {Object} [options]
   * @constructor
   */
  function ResetAllButton( options ) {

    options = _.extend( {
      radius: RESET_ALL_BUTTON_RADIUS,

      // Fine tuned in https://github.com/phetsims/tasks/issues/985 and should not be overriden lightly
      touchAreaDilation: 5.2,
      baseColor: PhetColorScheme.RESET_ALL_BUTTON_BASE_COLOR,
      arrowColor: 'white',
      tandem: Tandem.required,

      phetioDocumentation: 'The orange, round button that can be used to restore the initial state',

      // a11y
      innerContent: resetAllButtonNameString
    }, options );

    ResetButton.call( this, options );

    // @private - Mirrored property of `buttonModel.isFiringProperty`, but is phet-io instrumented.
    this.isFiringProperty = new DerivedProperty( [ this.buttonModel.isFiringProperty ], function( a ) { return a; }, {
      tandem: options.tandem.createTandem( 'isFiringProperty' ),
      phetioDocumentation: 'Temporarily becomes true while the Reset All button is firing.  Commonly used to disable audio effects during reset.',
      phetioType: DerivedPropertyIO( BooleanIO ),
      phetioState: false // this is a transient property based on user interaction, should not be stored in the state
    } );

    // a11y - when reset all button is fired, disable alerts so that there isn't an excessive stream of alerts
    // while many Properties are reset. When callbacks are ended for reset all, enable alerts again and announce an
    // alert that everything was reset.
    var resetUtterance = new ActivationUtterance( { alert: resetAllAlertString } );
    this.isFiringProperty.lazyLink( function( isFiring ) {
      utteranceQueue.enabled = !isFiring;

      if ( isFiring ) {
        utteranceQueue.clear();
      }
      else {
        utteranceQueue.addToBack( resetUtterance );
      }
    } );
  }

  sceneryPhet.register( 'ResetAllButton', ResetAllButton );

  return inherit( ResetButton, ResetAllButton, {

    /**
     * Make eligible for garbage collection.
     * @public
     */
    dispose: function() {

      this.isFiringProperty.dispose();

      ResetButton.prototype.dispose.call( this );
    }
  } );
} );
<|endoftext|>


<commit_before>/* -*- coding: utf-8 -*- */
/* global cforum, Faye */

cforum = {
  utils: {
    exec: function(controller, action) {
      var ns = cforum;
      action = (action === undefined) ? "init" : action;

      if(controller.length > 0) {
        for(var i = 0; i < controller.length; ++i) {
          if(!ns[controller[i]]) {
            return;
          }

          ns = ns[controller[i]];
        }

        if(typeof ns[action] == "function") {
          ns[action]();
        }
      }
    },

    init: function() {
      var body = document.body, controller = body.getAttribute("data-controller"), action = body.getAttribute("data-action");

      var pieces = controller.split('/');
      controller = [];
      for(var i = 0; i < pieces.length; ++i) {
        if(pieces[i] == "") {
          continue;
        }

        controller.push(pieces[i]);
      }

      cforum.utils.exec(["common"]);
      cforum.utils.exec(controller);
      cforum.utils.exec(controller, action);
    }
  },

  common: {
    init: function() {
      if(typeof Faye !== 'undefined') {
        cforum.client = new Faye.Client(cforum.fayeUrl, {timeout: 120, retry: 5});

        cforum.client.on('transport:up', function() { $(".cf-right-nav .username").addClass('connected'); });
        cforum.client.on('transport:down', function() { $(".cf-right-nav .username").removeClass('connected'); });
      }
    }
  }
};

$(document).ready(cforum.utils.init);

/* eof */
<commit_msg>fix: avoid JS errors when faye isn't loaded<commit_after>/* -*- coding: utf-8 -*- */
/* global cforum, Faye */

cforum = {
  utils: {
    exec: function(controller, action) {
      var ns = cforum;
      action = (action === undefined) ? "init" : action;

      if(controller.length > 0) {
        for(var i = 0; i < controller.length; ++i) {
          if(!ns[controller[i]]) {
            return;
          }

          ns = ns[controller[i]];
        }

        if(typeof ns[action] == "function") {
          ns[action]();
        }
      }
    },

    init: function() {
      var body = document.body, controller = body.getAttribute("data-controller"), action = body.getAttribute("data-action");

      var pieces = controller.split('/');
      controller = [];
      for(var i = 0; i < pieces.length; ++i) {
        if(pieces[i] == "") {
          continue;
        }

        controller.push(pieces[i]);
      }

      cforum.utils.exec(["common"]);
      cforum.utils.exec(controller);
      cforum.utils.exec(controller, action);
    }
  },

  common: {
    init: function() {
      if(typeof Faye !== 'undefined') {
        cforum.client = new Faye.Client(cforum.fayeUrl, {timeout: 120, retry: 5});

        cforum.client.on('transport:up', function() { $(".cf-right-nav .username").addClass('connected'); });
        cforum.client.on('transport:down', function() { $(".cf-right-nav .username").removeClass('connected'); });
      }
      else {
        cforum.client = {
          on: function() {},
          subscribe: function() {}
        }
      }
    }
  }
};

$(document).ready(cforum.utils.init);

/* eof */
<|endoftext|>


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

/**
 * A checkbox that is labeled with text, with an optional icon to the right of the text.
 * This also provides consistent font and textNode.maxWidth for all checkboxes in the sim.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const Checkbox = require( 'SUN/Checkbox' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const HBox = require( 'SCENERY/nodes/HBox' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const RichText = require( 'SCENERY/nodes/RichText' );

  class GQCheckbox extends Checkbox {

    /**
     * @param {string} text - supports RichText
     * @param {BooleanProperty} booleanProperty
     * @param {Object} [options]
     */
    constructor( text, booleanProperty, options ) {

      options = _.extend( {
        textFill: 'black',
        icon: null // {Node|null} optional icon, to the right of text
      }, options );

      const textNode = new RichText( text, {
        fill: options.textFill,
        font: new PhetFont( GQConstants.CHECKBOX_LABEL_FONT_SIZE ),
        maxWidth: GQConstants.CHECKBOX_TEXT_MAX_WIDTH
      } );

      let content = null;
      if ( options.icon ) {
        content = new HBox( {
          align: 'center',
          spacing: GQConstants.CHECKBOX_ICON_SPACING,
          children: [ textNode, options.icon ]
        } );
      }
      else {
        content = textNode;
      }

      super( content, booleanProperty, options );
    }
  }

  return graphingQuadratics.register( 'GQCheckbox', GQCheckbox );
} );<commit_msg>Tandem.required in GQCheckbox base class, #64<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * A checkbox that is labeled with text, with an optional icon to the right of the text.
 * This also provides consistent font and textNode.maxWidth for all checkboxes in the sim.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const Checkbox = require( 'SUN/Checkbox' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const HBox = require( 'SCENERY/nodes/HBox' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const RichText = require( 'SCENERY/nodes/RichText' );
  const Tandem = require( 'TANDEM/Tandem' );

  class GQCheckbox extends Checkbox {

    /**
     * @param {string} text - supports RichText
     * @param {BooleanProperty} booleanProperty
     * @param {Object} [options]
     */
    constructor( text, booleanProperty, options ) {

      options = _.extend( {
        textFill: 'black',
        icon: null, // {Node|null} optional icon, to the right of text
        tandem: Tandem.required // {Tandem} all Checkboxes in this sim must be instrumented
      }, options );

      const textNode = new RichText( text, {
        fill: options.textFill,
        font: new PhetFont( GQConstants.CHECKBOX_LABEL_FONT_SIZE ),
        maxWidth: GQConstants.CHECKBOX_TEXT_MAX_WIDTH
      } );

      let content = null;
      if ( options.icon ) {
        content = new HBox( {
          align: 'center',
          spacing: GQConstants.CHECKBOX_ICON_SPACING,
          children: [ textNode, options.icon ]
        } );
      }
      else {
        content = textNode;
      }

      super( content, booleanProperty, options );
    }
  }

  return graphingQuadratics.register( 'GQCheckbox', GQCheckbox );
} );<|endoftext|>


<commit_before>import {React, Component, PropTypes} from '../imports';
import {CookieStorage} from './storage/cookie-storage';
import {LocalStorage} from './storage/local-storage';
import {Authorization} from './authorization';
import {WSDropdown} from '../ws-dropdown/ws-dropdown';

/**
 * This component renders a generic header which provides authentication and language management
 *
 * Optionally call WSHeader.setStorageType('cookie', 'zalando') If you want a to use cookies instead of localStorage
 * to persist the tokens. You can call WSHeader.getAccessToken().then(token => ...) to get the current access token.
 * It will resolve null when no access token is present and therefore the user isn't logged in.
 */
export class WSHeader extends Component {

  /**
   * @type {Authorization}
   */
  static authorization = undefined;

  /**
   * Default storage instance
   * @type {AbstractStorage}
   */
  static storage = new LocalStorage();

  static defaultProps = {
    loginUrl: 'https://identity.zalando.com/oauth2/authorize',
    businessPartnerId: null,
    clientId: null,
    links: [],
    appName: 'Zalando',
    appLogo: null,
    onLocaleChange: () => {},
    onAuthChange: () => {}
  };

  static propTypes = {
    loginUrl: PropTypes.string,
    businessPartnerId: PropTypes.string,
    clientId: PropTypes.string,
    links: PropTypes.array,
    appName: PropTypes.string,
    appLogo: PropTypes.string,
    onLocaleChange: PropTypes.func,
    onAuthChange: PropTypes.func
  };

  /**
   * Initialize the storage
   * @param {string} type Can be either cookie or local
   * @param {string} name Storage name will be used as key prefix
   * @returns {void}
   */
  static setStorageType(type, name) {
    // Create storage for persisting keys
    if (type === 'cookie') {
      this.storage = new CookieStorage(name);
    } else {
      this.storage = new LocalStorage(name);
    }
  }

  /**
   * Tries to get the access token from authorization class
   * @param {string} queryString The current query string to parse the token from
   * @returns {string|null}
   */
  static getAccessToken(queryString = location.hash.substr(1)) {
    this.authorization = this.authorization || new Authorization(this.storage);
    if (!this.authorization.accessToken) {
      this.authorization.tryFetchToken(queryString);
    }
    return this.authorization.accessToken;
  }

  /**
   * Unauthorize will remove the access token from storage
   * @returns {void}
   */
  static removeAccessToken() {
    this.authorization = this.authorization || new Authorization(this.storage);
    this.authorization.unauthorize();
  }

  /**
   * Get abbreviation for the user the access token is issued for
   * @returns {string|null}
   */
  static getUserAbbreviation() {
    try {
      const token = this.getAccessToken();
      const parts = token.split('.');
      const json = JSON.parse(atob(parts[1]));
      // Find key which contains the name
      const nameKey = Object.keys(json).find(key => key.includes('managed-id'));
      return json[nameKey];
    } catch (e) {
      return null;
    }
  }

  /**
   * Retrieve the persisted locale
   * @returns {string}
   */
  static getLocale() {
    const locale = WSHeader.storage.get('locale') || window.navigator.language.replace(/([a-z]+)-.*/, '$1');
    if (['de', 'en'].indexOf(locale) === -1) {
      return 'en';
    }
    return locale;
  }

  /**
   * @param {Object} props React/Preact properties
   */
  constructor(props) {
    super(props);
    this.initState();
    this.initAuthorization(props);
    this.mounted = false;
    this.locales = [
      {label: 'German', value: 'de'},
      {label: 'English', value: 'en'}
    ];
    this.subMenus = [];
    this.menuItems = [];
    this.level2 = null;
  }

  /**
   * Detect mounted state to prevent calling setState to early
   * @returns {void}
   */
  componentDidMount() {
    this.mounted = true;
  }

  /**
   * Changes the locale to the given one
   * @param {string} newLocale The new locale
   * @returns {void}
   */
  setLocale(newLocale) {
    this.setState({locale: newLocale});
    WSHeader.storage.set('locale', newLocale);
    this.dispatchEvent('ws-locale-changed', newLocale);
    // Propagate value in React world
    if (typeof this.props.onLocaleChange === 'function') {
      this.props.onLocaleChange(newLocale);
    }
  }

  /**
   * Initialize the component state
   * @returns {void}
   */
  initState() {
    this.state = {
      isLoggedIn: !!(this.constructor.authorization && this.constructor.authorization.accessToken),
      locale: WSHeader.getLocale()
    };
  }

  /**
   * Initialize the OAuth2 authorization
   * @returns {void}
   */
  initAuthorization() {
    // Initialize authorization with implicit flow
    this.constructor.authorization = this.constructor.authorization || new Authorization(WSHeader.storage);
    // Listen to authorization changes
    this.constructor.authorization.onAccessTokenChange(accessToken => {
      if (this.mounted) {
        this.setState({isLoggedIn: !!accessToken});
      } else {
        this.state.isLoggedIn = !!accessToken;
      }

      this.dispatchEvent('ws-auth-changed', accessToken);
    });
    // Check if we was redirected from the auth page and an access token is available
    this.constructor.authorization.tryFetchToken(location.hash.substr(1));
    // Listen for authentication requests
    window.addEventListener('ws-authorize', () => this.login());
    // Listen for authentication removal requests
    window.addEventListener('ws-unauthorize', () => this.logout());
  }

  /**
   * Trigger login process
   * @returns {void}
   */
  login() {
    this.constructor.authorization.authorize(this.props.loginUrl, this.props.clientId, this.props.businessPartnerId);
  }

  /**
   * Delete tokens from storage to logout
   * @returns {void}
   */
  logout() {
    this.constructor.authorization.unauthorize();
  }

  /**
   * Get's called when the mouse enters a menu item
   * @param {number} index The index of the item in the link list
   * @returns {void}
   */
  enterMenuItem(index) {
    clearInterval(this.leaveTimeout);
    this.subMenus.forEach(subMenu => subMenu.classList.remove('is-active'));
    this.menuItems.forEach(item => item.classList.remove('is-hovered'));
    // Show sub menu or hide level 2 completely
    if (this.subMenus[index]) {
      this.level2.classList.add('is-active');
      const subMenu = this.subMenus[index];
      subMenu.classList.add('is-active');
      const item = this.menuItems[index];
      item.classList.add('is-hovered');
    } else {
      this.leaveLevel2();
    }
  }

  /**
   * Get's called when the mouse leaves a menu item
   * @param {number} index The index of the item in the link list
   * @returns {void}
   */
  leaveMenuItem(index) {
    this.leaveTimeout = setTimeout(() => {
      this.level2.classList.remove('is-active');
      if (this.subMenus[index]) {
        const subMenu = this.subMenus[index];
        subMenu.classList.remove('is-active');
        const item = this.menuItems[index];
        item.classList.remove('is-hovered');
      }
    }, 10);
  }

  /**
   * Get's called when the mouse enters into the level 2 (sub menu)
   * @returns {void}
   */
  enterLevel2() {
    clearInterval(this.leaveTimeout);
  }

  /**
   * Get's called when the mouse leaves the level 2 (sub menu)
   * @returns {void}
   */
  leaveLevel2() {
    this.subMenus.forEach(subMenu => subMenu.classList.remove('is-active'));
    this.menuItems.forEach(item => item.classList.remove('is-hovered'));
    this.level2.classList.remove('is-active');
  }

  /**
   * @returns {Object}
   */
  render() {
    return (
       { this.element = element; }}>

            {this.props.appLogo &&

            }
            {this.props.appName}

              {this.props.links.map((link, index) =>
                <li
                  key={`header-link${index}`}
                  onMouseEnter={() => this.enterMenuItem(index)}
                  onMouseLeave={() => this.leaveMenuItem(index)}
                  ref={element => { this.menuItems[index] = element; }}
                  className={(link.isCurrent) ? 'is-current' : null}
                >
                   { if (link.onClick) link.onClick(event); }}>
                    {link.label}

              )}

                <WSDropdown
                  className="locale"
                  icon="icon24 icon-sort-down"
                  items={this.locales}
                  text={this.state.locale}
                  onChange={item => this.setLocale(item.value)}
                  orientation="right"
                  type="anchor"
                />

              {!this.state.isLoggedIn ?

 this.login()}>
                  Login

              :

 this.logout()}>

              }

 this.enterLevel2()}
          onMouseLeave={() => this.leaveLevel2()}
          onClick={() => this.leaveLevel2()}
          ref={element => { this.level2 = element; }}
        >
          {this.props.links.map((parent, index) =>
            parent.children && parent.children.length &&
            <ul className="main-sub-menu" key={`sub-menu${index}`} ref={element => { this.subMenus[index] = element; }}>
              {parent.children.map((child, childIndex) =>
                <li key={`sub-link-${index}-${childIndex}`} className={(child.isCurrent) ? 'is-current' : null}>
                   { if (child.onClick) child.onClick(event); }}>
                    {child.label}

              )}

          )}

    );
  }
}
<commit_msg>add properties to show and hide login and locale and new property to set the root url<commit_after>import {React, Component, PropTypes} from '../imports';
import {CookieStorage} from './storage/cookie-storage';
import {LocalStorage} from './storage/local-storage';
import {Authorization} from './authorization';
import {WSDropdown} from '../ws-dropdown/ws-dropdown';

/**
 * This component renders a generic header which provides authentication and language management
 *
 * Optionally call WSHeader.setStorageType('cookie', 'zalando') If you want a to use cookies instead of localStorage
 * to persist the tokens. You can call WSHeader.getAccessToken().then(token => ...) to get the current access token.
 * It will resolve null when no access token is present and therefore the user isn't logged in.
 * @property {Object} props React properties object
 * @property {string} props.loginUrl property used to set the application login url
 * @property {string} props.businessPartnerId property used to set the application businessPartnerId
 * @property {string} props.clientId property used to set the application yourturn clientId
 * @property {Array<Object>} props.links property used to set the list of links with multiple levels
 * @property {string} props.appName property used to set the application name
 * @property {string} props.appLogo property used to set the application image logo
 * @property {string} props.rootUrl property used to set the root application url
 * @property {Boolean} props.showLocale Flag used to show the locale dropdown
 * @property {Boolean} props.showAuthorization Flag to show the login area
 * @property {Function} props.onLocaleChange Function used to propagate data
 * @property {Function} props.onAuthChange Function used to propagate data
 */
export class WSHeader extends Component {

  /**
   * @type {Authorization}
   */
  static authorization = undefined;

  /**
   * Default storage instance
   * @type {AbstractStorage}
   */
  static storage = new LocalStorage();

  static defaultProps = {
    loginUrl: 'https://identity.zalando.com/oauth2/authorize',
    businessPartnerId: null,
    clientId: null,
    links: [],
    appName: 'Zalando',
    appLogo: null,
    rootUrl: '#',
    showLocale: true,
    showAuthorization: true,
    onLocaleChange: () => {},
    onAuthChange: () => {}
  };

  static propTypes = {
    loginUrl: PropTypes.string,
    businessPartnerId: PropTypes.string,
    clientId: PropTypes.string,
    links: PropTypes.array,
    appName: PropTypes.string,
    appLogo: PropTypes.string,
    onLocaleChange: PropTypes.func,
    onAuthChange: PropTypes.func,
    rootUrl: PropTypes.string,
    showLocale: PropTypes.bool,
    showAuthorization: PropTypes.bool
  };

  /**
   * Initialize the storage
   * @param {string} type Can be either cookie or local
   * @param {string} name Storage name will be used as key prefix
   * @returns {void}
   */
  static setStorageType(type, name) {
    // Create storage for persisting keys
    if (type === 'cookie') {
      this.storage = new CookieStorage(name);
    } else {
      this.storage = new LocalStorage(name);
    }
  }

  /**
   * Tries to get the access token from authorization class
   * @param {string} queryString The current query string to parse the token from
   * @returns {string|null}
   */
  static getAccessToken(queryString = location.hash.substr(1)) {
    this.authorization = this.authorization || new Authorization(this.storage);
    if (!this.authorization.accessToken) {
      this.authorization.tryFetchToken(queryString);
    }
    return this.authorization.accessToken;
  }

  /**
   * Unauthorize will remove the access token from storage
   * @returns {void}
   */
  static removeAccessToken() {
    this.authorization = this.authorization || new Authorization(this.storage);
    this.authorization.unauthorize();
  }

  /**
   * Get abbreviation for the user the access token is issued for
   * @returns {string|null}
   */
  static getUserAbbreviation() {
    try {
      const token = this.getAccessToken();
      const parts = token.split('.');
      const json = JSON.parse(atob(parts[1]));
      // Find key which contains the name
      const nameKey = Object.keys(json).find(key => key.includes('managed-id'));
      return json[nameKey];
    } catch (e) {
      return null;
    }
  }

  /**
   * Retrieve the persisted locale
   * @returns {string}
   */
  static getLocale() {
    const locale = WSHeader.storage.get('locale') || window.navigator.language.replace(/([a-z]+)-.*/, '$1');
    if (['de', 'en'].indexOf(locale) === -1) {
      return 'en';
    }
    return locale;
  }

  /**
   * @param {Object} props React/Preact properties
   */
  constructor(props) {
    super(props);
    this.initState();
    this.initAuthorization(props);
    this.mounted = false;
    this.locales = [
      {label: 'German', value: 'de'},
      {label: 'English', value: 'en'}
    ];
    this.subMenus = [];
    this.menuItems = [];
    this.level2 = null;
  }

  /**
   * Detect mounted state to prevent calling setState to early
   * @returns {void}
   */
  componentDidMount() {
    this.mounted = true;
  }

  /**
   * Changes the locale to the given one
   * @param {string} newLocale The new locale
   * @returns {void}
   */
  setLocale(newLocale) {
    this.setState({locale: newLocale});
    WSHeader.storage.set('locale', newLocale);
    this.dispatchEvent('ws-locale-changed', newLocale);
    // Propagate value in React world
    if (typeof this.props.onLocaleChange === 'function') {
      this.props.onLocaleChange(newLocale);
    }
  }

  /**
   * Initialize the component state
   * @returns {void}
   */
  initState() {
    this.state = {
      isLoggedIn: !!(this.constructor.authorization && this.constructor.authorization.accessToken),
      locale: WSHeader.getLocale()
    };
  }

  /**
   * Initialize the OAuth2 authorization
   * @returns {void}
   */
  initAuthorization() {
    // Initialize authorization with implicit flow
    this.constructor.authorization = this.constructor.authorization || new Authorization(WSHeader.storage);
    // Listen to authorization changes
    this.constructor.authorization.onAccessTokenChange(accessToken => {
      if (this.mounted) {
        this.setState({isLoggedIn: !!accessToken});
      } else {
        this.state.isLoggedIn = !!accessToken;
      }

      this.dispatchEvent('ws-auth-changed', accessToken);
    });
    // Check if we was redirected from the auth page and an access token is available
    this.constructor.authorization.tryFetchToken(location.hash.substr(1));
    // Listen for authentication requests
    window.addEventListener('ws-authorize', () => this.login());
    // Listen for authentication removal requests
    window.addEventListener('ws-unauthorize', () => this.logout());
  }

  /**
   * Trigger login process
   * @returns {void}
   */
  login() {
    this.constructor.authorization.authorize(this.props.loginUrl, this.props.clientId, this.props.businessPartnerId);
  }

  /**
   * Delete tokens from storage to logout
   * @returns {void}
   */
  logout() {
    this.constructor.authorization.unauthorize();
  }

  /**
   * Get's called when the mouse enters a menu item
   * @param {number} index The index of the item in the link list
   * @returns {void}
   */
  enterMenuItem(index) {
    clearInterval(this.leaveTimeout);
    this.subMenus.forEach(subMenu => subMenu.classList.remove('is-active'));
    this.menuItems.forEach(item => item.classList.remove('is-hovered'));
    // Show sub menu or hide level 2 completely
    if (this.subMenus[index]) {
      this.level2.classList.add('is-active');
      const subMenu = this.subMenus[index];
      subMenu.classList.add('is-active');
      const item = this.menuItems[index];
      item.classList.add('is-hovered');
    } else {
      this.leaveLevel2();
    }
  }

  /**
   * Get's called when the mouse leaves a menu item
   * @param {number} index The index of the item in the link list
   * @returns {void}
   */
  leaveMenuItem(index) {
    this.leaveTimeout = setTimeout(() => {
      this.level2.classList.remove('is-active');
      if (this.subMenus[index]) {
        const subMenu = this.subMenus[index];
        subMenu.classList.remove('is-active');
        const item = this.menuItems[index];
        item.classList.remove('is-hovered');
      }
    }, 10);
  }

  /**
   * Get's called when the mouse enters into the level 2 (sub menu)
   * @returns {void}
   */
  enterLevel2() {
    clearInterval(this.leaveTimeout);
  }

  /**
   * Get's called when the mouse leaves the level 2 (sub menu)
   * @returns {void}
   */
  leaveLevel2() {
    this.subMenus.forEach(subMenu => subMenu.classList.remove('is-active'));
    this.menuItems.forEach(item => item.classList.remove('is-hovered'));
    this.level2.classList.remove('is-active');
  }

  /**
   * @returns {Object}
   */
  render() {
    return (
       { this.element = element; }}>

            {this.props.appLogo &&

            }
            {this.props.appName}

              {this.props.links.map((link, index) =>
                <li
                  key={`header-link${index}`}
                  onMouseEnter={() => this.enterMenuItem(index)}
                  onMouseLeave={() => this.leaveMenuItem(index)}
                  ref={element => { this.menuItems[index] = element; }}
                  className={(link.isCurrent) ? 'is-current' : null}
                >
                   { if (link.onClick) link.onClick(event); }}>
                    {link.label}

              )}

              {this.props.showLocale &&

                <WSDropdown
                  className="locale"
                  icon="icon24 icon-sort-down"
                  items={this.locales}
                  text={this.state.locale}
                  onChange={item => this.setLocale(item.value)}
                  orientation="right"
                  type="anchor"
                />

}
              {this.props.showAuthorization && (!this.state.isLoggedIn ?

 this.login()}>
                  Login

              :

 this.logout()}>

              )}

 this.enterLevel2()}
          onMouseLeave={() => this.leaveLevel2()}
          onClick={() => this.leaveLevel2()}
          ref={element => { this.level2 = element; }}
        >
          {this.props.links.map((parent, index) =>
            parent.children && parent.children.length &&
            <ul className="main-sub-menu" key={`sub-menu${index}`} ref={element => { this.subMenus[index] = element; }}>
              {parent.children.map((child, childIndex) =>
                <li key={`sub-link-${index}-${childIndex}`} className={(child.isCurrent) ? 'is-current' : null}>
                   { if (child.onClick) child.onClick(event); }}>
                    {child.label}

              )}

          )}

    );
  }
}
<|endoftext|>


<commit_before>//= require vendor/phaser.min.js
//= require firebase.js
//= require game/board.js
//= require game/characters.js
//= require game/images.js
//= require game/features.js

// Constants
CANVAS_WIDTH = 833;
CANVAS_HEIGHT = 715;
CANVAS_OFFSET = 60;
MAX_SCORE = 10;
DEFAULT_LIVES = 3;
player1 = false;
player2 = false;
currentPlayer = false;

// Variables
var score = 0;
var lives = DEFAULT_LIVES;
var apples = [];
var cherry;
var speedUp;
var slowDown;
var person;
var ghost;
var scoreText, livesText;
var starOne, starTwo;
var map;
var layer;
var cursors;
var music;
var fb;

$(document).ready(function() {
  var roomSession = $("#room-session").val();
  if (typeof roomSession !== "undefined") {
    fb = new firebase.firebaseSetup(roomSession);
  }

  // Recieve a message
  fb.chat.on("child_added", firebase.recieveMessage);

  // Send a message
  $("#chat-form").submit(function(event) {
    event.preventDefault();
    var name = $("#user-name").val()
    var content = $("#msg-input").val()
    $("#msg-input").val("");
    firebase.sendMessage(fb, name, content);
  });

  $("#instructions-button").click(function(){
    vex.dialog.buttons.YES.text = 'OK';
    vex.dialog.alert('Here are the rules of the game.

Player1:
Controls - Move your character with the arrow keys.
Objective - Collect all apples and powerups, or eat all the ghosts. The choice is yours.

Player2:
Controls - Select which ghost you want to control by pressing numbers 1 through 4. Then, move your character with the arrow keys.
Objective - Eat the hero until the hero has no lives left.

Need a moment? Press P to pause and R to resume.');
  });

  $("#aboutus-button").click(function(){
    vex.dialog.buttons.YES.text = 'OK';
    vex.dialog.alert('Hello! Welcome to Haunted! We are a team of 5 members that are currently pursuing our passion for coding @ Dev Bootcamp: David Sin, Rootul Patel, Sid Patel, Cassie Moy, and Julius Jung. We hope you enjoy playing this game as much as we enjoyed creating it. Check out our blog @ https://github.com/red-spotted-newts-2014/haunted !');
  });

  var urlModal = function() {
    vex.dialog.alert({
      message:'Send your friend this url to play!
<input id="game-url" type="text" value="http://haunted-game.herokuapp.com/' + roomSession + '">',
      callback: function() {
        confirmPlayerModal();
      }
    });
  }

 var confirmPlayerModal = function() {
    vex.dialog.buttons.YES.text = 'Hero';
    vex.dialog.buttons.NO.text = 'Ghost';
    vex.dialog.confirm({
      message: "Are you hero or ghost?",
      callback: function(value) {
        if (value) {
          currentPlayer = "player1";
          fb.player1.set(true);
        } else {
          currentPlayer = "player2";
          fb.player2.set(true);
        }
      }
    });
  }

  fb.pause.on("value", function(snapshot) {
    game.paused = snapshot.val();
  });

  fb.player2.on("value", function(snapshot) {
    if (snapshot.val()) {
      fb.pause.set(false);
    }
  });

  urlModal();
});

var game = new Phaser.Game( CANVAS_WIDTH, CANVAS_HEIGHT, Phaser.AUTO, 'pac', { preload: preload, create: create, update: update } );

function preload() {
  loadImages();
  game.load.tilemap('map', '/tilemap.json', null, Phaser.Tilemap.TILED_JSON);
  game.load.image('Desert', '/images/deserttile.png');
  game.load.audio('music', '/music.mp3');
};

function create() {
  game.physics.startSystem(Phaser.Physics.ARCADE);

  fb.pause.set(true);

  board.createBoard();
  characters.createCharacters();

  var gamePhysicsArray = [person, ghost, apples, cherry, speedUp, slowDown, starOne, starTwo];

  for (var i = 0; i < gamePhysicsArray.length; i++) {
    game.physics.arcade.enable(gamePhysicsArray[i]);
  }

  person.body.collideWorldBounds = true;
  ghost.body.collideWorldBounds = true;

  cursors = game.input.keyboard.createCursorKeys();
}

function update() {

  game.physics.arcade.collide(person,layer);
  game.physics.arcade.collide(ghost,layer);
  game.physics.arcade.overlap(person, apples, features.eatApple, null, this);
  game.physics.arcade.overlap(person, cherry, features.cherry, null, this);
  game.physics.arcade.overlap(person, speedUp, features.speedUp, null, this);
  game.physics.arcade.overlap(person, slowDown, features.slowDown, null, this);
  game.physics.arcade.overlap(ghost, speedUp, features.speedUp, null, this);
  game.physics.arcade.overlap(ghost, slowDown, features.slowDown, null, this);
  game.physics.arcade.overlap(person, starOne, features.teleportOne, null, this);
  game.physics.arcade.overlap(person, starTwo, features.teleportTwo, null, this);
  game.physics.arcade.overlap(person, ghost, features.pacMeetsGhost, null, this);

  livesText.text = 'lives: ' + lives;
  scoreText.text = 'score: ' + score;

  var movePlayer = function(character) {
    if (cursors.left.isDown){
      character.body.velocity.x = -200 * character.speedMultiplyer;
      character.body.velocity.y = 0;
      character.animations.play('left');
    } else if (cursors.right.isDown){
      character.body.velocity.x = 200 * character.speedMultiplyer;
      character.body.velocity.y = 0;
      character.animations.play('right');
    } else if (cursors.up.isDown){
      character.body.velocity.y = -200 * character.speedMultiplyer;
      character.body.velocity.x = 0;
      character.animations.play('up');
    } else if (cursors.down.isDown) {
      character.body.velocity.y = 200 * character.speedMultiplyer;
      character.body.velocity.x = 0;
      character.animations.play('bottom');
    }
  }

  if (currentPlayer === "player1") {
    person.userControl = true;
    movePlayer(person);
    if ((person.x !== person.lastx) || (person.y !== person.lasty )) {
      fb.person.set({
        x : person.position.x,
        y : person.position.y
      });
    }

    fb.ghost.on("value", function(snapshot) {
      ghost.x = snapshot.val().x
      ghost.y = snapshot.val().y
    });
  }

  if (currentPlayer === "player2") {
    movePlayer(ghost);
    if ((ghost.x !== ghost.lastx) || (ghost.y !== ghost.lasty )) {
      fb.ghost.set({
        x : ghost.position.x,
        y : ghost.position.y
      });
    }

    fb.person.on("value", function(snapshot) {
      person.x = snapshot.val().x
      person.y = snapshot.val().y
    });
  }
}
<commit_msg>testing pointer movement<commit_after>//= require vendor/phaser.min.js
//= require firebase.js
//= require game/board.js
//= require game/characters.js
//= require game/images.js
//= require game/features.js

// Constants
CANVAS_WIDTH = 833;
CANVAS_HEIGHT = 715;
CANVAS_OFFSET = 60;
MAX_SCORE = 10;
DEFAULT_LIVES = 3;
player1 = false;
player2 = false;
currentPlayer = false;

// Variables
var score = 0;
var lives = DEFAULT_LIVES;
var apples = [];
var cherry;
var speedUp;
var slowDown;
var person;
var ghost;
var scoreText, livesText;
var starOne, starTwo;
var map;
var layer;
var cursors;
var music;
var fb;

$(document).ready(function() {
  var roomSession = $("#room-session").val();
  if (typeof roomSession !== "undefined") {
    fb = new firebase.firebaseSetup(roomSession);
  }

  // Recieve a message
  fb.chat.on("child_added", firebase.recieveMessage);

  // Send a message
  $("#chat-form").submit(function(event) {
    event.preventDefault();
    var name = $("#user-name").val()
    var content = $("#msg-input").val()
    $("#msg-input").val("");
    firebase.sendMessage(fb, name, content);
  });

  $("#instructions-button").click(function(){
    vex.dialog.buttons.YES.text = 'OK';
    vex.dialog.alert('Here are the rules of the game.

Player1:
Controls - Move your character with the arrow keys.
Objective - Collect all apples and powerups, or eat all the ghosts. The choice is yours.

Player2:
Controls - Select which ghost you want to control by pressing numbers 1 through 4. Then, move your character with the arrow keys.
Objective - Eat the hero until the hero has no lives left.

Need a moment? Press P to pause and R to resume.');
  });

  $("#aboutus-button").click(function(){
    vex.dialog.buttons.YES.text = 'OK';
    vex.dialog.alert('Hello! Welcome to Haunted! We are a team of 5 members that are currently pursuing our passion for coding @ Dev Bootcamp: David Sin, Rootul Patel, Sid Patel, Cassie Moy, and Julius Jung. We hope you enjoy playing this game as much as we enjoyed creating it. Check out our blog @ https://github.com/red-spotted-newts-2014/haunted !');
  });

  var urlModal = function() {
    vex.dialog.alert({
      message:'Send your friend this url to play!
<input id="game-url" type="text" value="http://haunted-game.herokuapp.com/' + roomSession + '">',
      callback: function() {
        confirmPlayerModal();
      }
    });
  }

 var confirmPlayerModal = function() {
    vex.dialog.buttons.YES.text = 'Hero';
    vex.dialog.buttons.NO.text = 'Ghost';
    vex.dialog.confirm({
      message: "Are you hero or ghost?",
      callback: function(value) {
        if (value) {
          currentPlayer = "player1";
          fb.player1.set(true);
        } else {
          currentPlayer = "player2";
          fb.player2.set(true);
        }
      }
    });
  }

  fb.pause.on("value", function(snapshot) {
    game.paused = snapshot.val();
  });

  fb.player2.on("value", function(snapshot) {
    if (snapshot.val()) {
      fb.pause.set(false);
    }
  });

  urlModal();
});

var game = new Phaser.Game( CANVAS_WIDTH, CANVAS_HEIGHT, Phaser.AUTO, 'pac', { preload: preload, create: create, update: update } );

function preload() {
  loadImages();
  game.load.tilemap('map', '/tilemap.json', null, Phaser.Tilemap.TILED_JSON);
  game.load.image('Desert', '/images/deserttile.png');
  game.load.audio('music', '/music.mp3');
};

function create() {
  game.physics.startSystem(Phaser.Physics.ARCADE);

  fb.pause.set(true);

  board.createBoard();
  characters.createCharacters();

  var gamePhysicsArray = [person, ghost, apples, cherry, speedUp, slowDown, starOne, starTwo];

  for (var i = 0; i < gamePhysicsArray.length; i++) {
    game.physics.arcade.enable(gamePhysicsArray[i]);
  }

  person.body.collideWorldBounds = true;
  ghost.body.collideWorldBounds = true;

  cursors = game.input.keyboard.createCursorKeys();
}

function update() {

  game.physics.arcade.collide(person,layer);
  game.physics.arcade.collide(ghost,layer);
  game.physics.arcade.overlap(person, apples, features.eatApple, null, this);
  game.physics.arcade.overlap(person, cherry, features.cherry, null, this);
  game.physics.arcade.overlap(person, speedUp, features.speedUp, null, this);
  game.physics.arcade.overlap(person, slowDown, features.slowDown, null, this);
  game.physics.arcade.overlap(ghost, speedUp, features.speedUp, null, this);
  game.physics.arcade.overlap(ghost, slowDown, features.slowDown, null, this);
  game.physics.arcade.overlap(person, starOne, features.teleportOne, null, this);
  game.physics.arcade.overlap(person, starTwo, features.teleportTwo, null, this);
  game.physics.arcade.overlap(person, ghost, features.pacMeetsGhost, null, this);

  livesText.text = 'lives: ' + lives;
  scoreText.text = 'score: ' + score;

  var movePlayer = function(character) {
    if (cursors.left.isDown || pointer('left')){
      character.body.velocity.x = -200 * character.speedMultiplyer;
      character.body.velocity.y = 0;
      character.animations.play('left');
    } else if (cursors.right.isDown || pointer('right')){
      character.body.velocity.x = 200 * character.speedMultiplyer;
      character.body.velocity.y = 0;
      character.animations.play('right');
    } else if (cursors.up.isDown){
      character.body.velocity.y = -200 * character.speedMultiplyer;
      character.body.velocity.x = 0;
      character.animations.play('up');
    } else if (cursors.down.isDown) {
      character.body.velocity.y = 200 * character.speedMultiplyer;
      character.body.velocity.x = 0;
      character.animations.play('bottom');
    }
  }

  var pointer = function(direction) {
    if (game.input.mousePointer.isDown) {
      if (direction === "left") {
        if (game.input.mousePointer.x < 200) {
          return true;
        }
      } else if (direction === "right") {
        if (game.input.mousePointer.x > 600) {
          return true;
        }
      }
    }
    return false;
  }

  if (currentPlayer === "player1") {
    person.userControl = true;
    movePlayer(person);
    if ((person.x !== person.lastx) || (person.y !== person.lasty )) {
      fb.person.set({
        x : person.position.x,
        y : person.position.y
      });
    }

    fb.ghost.on("value", function(snapshot) {
      ghost.x = snapshot.val().x
      ghost.y = snapshot.val().y
    });
  }

  if (currentPlayer === "player2") {
    movePlayer(ghost);
    if ((ghost.x !== ghost.lastx) || (ghost.y !== ghost.lasty )) {
      fb.ghost.set({
        x : ghost.position.x,
        y : ghost.position.y
      });
    }

    fb.person.on("value", function(snapshot) {
      person.x = snapshot.val().x
      person.y = snapshot.val().y
    });
  }
}
<|endoftext|>


<commit_before>export const ActionTypes = (function() {
  var actions = {};
  [
    'ROM_RECEIVE',
    'EMU_RESET',
    'EMU_PAUSE',
    'EMU_RUN',
    'FPS_RECEIVE',
    'CANVAS_RECEIVE',
    'LOG_APPEND'
  ].forEach((val) => { actions[val] = val; });
  return actions;
})();
<commit_msg>Slimmer pattern for key mirrors<commit_after>export default [
  'ROM_RECEIVE',
  'EMU_RESET',
  'EMU_PAUSE',
  'EMU_RUN',
  'FPS_RECEIVE',
  'CANVAS_RECEIVE',
  'LOG_APPEND'
].reduce((o, k) => {o[k] = k; return o}, {});
<|endoftext|>


<commit_before>/**
 * Created by rinesnow on 16/3/29.
 */
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var session= require('express-session');
var moment = require('moment');

var app = express();

//连接数据库
var mongoose = require('mongoose');
var models = require('./models/models');

var User = models.User;
var Intern = models.Intern;

mongoose.connect('mongodb://localhost:27017/interns');
mongoose.connection.on('error',console.error.bind(console,'连接数据库失败!'));

//定义EJS模版引擎和末班文件位置
app.set('views', path.join(__dirname,'views'));
app.set('view engine','ejs');

//定义静态文件目录
app.use(express.static(path.join(__dirname,'public')));

//定义数据解析器
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

//建立session模型
app.use(session({
    secret:'liuchong',
    name:'intern',
    //一周内免登录
    cookie:{maxAge:1000 * 60 * 60*24*7},//设置session的保存时间为 60min*24小时*7一周
    resave: false,
    saveUnintiallized:true
}));

//添加  页面提示功能   目前没用
app.use(function(req, res, next) {
    res.locals.user = req.session.user;//???
    var err = req.session.error;//赋值给err变量,
    //var success = req.session.success;//赋值给err变量,

    delete req.session.error;//然后清空session内的数据
    res.locals.message = '';//初始化
    //res.locals.success = '';//初始化

    if (err) {
        //html通过js被操作,在html中用{%-message%}调用
        res.locals.message = '
' + err + '
';
    }
    next();//
});
//添加  页面提示功能   目前没用

var OAuth = require('wechat-oauth');
var client = new OAuth('wx75d11b4f981b1ded', '7a915c525f39451f3af19407012459ad');
app.get('/',function ifAuthorized(req,res){

    // var url = client.getAuthorizeURL('www.coderwitkey.com', 'STATE', 'snsapi_userinfo');
    var query = require('url').parse(req.url,true).query;
          console.log(query);

    // if(req.session.code==code){//为了避免程序崩，因为刷新授权会崩
    //     // res.render('error',{//一个error界面空的
    //     //      title:'请点左上角的X，重新进入。'
    //     // });
    // }else{
         var code = query.code;
         req.session.code=code;

          console.log(code);
          client.getUserByCode(code,function (err, result) {
      // var openid = result.openid;//必须要手动点击URL，原地刷新没用的。
       console.log('这是result:'+result.openid);
       User.findOne({openid:result.openid},function(err,user){
           if(err){
               console.log('这是err'+err);
           }
            if(user){//有这个user 那么直接跳转
                console.log(user.nickname)
                console.log(user.openid)
                req.session.user = user;
                res.render('login',{
                    user: req.session.user,//也要加?
                    title:'内推推推'
                });
                // return res.redirect('/');
            }
            // //对密码进行md5加密
            // var md5 = crypto.createHash('md5'),
            //     md5newopenid = md5.update(openid).digest('hex');
            //新建user对象用于保存数据
            else{
                var newUser = new User({

                    openid:result.openid,//openid 作为key存入，以后再用用户信息就用openid调。
                    nickname:result.nickname,
                    headimg:result.headimgurl
                });

                newUser.save(function(err,doc){
                    if(err){
                        console.log("保存用户信息失败！"+err);
                    }
                    console.log('保存用户信息成功!');//怎么实现弹出框!!!!??????
                    res.render('login',{
                        // user: req.session.user,//也要加?
                        title:'内推推推'
                    });
                    // return res.redirect('/');
                });
            }
        });
    });
    // }

});

//发布

//响应发布get请求
app.get('/post',function(req,res){
   console.log('发布!');
    res.render('post',{
        user: req.session.user,//也要加?
        title:'发布'
    });
});
//响应发布post请求
app.post('/post',function(req,res){

            var newIntern = new Intern({
                openid:req.session.user.openid,//用户的id
                company:req.body.company,
                email:req.body.email,
                category:req.body.category,
                content:req.body.content,
            });

            newIntern.save(function(err,doc){
                if(err){
                    console.log(err);
                    return res.redirect('/post');
                }
                else{
                    console.log('保存实习信息成功!');//怎么实现弹出框!!!!??????
                    res.render('login',{
                            // user: req.session.user,//也要加?
                            title:'内推推推'
                    });
                }

            });
});
//

app.get('/list',function(req,res){
     Intern.find(function(err,allInterns){
        if(err){
            console.log(err);
            res.render('login',{
                    // user: req.session.user,//也要加?
              title:'内推推推'
            });
        }
        else{
            res.render('list',{
            title:'内推列表',
            interns:allInterns,
            moment:moment
            });
        }

    })
});

app.get('/detail/:_id',function(req,res){//:id?
   console.log('查看实习!');
    Intern.findOne({_id:req.params._id}).exec(function(err,intern){
        if(err){
            console.log(err);
            return res.redirect('/');
        }
        if(intern) {
            res.render('detail', {
                title: '内推详情',
                intern: intern,
                moment:moment
            });
            console.log(intern);
        }
    });
});

app.listen(3000, function(req,res){
    console.log('app is running at port 3000');
});<commit_msg>new version<commit_after>/**
 * Created by rinesnow on 16/3/29.
 */
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var session= require('express-session');
var moment = require('moment');

var app = express();

//连接数据库
var mongoose = require('mongoose');
var models = require('./models/models');

var User = models.User;
var Intern = models.Intern;

mongoose.connect('mongodb://localhost:27017/interns');
mongoose.connection.on('error',console.error.bind(console,'连接数据库失败!'));

//定义EJS模版引擎和末班文件位置
app.set('views', path.join(__dirname,'views'));
app.set('view engine','ejs');

//定义静态文件目录
app.use(express.static(path.join(__dirname,'public')));

//定义数据解析器
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

//建立session模型
app.use(session({
    secret:'liuchong',
    name:'intern',
    //一周内免登录
    cookie:{maxAge:1000 * 60 * 60*24*7},//设置session的保存时间为 60min*24小时*7一周
    resave: false,
    saveUnintiallized:true
}));

//添加  页面提示功能   目前没用
app.use(function(req, res, next) {
    res.locals.user = req.session.user;//???
    var err = req.session.error;//赋值给err变量,
    //var success = req.session.success;//赋值给err变量,

    delete req.session.error;//然后清空session内的数据
    res.locals.message = '';//初始化
    //res.locals.success = '';//初始化

    if (err) {
        //html通过js被操作,在html中用{%-message%}调用
        res.locals.message = '
' + err + '
';
    }
    next();//
});
//添加  页面提示功能   目前没用

var OAuth = require('wechat-oauth');
var client = new OAuth('wx75d11b4f981b1ded', '7a915c525f39451f3af19407012459ad');
app.get('/',function ifAuthorized(req,res){

    // var url = client.getAuthorizeURL('www.coderwitkey.com', 'STATE', 'snsapi_userinfo');
    var query = require('url').parse(req.url,true).query;
          console.log(query);

    if(req.session.code==code){//为了避免程序崩，因为刷新授权会崩
        // res.render('error',{//一个error界面空的
        //      title:'请点左上角的X，重新进入。'
        // });
    }else{
         var code = query.code;
         req.session.code=code;

          console.log(code);
          client.getUserByCode(code,function (err, result) {
      // var openid = result.openid;//必须要手动点击URL，原地刷新没用的。
       console.log('这是result:'+result.openid);
       User.findOne({openid:result.openid},function(err,user){
           if(err){
               console.log('这是err'+err);
           }
            if(user){//有这个user 那么直接跳转
                console.log(user.nickname)
                console.log(user.openid)
                req.session.user = user;
                res.render('login',{
                    user: req.session.user,//也要加?
                    title:'内推推推'
                });
                // return res.redirect('/');
            }
            // //对密码进行md5加密
            // var md5 = crypto.createHash('md5'),
            //     md5newopenid = md5.update(openid).digest('hex');
            //新建user对象用于保存数据
            else{
                var newUser = new User({

                    openid:result.openid,//openid 作为key存入，以后再用用户信息就用openid调。
                    nickname:result.nickname,
                    headimg:result.headimgurl
                });

                newUser.save(function(err,doc){
                    if(err){
                        console.log("保存用户信息失败！"+err);
                    }
                    console.log('保存用户信息成功!');//怎么实现弹出框!!!!??????
                    res.render('login',{
                        // user: req.session.user,//也要加?
                        title:'内推推推'
                    });
                    // return res.redirect('/');
                });
            }
        });
    });
    // }

});

//发布

//响应发布get请求
app.get('/post',function(req,res){
   console.log('发布!');
    res.render('post',{
        user: req.session.user,//也要加?
        title:'发布'
    });
});
//响应发布post请求
app.post('/post',function(req,res){

            var newIntern = new Intern({
                openid:req.session.user.openid,//用户的id
                company:req.body.company,
                email:req.body.email,
                category:req.body.category,
                content:req.body.content,
            });

            newIntern.save(function(err,doc){
                if(err){
                    console.log(err);
                    return res.redirect('/post');
                }
                else{
                    console.log('保存实习信息成功!');//怎么实现弹出框!!!!??????
                    res.render('login',{
                            // user: req.session.user,//也要加?
                            title:'内推推推'
                    });
                }

            });
});
//

app.get('/list',function(req,res){
     Intern.find(function(err,allInterns){
        if(err){
            console.log(err);
            res.render('login',{
                    // user: req.session.user,//也要加?
              title:'内推推推'
            });
        }
        else{
            res.render('list',{
            title:'内推列表',
            interns:allInterns,
            moment:moment
            });
        }

    })
});

app.get('/detail/:_id',function(req,res){//:id?
   console.log('查看实习!');
    Intern.findOne({_id:req.params._id}).exec(function(err,intern){
        if(err){
            console.log(err);
            return res.redirect('/');
        }
        if(intern) {
            res.render('detail', {
                title: '内推详情',
                intern: intern,
                moment:moment
            });
            console.log(intern);
        }
    });
});

app.listen(3000, function(req,res){
    console.log('app is running at port 3000');
});<|endoftext|>