<commit_before>/* eslint-disable react/forbid-prop-types */
/* eslint-disable no-undef */
/* eslint-disable no-console */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

/* eslint-disable global-require */

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
  BackHandler,
  Image,
  AppState,
  Text,
  TouchableOpacity,
  TouchableWithoutFeedback,
  View,
} from 'react-native';

import { Scheduler } from 'sussol-utilities';
import { NavigationActions } from 'react-navigation';

import { FirstUsePage, FINALISABLE_PAGES } from './pages';

import { Synchroniser, PostSyncProcessor, SyncModal } from './sync';
import { FinaliseButton, NavigationBar, SyncState, Spinner } from './widgets';
import { FinaliseModal, LoginModal } from './widgets/modals';

import { getCurrentParams, getCurrentRouteName, ReduxNavigator, ROUTES } from './navigation';
import { syncCompleteTransaction, setSyncError } from './actions/SyncActions';
import { FinaliseActions } from './actions/FinaliseActions';
import { migrateDataToVersion } from './dataMigration';
import { SyncAuthenticator, UserAuthenticator } from './authentication';
import Settings from './settings/MobileAppSettings';
import Database from './database/BaseDatabase';
import { UIDatabase } from './database';

import globalStyles, { textStyles, SUSSOL_ORANGE } from './globalStyles';
import { LoadingIndicatorContext } from './context/LoadingIndicatorContext';
import { UserActions } from './actions';
import { debounce } from './utilities';
import { prevRouteNameSelector } from './navigation/selectors';
import { SupplierCredit } from './widgets/modalChildren/SupplierCredit';
import { ModalContainer } from './widgets/modals/ModalContainer';
import { SupplierCreditActions } from './actions/SupplierCreditActions';
import { PrescriptionActions } from './actions/PrescriptionActions';
import { selectTitle } from './selectors/supplierCredit';

const SYNC_INTERVAL = 10 * 60 * 1000; // 10 minutes in milliseconds.
const AUTHENTICATION_INTERVAL = 10 * 60 * 1000; // 10 minutes in milliseconds.

class MSupplyMobileAppContainer extends React.Component {
  handleBackEvent = debounce(
    () => {
      const { dispatch, prevRouteName, currentRouteName } = this.props;
      const { syncModalIsOpen } = this.state;

      // If finalise or sync modals are open, close them rather than navigating.
      if (syncModalIsOpen) {
        this.setState({ syncModalIsOpen: false });
        return true;
      }
      // If we are on base screen (e.g. home), back button should close app as we can't go back.
      if (!this.getCanNavigateBack()) {
        BackHandler.exitApp();
      } else {
        dispatch({ ...NavigationActions.back(), payload: { prevRouteName } });
      }
      if (currentRouteName === ROUTES.PRESCRIPTION) {
        UIDatabase.write(() => {
          UIDatabase.delete(
            'Transaction',
            UIDatabase.objects('Prescription').filtered('status != $0', 'finalised')
          );
          dispatch(PrescriptionActions.deletePrescription());
        });
      }

      return true;
    },
    400,
    true
  );

  constructor(props, ...otherArgs) {
    super(props, ...otherArgs);

    migrateDataToVersion(UIDatabase, Settings);
    this.userAuthenticator = new UserAuthenticator(UIDatabase, Settings);
    this.syncAuthenticator = new SyncAuthenticator(Settings);
    this.synchroniser = new Synchroniser(
      Database,
      this.syncAuthenticator,
      Settings,
      props.dispatch
    );
    this.postSyncProcessor = new PostSyncProcessor(UIDatabase, Settings);
    this.scheduler = new Scheduler();
    const isInitialised = this.synchroniser.isInitialised();
    this.scheduler.schedule(this.synchronise, SYNC_INTERVAL);
    this.scheduler.schedule(() => {
      const { currentUser } = this.props;
      if (currentUser !== null) {
        // Only re-authenticate if currently logged in.
        this.userAuthenticator.reauthenticate(this.onAuthentication);
      }
    }, AUTHENTICATION_INTERVAL);
    this.state = {
      isInitialised,
      isLoading: false,
      syncModalIsOpen: false,
      appState: null,
    };
  }

  componentDidMount = () => {
    BackHandler.addEventListener('hardwareBackPress', this.handleBackEvent);

    if (!__DEV__) {
      AppState.addEventListener('change', this.onAppStateChange);
    }
  };

  componentWillUnmount = () => {
    BackHandler.removeEventListener('hardwareBackPress', this.handleBackEvent);

    if (!__DEV__) {
      AppState.removeEventListener('change', this.onAppStateChange);
    }

    this.scheduler.clearAll();
  };

  onAppStateChange = nextAppState => {
    const { appState } = this.state;
    const { dispatch } = this.props;
    if (nextAppState?.match(/inactive|background/)) dispatch(UserActions.setTime());
    if (appState?.match(/inactive|background/) && nextAppState === 'active') {
      dispatch(UserActions.active());
    }

    this.setState({ appState: nextAppState });
  };

  onAuthentication = user => {
    const { dispatch } = this.props;
    dispatch(UserActions.login(user));
    this.postSyncProcessor.setUser(user);
  };

  onInitialised = () => {
    this.setState({ isInitialised: true });
    this.postSyncProcessor.processAnyUnprocessedRecords();
  };

  getCanNavigateBack = () => {
    const { navigationState } = this.props;

    return navigationState.index !== 0;
  };

  // eslint-disable-next-line class-methods-use-this
  getCurrentRouteName(navigationState) {
    if (!navigationState) return null;

    const route = navigationState.routes[navigationState.index];

    // dive into nested navigators
    if (route.routes) return getCurrentRouteName(route);

    return route.routeName;
  }

  runWithLoadingIndicator = async functionToRun => {
    UIDatabase.isLoading = true;
    // We here set up an asynchronous promise that will be resolved after a timeout
    // of 1 millisecond. This allows a fraction of a delay for the javascript thread
    // to unblock and allow the spinner animation to start up. The |functionToRun| should
    // not be run inside a |setTimeout| as that relegates to a lower priority, resulting
    // in very slow performance.
    await new Promise(resolve => {
      this.setState({ isLoading: true }, () => setTimeout(resolve, 1));
    });
    functionToRun();
    this.setState({ isLoading: false });
    UIDatabase.isLoading = false;
  };

  synchronise = async () => {
    const { syncState, dispatch, currentUser } = this.props;
    const { isInitialised } = this.state;

    if (!isInitialised || syncState.isSyncing) return; // Ignore if syncing.

    try {
      await this.userAuthenticator.authenticate(
        currentUser.username,
        null,
        currentUser.passwordHash
      );
      // True if most recent call to |this.synchroniser.synchronise()| failed.
      const lastSyncFailed = this.synchroniser.lastSyncFailed();
      const lastPostSyncProcessingFailed = this.postSyncProcessor.lastPostSyncProcessingFailed();
      await this.synchroniser.synchronise();
      if (lastSyncFailed || lastPostSyncProcessingFailed) {
        // If last sync was interrupted, it did not enter this block. If the app was closed, it did
        // not store any records left in the sync queue, so tables should be checked for unprocessed
        // records. If the last processing of the record queue was interrupted by app crash then all
        // records need to be checked.
        this.postSyncProcessor.processAnyUnprocessedRecords();
      } else {
        this.postSyncProcessor.processRecordQueue();
      }
      dispatch(syncCompleteTransaction());
    } catch (error) {
      dispatch(setSyncError(error.message));
    }
  };

  renderFinaliseButton = () => {
    const { finaliseItem, openFinaliseModal } = this.props;
    return (
      <FinaliseButton isFinalised={finaliseItem.record.isFinalised} onPress={openFinaliseModal} />
    );
  };

  renderLogo = () => {
    const { isInAdminMode } = this.state;

    return (
      <TouchableWithoutFeedback
        delayLongPress={3000}
        onLongPress={() => this.setState({ isInAdminMode: !isInAdminMode })}
      >
        <Image resizeMode="contain" source={require('./images/logo.png')} />
      </TouchableWithoutFeedback>
    );
  };

  renderLoadingIndicator = () => {
    const { isLoading } = this.state;
    return (
      <View style={globalStyles.loadingIndicatorContainer}>
        <Spinner isSpinning={isLoading} color={SUSSOL_ORANGE} />
      </View>
    );
  };

  renderPageTitle = () => {
    const { currentTitle } = this.props;
    return <Text style={textStyles}>{currentTitle}</Text>;
  };

  renderSyncState = () => {
    const { syncState } = this.props;
    return (
      <TouchableOpacity
        style={{ flexDirection: 'row' }}
        onPress={() => this.setState({ syncModalIsOpen: true })}
      >
        <SyncState state={syncState} />
      </TouchableOpacity>
    );
  };

  render() {
    const {
      dispatch,
      finaliseItem,
      navigationState,
      syncState,
      currentUser,
      finaliseModalOpen,
      closeFinaliseModal,
      closeSupplierCreditModal,
      supplierCreditModalOpen,
      creditTitle,
    } = this.props;
    const { isInAdminMode, isInitialised, isLoading, syncModalIsOpen } = this.state;

    if (!isInitialised) {
      return (
        <FirstUsePage
          synchroniser={this.synchroniser}
          onInitialised={this.onInitialised}
          syncState={syncState}
        />
      );
    }

    return (
      <LoadingIndicatorContext.Provider value={this.runWithLoadingIndicator}>
        <View style={globalStyles.appBackground}>
          <NavigationBar
            routeName={this.getCurrentRouteName(navigationState)}
            onPressBack={this.getCanNavigateBack() ? this.handleBackEvent : null}
            LeftComponent={this.getCanNavigateBack() ? this.renderPageTitle : null}
            CentreComponent={this.renderLogo}
            RightComponent={
              finaliseItem && finaliseItem?.visibleButton
                ? this.renderFinaliseButton
                : this.renderSyncState
            }
          />
          <ReduxNavigator
            state={navigationState}
            dispatch={dispatch}
            screenProps={{
              database: UIDatabase,
              settings: Settings,
              currentUser,
              routeName: navigationState.routes[navigationState.index].routeName,
              runWithLoadingIndicator: this.runWithLoadingIndicator,
              isInAdminMode,
            }}
          />
          <FinaliseModal
            database={UIDatabase}
            isOpen={finaliseModalOpen}
            onClose={closeFinaliseModal}
            finaliseItem={finaliseItem}
            user={currentUser}
            runWithLoadingIndicator={this.runWithLoadingIndicator}
          />
          <SyncModal
            database={UIDatabase}
            isOpen={syncModalIsOpen}
            state={syncState}
            onPressManualSync={this.synchronise}
            onClose={() => this.setState({ syncModalIsOpen: false })}
          />
          <LoginModal
            authenticator={this.userAuthenticator}
            settings={Settings}
            isAuthenticated={!!currentUser}
            onAuthentication={this.onAuthentication}
          />
          {isLoading && this.renderLoadingIndicator()}
          <ModalContainer
            isVisible={supplierCreditModalOpen}
            onClose={closeSupplierCreditModal}
            title={creditTitle}
            fullScreen
          >
            <SupplierCredit />
          </ModalContainer>
        </View>
      </LoadingIndicatorContext.Provider>
    );
  }
}

const mapDispatchToProps = dispatch => {
  const openFinaliseModal = () => dispatch(FinaliseActions.openModal());
  const closeFinaliseModal = () => dispatch(FinaliseActions.closeModal());
  const closeSupplierCreditModal = () => dispatch(SupplierCreditActions.close());

  return { dispatch, openFinaliseModal, closeFinaliseModal, closeSupplierCreditModal };
};

const mapStateToProps = state => {
  const { finalise, nav: navigationState, sync: syncState, supplierCredit } = state;
  const { open: supplierCreditModalOpen } = supplierCredit;
  const { finaliseModalOpen } = finalise;
  const currentParams = getCurrentParams(navigationState);
  const currentTitle = currentParams && currentParams.title;
  const currentRouteName = getCurrentRouteName(navigationState);
  const finaliseItem = FINALISABLE_PAGES[currentRouteName];
  if (finaliseItem && currentParams) {
    if (currentRouteName === ROUTES.PRESCRIPTION) finaliseItem.visibleButton = false;
    else finaliseItem.visibleButton = true;
    finaliseItem.record = currentParams[finaliseItem.recordToFinaliseKey];
  }

  return {
    currentRouteName,
    currentTitle,
    prevRouteName: prevRouteNameSelector(state),
    finaliseItem,
    navigationState,
    syncState,
    currentUser: state.user.currentUser,
    finaliseModalOpen,
    supplierCreditModalOpen,
    creditTitle: selectTitle(state),
  };
};

