<commit_before>// @flow

import type { AppState } from '../redux/redux-setup';
import {
  type Dimensions,
  dimensionsPropType,
  type MediaLibrarySelection,
} from 'lib/types/media-types';
import type { ViewToken, LayoutEvent } from '../types/react-native';
import type { ViewStyle } from '../types/styles';

import * as React from 'react';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';
import { KeyboardRegistry } from 'react-native-keyboard-input';
import invariant from 'invariant';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
import * as MediaLibrary from 'expo-media-library';

import { connect } from 'lib/utils/redux-utils';
import { extensionFromFilename } from 'lib/utils/file-utils';

import { store } from '../redux/redux-setup';
import {
  dimensionsSelector,
  contentBottomOffset,
} from '../selectors/dimension-selectors';
import MediaGalleryMedia from './media-gallery-media.react';
import Animated, { Easing } from 'react-native-reanimated';
import {
  type Colors,
  colorsPropType,
  colorsSelector,
  styleSelector,
} from '../themes/colors';
import SendMediaButton from './send-media-button.react';
import { getCompatibleMediaURI } from './identifier-utils';

const animationSpec = {
  duration: 400,
  easing: Easing.inOut(Easing.ease),
};

type Props = {|
  // Redux state
  screenDimensions: Dimensions,
  foreground: boolean,
  colors: Colors,
  styles: typeof styles,
|};
type State = {|
  selections: ?$ReadOnlyArray<MediaLibrarySelection>,
  error: ?string,
  containerHeight: ?number,
  // null means end reached; undefined means no fetch yet
  cursor: ?string,
  queuedMediaURIs: ?Set<string>,
  focusedMediaURI: ?string,
  screenWidth: number,
|};
class MediaGalleryKeyboard extends React.PureComponent<Props, State> {
  static propTypes = {
    screenDimensions: dimensionsPropType.isRequired,
    foreground: PropTypes.bool.isRequired,
    colors: colorsPropType.isRequired,
    styles: PropTypes.objectOf(PropTypes.object).isRequired,
  };
  mounted = false;
  fetchingPhotos = false;
  flatList: ?FlatList<MediaLibrarySelection>;
  viewableIndices: number[] = [];
  // eslint-disable-next-line import/no-named-as-default-member
  queueModeProgress = new Animated.Value(0);
  sendButtonStyle: ViewStyle;
  mediaSelected = false;

  constructor(props: Props) {
    super(props);
    const sendButtonScale = Animated.interpolate(this.queueModeProgress, {
      inputRange: [0, 1],
      outputRange: [1.3, 1],
    });
    this.sendButtonStyle = {
      opacity: this.queueModeProgress,
      transform: [{ scale: sendButtonScale }],
    };
    this.state = {
      selections: null,
      error: null,
      containerHeight: null,
      cursor: undefined,
      queuedMediaURIs: null,
      focusedMediaURI: null,
      screenWidth: props.screenDimensions.width,
    };
  }

  componentDidMount() {
    this.mounted = true;
    return this.fetchPhotos();
  }

  componentWillUnmount() {
    this.mounted = false;
  }

  componentDidUpdate(prevProps: Props, prevState: State) {
    const { width } = this.props.screenDimensions;
    if (width !== prevProps.screenDimensions.width) {
      // We keep screenWidth in this.state since that's what we pass in as
      // FlatList's extraData
      this.setState({ screenWidth: width });
    }

    const { queuedMediaURIs } = this.state;
    const prevQueuedMediaURIs = prevState.queuedMediaURIs;
    if (queuedMediaURIs && !prevQueuedMediaURIs) {
      // eslint-disable-next-line import/no-named-as-default-member
      Animated.timing(this.queueModeProgress, {
        ...animationSpec,
        toValue: 1,
      }).start();
    } else if (!queuedMediaURIs && prevQueuedMediaURIs) {
      // eslint-disable-next-line import/no-named-as-default-member
      Animated.timing(this.queueModeProgress, {
        ...animationSpec,
        toValue: 0,
      }).start();
    }

    const { flatList, viewableIndices } = this;
    const { selections, focusedMediaURI } = this.state;
    let scrollingSomewhere = false;
    if (flatList && selections) {
      let newURI;
      if (focusedMediaURI && focusedMediaURI !== prevState.focusedMediaURI) {
        newURI = focusedMediaURI;
      } else if (
        queuedMediaURIs &&
        (!prevQueuedMediaURIs ||
          queuedMediaURIs.size > prevQueuedMediaURIs.size)
      ) {
        const flowMadeMeDoThis = queuedMediaURIs;
        for (let queuedMediaURI of flowMadeMeDoThis) {
          if (prevQueuedMediaURIs && prevQueuedMediaURIs.has(queuedMediaURI)) {
            continue;
          }
          newURI = queuedMediaURI;
          break;
        }
      }
      let index;
      if (newURI !== null && newURI !== undefined) {
        index = selections.findIndex(({ uri }) => uri === newURI);
      }
      if (index !== null && index !== undefined) {
        if (index === viewableIndices[0]) {
          scrollingSomewhere = true;
          flatList.scrollToIndex({ index });
        } else if (index === viewableIndices[viewableIndices.length - 1]) {
          scrollingSomewhere = true;
          flatList.scrollToIndex({ index, viewPosition: 1 });
        }
      }
    }

    if (this.props.foreground && !prevProps.foreground) {
      this.fetchPhotos();
    }

    if (
      !scrollingSomewhere &&
      this.flatList &&
      this.state.selections &&
      prevState.selections &&
      this.state.selections.length > 0 &&
      prevState.selections.length > 0 &&
      this.state.selections[0].uri !== prevState.selections[0].uri
    ) {
      this.flatList.scrollToIndex({ index: 0 });
    }
  }

  guardedSetState(change) {
    if (this.mounted) {
      this.setState(change);
    }
  }

  async fetchPhotos(after?: ?string) {
    if (this.fetchingPhotos) {
      return;
    }
    this.fetchingPhotos = true;
    try {
      const hasPermission = await this.getPermissions();
      if (!hasPermission) {
        return;
      }
      const {
        assets,
        endCursor,
        hasNextPage,
      } = await MediaLibrary.getAssetsAsync({
        first: 20,
        after,
        mediaType: [MediaLibrary.MediaType.photo, MediaLibrary.MediaType.video],
        sortBy: [MediaLibrary.SortBy.modificationTime],
      });

      let firstRemoved = false,
        lastRemoved = false;

      const mediaURIs = this.state.selections
        ? this.state.selections.map(({ uri }) => uri)
        : [];
      const existingURIs = new Set(mediaURIs);
      let first = true;
      const selections = assets
        .map(asset => {
          const { id, height, width, filename, mediaType, duration } = asset;
          const isVideo = mediaType === MediaLibrary.MediaType.video;
          const uri = getCompatibleMediaURI(
            asset.uri,
            extensionFromFilename(filename),
          );

          if (existingURIs.has(uri)) {
            if (first) {
              firstRemoved = true;
            }
            lastRemoved = true;
            first = false;
            return null;
          }
          first = false;
          lastRemoved = false;
          existingURIs.add(uri);

          if (isVideo) {
            return {
              step: 'video_library',
              dimensions: { height, width },
              uri,
              filename,
              mediaNativeID: id,
              duration,
              selectTime: 0,
              sendTime: 0,
              retries: 0,
            };
          } else {
            return {
              step: 'photo_library',
              dimensions: { height, width },
              uri,
              filename,
              mediaNativeID: id,
              selectTime: 0,
              sendTime: 0,
              retries: 0,
            };
          }
        })
        .filter(Boolean);

      let appendOrPrepend = after ? 'append' : 'prepend';
      if (firstRemoved && !lastRemoved) {
        appendOrPrepend = 'append';
      } else if (!firstRemoved && lastRemoved) {
        appendOrPrepend = 'prepend';
      }

      let newSelections = selections;
      if (this.state.selections) {
        if (appendOrPrepend === 'prepend') {
          newSelections = [...newSelections, ...this.state.selections];
        } else {
          newSelections = [...this.state.selections, ...newSelections];
        }
      }

      this.guardedSetState({
        selections: newSelections,
        error: null,
        cursor: hasNextPage ? endCursor : null,
      });
    } catch (e) {
      this.guardedSetState({
        selections: null,
        error: 'something went wrong :(',
      });
    }
    this.fetchingPhotos = false;
  }

  async getPermissions(): Promise<boolean> {
    const { granted } = await MediaLibrary.requestPermissionsAsync();
    if (!granted) {
      this.guardedSetState({ error: "don't have permission :(" });
    }
    return granted;
  }

  get queueModeActive() {
    return !!this.state.queuedMediaURIs;
  }

  renderItem = (row: { item: MediaLibrarySelection }) => {
    const { containerHeight, queuedMediaURIs } = this.state;
    invariant(containerHeight, 'should be set');
    const { uri } = row.item;
    const isQueued = !!(queuedMediaURIs && queuedMediaURIs.has(uri));
    const { queueModeActive } = this;
    return (
      <MediaGalleryMedia
        selection={row.item}
        containerHeight={containerHeight}
        queueModeActive={queueModeActive}
        isQueued={isQueued}
        setMediaQueued={this.setMediaQueued}
        sendMedia={this.sendSingleMedia}
        isFocused={this.state.focusedMediaURI === uri}
        setFocus={this.setFocus}
        screenWidth={this.state.screenWidth}
        colors={this.props.colors}
      />
    );
  };

  ItemSeparator = () => {
    return <View style={this.props.styles.separator} />;
  };

  static keyExtractor(item: MediaLibrarySelection) {
    return item.uri;
  }

  render() {
    let content;
    const { selections, error, containerHeight } = this.state;
    if (selections && selections.length > 0 && containerHeight) {
      content = (
        <FlatList
          horizontal={true}
          data={selections}
          renderItem={this.renderItem}
          ItemSeparatorComponent={this.ItemSeparator}
          keyExtractor={MediaGalleryKeyboard.keyExtractor}
          scrollsToTop={false}
          showsHorizontalScrollIndicator={false}
          onEndReached={this.onEndReached}
          onEndReachedThreshold={5}
          onViewableItemsChanged={this.onViewableItemsChanged}
          extraData={this.state}
          ref={this.flatListRef}
        />
      );
    } else if (selections && containerHeight) {
      content = (
        <Text style={this.props.styles.error}>no media was found!</Text>
      );
    } else if (error) {
      content = <Text style={this.props.styles.error}>{error}</Text>;
    } else {
      content = (
        <ActivityIndicator
          color={this.props.colors.listSeparatorLabel}
          size="large"
          style={this.props.styles.loadingIndicator}
        />
      );
    }

    const { queuedMediaURIs } = this.state;
    const queueCount = queuedMediaURIs ? queuedMediaURIs.size : 0;
    return (
      <View
        style={this.props.styles.container}
        onLayout={this.onContainerLayout}
      >
        {content}
        <SendMediaButton
          onPress={this.sendQueuedMedia}
          queueCount={queueCount}
          pointerEvents={queuedMediaURIs ? 'auto' : 'none'}
          containerStyle={this.props.styles.sendButtonContainer}
          style={this.sendButtonStyle}
        />
      </View>
    );
  }

