<commit_before>import { Localized } from 'fluent-react/compat';
import * as React from 'react';
import { connect } from 'react-redux';
import { trackListening } from '../../../../services/tracker';
import { Clips } from '../../../../stores/clips';
import { Locale } from '../../../../stores/locale';
import StateTree from '../../../../stores/tree';
import URLS from '../../../../urls';
import NormalizedPlayer from '../../../normalized-player';
import {
  CheckIcon,
  CrossIcon,
  MicIcon,
  OldPlayIcon,
  ThumbsDownIcon,
  ThumbsUpIcon,
  VolumeIcon,
} from '../../../ui/icons';
import { LinkButton } from '../../../ui/ui';
import ContributionPage, {
  ContributionPillProps,
  SET_COUNT,
} from '../contribution';
import { PlayButton } from '../../../primary-buttons/primary-buttons';
import Pill from '../pill';

import './listen.css';

const VOTE_NO_PLAY_MS = 3000; // Threshold when to allow voting no

const VoteButton = ({
  type,
  ...props
}: { type: 'yes' | 'no' } & React.ButtonHTMLAttributes<any>) => (
  <button type="button" className={['vote-button', type].join(' ')} {...props}>
    {type === 'yes' && <ThumbsUpIcon />}
    {type === 'no' && <ThumbsDownIcon />}
    <Localized id={'vote-' + type}>
      <span />
    </Localized>
  </button>
);

interface PropsFromState {
  clips: Clips.Clip[];
  isLoading: boolean;
  locale: Locale.State;
}

interface PropsFromDispatch {
  removeClip: typeof Clips.actions.remove;
  vote: typeof Clips.actions.vote;
}

interface Props extends PropsFromState, PropsFromDispatch {}

interface State {
  clips: (Clips.Clip & { isValid?: boolean })[];
  hasPlayed: boolean;
  hasPlayedSome: boolean;
  isPlaying: boolean;
  isSubmitted: boolean;
}

const initialState: State = {
  clips: [],
  hasPlayed: false,
  hasPlayedSome: false,
  isPlaying: false,
  isSubmitted: false,
};

class ListenPage extends React.Component<Props, State> {
  audioPlayer = new NormalizedPlayer();
  playedSomeInterval: any;

  state: State = initialState;

  static getDerivedStateFromProps(props: Props, state: State) {
    if (state.clips.length > 0) return null;

    if (props.clips.length > 0) {
      return {
        clips: props.clips
          .slice(0, SET_COUNT)
          .map(clip => ({ ...clip, isValid: null })),
      };
    }

    return null;
  }

  componentWillUnmount() {
    clearInterval(this.playedSomeInterval);
    this.audioPlayer.close();
  }

  private getClipIndex() {
    return this.state.clips.findIndex(clip => clip.isValid === null);
  }

  private play = async () => {
    if (this.state.isPlaying) {
      this.stop();
      return;
    }

    this.audioPlayer.onended = this.hasPlayed;
    await this.audioPlayer.play(this.state.clips[this.getClipIndex()].audioSrc);
    this.setState({ isPlaying: true });
    clearInterval(this.playedSomeInterval);
    this.playedSomeInterval = setInterval(
      () => this.setState({ hasPlayedSome: true }),
      VOTE_NO_PLAY_MS
    );
  };

  private stop = () => {
    this.audioPlayer.stop();
    clearInterval(this.playedSomeInterval);
    this.setState({ isPlaying: false });
  };

  private hasPlayed = () => {
    this.setState({ hasPlayed: true, isPlaying: false });
    trackListening('listen', this.props.locale);
  };

  private vote = (isValid: boolean) => {
    const { clips } = this.state;
    const clipIndex = this.getClipIndex();

    clearInterval(this.playedSomeInterval);
    this.props.vote(isValid, this.state.clips[this.getClipIndex()].id);
    this.setState({
      hasPlayed: false,
      hasPlayedSome: false,
      isPlaying: false,
      isSubmitted: clipIndex === SET_COUNT - 1,
      clips: clips.map((clip, i) =>
        i === clipIndex ? { ...clip, isValid } : clip
      ),
    });
  };

  private voteYes = () => {
    if (!this.state.hasPlayed) {
      return;
    }
    this.vote(true);
    trackListening('vote-yes', this.props.locale);
  };

  private voteNo = () => {
    const { hasPlayed, hasPlayedSome } = this.state;
    if (!hasPlayed && !hasPlayedSome) {
      return;
    }
    this.vote(false);
    trackListening('vote-no', this.props.locale);
  };

  private handleSkip = () => {
    const { removeClip } = this.props;
    const { clips } = this.state;
    this.stop();
    removeClip(clips[this.getClipIndex()].id);
    this.setState({
      clips: clips.map((clip, i) =>
        this.getClipIndex() === i
          ? { ...this.props.clips.slice(SET_COUNT)[0], isValid: null }
          : clip
      ),
    });
  };

  private reset = () => this.setState(initialState);