MSupplyMobileAppContainer.defaultProps = {
  currentUser: null,
  currentTitle: '',
  finaliseItem: null,
  creditTitle: '',
};

MSupplyMobileAppContainer.propTypes = {
  currentTitle: PropTypes.string,
  dispatch: PropTypes.func.isRequired,
  finaliseItem: PropTypes.object,
  navigationState: PropTypes.object.isRequired,
  syncState: PropTypes.object.isRequired,
  currentUser: PropTypes.object,
  prevRouteName: PropTypes.string.isRequired,
  finaliseModalOpen: PropTypes.bool.isRequired,
  openFinaliseModal: PropTypes.func.isRequired,
  closeFinaliseModal: PropTypes.func.isRequired,
  closeSupplierCreditModal: PropTypes.func.isRequired,
  supplierCreditModalOpen: PropTypes.bool.isRequired,
  currentRouteName: PropTypes.string.isRequired,
  creditTitle: PropTypes.string,
};

export default connect(mapStateToProps, mapDispatchToProps)(MSupplyMobileAppContainer);
<commit_msg>Add switch using user auth to sync auth<commit_after>/* eslint-disable react/forbid-prop-types */
/* eslint-disable no-undef */
/* eslint-disable no-console */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

/* eslint-disable global-require */

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
  BackHandler,
  Image,
  AppState,
  Text,
  TouchableOpacity,
  TouchableWithoutFeedback,
  View,
} from 'react-native';

import { Scheduler } from 'sussol-utilities';
import { NavigationActions } from 'react-navigation';

import { FirstUsePage, FINALISABLE_PAGES } from './pages';

import { Synchroniser, PostSyncProcessor, SyncModal } from './sync';
import { FinaliseButton, NavigationBar, SyncState, Spinner } from './widgets';
import { FinaliseModal, LoginModal } from './widgets/modals';

import { getCurrentParams, getCurrentRouteName, ReduxNavigator, ROUTES } from './navigation';
import { syncCompleteTransaction, setSyncError } from './actions/SyncActions';
import { FinaliseActions } from './actions/FinaliseActions';
import { migrateDataToVersion } from './dataMigration';
import { SyncAuthenticator, UserAuthenticator } from './authentication';
import Settings from './settings/MobileAppSettings';
import Database from './database/BaseDatabase';
import { UIDatabase } from './database';
import { SETTINGS_KEYS } from './settings';

import globalStyles, { textStyles, SUSSOL_ORANGE } from './globalStyles';
import { LoadingIndicatorContext } from './context/LoadingIndicatorContext';
import { UserActions } from './actions';
import { debounce } from './utilities';
import { prevRouteNameSelector } from './navigation/selectors';
import { SupplierCredit } from './widgets/modalChildren/SupplierCredit';
import { ModalContainer } from './widgets/modals/ModalContainer';
import { SupplierCreditActions } from './actions/SupplierCreditActions';
import { PrescriptionActions } from './actions/PrescriptionActions';
import { selectTitle } from './selectors/supplierCredit';

const SYNC_INTERVAL = 10 * 60 * 1000; // 10 minutes in milliseconds.
const AUTHENTICATION_INTERVAL = 10 * 60 * 1000; // 10 minutes in milliseconds.

class MSupplyMobileAppContainer extends React.Component {
  handleBackEvent = debounce(
    () => {
      const { dispatch, prevRouteName, currentRouteName } = this.props;
      const { syncModalIsOpen } = this.state;

      // If finalise or sync modals are open, close them rather than navigating.
      if (syncModalIsOpen) {
        this.setState({ syncModalIsOpen: false });
        return true;
      }
      // If we are on base screen (e.g. home), back button should close app as we can't go back.
      if (!this.getCanNavigateBack()) {
        BackHandler.exitApp();
      } else {
        dispatch({ ...NavigationActions.back(), payload: { prevRouteName } });
      }
      if (currentRouteName === ROUTES.PRESCRIPTION) {
        UIDatabase.write(() => {
          UIDatabase.delete(
            'Transaction',
            UIDatabase.objects('Prescription').filtered('status != $0', 'finalised')
          );
          dispatch(PrescriptionActions.deletePrescription());
        });
      }

      return true;
    },
    400,
    true
  );

  constructor(props, ...otherArgs) {
    super(props, ...otherArgs);

    migrateDataToVersion(UIDatabase, Settings);
    this.userAuthenticator = new UserAuthenticator(UIDatabase, Settings);
    this.syncAuthenticator = new SyncAuthenticator(Settings);
    this.synchroniser = new Synchroniser(
      Database,
      this.syncAuthenticator,
      Settings,
      props.dispatch
    );
    this.postSyncProcessor = new PostSyncProcessor(UIDatabase, Settings);
    this.scheduler = new Scheduler();
    const isInitialised = this.synchroniser.isInitialised();
    this.scheduler.schedule(this.synchronise, SYNC_INTERVAL);
    this.scheduler.schedule(() => {
      const { currentUser } = this.props;
      if (currentUser !== null) {
        // Only re-authenticate if currently logged in.
        this.userAuthenticator.reauthenticate(this.onAuthentication);
      }
    }, AUTHENTICATION_INTERVAL);
    this.state = {
      isInitialised,
      isLoading: false,
      syncModalIsOpen: false,
      appState: null,
    };
  }

  componentDidMount = () => {
    BackHandler.addEventListener('hardwareBackPress', this.handleBackEvent);

    if (!__DEV__) {
      AppState.addEventListener('change', this.onAppStateChange);
    }
  };

  componentWillUnmount = () => {
    BackHandler.removeEventListener('hardwareBackPress', this.handleBackEvent);

    if (!__DEV__) {
      AppState.removeEventListener('change', this.onAppStateChange);
    }

    this.scheduler.clearAll();
  };

  onAppStateChange = nextAppState => {
    const { appState } = this.state;
    const { dispatch } = this.props;
    if (nextAppState?.match(/inactive|background/)) dispatch(UserActions.setTime());
    if (appState?.match(/inactive|background/) && nextAppState === 'active') {
      dispatch(UserActions.active());
    }

    this.setState({ appState: nextAppState });
  };

  onAuthentication = user => {
    const { dispatch } = this.props;
    dispatch(UserActions.login(user));
    this.postSyncProcessor.setUser(user);
  };

  onInitialised = () => {
    this.setState({ isInitialised: true });
    this.postSyncProcessor.processAnyUnprocessedRecords();
  };

  getCanNavigateBack = () => {
    const { navigationState } = this.props;

    return navigationState.index !== 0;
  };

  // eslint-disable-next-line class-methods-use-this
  getCurrentRouteName(navigationState) {
    if (!navigationState) return null;

    const route = navigationState.routes[navigationState.index];

    // dive into nested navigators
    if (route.routes) return getCurrentRouteName(route);

    return route.routeName;
  }

  runWithLoadingIndicator = async functionToRun => {
    UIDatabase.isLoading = true;
    // We here set up an asynchronous promise that will be resolved after a timeout
    // of 1 millisecond. This allows a fraction of a delay for the javascript thread
    // to unblock and allow the spinner animation to start up. The |functionToRun| should
    // not be run inside a |setTimeout| as that relegates to a lower priority, resulting
    // in very slow performance.
    await new Promise(resolve => {
      this.setState({ isLoading: true }, () => setTimeout(resolve, 1));
    });
    functionToRun();
    this.setState({ isLoading: false });
    UIDatabase.isLoading = false;
  };

  synchronise = async () => {
    const { syncState, dispatch } = this.props;
    const { isInitialised } = this.state;

    if (!isInitialised || syncState.isSyncing) return; // Ignore if syncing.

    try {
      const syncUrl = UIDatabase.getSetting(SETTINGS_KEYS.SYNC_URL);
      const syncSiteName = UIDatabase.getSetting(SETTINGS_KEYS.SYNC_SITE_NAME);
      const syncSitePasswordHash = UIDatabase.getSetting(SETTINGS_KEYS.SYNC_SITE_PASSWORD_HASH);

      await this.syncAuthenticator.authenticate(syncUrl, syncSiteName, null, syncSitePasswordHash);

      // True if most recent call to |this.synchroniser.synchronise()| failed.
      const lastSyncFailed = this.synchroniser.lastSyncFailed();
      const lastPostSyncProcessingFailed = this.postSyncProcessor.lastPostSyncProcessingFailed();
      await this.synchroniser.synchronise();
      if (lastSyncFailed || lastPostSyncProcessingFailed) {
        // If last sync was interrupted, it did not enter this block. If the app was closed, it did
        // not store any records left in the sync queue, so tables should be checked for unprocessed
        // records. If the last processing of the record queue was interrupted by app crash then all
        // records need to be checked.
        this.postSyncProcessor.processAnyUnprocessedRecords();
      } else {
        this.postSyncProcessor.processRecordQueue();
      }
      dispatch(syncCompleteTransaction());
    } catch (error) {
      dispatch(setSyncError(error.message));
    }
  };

  renderFinaliseButton = () => {
    const { finaliseItem, openFinaliseModal } = this.props;
    return (
      <FinaliseButton isFinalised={finaliseItem.record.isFinalised} onPress={openFinaliseModal} />
    );
  };

  renderLogo = () => {
    const { isInAdminMode } = this.state;

    return (
      <TouchableWithoutFeedback
        delayLongPress={3000}
        onLongPress={() => this.setState({ isInAdminMode: !isInAdminMode })}
      >
        <Image resizeMode="contain" source={require('./images/logo.png')} />
      </TouchableWithoutFeedback>
    );
  };

  renderLoadingIndicator = () => {
    const { isLoading } = this.state;
    return (
      <View style={globalStyles.loadingIndicatorContainer}>
        <Spinner isSpinning={isLoading} color={SUSSOL_ORANGE} />
      </View>
    );
  };

  renderPageTitle = () => {
    const { currentTitle } = this.props;
    return <Text style={textStyles}>{currentTitle}</Text>;
  };

  renderSyncState = () => {
    const { syncState } = this.props;
    return (
      <TouchableOpacity
        style={{ flexDirection: 'row' }}
        onPress={() => this.setState({ syncModalIsOpen: true })}
      >
        <SyncState state={syncState} />
      </TouchableOpacity>
    );
  };

  render() {
    const {
      dispatch,
      finaliseItem,
      navigationState,
      syncState,
      currentUser,
      finaliseModalOpen,
      closeFinaliseModal,
      closeSupplierCreditModal,
      supplierCreditModalOpen,
      creditTitle,
    } = this.props;
    const { isInAdminMode, isInitialised, isLoading, syncModalIsOpen } = this.state;

    if (!isInitialised) {
      return (
        <FirstUsePage
          synchroniser={this.synchroniser}
          onInitialised={this.onInitialised}
          syncState={syncState}
        />
      );
    }

    return (
      <LoadingIndicatorContext.Provider value={this.runWithLoadingIndicator}>
        <View style={globalStyles.appBackground}>
          <NavigationBar
            routeName={this.getCurrentRouteName(navigationState)}
            onPressBack={this.getCanNavigateBack() ? this.handleBackEvent : null}
            LeftComponent={this.getCanNavigateBack() ? this.renderPageTitle : null}
            CentreComponent={this.renderLogo}
            RightComponent={
              finaliseItem && finaliseItem?.visibleButton
                ? this.renderFinaliseButton
                : this.renderSyncState
            }
          />
          <ReduxNavigator
            state={navigationState}
            dispatch={dispatch}
            screenProps={{
              database: UIDatabase,
              settings: Settings,
              currentUser,
              routeName: navigationState.routes[navigationState.index].routeName,
              runWithLoadingIndicator: this.runWithLoadingIndicator,
              isInAdminMode,
            }}
          />
          <FinaliseModal
            database={UIDatabase}
            isOpen={finaliseModalOpen}
            onClose={closeFinaliseModal}
            finaliseItem={finaliseItem}
            user={currentUser}
            runWithLoadingIndicator={this.runWithLoadingIndicator}
          />
          <SyncModal
            database={UIDatabase}
            isOpen={syncModalIsOpen}
            state={syncState}
            onPressManualSync={this.synchronise}
            onClose={() => this.setState({ syncModalIsOpen: false })}
          />
          <LoginModal
            authenticator={this.userAuthenticator}
            settings={Settings}
            isAuthenticated={!!currentUser}
            onAuthentication={this.onAuthentication}
          />
          {isLoading && this.renderLoadingIndicator()}
          <ModalContainer
            isVisible={supplierCreditModalOpen}
            onClose={closeSupplierCreditModal}
            title={creditTitle}
            fullScreen
          >
            <SupplierCredit />
          </ModalContainer>
        </View>
      </LoadingIndicatorContext.Provider>
    );
  }
}