  flatListRef = (flatList: ?FlatList<MediaLibrarySelection>) => {
    this.flatList = flatList;
  };

  onContainerLayout = (event: LayoutEvent) => {
    this.guardedSetState({ containerHeight: event.nativeEvent.layout.height });
  };

  onEndReached = () => {
    const { cursor } = this.state;
    if (cursor !== null) {
      this.fetchPhotos(cursor);
    }
  };

  onViewableItemsChanged = (info: {
    viewableItems: ViewToken[],
    changed: ViewToken[],
  }) => {
    const viewableIndices = [];
    for (let { index } of info.viewableItems) {
      if (index !== null && index !== undefined) {
        viewableIndices.push(index);
      }
    }
    this.viewableIndices = viewableIndices;
  };

  setMediaQueued = (selection: MediaLibrarySelection, isQueued: boolean) => {
    this.setState((prevState: State) => {
      const prevQueuedMediaURIs = prevState.queuedMediaURIs
        ? [...prevState.queuedMediaURIs]
        : [];
      if (isQueued) {
        return {
          queuedMediaURIs: new Set([...prevQueuedMediaURIs, selection.uri]),
          focusedMediaURI: null,
        };
      }
      const queuedMediaURIs = prevQueuedMediaURIs.filter(
        uri => uri !== selection.uri,
      );
      if (queuedMediaURIs.length < prevQueuedMediaURIs.length) {
        return {
          queuedMediaURIs: new Set(queuedMediaURIs),
          focusedMediaURI: null,
        };
      }
      return null;
    });
  };

  setFocus = (selection: MediaLibrarySelection, isFocused: boolean) => {
    const { uri } = selection;
    if (isFocused) {
      this.setState({ focusedMediaURI: uri });
    } else if (this.state.focusedMediaURI === uri) {
      this.setState({ focusedMediaURI: null });
    }
  };

  sendSingleMedia = (selection: MediaLibrarySelection) => {
    this.sendMedia([selection]);
  };

  sendQueuedMedia = () => {
    const { selections, queuedMediaURIs } = this.state;
    if (!selections || !queuedMediaURIs) {
      return;
    }
    const queuedSelections = [];
    for (let uri of queuedMediaURIs) {
      for (let selection of selections) {
        if (selection.uri === uri) {
          queuedSelections.push(selection);
          break;
        }
      }
    }
    this.sendMedia(queuedSelections);
  };

  sendMedia(selections: $ReadOnlyArray<MediaLibrarySelection>) {
    if (this.mediaSelected) {
      return;
    }
    this.mediaSelected = true;

    const now = Date.now();
    const timeProps = {
      selectTime: now,
      sendTime: now,
    };
    const selectionsWithTime = selections.map(selection => ({
      ...selection,
      ...timeProps,
    }));

    KeyboardRegistry.onItemSelected(
      mediaGalleryKeyboardName,
      selectionsWithTime,
    );
  }
}

const mediaGalleryKeyboardName = 'MediaGalleryKeyboard';

const styles = {
  container: {
    alignItems: 'center',
    backgroundColor: 'listBackground',
    bottom: -contentBottomOffset,
    flexDirection: 'row',
    left: 0,
    position: 'absolute',
    right: 0,
    top: 0,
  },
  error: {
    color: 'listBackgroundLabel',
    flex: 1,
    fontSize: 28,
    marginBottom: contentBottomOffset,
    textAlign: 'center',
  },
  loadingIndicator: {
    flex: 1,
    marginBottom: contentBottomOffset,
  },
  sendButtonContainer: {
    bottom: 30,
    position: 'absolute',
    right: 30,
  },
  separator: {
    width: 2,
  },
};
const stylesSelector = styleSelector(styles);

const ReduxConnectedMediaGalleryKeyboard = connect((state: AppState) => ({
  screenDimensions: dimensionsSelector(state),
  foreground: state.foreground,
  colors: colorsSelector(state),
  styles: stylesSelector(state),
}))(MediaGalleryKeyboard);

function ReduxMediaGalleryKeyboard() {
  return (
    <Provider store={store}>
      <ReduxConnectedMediaGalleryKeyboard />
    </Provider>
  );
}

KeyboardRegistry.registerKeyboard(
  mediaGalleryKeyboardName,
  () => ReduxMediaGalleryKeyboard,
);

export { mediaGalleryKeyboardName };
<commit_msg>[native] Prevent phantom SendMediaButton on Android MediaGalleryKeyboard<commit_after>// @flow

import type { AppState } from '../redux/redux-setup';
import {
  type Dimensions,
  dimensionsPropType,
  type MediaLibrarySelection,
} from 'lib/types/media-types';
import type { ViewToken, LayoutEvent } from '../types/react-native';
import type { ViewStyle } from '../types/styles';

import * as React from 'react';
import {
  View,
  Text,
  FlatList,
  ActivityIndicator,
  Platform,
} from 'react-native';
import { KeyboardRegistry } from 'react-native-keyboard-input';
import invariant from 'invariant';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
import * as MediaLibrary from 'expo-media-library';

import { connect } from 'lib/utils/redux-utils';
import { extensionFromFilename } from 'lib/utils/file-utils';
import sleep from 'lib/utils/sleep';

import { store } from '../redux/redux-setup';
import {
  dimensionsSelector,
  contentBottomOffset,
} from '../selectors/dimension-selectors';
import MediaGalleryMedia from './media-gallery-media.react';
import Animated, { Easing } from 'react-native-reanimated';
import {
  type Colors,
  colorsPropType,
  colorsSelector,
  styleSelector,
} from '../themes/colors';
import SendMediaButton from './send-media-button.react';
import { getCompatibleMediaURI } from './identifier-utils';

const animationSpec = {
  duration: 400,
  easing: Easing.inOut(Easing.ease),
};

type Props = {|
  // Redux state
  screenDimensions: Dimensions,
  foreground: boolean,
  colors: Colors,
  styles: typeof styles,
|};
type State = {|
  selections: ?$ReadOnlyArray<MediaLibrarySelection>,
  error: ?string,
  containerHeight: ?number,
  // null means end reached; undefined means no fetch yet
  cursor: ?string,
  queuedMediaURIs: ?Set<string>,
  focusedMediaURI: ?string,
  screenWidth: number,
|};
class MediaGalleryKeyboard extends React.PureComponent<Props, State> {
  static propTypes = {
    screenDimensions: dimensionsPropType.isRequired,
    foreground: PropTypes.bool.isRequired,
    colors: colorsPropType.isRequired,
    styles: PropTypes.objectOf(PropTypes.object).isRequired,
  };
  mounted = false;
  fetchingPhotos = false;
  flatList: ?FlatList<MediaLibrarySelection>;
  viewableIndices: number[] = [];
  // eslint-disable-next-line import/no-named-as-default-member
  queueModeProgress = new Animated.Value(0);
  sendButtonStyle: ViewStyle;
  mediaSelected = false;

  constructor(props: Props) {
    super(props);
    const sendButtonScale = Animated.interpolate(this.queueModeProgress, {
      inputRange: [0, 1],
      outputRange: [1.3, 1],
    });
    this.sendButtonStyle = {
      opacity: this.queueModeProgress,
      transform: [{ scale: sendButtonScale }],
    };
    this.state = {
      selections: null,
      error: null,
      containerHeight: null,
      cursor: undefined,
      queuedMediaURIs: null,
      focusedMediaURI: null,
      screenWidth: props.screenDimensions.width,
    };
  }

  componentDidMount() {
    this.mounted = true;
    return this.fetchPhotos();
  }

  componentWillUnmount() {
    this.mounted = false;
  }

  componentDidUpdate(prevProps: Props, prevState: State) {
    const { width } = this.props.screenDimensions;
    if (width !== prevProps.screenDimensions.width) {
      // We keep screenWidth in this.state since that's what we pass in as
      // FlatList's extraData
      this.setState({ screenWidth: width });
    }

    const { queuedMediaURIs } = this.state;
    const prevQueuedMediaURIs = prevState.queuedMediaURIs;
    if (queuedMediaURIs && !prevQueuedMediaURIs) {
      // eslint-disable-next-line import/no-named-as-default-member
      Animated.timing(this.queueModeProgress, {
        ...animationSpec,
        toValue: 1,
      }).start();
    } else if (!queuedMediaURIs && prevQueuedMediaURIs) {
      // eslint-disable-next-line import/no-named-as-default-member
      Animated.timing(this.queueModeProgress, {
        ...animationSpec,
        toValue: 0,
      }).start();
    }

    const { flatList, viewableIndices } = this;
    const { selections, focusedMediaURI } = this.state;
    let scrollingSomewhere = false;
    if (flatList && selections) {
      let newURI;
      if (focusedMediaURI && focusedMediaURI !== prevState.focusedMediaURI) {
        newURI = focusedMediaURI;
      } else if (
        queuedMediaURIs &&
        (!prevQueuedMediaURIs ||
          queuedMediaURIs.size > prevQueuedMediaURIs.size)
      ) {
        const flowMadeMeDoThis = queuedMediaURIs;
        for (let queuedMediaURI of flowMadeMeDoThis) {
          if (prevQueuedMediaURIs && prevQueuedMediaURIs.has(queuedMediaURI)) {
            continue;
          }
          newURI = queuedMediaURI;
          break;
        }
      }
      let index;
      if (newURI !== null && newURI !== undefined) {
        index = selections.findIndex(({ uri }) => uri === newURI);
      }
      if (index !== null && index !== undefined) {
        if (index === viewableIndices[0]) {
          scrollingSomewhere = true;
          flatList.scrollToIndex({ index });
        } else if (index === viewableIndices[viewableIndices.length - 1]) {
          scrollingSomewhere = true;
          flatList.scrollToIndex({ index, viewPosition: 1 });
        }
      }
    }

    if (this.props.foreground && !prevProps.foreground) {
      this.fetchPhotos();
    }

    if (
      !scrollingSomewhere &&
      this.flatList &&
      this.state.selections &&
      prevState.selections &&
      this.state.selections.length > 0 &&
      prevState.selections.length > 0 &&
      this.state.selections[0].uri !== prevState.selections[0].uri
    ) {
      this.flatList.scrollToIndex({ index: 0 });
    }
  }