  render() {
    const {
      clips,
      hasPlayed,
      hasPlayedSome,
      isPlaying,
      isSubmitted,
    } = this.state;
    const clipIndex = this.getClipIndex();
    const activeClip = clips[clipIndex];
    return (
      <React.Fragment>
        <ContributionPage
          activeIndex={clipIndex}
          errorContent={
            !this.props.isLoading &&
            clips.length === 0 && (
              <div className="empty-container">
                <div className="error-card card-dimensions">
                  <Localized id="nothing-to-validate"><commit_msg>fix #1694 show record-instead message when not enough clips found<commit_after>import { Localized } from 'fluent-react/compat';
import * as React from 'react';
import { connect } from 'react-redux';
import { trackListening } from '../../../../services/tracker';
import { Clips } from '../../../../stores/clips';
import { Locale } from '../../../../stores/locale';
import StateTree from '../../../../stores/tree';
import URLS from '../../../../urls';
import NormalizedPlayer from '../../../normalized-player';
import {
  CheckIcon,
  CrossIcon,
  MicIcon,
  OldPlayIcon,
  ThumbsDownIcon,
  ThumbsUpIcon,
  VolumeIcon,
} from '../../../ui/icons';
import { LinkButton } from '../../../ui/ui';
import ContributionPage, {
  ContributionPillProps,
  SET_COUNT,
} from '../contribution';
import { PlayButton } from '../../../primary-buttons/primary-buttons';
import Pill from '../pill';

import './listen.css';

const VOTE_NO_PLAY_MS = 3000; // Threshold when to allow voting no

const VoteButton = ({
  type,
  ...props
}: { type: 'yes' | 'no' } & React.ButtonHTMLAttributes<any>) => (
  <button type="button" className={['vote-button', type].join(' ')} {...props}>
    {type === 'yes' && <ThumbsUpIcon />}
    {type === 'no' && <ThumbsDownIcon />}
    <Localized id={'vote-' + type}>
      <span />
    </Localized>
  </button>
);

interface PropsFromState {
  clips: Clips.Clip[];
  isLoading: boolean;
  locale: Locale.State;
}

interface PropsFromDispatch {
  removeClip: typeof Clips.actions.remove;
  vote: typeof Clips.actions.vote;
}

interface Props extends PropsFromState, PropsFromDispatch {}

interface State {
  clips: (Clips.Clip & { isValid?: boolean })[];
  hasPlayed: boolean;
  hasPlayedSome: boolean;
  isPlaying: boolean;
  isSubmitted: boolean;
}

const initialState: State = {
  clips: [],
  hasPlayed: false,
  hasPlayedSome: false,
  isPlaying: false,
  isSubmitted: false,
};

class ListenPage extends React.Component<Props, State> {
  audioPlayer = new NormalizedPlayer();
  playedSomeInterval: any;

  state: State = initialState;

  static getDerivedStateFromProps(props: Props, state: State) {
    if (state.clips.length > 0) return null;

    if (props.clips.length > 0) {
      return {
        clips: props.clips
          .slice(0, SET_COUNT)
          .map(clip => ({ ...clip, isValid: null })),
      };
    }

    return null;
  }

  componentWillUnmount() {
    clearInterval(this.playedSomeInterval);
    this.audioPlayer.close();
  }

  private getClipIndex() {
    return this.state.clips.findIndex(clip => clip.isValid === null);
  }

  private play = async () => {
    if (this.state.isPlaying) {
      this.stop();
      return;
    }

    this.audioPlayer.onended = this.hasPlayed;
    await this.audioPlayer.play(this.state.clips[this.getClipIndex()].audioSrc);
    this.setState({ isPlaying: true });
    clearInterval(this.playedSomeInterval);
    this.playedSomeInterval = setInterval(
      () => this.setState({ hasPlayedSome: true }),
      VOTE_NO_PLAY_MS
    );
  };

  private stop = () => {
    this.audioPlayer.stop();
    clearInterval(this.playedSomeInterval);
    this.setState({ isPlaying: false });
  };

  private hasPlayed = () => {
    this.setState({ hasPlayed: true, isPlaying: false });
    trackListening('listen', this.props.locale);
  };

  private vote = (isValid: boolean) => {
    const { clips } = this.state;
    const clipIndex = this.getClipIndex();

    clearInterval(this.playedSomeInterval);
    this.props.vote(isValid, this.state.clips[this.getClipIndex()].id);
    this.setState({
      hasPlayed: false,
      hasPlayedSome: false,
      isPlaying: false,
      isSubmitted: clipIndex === SET_COUNT - 1,
      clips: clips.map((clip, i) =>
        i === clipIndex ? { ...clip, isValid } : clip
      ),
    });
  };

  private voteYes = () => {
    if (!this.state.hasPlayed) {
      return;
    }
    this.vote(true);
    trackListening('vote-yes', this.props.locale);
  };

  private voteNo = () => {
    const { hasPlayed, hasPlayedSome } = this.state;
    if (!hasPlayed && !hasPlayedSome) {
      return;
    }
    this.vote(false);
    trackListening('vote-no', this.props.locale);
  };

  private handleSkip = () => {
    const { removeClip } = this.props;
    const { clips } = this.state;
    this.stop();
    removeClip(clips[this.getClipIndex()].id);
    this.setState({
      clips: clips.map((clip, i) =>
        this.getClipIndex() === i
          ? { ...this.props.clips.slice(SET_COUNT)[0], isValid: null }
          : clip
      ),
    });
  };

  private reset = () => this.setState(initialState);

  render() {
    const {
      clips,
      hasPlayed,
      hasPlayedSome,
      isPlaying,
      isSubmitted,
    } = this.state;
    const clipIndex = this.getClipIndex();
    const activeClip = clips[clipIndex];
    return (
      <React.Fragment>
        <ContributionPage
          activeIndex={clipIndex}
          errorContent={
            !this.props.isLoading &&
            (clips.length === 0 || !activeClip) && (
              <div className="empty-container">
                <div className="error-card card-dimensions">
                  <Localized id="nothing-to-validate">
{'fraction_non_alphanumeric': 0.16373871687857905, 'fraction_numerical': 0.0023294186159371058, 'mean_word_length': 2.734686480608916, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5213687', 'n_tokens_mistral': 3341, 'n_tokens_neox': 3272, 'n_words': 681}