const mapDispatchToProps = dispatch => {
  const openFinaliseModal = () => dispatch(FinaliseActions.openModal());
  const closeFinaliseModal = () => dispatch(FinaliseActions.closeModal());
  const closeSupplierCreditModal = () => dispatch(SupplierCreditActions.close());

  return { dispatch, openFinaliseModal, closeFinaliseModal, closeSupplierCreditModal };
};

const mapStateToProps = state => {
  const { finalise, nav: navigationState, sync: syncState, supplierCredit } = state;
  const { open: supplierCreditModalOpen } = supplierCredit;
  const { finaliseModalOpen } = finalise;
  const currentParams = getCurrentParams(navigationState);
  const currentTitle = currentParams && currentParams.title;
  const currentRouteName = getCurrentRouteName(navigationState);
  const finaliseItem = FINALISABLE_PAGES[currentRouteName];
  if (finaliseItem && currentParams) {
    if (currentRouteName === ROUTES.PRESCRIPTION) finaliseItem.visibleButton = false;
    else finaliseItem.visibleButton = true;
    finaliseItem.record = currentParams[finaliseItem.recordToFinaliseKey];
  }

  return {
    currentRouteName,
    currentTitle,
    prevRouteName: prevRouteNameSelector(state),
    finaliseItem,
    navigationState,
    syncState,
    currentUser: state.user.currentUser,
    finaliseModalOpen,
    supplierCreditModalOpen,
    creditTitle: selectTitle(state),
  };
};

MSupplyMobileAppContainer.defaultProps = {
  currentUser: null,
  currentTitle: '',
  finaliseItem: null,
  creditTitle: '',
};

MSupplyMobileAppContainer.propTypes = {
  currentTitle: PropTypes.string,
  dispatch: PropTypes.func.isRequired,
  finaliseItem: PropTypes.object,
  navigationState: PropTypes.object.isRequired,
  syncState: PropTypes.object.isRequired,
  currentUser: PropTypes.object,
  prevRouteName: PropTypes.string.isRequired,
  finaliseModalOpen: PropTypes.bool.isRequired,
  openFinaliseModal: PropTypes.func.isRequired,
  closeFinaliseModal: PropTypes.func.isRequired,
  closeSupplierCreditModal: PropTypes.func.isRequired,
  supplierCreditModalOpen: PropTypes.bool.isRequired,
  currentRouteName: PropTypes.string.isRequired,
  creditTitle: PropTypes.string,
};

export default connect(mapStateToProps, mapDispatchToProps)(MSupplyMobileAppContainer);
<|endoftext|>


<commit_before>// "count documents", a bit akin to "show collections"
shellHelper.count = function (what) {
    assert(typeof what == "string");

    var args = what.split( /\s+/ );
    what = args[0]
    args = args.splice(1)

    if (what == "documents" || what == "docs") {
        collectionNames = db.getCollectionNames().filter(function (collectionName) {
            // exclude "system" collections from "count" operation
            return !collectionName.startsWith('system.');
        });
        documentCounts = collectionNames.map(function (collectionName) {
            var count = db.getCollection(collectionName).count();
            return (count.commify() + " document(s)");
        });
        printPaddedColumns(collectionNames, documentCounts);
        return "";
    }

    throw "don't know how to count [" + what + "]";

}
<commit_msg>Implement the "count collections" helper<commit_after>// "count documents", a bit akin to "show collections"
shellHelper.count = function (what) {
    assert(typeof what == "string");

    var args = what.split( /\s+/ );
    what = args[0]
    args = args.splice(1)

    if (what == "collections" || what == "tables") {
        databaseNames = db.getMongo().getDatabaseNames();
        collectionCounts = databaseNames.map(function (databaseName) {
            var count = db.getMongo().getDB(databaseName).getCollectionNames().length;
            return (count.commify() + " collection(s)");
        });
        printPaddedColumns(databaseNames, collectionCounts);
        return "";
    }

    if (what == "documents" || what == "docs") {
        collectionNames = db.getCollectionNames().filter(function (collectionName) {
            // exclude "system" collections from "count" operation
            return !collectionName.startsWith('system.');
        });
        documentCounts = collectionNames.map(function (collectionName) {
            var count = db.getCollection(collectionName).count();
            return (count.commify() + " document(s)");
        });
        printPaddedColumns(collectionNames, documentCounts);
        return "";
    }

    throw "don't know how to count [" + what + "]";

}
<|endoftext|>


<commit_before>angular.module(PKG.name + '.feature.admin')
  .config(function($stateProvider, $urlRouterProvider, MYAUTH_ROLE) {
    $urlRouterProvider.when('/admin', '/admin/overview');
    $stateProvider
      .state('admin', {
        data: {
          authorizedRoles: MYAUTH_ROLE.all
        },
        url: '/admin',
        templateUrl: '/assets/features/admin/templates/admin.html',
        controller: 'AdminController'
      })
        .state('admin.overview', {
          url: '/overview',
          templateUrl: '/assets/features/admin/templates/overview.html'
        })
        .state('admin.system', {
          url: '/system',
          templateUrl: '/assets/features/admin/templates/system.html'
        })
        .state('admin.security', {
          url: '/security',
          templateUrl: '/assets/features/admin/templates/security.html'
        })
        .state('admin.nscreate', {
          url: '/namespace/create',
          templateUrl: '/assets/features/admin/templates/namespace/create.html',
          controller: 'NamespaceCreateController'
        })
        .state('admin.namespace', {
          url: '/ns/:nsId',
          templateUrl: '/assets/features/admin/templates/namespace.html'
        })
          .state('admin.namespace.settings', {
            url: '/settings',
            templateUrl: 'assets/features/admin/templates/namespace/settings.html'
          })
          .state('admin.namespace.users', {
            url: '/users',
            templateUrl: 'assets/features/admin/templates/namespace/users.html'
          })
          .state('admin.namespace.datatypes', {
            url: '/datatypes',
            templateUrl: 'assets/features/admin/templates/namespace/datatypes.html'
          })
          .state('admin.namespace.datasets', {
            url: '/datasets',
            templateUrl: 'assets/features/admin/templates/namespace/datasets.html'
          })
          .state('admin.namespace.apps', {
            url: '/apps',
            templateUrl: 'assets/features/admin/templates/namespace/apps.html'
          });
  });
<commit_msg>Fixes admin overview route<commit_after>angular.module(PKG.name + '.feature.admin')
  .config(function($stateProvider, $urlRouterProvider, MYAUTH_ROLE) {
    $stateProvider
      .state('admin', {
        abstract: true,
        data: {
          authorizedRoles: MYAUTH_ROLE.all
        },
        url: '/admin',
        templateUrl: '/assets/features/admin/templates/admin.html',
        controller: 'AdminController'
      })
        .state('admin.overview', {
          url: '',
          templateUrl: '/assets/features/admin/templates/overview.html'
        })
        .state('admin.system', {
          url: '/system',
          templateUrl: '/assets/features/admin/templates/system.html'
        })
        .state('admin.security', {
          url: '/security',
          templateUrl: '/assets/features/admin/templates/security.html'
        })
        .state('admin.nscreate', {
          url: '/namespace/create',
          templateUrl: '/assets/features/admin/templates/namespace/create.html',
          controller: 'NamespaceCreateController'
        })
        .state('admin.namespace', {
          url: '/ns/:nsId',
          templateUrl: '/assets/features/admin/templates/namespace.html'
        })
          .state('admin.namespace.settings', {
            url: '/settings',
            templateUrl: 'assets/features/admin/templates/namespace/settings.html'
          })
          .state('admin.namespace.users', {
            url: '/users',
            templateUrl: 'assets/features/admin/templates/namespace/users.html'
          })
          .state('admin.namespace.datatypes', {
            url: '/datatypes',
            templateUrl: 'assets/features/admin/templates/namespace/datatypes.html'
          })
          .state('admin.namespace.datasets', {
            url: '/datasets',
            templateUrl: 'assets/features/admin/templates/namespace/datasets.html'
          })
          .state('admin.namespace.apps', {
            url: '/apps',
            templateUrl: 'assets/features/admin/templates/namespace/apps.html'
          });
  });
<|endoftext|>


<commit_before>/**
* Deps
*/
var path = require('path');
var fs = require('fs');
var request = require('request');
var _ = require('underscore');
_.mixin(require('underscore.deferred'));

var helpers = require('./modules/helpers');
var builder = require('./modules/builder');

var DEBUG = false;

/**
* Configuration
*/

var config;

try {
    config = require('./local.config.js');
} catch(e) {
    config = require('./config.js');
}

/**
* Get quote
*/