  guardedSetState(change) {
    if (this.mounted) {
      this.setState(change);
    }
  }

  async fetchPhotos(after?: ?string) {
    if (this.fetchingPhotos) {
      return;
    }
    this.fetchingPhotos = true;
    try {
      const hasPermission = await this.getPermissions();
      if (!hasPermission) {
        return;
      }
      const {
        assets,
        endCursor,
        hasNextPage,
      } = await MediaLibrary.getAssetsAsync({
        first: 20,
        after,
        mediaType: [MediaLibrary.MediaType.photo, MediaLibrary.MediaType.video],
        sortBy: [MediaLibrary.SortBy.modificationTime],
      });

      let firstRemoved = false,
        lastRemoved = false;

      const mediaURIs = this.state.selections
        ? this.state.selections.map(({ uri }) => uri)
        : [];
      const existingURIs = new Set(mediaURIs);
      let first = true;
      const selections = assets
        .map(asset => {
          const { id, height, width, filename, mediaType, duration } = asset;
          const isVideo = mediaType === MediaLibrary.MediaType.video;
          const uri = getCompatibleMediaURI(
            asset.uri,
            extensionFromFilename(filename),
          );

          if (existingURIs.has(uri)) {
            if (first) {
              firstRemoved = true;
            }
            lastRemoved = true;
            first = false;
            return null;
          }
          first = false;
          lastRemoved = false;
          existingURIs.add(uri);

          if (isVideo) {
            return {
              step: 'video_library',
              dimensions: { height, width },
              uri,
              filename,
              mediaNativeID: id,
              duration,
              selectTime: 0,
              sendTime: 0,
              retries: 0,
            };
          } else {
            return {
              step: 'photo_library',
              dimensions: { height, width },
              uri,
              filename,
              mediaNativeID: id,
              selectTime: 0,
              sendTime: 0,
              retries: 0,
            };
          }
        })
        .filter(Boolean);

      let appendOrPrepend = after ? 'append' : 'prepend';
      if (firstRemoved && !lastRemoved) {
        appendOrPrepend = 'append';
      } else if (!firstRemoved && lastRemoved) {
        appendOrPrepend = 'prepend';
      }

      let newSelections = selections;
      if (this.state.selections) {
        if (appendOrPrepend === 'prepend') {
          newSelections = [...newSelections, ...this.state.selections];
        } else {
          newSelections = [...this.state.selections, ...newSelections];
        }
      }

      this.guardedSetState({
        selections: newSelections,
        error: null,
        cursor: hasNextPage ? endCursor : null,
      });
    } catch (e) {
      this.guardedSetState({
        selections: null,
        error: 'something went wrong :(',
      });
    }
    this.fetchingPhotos = false;
  }

  async getPermissions(): Promise<boolean> {
    // For some reason, react-native-reanimated@1.8.0 on Android renders the
    // very first frame of SendMediaButton incorrectly. opacity is set to new
    // Value(0), but on the first frame it appears as 1. On Android requesting
    // permissions freezes your current activity, so we want to make sure that
    // we're rendering correctly before the freeze occurs.
    if (Platform.OS === 'android') {
      await sleep(5);
    }
    const { granted } = await MediaLibrary.requestPermissionsAsync();
    if (!granted) {
      this.guardedSetState({ error: "don't have permission :(" });
    }
    return granted;
  }

  get queueModeActive() {
    return !!this.state.queuedMediaURIs;
  }

  renderItem = (row: { item: MediaLibrarySelection }) => {
    const { containerHeight, queuedMediaURIs } = this.state;
    invariant(containerHeight, 'should be set');
    const { uri } = row.item;
    const isQueued = !!(queuedMediaURIs && queuedMediaURIs.has(uri));
    const { queueModeActive } = this;
    return (
      <MediaGalleryMedia
        selection={row.item}
        containerHeight={containerHeight}
        queueModeActive={queueModeActive}
        isQueued={isQueued}
        setMediaQueued={this.setMediaQueued}
        sendMedia={this.sendSingleMedia}
        isFocused={this.state.focusedMediaURI === uri}
        setFocus={this.setFocus}
        screenWidth={this.state.screenWidth}
        colors={this.props.colors}
      />
    );
  };

  ItemSeparator = () => {
    return <View style={this.props.styles.separator} />;
  };

  static keyExtractor(item: MediaLibrarySelection) {
    return item.uri;
  }

  render() {
    let content;
    const { selections, error, containerHeight } = this.state;
    if (selections && selections.length > 0 && containerHeight) {
      content = (
        <FlatList
          horizontal={true}
          data={selections}
          renderItem={this.renderItem}
          ItemSeparatorComponent={this.ItemSeparator}
          keyExtractor={MediaGalleryKeyboard.keyExtractor}
          scrollsToTop={false}
          showsHorizontalScrollIndicator={false}
          onEndReached={this.onEndReached}
          onEndReachedThreshold={5}
          onViewableItemsChanged={this.onViewableItemsChanged}
          extraData={this.state}
          ref={this.flatListRef}
        />
      );
    } else if (selections && containerHeight) {
      content = (
        <Text style={this.props.styles.error}>no media was found!</Text>
      );
    } else if (error) {
      content = <Text style={this.props.styles.error}>{error}</Text>;
    } else {
      content = (
        <ActivityIndicator
          color={this.props.colors.listSeparatorLabel}
          size="large"
          style={this.props.styles.loadingIndicator}
        />
      );
    }

    const { queuedMediaURIs } = this.state;
    const queueCount = queuedMediaURIs ? queuedMediaURIs.size : 0;
    return (
      <View
        style={this.props.styles.container}
        onLayout={this.onContainerLayout}
      >
        {content}
        <SendMediaButton
          onPress={this.sendQueuedMedia}
          queueCount={queueCount}
          pointerEvents={queuedMediaURIs ? 'auto' : 'none'}
          containerStyle={this.props.styles.sendButtonContainer}
          style={this.sendButtonStyle}
        />
      </View>
    );
  }

  flatListRef = (flatList: ?FlatList<MediaLibrarySelection>) => {
    this.flatList = flatList;
  };

  onContainerLayout = (event: LayoutEvent) => {
    this.guardedSetState({ containerHeight: event.nativeEvent.layout.height });
  };

  onEndReached = () => {
    const { cursor } = this.state;
    if (cursor !== null) {
      this.fetchPhotos(cursor);
    }
  };

  onViewableItemsChanged = (info: {
    viewableItems: ViewToken[],
    changed: ViewToken[],
  }) => {
    const viewableIndices = [];
    for (let { index } of info.viewableItems) {
      if (index !== null && index !== undefined) {
        viewableIndices.push(index);
      }
    }
    this.viewableIndices = viewableIndices;
  };

  setMediaQueued = (selection: MediaLibrarySelection, isQueued: boolean) => {
    this.setState((prevState: State) => {
      const prevQueuedMediaURIs = prevState.queuedMediaURIs
        ? [...prevState.queuedMediaURIs]
        : [];
      if (isQueued) {
        return {
          queuedMediaURIs: new Set([...prevQueuedMediaURIs, selection.uri]),
          focusedMediaURI: null,
        };
      }
      const queuedMediaURIs = prevQueuedMediaURIs.filter(
        uri => uri !== selection.uri,
      );
      if (queuedMediaURIs.length < prevQueuedMediaURIs.length) {
        return {
          queuedMediaURIs: new Set(queuedMediaURIs),
          focusedMediaURI: null,
        };
      }
      return null;
    });
  };

  setFocus = (selection: MediaLibrarySelection, isFocused: boolean) => {
    const { uri } = selection;
    if (isFocused) {
      this.setState({ focusedMediaURI: uri });
    } else if (this.state.focusedMediaURI === uri) {
      this.setState({ focusedMediaURI: null });
    }
  };

  sendSingleMedia = (selection: MediaLibrarySelection) => {
    this.sendMedia([selection]);
  };

  sendQueuedMedia = () => {
    const { selections, queuedMediaURIs } = this.state;
    if (!selections || !queuedMediaURIs) {
      return;
    }
    const queuedSelections = [];
    for (let uri of queuedMediaURIs) {
      for (let selection of selections) {
        if (selection.uri === uri) {
          queuedSelections.push(selection);
          break;
        }
      }
    }
    this.sendMedia(queuedSelections);
  };

  sendMedia(selections: $ReadOnlyArray<MediaLibrarySelection>) {
    if (this.mediaSelected) {
      return;
    }
    this.mediaSelected = true;

    const now = Date.now();
    const timeProps = {
      selectTime: now,
      sendTime: now,
    };
    const selectionsWithTime = selections.map(selection => ({
      ...selection,
      ...timeProps,
    }));

    KeyboardRegistry.onItemSelected(
      mediaGalleryKeyboardName,
      selectionsWithTime,
    );
  }
}

const mediaGalleryKeyboardName = 'MediaGalleryKeyboard';

const styles = {
  container: {
    alignItems: 'center',
    backgroundColor: 'listBackground',
    bottom: -contentBottomOffset,
    flexDirection: 'row',
    left: 0,
    position: 'absolute',
    right: 0,
    top: 0,
  },
  error: {
    color: 'listBackgroundLabel',
    flex: 1,
    fontSize: 28,
    marginBottom: contentBottomOffset,
    textAlign: 'center',
  },
  loadingIndicator: {
    flex: 1,
    marginBottom: contentBottomOffset,
  },
  sendButtonContainer: {
    bottom: 30,
    position: 'absolute',
    right: 30,
  },
  separator: {
    width: 2,
  },
};
const stylesSelector = styleSelector(styles);

const ReduxConnectedMediaGalleryKeyboard = connect((state: AppState) => ({
  screenDimensions: dimensionsSelector(state),
  foreground: state.foreground,
  colors: colorsSelector(state),
  styles: stylesSelector(state),
}))(MediaGalleryKeyboard);

function ReduxMediaGalleryKeyboard() {
  return (
    <Provider store={store}>
      <ReduxConnectedMediaGalleryKeyboard />
    </Provider>
  );
}

KeyboardRegistry.registerKeyboard(
  mediaGalleryKeyboardName,
  () => ReduxMediaGalleryKeyboard,
);

export { mediaGalleryKeyboardName };
<|endoftext|>


<commit_before>function setBackToDefault(formType){
		if(formType == "pca"){
			document.getElementById("variancePercentile").value = "70.0";
		}
		else {document.getElementById("variancePercentile").value = "95.0";}
		document.getElementById("geneList").value = "none";

	}

function checkNull(text, checkName){
	if(text.value == "")	{
		scroll(0,0);
		text.focus();
		text.style.border = "2px solid red";
		alert("Please Fill in a Unique Query Name");
		return false;
	}
	if (!checkIsURLSafe(text)){

		alert("Please enter a query name without any special characters such as !@#$%^&*()+=[]\';,./{}|\":<>?");
		return false;
	}
	if (checkName == "true")
		return checkQueryName();
	else
		return true;
}

function checkIsURLSafe(textField){
	var iChars = "!@#$%^&*()+=[]\';,./{}|\":<>?";
	var text = textField.value;

	for (var i = 0; i < text.length; i++) {
		if (iChars.indexOf(text.charAt(i)) != -1)
		{
			return false;
		}
	}
	return true;
}

function clearSpecialCharacters(textField) {
	var text = textField.value;

	re = /\$|,|@|#|~|`|\%|\*|\^|\&|\(|\)|\+|\=|\[|\-|\_|\]|\[|\}|\{|\;|\:|\'|\"|\<|\>|\?|\||\\|\!|\$|\./g;
          // remove special characters like "$" and "," etc...
	text = text.replace(re, "");
	textField.value = text;
}

function spawnx(url,winw,winh, name) {

  var w = window.open(url, name,
      "screenX=0,screenY=0,status=yes,toolbar=no,menubar=no,location=no,width=" + winw + ",height=" + winh +
      ",scrollbars=yes,resizable=yes");

	//check for pop-up blocker
	if (w==null || typeof(w)=="undefined") {
		alert("You have pop-ups blocked.  Please click the highlighted link at the top of this page to view the report.  You may disable your pop-up blocker for this site to avoid doing this in the future.");
		/*
		if(document.all) {
			  document.all.popup.visible = "true";
			  document.all.popup.className = "pop";
		      document.all.popup.innerText = "You have pop-ups blocked.  Click here to view the report.";

		    } else {
			  document.getElementById('popup').visible = "true";
			  document.getElementById('popup').className= "pop";
		      document.getElementById('popup').innerHTML = "You have pop-ups blocked.  Click here to view the report.";
		}
		*/
		document.write("

You have pop-ups blocked.  Click here to view the report.");
		//scroll(0, 8000);
	} else {
		w.focus();

	}
}

function spawn(url,winw,winh) {
  var w = window.open(url, "_blank",
      "screenX=0,screenY=0,status=yes,toolbar=no,menubar=no,location=no,width=" + winw + ",height=" + winh +
      ",scrollbars=yes,resizable=yes");
}

function deleteRow(queryNum){
//get elements body and table
 var mybody=document.getElementsByTagName("body").item(0);
 var mytable=mybody.getElementsByTagName("table").item(0);

 //show warning if user has deleted all but one row
 if (mytable.rows.length == 2){
 alert('Deleting any more rows will create an invalid query');
 }
 //delete rows by finding the row id and the row's index
 else {
  var thisTR = document.getElementById(queryNum);
  var thisTRIndex = thisTR.rowIndex;
  mytable.deleteRow(thisTRIndex);
   }
 }

 function onRadioSimpleSearch(id) {

	 if (id == "geneExpPlot" || id == "samplePlotRadio")	{
	  		document.getElementById("baselineGroupName").disabled = true;

	 } else if (id == "kapMaiPlotGE" || id == "kapMaiPlotCN")	{

		 document.getElementById("baselineGroupName").disabled = false;

	 }
 }

function onRadioCloneId(idx) {

	if (idx == 0){
		 document.getElementById("cloneListSpecify").disabled = false;
		 document.getElementById("cloneListFileDD").disabled = true;
		 document.getElementById("cloneListSpecify").focus();

//		 document.forms[0].cloneList.disabled = false;
//		  document.forms[0].cloneListFile.disabled = true;
//		  document.forms[0].cloneListSpecify.focus();

	 } else if (idx == 1) {
		 document.getElementById("cloneListSpecify").value = "";
		 document.getElementById("cloneListSpecify").disabled = true;
		 document.getElementById("cloneListFileDD").disabled = false;
	      //document.forms[0].cloneListFile.focus();
	 }
 }

function onRadioSampleGroup(idx) {
	 alert("in onRadioSampleGroup");
	    if(i == 0){
	    	document.getElementById("sampleGroup1").disabled = true;
	    	document.getElementById("sampleGroup2").disabled = false;
	    } else if(i == 1){
	    	document.getElementById("sampleGroup1").value = "";
	    	document.getElementById("sampleGroup1").disabled = true;
	    	document.getElementById("sampleGroup2").disabled = false;
	    }

 }

 function onRadio(formElement,i){

   //selected index of the selected
	var element = formElement.name;

	 if(element == "tumorType" || formElement == "tumorType"){
	    document.forms[0].tumorGrade.options.length = 1;
	        myOption1 = new Option("I","one");
		    myOption2 = new Option("II","two");
		    myOption3 = new Option("III","three");
		    myOption4 = new Option("IV","four");

		if(i == "ALL"){
		 document.forms[0].tumorGrade.options[1] = myOption1;
	     document.forms[0].tumorGrade.options[2] = myOption2;
	     document.forms[0].tumorGrade.options[3] = myOption3;
	     document.forms[0].tumorGrade.options[4] = myOption4;
	     }
	    if(i == "ASTROCYTOMA"){
	     document.forms[0].tumorGrade.options[1] = myOption2;
	     document.forms[0].tumorGrade.options[2] = myOption3;
	     }
	    if(i == "GBM"){
	     document.forms[0].tumorGrade.options[1] = myOption4;
	     }
	    if(i == "OLIG"){
	     document.forms[0].tumorGrade.options[1] = myOption2;
	     document.forms[0].tumorGrade.options[2] = myOption3;
	     }
	    if(i == "MIXED"){
	     document.forms[0].tumorGrade.options[1] = myOption1;
	     document.forms[0].tumorGrade.options[2] = myOption2;
	     document.forms[0].tumorGrade.options[3] = myOption3;
	     }
	  }

	  if(element == "isAllGenesQuery"){
	   if(i == 0){
	    document.forms[0].allGeneQuery.disabled = true;
	    checkToggle(formElement, "qrows");
	    }
	   if(i == 1){
	    document.forms[0].allGeneQuery.disabled = false;
	    checkToggle(formElement, "qrows");
	    }
	   }

	  if(element == "geneGroup"){
	      if (i == 0){

		  document.forms[0].geneList.disabled = false;
		  document.forms[0].geneType.disabled = false;
		  document.forms[0].geneFile.disabled = true;
		  document.forms[0].geneList.focus();
		  }
	      if (i == 1) {

	      document.forms[0].geneList.value = "";
	      document.forms[0].geneList.disabled = true;
		  document.forms[0].geneFile.disabled = false;
		  document.forms[0].geneFile.focus();
	      }
	   }

	   if(element == "cloneId"){
	      if (i == 0){
	      document.forms[0].cloneListSpecify.disabled = false;
		  document.forms[0].cloneList.disabled = false;
		  document.forms[0].cloneListFile.disabled = true;
 		  document.forms[0].cloneListSpecify.focus();

		  }
	      if (i == 1) {
	      document.forms[0].cloneListSpecify.value = "";
		  document.forms[0].cloneListSpecify.disabled = true;
		  document.forms[0].cloneListFile.disabled = false;
	      //document.forms[0].cloneListFile.focus();
	      }
	   }
	   if(element == "snpId"){
	      if (i == 0){
	        document.forms[0].snpListSpecify.disabled = false;
	        document.forms[0].snpList.disabled = false;
			document.forms[0].snpListSpecify.focus();
			document.forms[0].snpListFile.value = "";
	        document.forms[0].snpListFile.disabled = true;
	      }
	      if (i == 1){
	        document.forms[0].snpListSpecify.value = "";
	        document.forms[0].snpListSpecify.disabled = true;
	        document.forms[0].snpListFile.disabled = false;
			//document.forms[0].snpListFile.focus();
	      }
	   }

  }

 function alertUser(where){

if(confirm("This will eliminate all data currently entered in the form and will not add a query"))
{
	if (where == 'menu')
		location.href='menu.do';
	if (where == 'analysisHome')
		location.href='analysisHome.do';
}

 }

function hideLoadingMessage(){
	if(document.getElementById('spnLoading') != null)
			document.getElementById('spnLoading').style.display = "none" ;
}

function checkToggle(box, id)	{
	if(box.value == 'false')
		toggleDiv(id, false);
	else
		toggleDiv(id, true);
}

function toggleDiv(id, state)	{
		if(state)
		    document.getElementById(id).style.display = "none";
		else
			document.getElementById(id).style.display = "block";
}

function preMove(test, fbox, tbox)	{

	if(test)	{
		move(fbox, tbox);
	}
	else	{
		//alert("Invalid Selection");
	}
}

function move(fbox, tbox) {

	var arrFbox = new Array();
	var arrTbox = new Array();
	var arrLookup = new Array();
	var i;
	for(i = 0; i < tbox.options.length; i++) {
		arrLookup[tbox.options[i].text] = tbox.options[i].value;
		arrTbox[i] = tbox.options[i].text;
	}
	var fLength = 0;
	var tLength = arrTbox.length;
	for(i = 0; i < fbox.options.length; i++) {
		arrLookup[fbox.options[i].text] = fbox.options[i].value;
		if (fbox.options[i].selected && fbox.options[i].value != "") {
			arrTbox[tLength] = fbox.options[i].text;
			tLength++;
		}
		else {
			arrFbox[fLength] = fbox.options[i].text;
			fLength++;
		}
	}
	//arrFbox.sort();
	//arrTbox.sort();
	fbox.length = 0;
	tbox.length = 0;
	var c;
	for(c = 0; c < arrFbox.length; c++) {
		var no = new Option();
		no.value = arrLookup[arrFbox[c]];
		no.text = arrFbox[c];
		fbox[c] = no;
	}
	for(c = 0; c < arrTbox.length; c++) {
		var no = new Option();
		no.value = arrLookup[arrTbox[c]];
		no.text = arrTbox[c];
		tbox[c] = no;
	}
}

function saveMe(tbox,fbox) {

	var strValues = "";

	//if(tbox == null || fbox == null || !tbox.length || !fbox.length){

	if(tbox == null || fbox == null || !tbox.length){
		return;
	}

	var boxLength = 0;
	if(tbox.length)	{ boxLength = tbox.length; }

	var fboxLength = 0;
	if(fbox.length) { fboxLength = fbox.length;}

	var count = 0;
	if (boxLength != 0) {

		for (i = 0; i < boxLength; i++) {
			if (count == 0) {
				strValues = tbox.options[i].value;
			}
			else {
				strValues = strValues + "," + tbox.options[i].value;
			}
			count++;
		}
	}

	var posOfAllSamples=strValues.indexOf("REST OF ALL SAMPLES")
	var posOfAllGliomas=strValues.indexOf("REST OF ALL GLIOMAS")

	if (strValues.length == 0) {
		alert("You have not made any selections");
	}
	else if (posOfAllSamples >= 0 && posOfAllGliomas >= 0) {
		alert("You cannot perform class comparison with overlapping groups.  Therefore you cannot select both 'REST OF ALL SAMPLES' AND 'REST OF ALL GLIOMAS'.  Please select one or the other.")
	}
	else {

		//alert("Here are your values you've selected:\r\n" + strValues);
		for(i=0; i < boxLength; i++)
		{
		    tbox.options[i].selected = true;

		}

	}
}

function toggleSDiv(divId,aId){

			var divId = divId;
			var aId1 = aId;

			var div = document.getElementById(divId);
			var a = document.getElementById(aId);

			if(div.className == 'divHide'){
				div.className = 'divShow';
				a.innerHTML = ' - ';
				}
			else {
				div.className = 'divHide';
				a.innerHTML = ' + ';
				}
			}

function HelpWrapper(topic)	{
	var ta = "";
	switch(topic)	{
		case  "Clinical":
			ta = "Clinical_report";
			break;
		case "clinicalPlot":
			ta = "Clinical_plot";
			break;
		case "Gene Expression Sample":
			ta = "Gene_Expression_Sample";
			break;
		case "Gene Expression Disease":
			ta = "Gene_Expression_Disease";
			break;
		case "Copy Number":
			ta = "Copy_Number_Sample";
			break;
		default:
			ta= "Clinical";
			break;
	}

	try	{
		Help.popHelp(ta);
	}
	catch(err){alert(err);}
}

<commit_msg>Fixed a bug in onRadioSampleGroup()<commit_after>function setBackToDefault(formType){
		if(formType == "pca"){
			document.getElementById("variancePercentile").value = "70.0";
		}
		else {document.getElementById("variancePercentile").value = "95.0";}
		document.getElementById("geneList").value = "none";

	}

function checkNull(text, checkName){
	if(text.value == "")	{
		scroll(0,0);
		text.focus();
		text.style.border = "2px solid red";
		alert("Please Fill in a Unique Query Name");
		return false;
	}
	if (!checkIsURLSafe(text)){

		alert("Please enter a query name without any special characters such as !@#$%^&*()+=[]\';,./{}|\":<>?");
		return false;
	}
	if (checkName == "true")
		return checkQueryName();
	else
		return true;
}

function checkIsURLSafe(textField){
	var iChars = "!@#$%^&*()+=[]\';,./{}|\":<>?";
	var text = textField.value;

	for (var i = 0; i < text.length; i++) {
		if (iChars.indexOf(text.charAt(i)) != -1)
		{
			return false;
		}
	}
	return true;
}

function clearSpecialCharacters(textField) {
	var text = textField.value;

	re = /\$|,|@|#|~|`|\%|\*|\^|\&|\(|\)|\+|\=|\[|\-|\_|\]|\[|\}|\{|\;|\:|\'|\"|\<|\>|\?|\||\\|\!|\$|\./g;
          // remove special characters like "$" and "," etc...
	text = text.replace(re, "");
	textField.value = text;
}

function spawnx(url,winw,winh, name) {

  var w = window.open(url, name,
      "screenX=0,screenY=0,status=yes,toolbar=no,menubar=no,location=no,width=" + winw + ",height=" + winh +
      ",scrollbars=yes,resizable=yes");

	//check for pop-up blocker
	if (w==null || typeof(w)=="undefined") {
		alert("You have pop-ups blocked.  Please click the highlighted link at the top of this page to view the report.  You may disable your pop-up blocker for this site to avoid doing this in the future.");
		/*
		if(document.all) {
			  document.all.popup.visible = "true";
			  document.all.popup.className = "pop";
		      document.all.popup.innerText = "You have pop-ups blocked.  Click here to view the report.";

		    } else {
			  document.getElementById('popup').visible = "true";
			  document.getElementById('popup').className= "pop";
		      document.getElementById('popup').innerHTML = "You have pop-ups blocked.  Click here to view the report.";
		}
		*/
		document.write("

You have pop-ups blocked.  Click here to view the report.");
		//scroll(0, 8000);
	} else {
		w.focus();

	}
}

function spawn(url,winw,winh) {
  var w = window.open(url, "_blank",
      "screenX=0,screenY=0,status=yes,toolbar=no,menubar=no,location=no,width=" + winw + ",height=" + winh +
      ",scrollbars=yes,resizable=yes");
}

function deleteRow(queryNum){
//get elements body and table
 var mybody=document.getElementsByTagName("body").item(0);
 var mytable=mybody.getElementsByTagName("table").item(0);

 //show warning if user has deleted all but one row
 if (mytable.rows.length == 2){
 alert('Deleting any more rows will create an invalid query');
 }
 //delete rows by finding the row id and the row's index
 else {
  var thisTR = document.getElementById(queryNum);
  var thisTRIndex = thisTR.rowIndex;
  mytable.deleteRow(thisTRIndex);
   }
 }

 function onRadioSimpleSearch(id) {

	 if (id == "geneExpPlot" || id == "samplePlotRadio")	{
	  		document.getElementById("baselineGroupName").disabled = true;

	 } else if (id == "kapMaiPlotGE" || id == "kapMaiPlotCN")	{

		 document.getElementById("baselineGroupName").disabled = false;

	 }
 }

function onRadioCloneId(idx) {

	if (idx == 0){
		 document.getElementById("cloneListSpecify").disabled = false;
		 document.getElementById("cloneListFileDD").disabled = true;
		 document.getElementById("cloneListSpecify").focus();

//		 document.forms[0].cloneList.disabled = false;
//		  document.forms[0].cloneListFile.disabled = true;
//		  document.forms[0].cloneListSpecify.focus();

	 } else if (idx == 1) {
		 document.getElementById("cloneListSpecify").value = "";
		 document.getElementById("cloneListSpecify").disabled = true;
		 document.getElementById("cloneListFileDD").disabled = false;
	      //document.forms[0].cloneListFile.focus();
	 }
 }

function onRadioSampleGroup(idx) {
	 if(idx == 0){
		document.getElementById("sampleFile").disable = true;
	    document.getElementById("sampleList").disabled = false;
	 } else if(idx == 1){
		document.getElementById("sampleList").value = "";
	    document.getElementById("sampleList").disabled = true;
	    document.getElementById("sampleFile").disabled = false;
	 }

 }

 function onRadio(formElement,i){

   //selected index of the selected
	var element = formElement.name;

	 if(element == "tumorType" || formElement == "tumorType"){
	    document.forms[0].tumorGrade.options.length = 1;
	        myOption1 = new Option("I","one");
		    myOption2 = new Option("II","two");
		    myOption3 = new Option("III","three");
		    myOption4 = new Option("IV","four");

		if(i == "ALL"){
		 document.forms[0].tumorGrade.options[1] = myOption1;
	     document.forms[0].tumorGrade.options[2] = myOption2;
	     document.forms[0].tumorGrade.options[3] = myOption3;
	     document.forms[0].tumorGrade.options[4] = myOption4;
	     }
	    if(i == "ASTROCYTOMA"){
	     document.forms[0].tumorGrade.options[1] = myOption2;
	     document.forms[0].tumorGrade.options[2] = myOption3;
	     }
	    if(i == "GBM"){
	     document.forms[0].tumorGrade.options[1] = myOption4;
	     }
	    if(i == "OLIG"){
	     document.forms[0].tumorGrade.options[1] = myOption2;
	     document.forms[0].tumorGrade.options[2] = myOption3;
	     }
	    if(i == "MIXED"){
	     document.forms[0].tumorGrade.options[1] = myOption1;
	     document.forms[0].tumorGrade.options[2] = myOption2;
	     document.forms[0].tumorGrade.options[3] = myOption3;
	     }
	  }

	  if(element == "isAllGenesQuery"){
	   if(i == 0){
	    document.forms[0].allGeneQuery.disabled = true;
	    checkToggle(formElement, "qrows");
	    }
	   if(i == 1){
	    document.forms[0].allGeneQuery.disabled = false;
	    checkToggle(formElement, "qrows");
	    }
	   }

	  if(element == "geneGroup"){
	      if (i == 0){

		  document.forms[0].geneList.disabled = false;
		  document.forms[0].geneType.disabled = false;
		  document.forms[0].geneFile.disabled = true;
		  document.forms[0].geneList.focus();
		  }
	      if (i == 1) {

	      document.forms[0].geneList.value = "";
	      document.forms[0].geneList.disabled = true;
		  document.forms[0].geneFile.disabled = false;
		  document.forms[0].geneFile.focus();
	      }
	   }

	   if(element == "cloneId"){
	      if (i == 0){
	      document.forms[0].cloneListSpecify.disabled = false;
		  document.forms[0].cloneList.disabled = false;
		  document.forms[0].cloneListFile.disabled = true;
 		  document.forms[0].cloneListSpecify.focus();

		  }
	      if (i == 1) {
	      document.forms[0].cloneListSpecify.value = "";
		  document.forms[0].cloneListSpecify.disabled = true;
		  document.forms[0].cloneListFile.disabled = false;
	      //document.forms[0].cloneListFile.focus();
	      }
	   }
	   if(element == "snpId"){
	      if (i == 0){
	        document.forms[0].snpListSpecify.disabled = false;
	        document.forms[0].snpList.disabled = false;
			document.forms[0].snpListSpecify.focus();
			document.forms[0].snpListFile.value = "";
	        document.forms[0].snpListFile.disabled = true;
	      }
	      if (i == 1){
	        document.forms[0].snpListSpecify.value = "";
	        document.forms[0].snpListSpecify.disabled = true;
	        document.forms[0].snpListFile.disabled = false;
			//document.forms[0].snpListFile.focus();
	      }
	   }

  }

 function alertUser(where){

if(confirm("This will eliminate all data currently entered in the form and will not add a query"))
{
	if (where == 'menu')
		location.href='menu.do';
	if (where == 'analysisHome')
		location.href='analysisHome.do';
}

 }

function hideLoadingMessage(){
	if(document.getElementById('spnLoading') != null)
			document.getElementById('spnLoading').style.display = "none" ;
}

function checkToggle(box, id)	{
	if(box.value == 'false')
		toggleDiv(id, false);
	else
		toggleDiv(id, true);
}

function toggleDiv(id, state)	{
		if(state)
		    document.getElementById(id).style.display = "none";
		else
			document.getElementById(id).style.display = "block";
}

function preMove(test, fbox, tbox)	{

	if(test)	{
		move(fbox, tbox);
	}
	else	{
		//alert("Invalid Selection");
	}
}

function move(fbox, tbox) {

	var arrFbox = new Array();
	var arrTbox = new Array();
	var arrLookup = new Array();
	var i;
	for(i = 0; i < tbox.options.length; i++) {
		arrLookup[tbox.options[i].text] = tbox.options[i].value;
		arrTbox[i] = tbox.options[i].text;
	}
	var fLength = 0;
	var tLength = arrTbox.length;
	for(i = 0; i < fbox.options.length; i++) {
		arrLookup[fbox.options[i].text] = fbox.options[i].value;
		if (fbox.options[i].selected && fbox.options[i].value != "") {
			arrTbox[tLength] = fbox.options[i].text;
			tLength++;
		}
		else {
			arrFbox[fLength] = fbox.options[i].text;
			fLength++;
		}
	}
	//arrFbox.sort();
	//arrTbox.sort();
	fbox.length = 0;
	tbox.length = 0;
	var c;
	for(c = 0; c < arrFbox.length; c++) {
		var no = new Option();
		no.value = arrLookup[arrFbox[c]];
		no.text = arrFbox[c];
		fbox[c] = no;
	}
	for(c = 0; c < arrTbox.length; c++) {
		var no = new Option();
		no.value = arrLookup[arrTbox[c]];
		no.text = arrTbox[c];
		tbox[c] = no;
	}
}

function saveMe(tbox,fbox) {

	var strValues = "";

	//if(tbox == null || fbox == null || !tbox.length || !fbox.length){

	if(tbox == null || fbox == null || !tbox.length){
		return;
	}

	var boxLength = 0;
	if(tbox.length)	{ boxLength = tbox.length; }

	var fboxLength = 0;
	if(fbox.length) { fboxLength = fbox.length;}

	var count = 0;
	if (boxLength != 0) {

		for (i = 0; i < boxLength; i++) {
			if (count == 0) {
				strValues = tbox.options[i].value;
			}
			else {
				strValues = strValues + "," + tbox.options[i].value;
			}
			count++;
		}
	}

	var posOfAllSamples=strValues.indexOf("REST OF ALL SAMPLES")
	var posOfAllGliomas=strValues.indexOf("REST OF ALL GLIOMAS")

	if (strValues.length == 0) {
		alert("You have not made any selections");
	}
	else if (posOfAllSamples >= 0 && posOfAllGliomas >= 0) {
		alert("You cannot perform class comparison with overlapping groups.  Therefore you cannot select both 'REST OF ALL SAMPLES' AND 'REST OF ALL GLIOMAS'.  Please select one or the other.")
	}
	else {

		//alert("Here are your values you've selected:\r\n" + strValues);
		for(i=0; i < boxLength; i++)
		{
		    tbox.options[i].selected = true;

		}

	}
}

function toggleSDiv(divId,aId){

			var divId = divId;
			var aId1 = aId;

			var div = document.getElementById(divId);
			var a = document.getElementById(aId);

			if(div.className == 'divHide'){
				div.className = 'divShow';
				a.innerHTML = ' - ';
				}
			else {
				div.className = 'divHide';
				a.innerHTML = ' + ';
				}
			}

function HelpWrapper(topic)	{
	var ta = "";
	switch(topic)	{
		case  "Clinical":
			ta = "Clinical_report";
			break;
		case "clinicalPlot":
			ta = "Clinical_plot";
			break;
		case "Gene Expression Sample":
			ta = "Gene_Expression_Sample";
			break;
		case "Gene Expression Disease":
			ta = "Gene_Expression_Disease";
			break;
		case "Copy Number":
			ta = "Copy_Number_Sample";
			break;
		default:
			ta= "Clinical";
			break;
	}

	try	{
		Help.popHelp(ta);
	}
	catch(err){alert(err);}
}

<|endoftext|>


<commit_before>import Ember from 'ember';

import I18nService from 'ember-i18n/services/i18n';
import I18nRuLocale from 'ember-flexberry/locales/ru/translations';
import I18nEnLocale from 'ember-flexberry/locales/en/translations';

import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

const animationDuration = Ember.$.fn.accordion.settings.duration + 100;

moduleForComponent('flexberry-dropdown', 'Integration | Component | flexberry dropdown', {
  integration: true,

  beforeEach: function () {
    this.register('locale:ru/translations', I18nRuLocale);
    this.register('locale:en/translations', I18nEnLocale);
    this.register('service:i18n', I18nService);

    this.inject.service('i18n', { as: 'i18n' });
    Ember.Component.reopen({
      i18n: Ember.inject.service('i18n')
    });

    // Set 'ru' as initial locale.
    this.set('i18n.locale', 'ru');
  }
});

test('it renders properly', function(assert) {
  assert.expect(6);

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    class=class
  }}`);

  // Retrieve component.
  let $component = this.$().children();

  // Check wrapper's additional CSS-classes.
  let additioanlCssClasses = 'scrolling compact fluid';
  this.set('class', additioanlCssClasses);
  Ember.A(additioanlCssClasses.split(' ')).forEach((cssClassName, index) => {
    assert.strictEqual(
    $component.hasClass(cssClassName),
    true,
    'Component\'s wrapper has additional css class \'' + cssClassName + '\'');
  });

  // Clean up wrapper's additional CSS-classes.
  this.set('class', '');
  Ember.A(additioanlCssClasses.split(' ')).forEach((cssClassName, index) => {
    assert.strictEqual(
    $component.hasClass(cssClassName),
    false,
    'Component\'s wrapper hasn\'t additional css class \'' + cssClassName + '\'');
  });
});

test('readonly mode works properly', function(assert) {
  assert.expect(3);

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    class=class
  }}`);

  // Retrieve component.
  let $component = this.$().children();

  // Check that readonly (disabled) attribute doesn't exist yet.
  assert.strictEqual($component.hasClass('disabled'), false, 'Component\'s hasn\'t readonly');

  // Activate readonly mode & check that readonly (disabled) attribute exists now & has value equals to 'readonly'.
  this.set('class', 'disabled');
  assert.strictEqual($component.hasClass('disabled'), true, 'Component\'s has readonly');

  // Check that readonly (disabled) attribute doesn't exist now.
  this.set('class', '');
  assert.strictEqual($component.hasClass('disabled'), false, 'Component\'s hasn\'t readonly');
});