function getQuote() {
    var defer = new _.Deferred();
    var person = helpers.choice(config.content.persons);
    var font = helpers.choice(config.availableFonts);

    var lyricsURL = 'http://api.lyricsnmusic.com/songs?per_page=100&api_key=',
        artistParameter = '&artist=' + helpers.choice(config.content.artists),
        fullURL = lyricsURL + config.lyricsAPI + artistParameter;

    var parseResponse = function(body) {
        var chosenQuote = helpers.choice(body).snippet.split('\n');

        var finalQuote = _.chain(chosenQuote)
            .pop()
            .map(function(part) {
                return part.replace(/^[\.,]+|[\.,]+$|\(.*\)|\[.*\]|\s+[\.,]\s+/g, '')
                    .replace(/\s+([.,!":])/g, '$1')
                    .trim();
            })
            .filter(function(part) {
                return part !== '';
            })
            .value()
            .join('. ');

        return defer.resolve({
            quote: finalQuote,
            image: path.join(config.imagesFolder, helpers.choice(person.images)),
            signature: person.signature,
            font: font
        });
    };

    if(DEBUG) {
        parseResponse(config.content.debugContent);
    } else {
        request(fullURL, function(error, response, body) {
            if (!error && response.statusCode === 200) {
                parseResponse(JSON.parse(body));
            } else {
                defer.reject(error ? error : 'Timeout. Try again.');
            }
        });
    }

    return defer.promise();
}

/**
* Generator
*/

function generate() {
    var defer = new _.Deferred();

    getQuote().done(function(result) {
        builder(result, config).done(function(output) {
            result.output = output;
            return defer.resolve(result);
        }).fail(function(err) {
            console.log('Image generation error: ', err);
        });
    }).fail(function(err){
        console.log('Lyric API error: ', err);
    });

    return defer.promise();
}

/**
* Tweeter
*/

function tweet() {
    generate().then(function(myTweet) {

        var maxLength = 117;

        var cutStatus = '"' + myTweet.quote.substring(0, maxLength - (myTweet.signature.length + 6)) + '..." ';

        var status = cutStatus + myTweet.signature;

        if(DEBUG) {
            console.log({
                debug: true,
                originalStatus: myTweet.quote,
                status: status,
                statusLength: status.length,
                output: myTweet.output
            });

            return;
        }

        var params = {
            url: 'https://api.twitter.com/1.1/statuses/update_with_media.json',
            oauth: config.oauth
        };

        var r = request.post(params, function(err, response, body) {
            if(err) {
                throw err;
            }

            console.log(body);
        });

        var form = r.form();
        form.append('status', status);
        form.append('media[]', fs.createReadStream(myTweet.output));

    });
}

// Tweet every 60 minutes
if(!DEBUG) {
    setInterval(function () {
      try {
        tweet();
      }
      catch (e) {
        console.log(e);
      }
     }, config.tweetInterval);
}

// Tweet once on initialization
tweet();
<commit_msg>Better string cleaning<commit_after>/**
* Deps
*/
var path = require('path');
var fs = require('fs');
var request = require('request');
var _ = require('underscore');
_.mixin(require('underscore.deferred'));

var helpers = require('./modules/helpers');
var builder = require('./modules/builder');

var DEBUG = false;

/**
* Configuration
*/

var config;

try {
    config = require('./local.config.js');
} catch(e) {
    config = require('./config.js');
}

/**
* Get quote
*/

function getQuote() {
    var defer = new _.Deferred();
    var person = helpers.choice(config.content.persons);
    var font = helpers.choice(config.availableFonts);

    var lyricsURL = 'http://api.lyricsnmusic.com/songs?per_page=100&api_key=',
        artistParameter = '&artist=' + helpers.choice(config.content.artists),
        fullURL = lyricsURL + config.lyricsAPI + artistParameter;

    var parseResponse = function(body) {
        var chosenQuote = helpers.choice(body).snippet.split('\n');

        var finalQuote = _.chain(chosenQuote)
            .pop()
            .map(function(part) {
                return part.trim()
                    .replace(/^[\.,]+|[\.,]+$|\(.*\)|\[.*\]|\s+[\.,]\s+/g, '')
                    .replace(/\s+([.,!":])/g, '$1')
                    .trim();
            })
            .filter(function(part) {
                return part !== '';
            })
            .value()
            .join('. ');

        return defer.resolve({
            quote: finalQuote,
            image: path.join(config.imagesFolder, helpers.choice(person.images)),
            signature: person.signature,
            font: font
        });
    };

    if(DEBUG) {
        parseResponse(config.content.debugContent);
    } else {
        request(fullURL, function(error, response, body) {
            if (!error && response.statusCode === 200) {
                parseResponse(JSON.parse(body));
            } else {
                defer.reject(error ? error : 'Timeout. Try again.');
            }
        });
    }

    return defer.promise();
}

/**
* Generator
*/

function generate() {
    var defer = new _.Deferred();

    getQuote().done(function(result) {
        builder(result, config).done(function(output) {
            result.output = output;
            return defer.resolve(result);
        }).fail(function(err) {
            console.log('Image generation error: ', err);
        });
    }).fail(function(err){
        console.log('Lyric API error: ', err);
    });

    return defer.promise();
}

/**
* Tweeter
*/

function tweet() {
    generate().then(function(myTweet) {

        var maxLength = 117;

        var cutStatus = '"' + myTweet.quote.substring(0, maxLength - (myTweet.signature.length + 6)) + '..." ';

        var status = cutStatus + myTweet.signature;

        if(DEBUG) {
            console.log({
                debug: true,
                originalStatus: myTweet.quote,
                status: status,
                statusLength: status.length,
                output: myTweet.output
            });

            return;
        }

        var params = {
            url: 'https://api.twitter.com/1.1/statuses/update_with_media.json',
            oauth: config.oauth
        };

        var r = request.post(params, function(err, response, body) {
            if(err) {
                throw err;
            }

            console.log(body);
        });

        var form = r.form();
        form.append('status', status);
        form.append('media[]', fs.createReadStream(myTweet.output));

    });
}

// Tweet every 60 minutes
if(!DEBUG) {
    setInterval(function () {
      try {
        tweet();
      }
      catch (e) {
        console.log(e);
      }
     }, config.tweetInterval);
}

// Tweet once on initialization
tweet();
<|endoftext|>


<commit_before>import {h, Component} from 'preact';

export default class Options extends Component {
    shouldComponentUpdate({colors, sizes, fixed, setColor, setSize, activeColor, activeSize, blockButton, buttonOutline, setOptions}) {
        return colors != this.props.colors ||
            sizes != this.props.sizes ||
            fixed != this.props.fixed ||
            setColor != this.props.setColor ||
            activeColor != this.props.activeColor ||
            activeSize != this.props.activeSize ||
            setSize != this.props.setSize ||
            setOptions != this.props.setOptions ||
            blockButton != this.props.blockButton ||
            buttonOutline != this.props.buttonOutline;
    }

    render({colors, sizes, fixed, setColor, setSize, activeColor, activeSize, blockButton, buttonOutline, setOptions}) {
        let activeSizeIndex = Object.keys(sizes).findIndex((size) => size === activeSize);
        let activeColorIndex = Object.keys(colors).find((color) => color === activeColor);
console.log(blockButton, buttonOutline)
        return (

                        {
                            Object.keys(colors).map((color, i) => {
                                return

 setColor(color)}
                                         style={{
                                             background: colors[color]
                                         }}>

                            })
                        }

                            {
                                Object.keys(sizes).map((size, i) => {
                                    return

 setSize(size)}>
                                            {sizes[size]}

                                })
                            }

                        <div class="options-checkbox-container animated fadeInUp hint--left hint--rounded hint--bounce"
                             data-hint="Full width buttons, Enable: `bttn-block`"
                             onClick={()=>setOptions('blockButton', !blockButton)}
                             style={{'animation-delay': '1s'}}>
                            <input class="options-checkbox options-checkbox-flat"
                                   checked={blockButton}
                                   id="block-indicator"
                                   type="checkbox"/>

                            Block Buttons

                        <div class="options-checkbox-container animated fadeInUp hint--left hint--rounded hint--bounce"
                             data-hint="Show outline when navigating with keyboard, Disable: `bttn-no-outline`"
                             onClick={()=>setOptions('buttonOutline', !buttonOutline)}
                             style={{'animation-delay': '1.1s'}}>
                            <input class="options-checkbox options-checkbox-flat"
                                   checked={buttonOutline}
                                   id="disable-outline"
                                   type="checkbox"/>

                            Outline(Accessibility)

        );
    }
}
<commit_msg>Demo fixes<commit_after>import {h, Component} from 'preact';

export default class Options extends Component {
    shouldComponentUpdate({colors, sizes, fixed, setColor, setSize, activeColor, activeSize, blockButton, buttonOutline, setOptions}) {
        return colors != this.props.colors ||
            sizes != this.props.sizes ||
            fixed != this.props.fixed ||
            setColor != this.props.setColor ||
            activeColor != this.props.activeColor ||
            activeSize != this.props.activeSize ||
            setSize != this.props.setSize ||
            setOptions != this.props.setOptions ||
            blockButton != this.props.blockButton ||
            buttonOutline != this.props.buttonOutline;
    }

    render({colors, sizes, fixed, setColor, setSize, activeColor, activeSize, blockButton, buttonOutline, setOptions}) {
        let activeSizeIndex = Object.keys(sizes).findIndex((size) => size === activeSize);
        let activeColorIndex = Object.keys(colors).find((color) => color === activeColor);
console.log(blockButton, buttonOutline)
        return (

                        {
                            Object.keys(colors).map((color, i) => {
                                return

 setColor(color)}
                                         style={{
                                             background: colors[color]
                                         }}>

                            })
                        }

                            {
                                Object.keys(sizes).map((size, i) => {
                                    return

 setSize(size)}>
                                            {sizes[size]}

                                })
                            }

                        <div class="options-checkbox-container animated fadeInUp hint--left hint--rounded hint--bounce"
                             data-hint="Full width buttons, Enable: `bttn-block`"
                             onClick={()=>setOptions('blockButton', !blockButton)}
                             style={{'animation-delay': '1s'}}>
                            <input class="options-checkbox options-checkbox-flat"
                                   checked={blockButton}
                                   id="block-indicator"
                                   type="checkbox"/>

                            Block Buttons

                        <div class="options-checkbox-container animated fadeInUp hint--left hint--rounded hint--bounce"
                             data-hint="Button outline when navigating with keyboard, Disable: `bttn-no-outline`"
                             onClick={()=>setOptions('buttonOutline', !buttonOutline)}
                             style={{'animation-delay': '1.1s'}}>
                            <input class="options-checkbox options-checkbox-flat"
                                   checked={buttonOutline}
                                   id="disable-outline"
                                   type="checkbox"/>

                            Outline(Accessibility)

        );
    }
}
<|endoftext|>


<commit_before>const RANDOM_BONUS = 100;
const CREATE_BONUS = 8;
const TYPE_BONUS_HEIGHT = "bonus_height";
const TYPE_BONUS_WIDTH = "bonus_width";
const NB_COLLECTIBLE = 2;
const TIMER_COLLECTIBLES = 2000;

Collectible = function(x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.color = '#00ff00';
    this.type = "Unknown";
    //this.m_scoring = new jaws.Text({text: this.text, x: x, y: y});
}

Collectible.prototype.draw = function(viewport) {
    var ctx = jaws.context;
    ctx.beginPath();
    ctx.moveTo(this.x - viewport.x, this.y - this.height - viewport.y);
    ctx.lineTo(this.x - viewport.x, this.y - viewport.y);
    ctx.lineTo(this.x + this.width - viewport.x, this.y - viewport.y);
    ctx.lineTo(this.x + this.width - viewport.x, this.y - this.height - viewport.y);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.strokeStyle = '#ff0000';
    ctx.stroke();
    ctx.fill();
}

Collectibles = function() {
    this.collectibles = [];
    this.timer = new Timer();
    this.timer.start();
}

Collectibles.prototype.createCollectible = function(x, collectible) {
    var b = new SAT.Box(new SAT.Vector(x, y), collectible.width, collectible.height);
    this.collectibles.push({sprite: collectible, box: b, type: collectible.type});
}

Collectibles.prototype.manageCollectibles = function(viewport) {
    if( this.timer.getInterval() >= TIMER_COLLECTIBLES) {
        this.timer.reset();
        collectibleId = Math.floor(Math.random() * NB_COLLECTIBLE) + 1;
        var collectible = null;
        x = viewport.x + 600;
        y = 500;
        switch(collectibleId) {
            case 1:
                collectible = new BonusHeight(x, y, 30, 30);
            break;
            case 2:
                collectible = new BonusWidth(x, y, 30, 30);
            break;
        }
        this.createCollectible(x, collectible);
    }
}

Collectibles.prototype.collidedAt = function(character_hitbox) {
    for(var index = 0; index < this.collectibles.length; ++index) {
        var response = new SAT.Response();
        var collided = SAT.testPolygonPolygon(character_hitbox.toPolygon(), this.collectibles[index].box.toPolygon(), response);
        if(collided) {
            var type = this.collectibles[index].type;
            this.collectibles.splice(index, 1);
            return type;
        }
    }
    return false;
}

Collectibles.prototype.draw = function(viewport) {
    for(var i = 0; i < this.collectibles.length; i++) {
        this.collectibles[ i ].sprite.draw(viewport);
    }
}

BonusHeight = function (x, y, width, height) {
    Collectible.call(this, x, y, width, height);
    this.color = "#00f000";
    this.type = TYPE_BONUS_HEIGHT;

}

BonusHeight.prototype.draw = function(viewport) {
   Collectible.prototype.draw.call(this, viewport);
}

BonusWidth = function (x, y, width, height) {
    Collectible.call(this, x, y, width, height);
    this.color = "#00f0ff";
    this.type = TYPE_BONUS_WIDTH;

}

BonusWidth.prototype.draw = function(viewport) {
   Collectible.prototype.draw.call(this, viewport);
}
<commit_msg>handle different collectibles + displaying<commit_after>const RANDOM_BONUS = 100;
const CREATE_BONUS = 8;

const TYPE_BONUS_HEIGHT = "bonus_height";
const TYPE_BONUS_WIDTH = "bonus_width";
const WIDTH_TRIANGLE = 12;
const HEIGHT_TRIANGLE = 12;
const WIDTH_LINE = 20;
const HEIGHT_LINE = 7;

const NB_COLLECTIBLE = 2;
const TIMER_COLLECTIBLES = 1500;//15000

Collectible = function(x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.color = '#00ff00';
    this.type = "Unknown";
}

Collectible.prototype.draw = function(viewport) {
    var ctx = jaws.context;
    ctx.beginPath();
    ctx.moveTo(this.x - viewport.x, this.y - this.height - viewport.y);
    ctx.lineTo(this.x - viewport.x, this.y - viewport.y);
    ctx.lineTo(this.x + this.width - viewport.x, this.y - viewport.y);
    ctx.lineTo(this.x + this.width - viewport.x, this.y - this.height - viewport.y);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.strokeStyle = '#ff0000';
    ctx.stroke();
    ctx.fill();
}

Collectibles = function() {
    this.collectibles = [];
    this.timer = new Timer();
    this.timer.start();
}

Collectibles.prototype.createCollectible = function(x, collectible) {
    var b = new SAT.Box(new SAT.Vector(x, y), collectible.width, collectible.height);
    this.collectibles.push({sprite: collectible, box: b, type: collectible.type});
}

Collectibles.prototype.manageCollectibles = function(viewport) {
    if( this.timer.getInterval() >= TIMER_COLLECTIBLES) {
        this.timer.reset();
        collectibleId = Math.floor(Math.random() * NB_COLLECTIBLE) + 1;
        var collectible = null;
        x = viewport.x + 600;
        y = 500;
        switch(collectibleId) {
            case 1:
                collectible = new BonusWidth(x, y, WIDTH_TRIANGLE * 2 + WIDTH_LINE, HEIGHT_TRIANGLE);
            break;
            case 2:
                collectible = new BonusHeight(x, y, WIDTH_TRIANGLE, HEIGHT_TRIANGLE * 2 + HEIGHT_LINE);
            break;
        }
        this.createCollectible(x, collectible);
    }
}

Collectibles.prototype.collidedAt = function(character_hitbox) {
    for(var index = 0; index < this.collectibles.length; ++index) {
        var response = new SAT.Response();
        var collided = SAT.testPolygonPolygon(character_hitbox.toPolygon(), this.collectibles[index].box.toPolygon(), response);
        if(collided) {
            var type = this.collectibles[index].type;
            this.collectibles.splice(index, 1);
            return type;
        }
    }
    return false;
}

Collectibles.prototype.draw = function(viewport) {
    for(var i = 0; i < this.collectibles.length; i++) {
        this.collectibles[ i ].sprite.draw(viewport);
    }
}

BonusWidth = function (x, y, width, height) {
    Collectible.call(this, x, y, width, height);
    this.color = "#00f000";
    this.type = TYPE_BONUS_HEIGHT;
    this.widthTriangle = WIDTH_TRIANGLE;
    this.heightTriangle = HEIGHT_TRIANGLE;
    this.widthLine = WIDTH_LINE;
    this.heightLine = HEIGHT_LINE;

}

BonusWidth.prototype.draw = function(viewport) {
    var ctx = jaws.context;
    //left triangle
    ctx.beginPath();
    ctx.moveTo(this.x - viewport.x + this.widthTriangle/2, this.y - this.heightTriangle - viewport.y);
    ctx.lineTo(this.x + this.widthTriangle/2 + this.widthTriangle - viewport.x, this.y - viewport.y);
    ctx.lineTo(this.x - this.widthTriangle/2 - viewport.x, this.y - viewport.y);
    ctx.fill();
    ctx.fillStyle = this.color;
    ctx.fill();

    //central stripe
    var newOrigin = {x: this.x + this.widthTriangle/2 - this.heightLine/2, y: this.y + this.heightLine + this.heightTriangle}
    ctx.beginPath();
    ctx.moveTo(newOrigin.x - viewport.x, newOrigin.y - this.widthLine - viewport.y);
    ctx.lineTo(newOrigin.x - viewport.x, newOrigin.y - viewport.y);
    ctx.lineTo(newOrigin.x + this.heightLine - viewport.x, newOrigin.y - viewport.y);
    ctx.lineTo(newOrigin.x + this.heightLine - viewport.x, newOrigin.y - this.widthLine - viewport.y);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();

    //right triangle
    newOrigin = {x: this.x, y: this.y + this.heightLine + this.heightTriangle}
    ctx.beginPath();
    ctx.moveTo(newOrigin.x - viewport.x + this.widthTriangle/2, newOrigin.y + this.heightTriangle - viewport.y);
    ctx.lineTo(newOrigin.x +this.widthTriangle/2 + this.widthTriangle - viewport.x, newOrigin.y - viewport.y);
    ctx.lineTo(newOrigin.x - this.widthTriangle/2 - viewport.x, newOrigin.y - viewport.y);
    ctx.fill();
    ctx.fillStyle = this.color;
    ctx.fill();
}

BonusHeight = function (x, y, width, height) {
    Collectible.call(this, x, y, width, height);
    this.color = "#00f0ff";
    this.type = TYPE_BONUS_WIDTH;
    this.widthTriangle = WIDTH_TRIANGLE;
    this.heightTriangle = HEIGHT_TRIANGLE;
    this.widthLine = 20;
    this.heightLine = 7;

}

BonusHeight.prototype.draw = function(viewport) {
    var ctx = jaws.context;
    //left triangle
    ctx.beginPath();
    ctx.moveTo(this.x - viewport.x, this.y - viewport.y);
    ctx.lineTo(this.x + this.widthTriangle - viewport.x, this.y - this.heightTriangle - viewport.y);
    ctx.lineTo(this.x + this.widthTriangle - viewport.x, this.y + this.heightTriangle - viewport.y);
    ctx.fill();
    ctx.fillStyle = this.color;
    ctx.fill();

    //central stripe
    var newOrigin = {x: this.x + this.widthTriangle, y: this.y + this.heightLine/2}
    ctx.beginPath();
    ctx.moveTo(newOrigin.x - viewport.x, newOrigin.y - this.heightLine - viewport.y);
    ctx.lineTo(newOrigin.x - viewport.x, newOrigin.y - viewport.y);
    ctx.lineTo(newOrigin.x + this.widthLine - viewport.x, newOrigin.y - viewport.y);
    ctx.lineTo(newOrigin.x + this.widthLine - viewport.x, newOrigin.y - this.heightLine - viewport.y);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();

    // right triangle
    newOrigin = {x: newOrigin.x + this.widthLine, y: this.y}
    ctx.beginPath();
    ctx.moveTo(newOrigin.x + this.widthTriangle - viewport.x, newOrigin.y - viewport.y);
    ctx.lineTo(newOrigin.x - viewport.x, newOrigin.y - this.heightTriangle - viewport.y);
    ctx.lineTo(newOrigin.x - viewport.x, newOrigin.y + this.heightTriangle - viewport.y);
    ctx.fill();
    ctx.fillStyle = this.color;
    ctx.fill();

}<|endoftext|>


<commit_before>/* Hammer-time - v0.3.0
 * http://github.com/hammerjs/hammer-time
 *
 * Copyright Alexander Schmitz and other contributors
 * Released under the MIT license
 *
 * Expiramental fastclick based on a partial polyfill of
 * touch-action: none; CSS property
 */

( function() {

// If there is native touch action bail the hammer has already dropped
if ( document.documentElement.style[ "touch-action" ] !== undefined ||
		document.documentElement.style[ "-ms-touch-action" ] ) {
	return;
}

//Check if a global Hammer object already exists
window.Hammer = window.Hammer || {};

// We need to save the last touch start in iOS 8 if it is more then 150ms
// it will trigger native fast click which cant ne stoped even with all the might
// of thors hammer and a return false
var MO = window.MutationObserver || window.WebKitMutationObserver;
var touchMatchNone = /touch-action[:][\s]*(none)[^;'"]*/;
var touchMatchManipulation = /touch-action[:][\s]*(manipulation)[^;'"]*/;
var touchMatch = /touch-action/;
var iOS = ( navigator.userAgent.match( /(iPad|iPhone|iPod)/g ) ? true : false );
var gl = ( function() {
	try {
		var canvas = document.createElement( "canvas" );
		return !!( window.WebGLRenderingContext && ( canvas.getContext( "webgl" ) ||
			canvas.getContext( "experimental-webgl" ) ) );
	}
	catch ( e ) { return false; } } )();
var timeTouch = gl && iOS;

window.Hammer.time = {

	// Checkes if an element has touch action none in its style attribute and thusly should
	// be hammered upon
	getTouchAction: function( element ) {
		return this.checkStyleString( element.getAttribute( "style" ) );
	},
	checkStyleString: function( style ) {
		if ( !touchMatch.test( style ) ) {
			return undefined;
		}
		if ( touchMatchNone.test( style ) ) {
			return "none";
		}
		if ( touchMatchManipulation.test( style ) ) {
			return "manipulation";
		}
		return true;
	},
	shouldHammer: function( e ) {
		var parentAction = this.hasParent( e.target );
		return ( parentAction && ( !timeTouch || Date.now() - e.target.lastStart < 125 ) ) ?
				parentAction : false;
	},
	touchHandler: function( e ) {
		var pos = e.target.getBoundingClientRect();
		var scrolled = pos.top !== this.pos.top || pos.left !== this.pos.left;
		var hammerType = this.shouldHammer( e );

		// Check both if we should trigger fast click and the time to avoid a double trigger with
		// native fast click
		if ( hammerType === "none" ||
					( scrolled === false && hammerType === "manipulation" ) ) {
			if ( e.type === "touchend" ) {
				e.target.focus();

				// Wait for next tic so events fire in proper order
				setTimeout( function() {
					e.target.click();
				}, 0 );
			}

			// Prevent the click which will come after this otherwise but with a 300ms delay
			e.preventDefault();
		}
		this.scrolled = false;
		delete e.target.lastStart;
	},
	styleWatcher: function( mutations ) {
		mutations.forEach( this.styleUpdater, this );
	},
	styleUpdater: function( mutation ) {

		// We just caused this update bail
		if ( mutation.target.updateNext ) {
			mutation.target.updateNext = false;
			return;
		}

		var touchAction = this.getTouchAction( mutation.target );

		if ( touchAction ) {
			if ( touchAction !== "none" ) {
				mutation.target.hadTouchNone = false;
			}
			return;
		}
		if ( !touchAction &&
				(
					( mutation.oldValue && this.checkStyleString( mutation.oldValue ) ) ||
					mutation.target.hadTouchNone
				)
			) {

			// We save this because durring animations which update the style in quick succession
			// style tag can update quicker then the mutation observer fires so we lose the oldValue
			// property which contains our refrence to the original which contained touch-action.
			mutation.target.hadTouchNone = true;

			// Save the fact that we caused the next update
			mutation.target.updateNext = false;

			// Add touch action back to the style attribute
			mutation.target
				.setAttribute( "style",
					mutation.target.getAttribute( "style" ) + " touch-action: none;"
				);

		}
	},
	hasParent: function( node ) {
		var touchAction;
		for ( var cur = node; cur && cur.parentNode; cur = cur.parentNode ) {
			touchAction = this.getTouchAction( cur );
			if ( touchAction ) {
				return touchAction;
			}
		}
		return false;
	},
	installStartEvents: function() {
		document.addEventListener( "touchstart", function( e ) {
			this.pos = e.target.getBoundingClientRect();
			if ( timeTouch && this.hasParent( e.target ) ) {
				e.target.lastStart = Date.now();
			}
		}.bind( this ) );
	},
	installEndEvents: function() {
		document.addEventListener( "touchend", this.touchHandler.bind( this ), true );
		document.addEventListener( "mouseup", this.touchHandler.bind( this ), true );
	},
	installObserver: function() {

		// We need to observe changes to style attributes because if something updates the style
		// attribute it will remove the touch-action property because browsers santitize the style
		// attribute when its properties are updated.
		this.observer = new MO( this.styleWatcher.bind( this ) ).observe( document, {
			subtree: true,
			attributes: true,
			attributeOldValue: true,
			attributeFilter: [ "style" ]
		} );
	},
	install: function() {
		this.installEndEvents();
		this.installStartEvents();
		this.installObserver();
	}
};

window.Hammer.time.install();

} )();
<commit_msg>Prevent errors by exiting early for unsupported features.<commit_after>/* Hammer-time - v0.3.0
 * http://github.com/hammerjs/hammer-time
 *
 * Copyright Alexander Schmitz and other contributors
 * Released under the MIT license
 *
 * Expiramental fastclick based on a partial polyfill of
 * touch-action: none; CSS property
 */

( function() {
	// Detect support for necessary features;
	var MO = window.MutationObserver || window.WebKitMutationObserver;
	var touchevents = ('ontouchstart' in window) ||
	                  (window.DocumentTouch && document instanceof DocumentTouch);
	var nativeTouchAction = document.documentElement.style[ "touch-action" ] !== undefined ||
			                            document.documentElement.style[ "-ms-touch-action" ];

// If there is native touch action bail the hammer has already dropped
if ( nativeTouchAction || !touchevents || !MO ) {
	return;
}

//Check if a global Hammer object already exists
window.Hammer = window.Hammer || {};

// We need to save the last touch start in iOS 8 if it is more then 150ms
// it will trigger native fast click which cant ne stoped even with all the might
// of thors hammer and a return false
var touchMatchNone = /touch-action[:][\s]*(none)[^;'"]*/;
var touchMatchManipulation = /touch-action[:][\s]*(manipulation)[^;'"]*/;
var touchMatch = /touch-action/;
var iOS = ( navigator.userAgent.match( /(iPad|iPhone|iPod)/g ) ? true : false );
var gl = ( function() {
	try {
		var canvas = document.createElement( "canvas" );
		return !!( window.WebGLRenderingContext && ( canvas.getContext( "webgl" ) ||
			canvas.getContext( "experimental-webgl" ) ) );
	}
	catch ( e ) { return false; } } )();
var timeTouch = gl && iOS;

window.Hammer.time = {

	// Checkes if an element has touch action none in its style attribute and thusly should
	// be hammered upon
	getTouchAction: function( element ) {
		return this.checkStyleString( element.getAttribute( "style" ) );
	},
	checkStyleString: function( style ) {
		if ( !touchMatch.test( style ) ) {
			return undefined;
		}
		if ( touchMatchNone.test( style ) ) {
			return "none";
		}
		if ( touchMatchManipulation.test( style ) ) {
			return "manipulation";
		}
		return true;
	},
	shouldHammer: function( e ) {
		var parentAction = this.hasParent( e.target );
		return ( parentAction && ( !timeTouch || Date.now() - e.target.lastStart < 125 ) ) ?
				parentAction : false;
	},
	touchHandler: function( e ) {
		var pos = e.target.getBoundingClientRect();
		var scrolled = pos.top !== this.pos.top || pos.left !== this.pos.left;
		var hammerType = this.shouldHammer( e );

		// Check both if we should trigger fast click and the time to avoid a double trigger with
		// native fast click
		if ( hammerType === "none" ||
					( scrolled === false && hammerType === "manipulation" ) ) {
			if ( e.type === "touchend" ) {
				e.target.focus();

				// Wait for next tic so events fire in proper order
				setTimeout( function() {
					e.target.click();
				}, 0 );
			}

			// Prevent the click which will come after this otherwise but with a 300ms delay
			e.preventDefault();
		}
		this.scrolled = false;
		delete e.target.lastStart;
	},
	styleWatcher: function( mutations ) {
		mutations.forEach( this.styleUpdater, this );
	},
	styleUpdater: function( mutation ) {

		// We just caused this update bail
		if ( mutation.target.updateNext ) {
			mutation.target.updateNext = false;
			return;
		}

		var touchAction = this.getTouchAction( mutation.target );

		if ( touchAction ) {
			if ( touchAction !== "none" ) {
				mutation.target.hadTouchNone = false;
			}
			return;
		}
		if ( !touchAction &&
				(
					( mutation.oldValue && this.checkStyleString( mutation.oldValue ) ) ||
					mutation.target.hadTouchNone
				)
			) {

			// We save this because durring animations which update the style in quick succession
			// style tag can update quicker then the mutation observer fires so we lose the oldValue
			// property which contains our refrence to the original which contained touch-action.
			mutation.target.hadTouchNone = true;

			// Save the fact that we caused the next update
			mutation.target.updateNext = false;

			// Add touch action back to the style attribute
			mutation.target
				.setAttribute( "style",
					mutation.target.getAttribute( "style" ) + " touch-action: none;"
				);

		}
	},
	hasParent: function( node ) {
		var touchAction;
		for ( var cur = node; cur && cur.parentNode; cur = cur.parentNode ) {
			touchAction = this.getTouchAction( cur );
			if ( touchAction ) {
				return touchAction;
			}
		}
		return false;
	},
	installStartEvents: function() {
		document.addEventListener( "touchstart", function( e ) {
			this.pos = e.target.getBoundingClientRect();
			if ( timeTouch && this.hasParent( e.target ) ) {
				e.target.lastStart = Date.now();
			}
		}.bind( this ) );
	},
	installEndEvents: function() {
		document.addEventListener( "touchend", this.touchHandler.bind( this ), true );
		document.addEventListener( "mouseup", this.touchHandler.bind( this ), true );
	},
	installObserver: function() {

		// We need to observe changes to style attributes because if something updates the style
		// attribute it will remove the touch-action property because browsers santitize the style
		// attribute when its properties are updated.
		this.observer = new MO( this.styleWatcher.bind( this ) ).observe( document, {
			subtree: true,
			attributes: true,
			attributeOldValue: true,
			attributeFilter: [ "style" ]
		} );
	},
	install: function() {
		this.installEndEvents();
		this.installStartEvents();
		this.installObserver();
	}
};

window.Hammer.time.install();

} )();
<|endoftext|>


<commit_before>var utils = {};
var mButton = require('mobile-button');
var chessground = require('chessground');
var i18n = require('./i18n');

utils.hasNetwork = function() {
  var t = window.navigator.connection.type;
  return t !== window.Connection.NONE && t !== window.Connection.UNKNOWN;
};

/*
 * Util function to handle xhr errors per request. We don't want to show alerts
 * for every request, this is why it's not done in the extract function of
 * m.request.
 *
 * @param {Error} error The error thrown in extract function (see http.js)
 */
utils.handleXhrError = function(error) {
  if (!utils.hasNetwork())
    window.navigator.notification.alert(i18n('noInternetConnection'), null, i18n('connectionError'));
  else
    window.plugins.toast.show(i18n(error.message), 'short', 'center');
};

utils.lichessSri = Math.random().toString(36).substring(2);

utils.serializeQueryParameters = function(obj) {
  var str = '';
  for (var key in obj) {
    if (str !== '') {
      str += '&';
    }
    str += key + '=' + obj[key];
  }
  return str;
};

function partialApply(fn, args) {
  return fn.bind.apply(fn, [null].concat(args));
}

utils.partialƒ = function() {
  return partialApply(arguments[0], Array.prototype.slice.call(arguments, 1));
};

utils.ƒ = function() {
  var args = arguments, fn = arguments[0];
  return function() {
    fn.apply(fn, Array.prototype.slice.call(args, 1));
  };
};

// convenience function to bind a touchend mobile button handler in mithril
function bindTouchendButton(scrollable, handler) {
  return function(el, isUpdate, context) {
    if (!isUpdate) {
      var options = {
        el: el,
        f: function(e) {
          m.startComputation();
          handler(e, el);
          m.endComputation();
        },
        monotouchable: true,
        setActiveCls: false
      };
      if (scrollable) options.tolerance = 5;
      var constr = scrollable ? mButton.ScrollableX.Touchend : mButton.Touchend;
      var button = new constr(options);

      context.onunload = function() {
        if (button) button.unbind();
      };
    }
  };
}

utils.ontouchend = utils.partialƒ(bindTouchendButton, false);
utils.ontouchendScroll = utils.partialƒ(bindTouchendButton, true);

var viewPortDims = null;
utils.getViewportDims = function() {
  if (viewPortDims) return viewPortDims;
  var e = document.documentElement;
  viewPortDims = { vw: e.clientWidth, vh: e.clientHeight };
  return viewPortDims;
};

utils.viewOnlyBoard = function(fen, lastMove, orientation) {
  var ctrl = new chessground.controller({
    viewOnly: true,
    minimalDom: true,
    coordinates: false,
    fen: fen,
    lastMove: lastMove,
    orientation: orientation || 'white'
  });
  return m('div.board.grey.merida', [
    chessground.view(ctrl)
  ]);
};

utils.classSet = function(classes) {
  var arr = [];
  for (var i in classes) {
    if (classes[i]) arr.push(i);
  }
  return arr.join(' ');
};

utils.noop = function() {
};

utils.backHistory = function() {
  window.history.go(-1);
  if (window.navigator.app && window.navigator.app.backHistory)
    window.navigator.app.backHistory();
};

module.exports = utils;
<commit_msg>fix view only chessground elements - resolves #29<commit_after>var utils = {};
var mButton = require('mobile-button');
var chessground = require('chessground');
var i18n = require('./i18n');

utils.hasNetwork = function() {
  var t = window.navigator.connection.type;
  return t !== window.Connection.NONE && t !== window.Connection.UNKNOWN;
};

/*
 * Util function to handle xhr errors per request. We don't want to show alerts
 * for every request, this is why it's not done in the extract function of
 * m.request.
 *
 * @param {Error} error The error thrown in extract function (see http.js)
 */
utils.handleXhrError = function(error) {
  if (!utils.hasNetwork())
    window.navigator.notification.alert(i18n('noInternetConnection'), null, i18n('connectionError'));
  else
    window.plugins.toast.show(i18n(error.message), 'short', 'center');
};

utils.lichessSri = Math.random().toString(36).substring(2);

utils.serializeQueryParameters = function(obj) {
  var str = '';
  for (var key in obj) {
    if (str !== '') {
      str += '&';
    }
    str += key + '=' + obj[key];
  }
  return str;
};

function partialApply(fn, args) {
  return fn.bind.apply(fn, [null].concat(args));
}

utils.partialƒ = function() {
  return partialApply(arguments[0], Array.prototype.slice.call(arguments, 1));
};

utils.ƒ = function() {
  var args = arguments, fn = arguments[0];
  return function() {
    fn.apply(fn, Array.prototype.slice.call(args, 1));
  };
};

// convenience function to bind a touchend mobile button handler in mithril
function bindTouchendButton(scrollable, handler) {
  return function(el, isUpdate, context) {
    if (!isUpdate) {
      var options = {
        el: el,
        f: function(e) {
          m.startComputation();
          handler(e, el);
          m.endComputation();
        },
        monotouchable: true,
        setActiveCls: false
      };
      if (scrollable) options.tolerance = 5;
      var constr = scrollable ? mButton.ScrollableX.Touchend : mButton.Touchend;
      var button = new constr(options);

      context.onunload = function() {
        if (button) button.unbind();
      };
    }
  };
}

utils.ontouchend = utils.partialƒ(bindTouchendButton, false);
utils.ontouchendScroll = utils.partialƒ(bindTouchendButton, true);

var viewPortDims = null;
utils.getViewportDims = function() {
  if (viewPortDims) return viewPortDims;
  var e = document.documentElement;
  viewPortDims = { vw: e.clientWidth, vh: e.clientHeight };
  return viewPortDims;
};

utils.viewOnlyBoard = function(fen, lastMove, orientation) {
  var config = {
    viewOnly: true,
    minimalDom: true,
    coordinates: false,
    fen: fen,
    lastMove: lastMove,
    orientation: orientation || 'white'
  };
  return m('div.board.grey.merida', {
    config: function(el, isUpdate, ctx) {
      if (ctx.ground) ctx.ground.set(config);
      else ctx.ground = chessground(el, config);
    }
  });
};

utils.classSet = function(classes) {
  var arr = [];
  for (var i in classes) {
    if (classes[i]) arr.push(i);
  }
  return arr.join(' ');
};

utils.noop = function() {
};

utils.backHistory = function() {
  window.history.go(-1);
  if (window.navigator.app && window.navigator.app.backHistory)
    window.navigator.app.backHistory();
};

module.exports = utils;
<|endoftext|>


<commit_before>/* global Airbrake*/
import Ember from "ember";
import config from "../config/environment.js";

var isSetup = false;

function setupAirbrake(){
  Airbrake.addReporter(Airbrake.consoleReporter);
  Airbrake.setProject(config.airbrake.projectId, config.airbrake.projectKey);
  Airbrake.setEnvironmentName(config.environment);
  Airbrake.addEnvironment({
    user_agent: window.navigator.userAgent
  });

  var originalOnError = Ember.onerror || Ember.K;
  Ember.onerror = function(err) { // any ember error
    originalOnError(err);
    Airbrake.push(err);
  };
  Ember.RSVP.on('error',function(err){ // any promise error
    Airbrake.push(err);
  });
  window.onerror = function(message, file, line, column, error){ // window general errors.
    if (message === 'Script error.') {
      // Ignore.
      return;
    }

    if (error) {
      Airbrake.push({error: error});
    } else {
      Airbrake.push({error: {
        message: message,
        fileName: file,
        lineNumber: line,
        columnNumber: column || 0
      }});
    }
  };
}

export function initialize(/* container, application */) {
  if (config.airbrake && !isSetup) {
    isSetup = true;
    if (Airbrake.setProject) {
      setupAirbrake();
    } else {
      Airbrake.onload = setupAirbrake;
    }
  }
}

export default {
  name: 'airbrake',
  initialize: initialize
};
<commit_msg>Allow setting custom host<commit_after>/* global Airbrake*/
import Ember from "ember";
import config from "../config/environment.js";

var isSetup = false;

function setupAirbrake(){
  Airbrake.addReporter(Airbrake.consoleReporter);
  Airbrake.setProject(config.airbrake.projectId, config.airbrake.projectKey);
  Airbrake.setEnvironmentName(config.environment);
  if (config.airbrake.host) {
    Airbrake.setHost(config.airbrake.host);
  }
  Airbrake.addEnvironment({
    user_agent: window.navigator.userAgent
  });

  var originalOnError = Ember.onerror || Ember.K;
  Ember.onerror = function(err) { // any ember error
    originalOnError(err);
    Airbrake.push(err);
  };
  Ember.RSVP.on('error',function(err){ // any promise error
    Airbrake.push(err);
  });
  window.onerror = function(message, file, line, column, error){ // window general errors.
    if (message === 'Script error.') {
      // Ignore.
      return;
    }

    if (error) {
      Airbrake.push({error: error});
    } else {
      Airbrake.push({error: {
        message: message,
        fileName: file,
        lineNumber: line,
        columnNumber: column || 0
      }});
    }
  };
}

export function initialize(/* container, application */) {
  if (config.airbrake && !isSetup) {
    isSetup = true;
    if (Airbrake.setProject) {
      setupAirbrake();
    } else {
      Airbrake.onload = setupAirbrake;
    }
  }
}

export default {
  name: 'airbrake',
  initialize: initialize
};
<|endoftext|>


<commit_before>var through2 = require('through2');
var path = require('path');
var PluginError = require('gulp-util').PluginError;

var HEX_DIGITS = '0123456789abcdef'.split('');
var OCTAL_DIGITS = '01234567'.split('');

module.exports = ddescribeIit;
function ddescribeIit(opt) {
  'use strict';
  opt = opt || { allowDisabledTests: true, noColor: false };
  var noColor = getOrDefault(opt, 'noColor', false);
  var supports_colors =  !noColor && /* istanbul ignore next */ (function() {
    // E2E testable, but trivial --- ignored
    if (process.argv.indexOf('--no-color') !== -1) return false;
    if (process.stdout && !process.stdout.isTTY) return false;
    if (process.platform === 'win32') return true;
    if ('COLORTERM' in process.env) return true;
    if (process.env.TERM === 'dumb') return false;
    if (/^xterm|^vt100|color|ansi|cygwin|linux/i.test(process.env.TERM)) return true;
    return false;
  })();

  var colors = {
    red: color(31, 39),
    gray: color(90, 39)
  };
  function color(open, close) {
    if (!supports_colors) return null;
    return {
      open: '\u001b[' + open + 'm',
      close: '\u001b[' + close + 'm'
    };
  }

  function getOrDefault(o, key, def) {
    var val = o[key];
    if (val === void 0) val = def;
    return val;
  }

  var allowDisabledTests = getOrDefault(opt, 'allowDisabledTests', true);
  var basePath = getOrDefault(opt, 'basePath', process.cwd());
  var tabWidth = getOrDefault(opt, 'tabWidth', 4);
  if (typeof tabWidth !== 'number') tabWidth = 4;
  if (tabWidth < 2) tabWidth = 2;
  if (tabWidth > 8) tabWidth = 8;
  var tabString = '  ';
  while (tabString.length < tabWidth) tabString += ' ';

  var BAD_FUNCTIONS = [
    // jasmine / minijasminenode / angular
    'iit',
    'ddescribe',

    // jasmine 2.0 "focused" specs
    'fit',
    'fdescribe',

    // mocha
    'it.only',
    'describe.only',
  ];

  var DISABLED_TEST_FUNCTIONS = [
    // jasmine / minijasminenode / angular
    'xit',
    'xdescribe',

    // TODO(@caitp): support mocha `this.skip()` api?
  ];

  if (!allowDisabledTests) {
    BAD_FUNCTIONS = BAD_FUNCTIONS.concat(DISABLED_TEST_FUNCTIONS);
  }

  function makeErrorContext(lines, lineNo, column, word, renderColumn) {
    var words = word.split('\n');
    var before = lineNo > 1 ? lines[lineNo - 2] : null;
    var current = lines[lineNo - 1];

    var result = '';
    if (before !== null) result += (normalize(lines.length, lineNo - 1) + before + '\n');
    result += (normalize(lines.length, lineNo) + current + '\n');
    var word0 = words.shift().replace(/\s+$/, '');

    result += (normalize(lines.length, lineNo, true) + underline(renderColumn, word0.length) + '\n');
    while (words.length) {
      var nextLine = lines[lineNo++];
      result += (normalize(lines.length, lineNo) + nextLine + '\n');
      var start = /[^\s]/.exec(words[0]);
      if (start) {
        start = start.index;
        var end = /\s*$/.exec(words[0].slice(start));
        end = end ? end.index : words[0].length;
        result += (normalize(lines.length, lineNo, true) + underline(start, end) + '\n');
      }
      words.shift();
    }
    var after = lineNo < lines.length ? lines[lineNo] : null;
    if (after !== null) result += (normalize(lines.length, lineNo + 1) + after + '\n');

    return result;

    function normalize(total, num, blank) {
      var needed = ' ' + total.toString(10);
      if (blank) {
        num = ' ';
        while (num.length < needed.length) num += ' ';
      } else {
        num = num.toString(10);
        while (num.length < needed.length) num = ' ' + num;
      }
      return gray(num + '| ');
    }

    function underline(offset, length) {
      var s = '';
      while (s.length < offset) s += ' ';
      var u = '';
      while (u.length < length) u += '^';
      return s + red(u);
    }
  }

  var BAD_FUNCTIONS_SRC = BAD_FUNCTIONS.map(function(fn) {
    fn = fn.split('.').
      reduce(function joinIdentifiers(current, id) {
        var identifier = transformIdentifier(id);
        if (!current) return identifier;
        var stringKey = transformString(id);
        id =
          // MemberExpression . Identifier
          '(?:\\.\\s*' + identifier + ')|' +
          // MemberExpression [ StringLiteral ]
          '(?:\\[\\s*\"' + stringKey + '\"\\s*\\])|' +
          '(?:\\[\\s*\'' + stringKey + '\'\\s*\\])';
        return current + '\\s*?(?:' + id + ')';
      }, "");
    return '(?:' + fn + ')';

    function transformIdentifier(id) {
      var s = '';
      for (var i = 0; i < id.length; ++i) {
        var c = id.charAt(i);
        var x = toHex4Digits(c);
        // UnicodeEscapeSequence :: u Hex4Digits
        var unicode1 = '\\\\u' + x;
        // UnicodeEscapeSequence :: u { Hex4Digits }
        var unicode2 = '\\\\u\\{0*' + x.slice(2) + '\\}';
        s += '(?:' + c + '|(?:' + unicode1 + ')|(?:' + unicode2 + '))';
      }
      return s;
    }

    function transformString(id) {
      var s = '';
      for (var i = 0; i < id.length; ++i) {
        var c = id.charAt(i);
        var x = toHex4Digits(c);
        var o = toOctal(c);
        // UnicodeEscapeSequence :: u Hex4Digits
        var unicode1 = '\\\\u' + x;
        // UnicodeEscapeSequence :: u { Hex4Digits }
        var unicode2 = '\\\\u\\{0*' + x.slice(2) + '\\}';
        // HexEscapeSequence :: x HexDigit HexDigit
        var hex = '\\\\x' + x.slice(2);
        var octal = '\\\\' + o;
        var ec = /['"\\nfnrtv]/.test(c) ? '' : ('|(?:\\\\' + c + ')');
        s += '(?:' + c + ec + '|(?:' + unicode1 + ')|(?:' + unicode2 + ')|(?:' + hex + ')|(?:' + octal + '))';
      }
      return s;
    }
    function toHex4Digits(c) {
      var x = '';
      var len = 0;
      c = c.charCodeAt(0);

      while (c) {
        var d = HEX_DIGITS[c & 0xF];
        c >>= 4;
        ++len;
        if (/[a-f]/.test(d)) {
          x = '[' + d + d.toUpperCase() + ']' + x;
        } else {
          x = d + x;
        }
      }
      if (len === 1) x = '000' + x;
      else if (len === 2) x = '00' + x;
      // TODO(@caitp): Add support for > 8bit codepoints if ever needed
      return x;
    }

    function toOctal(c) {
      var o = '';
      var len = 0;
      c = c.charCodeAt(0);
      while (c) {
        o = OCTAL_DIGITS[c & 0x7] + o;
        len++;
        c >>= 3;
      }
      return o;
    }
  }).join('|');
  var regexp = new RegExp('(^|\\s)(' + BAD_FUNCTIONS_SRC + ')\\s*\\(', 'm');
  var errors = [];
  return through2.obj(function processFile(file, enc, cb) {
    if (file.isNull()) return cb(null, file);
    if (file.isStream()) return cb(new PluginError('gulp-ddescribe-iit', 'Streaming not supported'));

    var path = file.path;
    var contents = file.contents.toString();
    var originalContents = contents;
    var renderContents = originalContents.replace(/\t/g, tabString);
    var lines = originalContents.split('\n');
    var renderLines = renderContents.split('\n');
    var start = 0;
    var lineStarts = lines.map(lineStartMapper);
    start = 0;
    var renderLineStarts = renderLines.map(lineStartMapper);
    function lineStartMapper(line) {
      var l = start;
      start += (line.length + 1);
      return l;
    }
    var pos = 0;
    var renderPos = 0;
    var match;
    while (match = regexp.exec(contents)) {
      var renderMatch = regexp.exec(renderContents);
      var index = pos + match.index + match[1].length;
      var renderIndex = renderPos + renderMatch.index + renderMatch[1].length;
      var wordLines = match[2].split('\n').length;
      if (wordLines > 1) {
        pos = index + match[2].split('\n').join(' ').length;
        renderPos = renderIndex + renderMatch[2].split('\n').join(' ').length;
      } else {
        pos = index + match[2].length;
        renderPos = renderIndex + renderMatch[2].length;
      }
      contents = contents.slice(match.index + match[1].length + match[2].length);
      renderContents = renderContents.slice(renderMatch.index + renderMatch[1].length +
                                            renderMatch[2].length);

      // Location of error
      var lineNo = originalContents.substr(0, pos).split('\n').length;
      if (wordLines > 1) lineNo -= wordLines - 1;
      var lineStart = lineStarts[lineNo - 1];
      var renderLineStart = renderLineStarts[lineNo - 1];
      var column = max(1, (index - lineStart) + 1);
      var renderColumn = max(0, (renderIndex - renderLineStart));

      var word = match[2].replace(/\t/g, tabString);
      console.log(match[2]);
      errors.push({
        file: toRelativePath(basePath, file.path),
        str: simplifyString(match[2]),
        line: lineNo,
        column: column,
        context: makeErrorContext(renderLines, lineNo, column, word, renderColumn)
      });
    }
    cb();
  }, function flushStream(cb) {
    if (errors.length) {
      var error = new PluginError('gulp-ddescribe-iit', {
        message: '\n' + errors.map(function(error) {
          return 'Found `' + error.str + '` in ' +
                 error.file + ':' + error.line + ':' + error.column + '\n' +
                 error.context;
        }).join('\n\n'),
        showStack: false,
        showProperties: false
      });
      error.raw = errors;
      this.emit('error', error);
      cb();
    }
  });

  function simplifyString(str) {
    return str.
              replace(/\s+/g, '').
              replace(/(\\u([0-9a-fA-F]{4}))/g, replaceHex).
              replace(/(\\u\{([0-9a-fA-F]+)\})/g, replaceHex).
              replace(/(\\x([0-9a-fA-F]{2}))/g, replaceHex).
              replace(/(\\([0-7]{1,3}))/g, replaceOctal).
              replace(/(\\(.))/g, function($0, $1, $2) {
                // SingleCharacterEscape is not found in any matches that matter
                return $2;
              });

    function replaceHex($0, $1, $2) {
      return String.fromCharCode(parseInt($2, 16));
    }

    function replaceOctal($0, $1, $2) {
      return String.fromCharCode(parseInt($2, 8));
    }
  }

  // Stylize that output :D
  function max(a, b) {
    return a > b ? a : b;
  }

  function gray(str) {
    return colorize('gray', str);
  }

  function red(str) {
    return colorize('red', str);
  }

  function colorize(code, str) {
    var c = colors[code];
    if (!c) return str;
    return '' + c.open + str + c.close;
  }

  function toRelativePath(basePath, filePath) {
    if (!basePath) return filePath;
    return path.relative(basePath, filePath);
  }
}
<commit_msg>fix(through2): always call cb() on flush<commit_after>var through2 = require('through2');
var path = require('path');
var PluginError = require('gulp-util').PluginError;

var HEX_DIGITS = '0123456789abcdef'.split('');
var OCTAL_DIGITS = '01234567'.split('');

module.exports = ddescribeIit;
function ddescribeIit(opt) {
  'use strict';
  opt = opt || { allowDisabledTests: true, noColor: false };
  var noColor = getOrDefault(opt, 'noColor', false);
  var supports_colors =  !noColor && /* istanbul ignore next */ (function() {
    // E2E testable, but trivial --- ignored
    if (process.argv.indexOf('--no-color') !== -1) return false;
    if (process.stdout && !process.stdout.isTTY) return false;
    if (process.platform === 'win32') return true;
    if ('COLORTERM' in process.env) return true;
    if (process.env.TERM === 'dumb') return false;
    if (/^xterm|^vt100|color|ansi|cygwin|linux/i.test(process.env.TERM)) return true;
    return false;
  })();

  var colors = {
    red: color(31, 39),
    gray: color(90, 39)
  };
  function color(open, close) {
    if (!supports_colors) return null;
    return {
      open: '\u001b[' + open + 'm',
      close: '\u001b[' + close + 'm'
    };
  }

  function getOrDefault(o, key, def) {
    var val = o[key];
    if (val === void 0) val = def;
    return val;
  }

  var allowDisabledTests = getOrDefault(opt, 'allowDisabledTests', true);
  var basePath = getOrDefault(opt, 'basePath', process.cwd());
  var tabWidth = getOrDefault(opt, 'tabWidth', 4);
  if (typeof tabWidth !== 'number') tabWidth = 4;
  if (tabWidth < 2) tabWidth = 2;
  if (tabWidth > 8) tabWidth = 8;
  var tabString = '  ';
  while (tabString.length < tabWidth) tabString += ' ';

  var BAD_FUNCTIONS = [
    // jasmine / minijasminenode / angular
    'iit',
    'ddescribe',

    // jasmine 2.0 "focused" specs
    'fit',
    'fdescribe',

    // mocha
    'it.only',
    'describe.only',
  ];

  var DISABLED_TEST_FUNCTIONS = [
    // jasmine / minijasminenode / angular
    'xit',
    'xdescribe',

    // TODO(@caitp): support mocha `this.skip()` api?
  ];

  if (!allowDisabledTests) {
    BAD_FUNCTIONS = BAD_FUNCTIONS.concat(DISABLED_TEST_FUNCTIONS);
  }

  function makeErrorContext(lines, lineNo, column, word, renderColumn) {
    var words = word.split('\n');
    var before = lineNo > 1 ? lines[lineNo - 2] : null;
    var current = lines[lineNo - 1];

    var result = '';
    if (before !== null) result += (normalize(lines.length, lineNo - 1) + before + '\n');
    result += (normalize(lines.length, lineNo) + current + '\n');
    var word0 = words.shift().replace(/\s+$/, '');

    result += (normalize(lines.length, lineNo, true) + underline(renderColumn, word0.length) + '\n');
    while (words.length) {
      var nextLine = lines[lineNo++];
      result += (normalize(lines.length, lineNo) + nextLine + '\n');
      var start = /[^\s]/.exec(words[0]);
      if (start) {
        start = start.index;
        var end = /\s*$/.exec(words[0].slice(start));
        end = end ? end.index : words[0].length;
        result += (normalize(lines.length, lineNo, true) + underline(start, end) + '\n');
      }
      words.shift();
    }
    var after = lineNo < lines.length ? lines[lineNo] : null;
    if (after !== null) result += (normalize(lines.length, lineNo + 1) + after + '\n');

    return result;

    function normalize(total, num, blank) {
      var needed = ' ' + total.toString(10);
      if (blank) {
        num = ' ';
        while (num.length < needed.length) num += ' ';
      } else {
        num = num.toString(10);
        while (num.length < needed.length) num = ' ' + num;
      }
      return gray(num + '| ');
    }

    function underline(offset, length) {
      var s = '';
      while (s.length < offset) s += ' ';
      var u = '';
      while (u.length < length) u += '^';
      return s + red(u);
    }
  }

  var BAD_FUNCTIONS_SRC = BAD_FUNCTIONS.map(function(fn) {
    fn = fn.split('.').
      reduce(function joinIdentifiers(current, id) {
        var identifier = transformIdentifier(id);
        if (!current) return identifier;
        var stringKey = transformString(id);
        id =
          // MemberExpression . Identifier
          '(?:\\.\\s*' + identifier + ')|' +
          // MemberExpression [ StringLiteral ]
          '(?:\\[\\s*\"' + stringKey + '\"\\s*\\])|' +
          '(?:\\[\\s*\'' + stringKey + '\'\\s*\\])';
        return current + '\\s*?(?:' + id + ')';
      }, "");
    return '(?:' + fn + ')';

    function transformIdentifier(id) {
      var s = '';
      for (var i = 0; i < id.length; ++i) {
        var c = id.charAt(i);
        var x = toHex4Digits(c);
        // UnicodeEscapeSequence :: u Hex4Digits
        var unicode1 = '\\\\u' + x;
        // UnicodeEscapeSequence :: u { Hex4Digits }
        var unicode2 = '\\\\u\\{0*' + x.slice(2) + '\\}';
        s += '(?:' + c + '|(?:' + unicode1 + ')|(?:' + unicode2 + '))';
      }
      return s;
    }

    function transformString(id) {
      var s = '';
      for (var i = 0; i < id.length; ++i) {
        var c = id.charAt(i);
        var x = toHex4Digits(c);
        var o = toOctal(c);
        // UnicodeEscapeSequence :: u Hex4Digits
        var unicode1 = '\\\\u' + x;
        // UnicodeEscapeSequence :: u { Hex4Digits }
        var unicode2 = '\\\\u\\{0*' + x.slice(2) + '\\}';
        // HexEscapeSequence :: x HexDigit HexDigit
        var hex = '\\\\x' + x.slice(2);
        var octal = '\\\\' + o;
        var ec = /['"\\nfnrtv]/.test(c) ? '' : ('|(?:\\\\' + c + ')');
        s += '(?:' + c + ec + '|(?:' + unicode1 + ')|(?:' + unicode2 + ')|(?:' + hex + ')|(?:' + octal + '))';
      }
      return s;
    }
    function toHex4Digits(c) {
      var x = '';
      var len = 0;
      c = c.charCodeAt(0);

      while (c) {
        var d = HEX_DIGITS[c & 0xF];
        c >>= 4;
        ++len;
        if (/[a-f]/.test(d)) {
          x = '[' + d + d.toUpperCase() + ']' + x;
        } else {
          x = d + x;
        }
      }
      if (len === 1) x = '000' + x;
      else if (len === 2) x = '00' + x;
      // TODO(@caitp): Add support for > 8bit codepoints if ever needed
      return x;
    }

    function toOctal(c) {
      var o = '';
      var len = 0;
      c = c.charCodeAt(0);
      while (c) {
        o = OCTAL_DIGITS[c & 0x7] + o;
        len++;
        c >>= 3;
      }
      return o;
    }
  }).join('|');
  var regexp = new RegExp('(^|\\s)(' + BAD_FUNCTIONS_SRC + ')\\s*\\(', 'm');
  var errors = [];
  return through2.obj(function processFile(file, enc, cb) {
    if (file.isNull()) return cb(null, file);
    if (file.isStream()) return cb(new PluginError('gulp-ddescribe-iit', 'Streaming not supported'));

    var path = file.path;
    var contents = file.contents.toString();
    var originalContents = contents;
    var renderContents = originalContents.replace(/\t/g, tabString);
    var lines = originalContents.split('\n');
    var renderLines = renderContents.split('\n');
    var start = 0;
    var lineStarts = lines.map(lineStartMapper);
    start = 0;
    var renderLineStarts = renderLines.map(lineStartMapper);
    function lineStartMapper(line) {
      var l = start;
      start += (line.length + 1);
      return l;
    }
    var pos = 0;
    var renderPos = 0;
    var match;
    while (match = regexp.exec(contents)) {
      var renderMatch = regexp.exec(renderContents);
      var index = pos + match.index + match[1].length;
      var renderIndex = renderPos + renderMatch.index + renderMatch[1].length;
      var wordLines = match[2].split('\n').length;
      if (wordLines > 1) {
        pos = index + match[2].split('\n').join(' ').length;
        renderPos = renderIndex + renderMatch[2].split('\n').join(' ').length;
      } else {
        pos = index + match[2].length;
        renderPos = renderIndex + renderMatch[2].length;
      }
      contents = contents.slice(match.index + match[1].length + match[2].length);
      renderContents = renderContents.slice(renderMatch.index + renderMatch[1].length +
                                            renderMatch[2].length);

      // Location of error
      var lineNo = originalContents.substr(0, pos).split('\n').length;
      if (wordLines > 1) lineNo -= wordLines - 1;
      var lineStart = lineStarts[lineNo - 1];
      var renderLineStart = renderLineStarts[lineNo - 1];
      var column = max(1, (index - lineStart) + 1);
      var renderColumn = max(0, (renderIndex - renderLineStart));

      var word = match[2].replace(/\t/g, tabString);
      console.log(match[2]);
      errors.push({
        file: toRelativePath(basePath, file.path),
        str: simplifyString(match[2]),
        line: lineNo,
        column: column,
        context: makeErrorContext(renderLines, lineNo, column, word, renderColumn)
      });
    }
    cb();
  }, function flushStream(cb) {
    if (errors.length) {
      var error = new PluginError('gulp-ddescribe-iit', {
        message: '\n' + errors.map(function(error) {
          return 'Found `' + error.str + '` in ' +
                 error.file + ':' + error.line + ':' + error.column + '\n' +
                 error.context;
        }).join('\n\n'),
        showStack: false,
        showProperties: false
      });
      error.raw = errors;
      this.emit('error', error);
    }
    cb();
  });

  function simplifyString(str) {
    return str.
              replace(/\s+/g, '').
              replace(/(\\u([0-9a-fA-F]{4}))/g, replaceHex).
              replace(/(\\u\{([0-9a-fA-F]+)\})/g, replaceHex).
              replace(/(\\x([0-9a-fA-F]{2}))/g, replaceHex).
              replace(/(\\([0-7]{1,3}))/g, replaceOctal).
              replace(/(\\(.))/g, function($0, $1, $2) {
                // SingleCharacterEscape is not found in any matches that matter
                return $2;
              });

    function replaceHex($0, $1, $2) {
      return String.fromCharCode(parseInt($2, 16));
    }

    function replaceOctal($0, $1, $2) {
      return String.fromCharCode(parseInt($2, 8));
    }
  }

  // Stylize that output :D
  function max(a, b) {
    return a > b ? a : b;
  }

  function gray(str) {
    return colorize('gray', str);
  }

  function red(str) {
    return colorize('red', str);
  }

  function colorize(code, str) {
    var c = colors[code];
    if (!c) return str;
    return '' + c.open + str + c.close;
  }

  function toRelativePath(basePath, filePath) {
    if (!basePath) return filePath;
    return path.relative(basePath, filePath);
  }
}
<|endoftext|>