test('itemsObject render properly', function(assert) {
  assert.expect(4);

  // Create objects for testing.
  let itemsObject = {
    item1: 'Caption1',
    item2: 'Caption2',
    item3: 'Caption3'
  };
  this.set('itemsObject', itemsObject);

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    items=itemsObject
  }}`);

  // Retrieve component.
  let $component = this.$().children();
  let $dropdownMenu = $component.children('div.menu');
  let $dropdomnItem = $dropdownMenu.children('div.item');

  // Check that  class 'item' in <menu> exist.
  assert.strictEqual($dropdomnItem.hasClass('item'), true, 'Component\'s has class');

  // Check component's captions and objects.
  let itemsObjectKeys = Object.keys(itemsObject);
  $dropdomnItem.each(function(i) {
    let $item = Ember.$(this);
    let itemKey = itemsObjectKeys[i];
    let itemCaption = itemsObject[itemKey];

    // Check that the captions matches the objects.
    assert.strictEqual($item.attr('data-value'), itemCaption, 'Component\'s item\'s сaptions matches the objects');
  });
});

test('itemsArray render properly', function(assert) {
  assert.expect(4);

  // Create array for testing.
  let itemsArray = ['Caption1', 'Caption2', 'Caption3'];
  this.set('itemsArray', itemsArray);

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    items=itemsArray
  }}`);

  // Retrieve component.
  let $component = this.$().children();
  let $dropdownMenu = $component.children('div.menu');
  let $dropdomnItem = $dropdownMenu.children('div.item');

  // Check that  class 'item' in <menu> exist.
  assert.strictEqual($dropdomnItem.hasClass('item'), true, 'Component\'s has class');

  // Check component's captions and array.
  $dropdomnItem.each(function(i) {
    let $item = Ember.$(this);
    let itemCaption = itemsArray[i];

    // Check that the captions matches the array.
    assert.strictEqual($item.attr('data-value'), itemCaption, 'Component\'s item\'s сaptions matches the array');
  });
});

// Тут проблема с кликом.
test('animation dropdown', function(assert) {

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    class=class
  }}`);

  // Retrieve component.
  let $component = this.$().children();
  let $dropdownMenu = $component.children('div.menu');
  let $dropdomnItem = $dropdownMenu.children('div.item');

  // Check that component is collapsed by default.
  assert.strictEqual($component.hasClass('upward'), false, 'net');
  assert.strictEqual($component.hasClass('active'), false, 'net');
  assert.strictEqual($component.hasClass('visible'), false, 'net');
  assert.strictEqual($dropdownMenu.hasClass('transition'), false, 'net');
  assert.strictEqual($dropdownMenu.hasClass('visible'), false, 'net');

  let expandAnimationCompleted = new Ember.RSVP.Promise((resolve, reject) => {
    // Try to expand component.
    // Semantic UI will start asynchronous animation after click, so we need Ember.run here.
    // Вот тут возникает проблема. В $component все приходит, но $component.click возвращает ошибку,
    // словно $component не определен. Соответственно дальше ничего не идет.
    Ember.run(() => {
      $component.click();
    });

    // Wait animation to check component's state.
    Ember.run(() => {
      let animation = assert.async();
      setTimeout(() => {
        // Check that component is animating now.
        assert.strictEqual($dropdownMenu.hasClass('animating'), true);

        // Tell to test method that asynchronous operation completed.
        animation();

      }, animationDuration / 2);
    });

    // Wait for expand animation to be completed & check component's state.
    Ember.run(() => {
      let animationCompleted = assert.async();
      setTimeout(() => {
        // Check that component is expanded now.
        assert.strictEqual($component.hasClass('active'), true);
        assert.strictEqual($dropdownMenu.hasClass('visible'), true);

        // Tell to test method that asynchronous operation completed.
        animationCompleted();

        // Resolve 'expandAnimationCompleted' promise.
        resolve();
      }, animationDuration);
    });
  });
});
<commit_msg>Add animation with/out value.<commit_after>import Ember from 'ember';

import I18nService from 'ember-i18n/services/i18n';
import I18nRuLocale from 'ember-flexberry/locales/ru/translations';
import I18nEnLocale from 'ember-flexberry/locales/en/translations';

import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

const animationDuration = Ember.$.fn.dropdown.settings.duration + 100;

moduleForComponent('flexberry-dropdown', 'Integration | Component | flexberry dropdown', {
  integration: true,

  beforeEach: function () {
    this.register('locale:ru/translations', I18nRuLocale);
    this.register('locale:en/translations', I18nEnLocale);
    this.register('service:i18n', I18nService);

    this.inject.service('i18n', { as: 'i18n' });
    Ember.Component.reopen({
      i18n: Ember.inject.service('i18n')
    });

    // Set 'ru' as initial locale.
    this.set('i18n.locale', 'ru');
  }
});

test('it renders properly', function(assert) {
  assert.expect(15);

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    class=class
  }}`);

  // Retrieve component.
  let $component = this.$().children();
  let $dropdownIcon = $component.children('i.icon');
  let $dropdownText = $component.children('div.text');
  let $dropdownMenu = $component.children('div.menu');

  // Check wrapper <div>.
  assert.strictEqual($component.prop('tagName'), 'DIV', 'Component\'s wrapper is a <div>');
  assert.strictEqual($component.hasClass('flexberry-dropdown'), true, 'Component\'s wrapper has \' flexberry-dropdown\' css-class');
  assert.strictEqual($component.hasClass('ui'), true, 'Component\'s wrapper has \'ui\' css-class');
  assert.strictEqual($component.hasClass('selection'), true, 'Component\'s wrapper has \'selection\' css-class');
  assert.strictEqual($component.hasClass('ember-view'), true, 'Component\'s wrapper has \'ember-view\' css-class');
  assert.strictEqual($component.hasClass('dropdown'), true, 'Component\'s wrapper has \'dropdown\' css-class');
  assert.strictEqual($dropdownIcon.hasClass('dropdown icon'), true, 'Component\'s wrapper has \'dropdown icon\' css-class');
  assert.strictEqual($dropdownText.hasClass('default text'), true, 'Component\'s wrapper has \'default text\' css-class');
  assert.strictEqual($dropdownMenu.hasClass('menu'), true, 'Component\'s wrapper has \'menu\' css-class');

  // Check wrapper's additional CSS-classes.
  let additioanlCssClasses = 'scrolling compact fluid';
  this.set('class', additioanlCssClasses);
  Ember.A(additioanlCssClasses.split(' ')).forEach((cssClassName, index) => {
    assert.strictEqual(
    $component.hasClass(cssClassName),
    true,
    'Component\'s wrapper has additional css class \'' + cssClassName + '\'');
  });

  // Clean up wrapper's additional CSS-classes.
  this.set('class', '');
  Ember.A(additioanlCssClasses.split(' ')).forEach((cssClassName, index) => {
    assert.strictEqual(
    $component.hasClass(cssClassName),
    false,
    'Component\'s wrapper hasn\'t additional css class \'' + cssClassName + '\'');
  });
});

test('placeholder', function (assert) {

});

test('readonly mode works properly', function(assert) {
  assert.expect(3);

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    class=class
  }}`);

  // Retrieve component.
  let $component = this.$().children();

  // Check that readonly (disabled) attribute doesn't exist yet.
  assert.strictEqual($component.hasClass('disabled'), false, 'Component\'s hasn\'t readonly');

  // Activate readonly mode & check that readonly (disabled) attribute exists now & has value equals to 'readonly'.
  this.set('class', 'disabled');
  assert.strictEqual($component.hasClass('disabled'), true, 'Component\'s has readonly');

  // Check that readonly (disabled) attribute doesn't exist now.
  this.set('class', '');
  assert.strictEqual($component.hasClass('disabled'), false, 'Component\'s hasn\'t readonly');
});

test('itemsObject render properly', function(assert) {
  assert.expect(4);

  // Create objects for testing.
  let itemsObject = {
    item1: 'Caption1',
    item2: 'Caption2',
    item3: 'Caption3'
  };
  this.set('itemsObject', itemsObject);

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    items=itemsObject
  }}`);

  // Retrieve component.
  let $component = this.$().children();
  let $dropdownMenu = $component.children('div.menu');
  let $dropdomnItem = $dropdownMenu.children('div.item');

  // Check that  class 'item' in <menu> exist.
  assert.strictEqual($dropdomnItem.hasClass('item'), true, 'Component\'s has class');

  // Check component's captions and objects.
  let itemsObjectKeys = Object.keys(itemsObject);
  $dropdomnItem.each(function(i) {
    let $item = Ember.$(this);
    let itemKey = itemsObjectKeys[i];
    let itemCaption = itemsObject[itemKey];

    // Check that the captions matches the objects.
    assert.strictEqual($item.attr('data-value'), itemCaption, 'Component\'s item\'s сaptions matches the objects');
  });
});

test('itemsArray render properly', function(assert) {
  assert.expect(4);

  // Create array for testing.
  let itemsArray = ['Caption1', 'Caption2', 'Caption3'];
  this.set('itemsArray', itemsArray);

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    items=itemsArray
  }}`);

  // Retrieve component.
  let $component = this.$().children();
  let $dropdownMenu = $component.children('div.menu');
  let $dropdomnItem = $dropdownMenu.children('div.item');

  // Check that  class 'item' in <menu> exist.
  assert.strictEqual($dropdomnItem.hasClass('item'), true, 'Component\'s has class');

  // Check component's captions and array.
  $dropdomnItem.each(function(i) {
    let $item = Ember.$(this);
    let itemCaption = itemsArray[i];

    // Check that the captions matches the array.
    assert.strictEqual($item.attr('data-value'), itemCaption, 'Component\'s item\'s сaptions matches the array');
  });
});

test('animation dropdown without selecting a value', function(assert) {
  assert.expect(10);

  // Create array for testing.
  let itemsArray = ['Caption1', 'Caption2', 'Caption3'];
  this.set('itemsArray', itemsArray);

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    items=itemsArray
  }}`);

  // Retrieve component.
  let $component = this.$().children();
  let $dropdownMenu = $component.children('div.menu');

  // Check that component is collapsed by default.
  assert.strictEqual($component.hasClass('upward'), false, 'net');
  assert.strictEqual($component.hasClass('active'), false, 'net');
  assert.strictEqual($component.hasClass('visible'), false, 'net');
  assert.strictEqual($dropdownMenu.hasClass('transition'), false, 'net');
  assert.strictEqual($dropdownMenu.hasClass('visible'), false, 'net');

  let menuAnimationCompleted = new Ember.RSVP.Promise((resolve, reject) => {
    // Try to expand component.
    // Semantic UI will start asynchronous animation after click, so we need Ember.run here.
    Ember.run(() => {
      $component.click();
    });

    // Wait animation to check component's state.
    Ember.run(() => {
      let animation = assert.async();
      setTimeout(() => {
        // Check that component is animating now.
        assert.strictEqual($dropdownMenu.hasClass('animating'), true);

        // Tell to test method that asynchronous operation completed.
        animation();

      }, animationDuration / 2);
    });

    // Wait for expand animation to be completed & check component's state.
    Ember.run(() => {
      let animationCompleted = assert.async();
      setTimeout(() => {
        // Check that component is expanded now.
        assert.strictEqual($component.hasClass('active'), true);
        assert.strictEqual($dropdownMenu.hasClass('visible'), true);

        // Tell to test method that asynchronous operation completed.
        animationCompleted();

        // Resolve 'menuAnimationCompleted' promise.
        resolve();
      }, animationDuration);
    });
  });

  // Wait animation to be completed (when resolve will be called inside previous timeout).
  // Then try to collapse component.
  menuAnimationCompleted.then(() => {
    // Semantic UI will start asynchronous animation after click, so we need Ember.run here.
    Ember.run(() => {
      $component.click();
    });

    // Wait for collapse animation to be completed & check component's state.
    Ember.run(() => {
      let animationCompleted = assert.async();
      setTimeout(() => {
        // Check that component is expanded now.
        assert.strictEqual($component.hasClass('active'), false);
        assert.strictEqual($dropdownMenu.hasClass('visible'), false);

        animationCompleted();
      }, animationDuration);
    });
  });
});

test('animation dropdown with selecting a value', function(assert) {

  // Create array for testing.
  let itemsArray = ['Caption1'];
  this.set('itemsArray', itemsArray);

  // Render component.
  this.render(hbs`{{flexberry-dropdown
    items=itemsArray
  }}`);

  // Retrieve component.
  let $component = this.$().children();
  let $dropdownMenu = $component.children('div.menu');
  let $dropdownText = $component.children('div.text');
  let $dropdomnItem = $dropdownMenu.children('div.item');

  let menuAnimationCompleted = new Ember.RSVP.Promise((resolve, reject) => {
    // Try to expand component.
    // Semantic UI will start asynchronous animation after click, so we need Ember.run here.
    Ember.run(() => {
      $component.click();
    });

    // Wait animation to check component's state.
    Ember.run(() => {
      let animation = assert.async();
      setTimeout(() => {
        // Check that component is animating now.
        assert.strictEqual($dropdownMenu.hasClass('animating'), true);

        // Tell to test method that asynchronous operation completed.
        animation();

      }, animationDuration / 2);
    });

    // Wait for expand animation to be completed & check component's state.
    Ember.run(() => {
      let animationCompleted = assert.async();
      setTimeout(() => {

        // Tell to test method that asynchronous operation completed.
        animationCompleted();

        // Resolve 'menuAnimationCompleted' promise.
        resolve();
      }, animationDuration);
    });
  });

  let itemAnimationCompleted = new Ember.RSVP.Promise((resolve, reject) => {
    // Try to expand component.
    // Semantic UI will start asynchronous animation after click, so we need Ember.run here.
    Ember.run(() => {
      $dropdomnItem.click();
    });

    // Wait animation to check component's state.
    Ember.run(() => {
      let animation = assert.async();
      setTimeout(() => {
        // Check that component is animating now.
        assert.strictEqual($dropdownMenu.hasClass('animating'), true);

        // Tell to test method that asynchronous operation completed.
        animation();

      }, animationDuration / 2);
    });

    // Wait for expand animation to be completed & check component's state.
    Ember.run(() => {
      let animationCompleted = assert.async();
      setTimeout(() => {
        // Check that component is expanded now.
        assert.strictEqual($dropdownMenu.hasClass('visible'), true);
        assert.strictEqual($dropdomnItem.hasClass('active'), true);
        assert.strictEqual($dropdomnItem.hasClass('selected'), true);

        // Tell to test method that asynchronous operation completed.
        animationCompleted();

        // Resolve 'menuAnimationCompleted' promise.
        resolve();
      }, animationDuration);
    });
  });

  // Wait animation to be completed (when resolve will be called inside previous timeout).
  // Then try to collapse component.
  itemAnimationCompleted.then(() => {
    // Wait for collapse animation to be completed & check component's state.
    Ember.run(() => {
      let animationCompleted = assert.async();
      setTimeout(() => {
        // Check that component is expanded now.
        assert.strictEqual($dropdownText.hasClass('default text'), false);
        assert.strictEqual($dropdownText.hasClass('text'), true);
        assert.strictEqual($dropdownMenu.hasClass('hidden'), true);
        assert.strictEqual($dropdomnItem.hasClass('active'), true);
        assert.strictEqual($dropdomnItem.hasClass('selected'), true);

        animationCompleted();
      }, animationDuration);
    });
  });
});
<|endoftext|>


<commit_before>import {
  omit,
  decodeBase64Url
} from './utils'

const reservedUserProperties = [
  '_id',
  '_rev',
  'name',
  'password',
  'roles',
  'type',
  'salt',
  'derived_key',
  'password_scheme',
  'iterations'
]

class LoginService {
  constructor (
    $http,
    $window,
    $rootRouter,
    config,
    sessionService,
    toastService,
    mainService
  ) {
    this.$http = $http
    this.$window = $window
    this.$rootRouter = $rootRouter
    this.config = config
    this.sessionService = sessionService
    this.toastService = toastService
    this.mainService = mainService
  }

  createUser (username, token) {
    const data = {
      username,
      token
    }
    const opts = {
      headers: {
        'x-api-key': this.config.middleware.key
      }
    }
    return this.$http.post(this.config.middleware.url + '/users', data, opts)
  }

  handleLoginError (error) {
    let message = 'Could not log you in'
    if (error && error.data && error.data.message) {
      message += `. The response was "${error.data.message}".`
    }
    return this.toastService.error('login-failed', message)
  }

  setSession (session) {
    const doc = Object.assign({}, session, {
      _id: '_local/session'
    })
    const opts = {
      forceUpdate: true
    }
    return this.sessionService.save(doc, opts)
  }

  init (session) {
    this.mainService.init(session)
    this.$rootRouter.navigate(['/Nav'])
    return session
  }

  navLogin () {
    this.$window.location.href = this.config.nav + '/SignIn.aspx'
  }

  navLogout () {
    this.$window.location.href = this.config.nav + '/SignOut.aspx'
  }

  loginOrCreateUser (username, token) {
    const retry = () => {
      return this.createUser(username, token)
        .then(() => this.sessionService.login(username, token))
    }
    return this.sessionService.login(username, token)
      .catch(retry)
  }

  getSession () {
    return this.sessionService.get('_local/session')
  }

  canActivate () {
    const isValidSession = (local, remote) => {
      if (!remote.ok) {
        return Promise.reject('session invalid')
      }
      if (!(remote.userCtx && remote.userCtx.name)) {
        return Promise.reject('missing user context in remote session')
      }
      if (!local.userName) {
        return Promise.reject('missing user name in local session')
      }
      if (remote.userCtx.name.toLowerCase() !== local.userName.toLowerCase()) {
        return Promise.reject(`session mismatch for user ${local.userName}`)
      }
    }

    const checkSession = localSession => {
      return this.sessionService.getSession()
        .then(remoteSession => isValidSession(localSession, remoteSession))
        .then(() => this.init(localSession))
        .catch(err => {
          if (angular.isObject(err) && err.status === 0) {
            // User looks to be offline, grant login
            return this.init(localSession)
          }
          throw err
        })
    }

    return this.getSession()
      .then(localSession => checkSession(localSession))
      .catch(err => this.navLogin(err))
  }

  updateSession (username, session) {
    const omitted = omit(session, reservedUserProperties)
    const metadata = Object.assign(omitted, {
      lastLogin: new Date().toISOString()
    })

    const updateIdRev = res => {
      if (!res.ok) {
        return session
      }
      return Object.assign(metadata, omit(res, ['ok']))
    }

    const opts = {
      metadata
    }
    return this.sessionService.putUser(username, opts)
      .then(updateIdRev)
  }

  login (instruction) {
    if (!(instruction.params.username && instruction.params.token)) {
      return this.canActivate()
    }

    const username = this.$window.decodeURI(instruction.params.username)
      .toLowerCase()
    const token = decodeBase64Url(instruction.params.token)

    return this.loginOrCreateUser(username, token)
      .then(() => this.sessionService.getUser(username))
      .then(session => this.updateSession(username, session))
      .then(session => this.setSession(session))
      .then(session => this.init(session))
      .catch(err => this.handleLoginError(err))
  }

  logout () {
    return this.getSession()
      .then(session => this.sessionService.remove(session))
  }
}

LoginService.$inject = [
  '$http',
  '$window',
  '$rootRouter',
  'config',
  'sessionService',
  'toastService',
  'mainService'
]

export default LoginService
<commit_msg>fix: use navigator online to grant offline login<commit_after>import {
  omit,
  decodeBase64Url
} from './utils'

const reservedUserProperties = [
  '_id',
  '_rev',
  'name',
  'password',
  'roles',
  'type',
  'salt',
  'derived_key',
  'password_scheme',
  'iterations'
]

class LoginService {
  constructor (
    $http,
    $window,
    $rootRouter,
    config,
    sessionService,
    toastService,
    mainService
  ) {
    this.$http = $http
    this.$window = $window
    this.$rootRouter = $rootRouter
    this.config = config
    this.sessionService = sessionService
    this.toastService = toastService
    this.mainService = mainService
  }

  createUser (username, token) {
    const data = {
      username,
      token
    }
    const opts = {
      headers: {
        'x-api-key': this.config.middleware.key
      }
    }
    return this.$http.post(this.config.middleware.url + '/users', data, opts)
  }

  handleLoginError (error) {
    let message = 'Could not log you in'
    if (error && error.data && error.data.message) {
      message += `. The response was "${error.data.message}".`
    }
    return this.toastService.error('login-failed', message)
  }

  setSession (session) {
    const doc = Object.assign({}, session, {
      _id: '_local/session'
    })
    const opts = {
      forceUpdate: true
    }
    return this.sessionService.save(doc, opts)
  }

  init (session) {
    this.mainService.init(session)
    this.$rootRouter.navigate(['/Nav'])
    return session
  }

  navLogin () {
    this.$window.location.href = this.config.nav + '/SignIn.aspx'
  }

  navLogout () {
    this.$window.location.href = this.config.nav + '/SignOut.aspx'
  }

  loginOrCreateUser (username, token) {
    const retry = () => {
      return this.createUser(username, token)
        .then(() => this.sessionService.login(username, token))
    }
    return this.sessionService.login(username, token)
      .catch(retry)
  }

  getSession () {
    return this.sessionService.get('_local/session')
  }

  canActivate () {
    const isValidSession = (local, remote) => {
      if (!remote.ok) {
        return Promise.reject('session invalid')
      }
      if (!(remote.userCtx && remote.userCtx.name)) {
        return Promise.reject('missing user context in remote session')
      }
      if (!local.userName) {
        return Promise.reject('missing user name in local session')
      }
      if (remote.userCtx.name.toLowerCase() !== local.userName.toLowerCase()) {
        return Promise.reject(`session mismatch for user ${local.userName}`)
      }
    }

    const checkSession = localSession => {
      return this.sessionService.getSession()
        .then(remoteSession => isValidSession(localSession, remoteSession))
        .then(() => this.init(localSession))
        .catch(err => {
          if (!this.$window.navigator.onLine) {
            // User looks to be offline, grant login
            return this.init(localSession)
          }
          throw err
        })
    }

    return this.getSession()
      .then(localSession => checkSession(localSession))
      .catch(err => this.navLogin(err))
  }

  updateSession (username, session) {
    const omitted = omit(session, reservedUserProperties)
    const metadata = Object.assign(omitted, {
      lastLogin: new Date().toISOString()
    })

    const updateIdRev = res => {
      if (!res.ok) {
        return session
      }
      return Object.assign(metadata, omit(res, ['ok']))
    }

    const opts = {
      metadata
    }
    return this.sessionService.putUser(username, opts)
      .then(updateIdRev)
  }

  login (instruction) {
    if (!(instruction.params.username && instruction.params.token)) {
      return this.canActivate()
    }

    const username = this.$window.decodeURI(instruction.params.username)
      .toLowerCase()
    const token = decodeBase64Url(instruction.params.token)

    return this.loginOrCreateUser(username, token)
      .then(() => this.sessionService.getUser(username))
      .then(session => this.updateSession(username, session))
      .then(session => this.setSession(session))
      .then(session => this.init(session))
      .catch(err => this.handleLoginError(err))
  }

  logout () {
    return this.getSession()
      .then(session => this.sessionService.remove(session))
  }
}

LoginService.$inject = [
  '$http',
  '$window',
  '$rootRouter',
  'config',
  'sessionService',
  'toastService',
  'mainService'
]

export default LoginService
<|endoftext|>


<commit_before>const webpack = require('webpack')
const merge = require('webpack-merge')
const str = JSON.stringify
const CommonConfig = require('./common.config.js')

const stylesheet = require('./stylesheet.config')
const uglify = require('./uglify.config')

module.exports = merge(
  CommonConfig,
  stylesheet(),
  uglify(),
  {
    plugins: [
      new webpack.DefinePlugin({
        __DEV__: false,
        __RUN_MODE__: str(process.env.RUN_MODE || ''),
        __BACKEND_URL__: str(process.env.RUN_MODE ? process.env.BACKEND_URL : 'http://localhost:8080'),
        __WS_URL__: str(process.env.RUN_MODE ? process.env.WS_URL : ''),
        __PACKAGE_SERVER__: str(process.env.PACKAGE_SERVER || process.env.HTML_BASE_URL),
      }),
    ]
  }
)
<commit_msg>config output publicPath<commit_after>const webpack = require('webpack')
const merge = require('webpack-merge')
const str = JSON.stringify
const CommonConfig = require('./common.config.js')

const stylesheet = require('./stylesheet.config')
const uglify = require('./uglify.config')

module.exports = merge(
  CommonConfig,
  stylesheet(),
  uglify(),
  {
    output: {
      publicPath: process.env.RUN_MODE ? '/rs' : '/',
    },
    plugins: [
      new webpack.DefinePlugin({
        __DEV__: false,
        __RUN_MODE__: str(process.env.RUN_MODE || ''),
        __BACKEND_URL__: str(process.env.RUN_MODE ? process.env.BACKEND_URL : 'http://localhost:8080'),
        __WS_URL__: str(process.env.RUN_MODE ? process.env.WS_URL : ''),
        __PACKAGE_SERVER__: str(process.env.PACKAGE_SERVER || process.env.HTML_BASE_URL),
      }),
    ]
  }
)
<|endoftext|>