{"version":3,"sources":["webpack:///./src/components/primary-buttons/primary-buttons.tsx","webpack:///./src/components/pages/contribution/speak/utility.ts","webpack:///./src/components/pages/contribution/speak/audio-web.ts","webpack:///./src/audio-recorder-polyfill/index.cjs","webpack:///./src/audio-recorder-polyfill/wave-encoder/index.cjs","webpack:///./src/components/share-modal/notifications-yubi.svg","webpack:///./src/components/pages/contribution/report/success.svg","webpack:///./src/components/pages/contribution/images/users_celebrating.svg","webpack:///./src/components/thousand-recordings-modal/award.svg","webpack:///./src/components/pages/contribution/waves.svg","webpack:///./src/components/pages/contribution/yubi-with-trophy.svg","webpack:///./src/components/terms-modal.tsx","webpack:///./src/components/share-buttons-extended/share-buttons-extended.tsx","webpack:///./src/components/share-modal/share-modal.tsx","webpack:///./src/components/pages/contribution/report/report.tsx","webpack:///./src/components/pages/contribution/success.tsx","webpack:///./src/components/pages/contribution/wave.ts","webpack:///./src/components/thousand-recordings-modal/thousand-recordings-modal.tsx","webpack:///./src/components/pages/contribution/phrase-set-picker/index.tsx","webpack:///./src/components/pages/contribution/contribution.tsx","webpack:///./src/components/pages/contribution/pill.tsx","webpack:///./src/components/pages/contribution/speak/recording-pill.tsx","webpack:///./src/services/logger.ts","webpack:///./src/components/pages/contribution/speak/speak.tsx"],"names":["PrimaryButton","className","to","trackClass","props","disabled","join","type","PlayTTSButton","status","id","RecordButton","Voice","PlayButton","isPlaying","shouldUseFixForIOSDevice","os_name","AudioRecorder","AudioError","AudioWeb","this","recorderListeners","start","dataavailable","stop","analyze","bind","microphone","Promise","res","rej","deny","error","NotAllowedError","NOT_ALLOWED","NotFoundError","NO_MIC","name","resolve","stream","navigator","mediaDevices","getUserMedia","audio","then","webkitGetUserMedia","mozGetUserMedia","NO_SUPPORT","window","MediaRecorder","notSupported","analyzerNode","getByteFrequencyData","frequencyBins","volumeCallback","Math","max","cb","bufferSize","isReady","getMicrophone","audioContext","AudioContext","webkitAudioContext","sourceNode","createMediaStreamSource","volumeNode","createGain","createAnalyser","outputNode","createMediaStreamDestination","channelCount","connect","prototype","mimeType","recorder","fftSize","smoothingTimeConstant","Uint8Array","frequencyBinCount","jsNode","createScriptProcessor","destination","processor","_rej","chunks","removeEventListener","_e","e","push","data","addEventListener","onaudioprocess","console","undefined","blob","Blob","recordingDevice","device_default_input_label","label","device_default_input_is_bluetooth","toLocaleLowerCase","includes","url","URL","createObjectURL","created_at","Date","toISOString","reject","track","getTracks","context","waveEncoder","method","event","Event","Error","config","state","em","document","createDocumentFragment","encoder","fn","js","toString","replace","Worker","createWorker","dispatchEvent","timeslice","contextExternal","processorExternal","clone","input","postMessage","sampleRate","inputBuffer","getChannelData","slicing","setInterval","requestData","forEach","disconnect","clearInterval","args","isTypeSupported","module","exports","recorded","onmessage","buffer","length","i","index","sample","encode","bufferLength","wav","view","DataView","setUint32","setUint16","set","dump","getString","onAgree","onDisagree","elems","termsLink","href","TERMS","target","privacyLink","PRIVACY","innerClassName","titleClassName","contentClassName","onRequestClose","buttons","handler","variant","shareTextId","reverse","color","rel","LINKEDIN","encodeURIComponent","HTTP_ROOT","src","alt","CheckboxRow","children","title","ReportModal","kind","reasons","onSubmitted","api","selectedReasons","setSelectedReasons","otherText","setOtherText","submitStatus","setSubmitStatus","outline","rounded","onClick","map","reason","key","value","onChange","checked","attrs","placeholder","style","marginTop","trim","Object","values","some","Boolean","report","entries","filter","_key","concat","ReportButton","width","height","viewBox","d","fill","fillRule","xlinkHref","mask","onReset","account","hasAccount","link","createElement","click","RATIO","devicePixelRatio","WIDTH","HEIGHT","Curve","assign","tick","respawn","amplitude","random","seed","openClass","y","abs","sin","baseAmplitude","direction","speed","PI","ctx","beginPath","xBase","yBase","x","xInit","equation","lineTo","h","gradient","createRadialGradient","addColorStop","fillStyle","closePath","_draw","lastFPSCheckAt","lowFPSCount","framesInLastSecond","Wave","canvas","colors","shouldDraw","getContext","curves","reduce","arr","draw","globalCompositeOperation","fillRect","clear","curve","requestAnimationFrame","now","performance","slice","findIndex","t","EMAIL","vars","count","email","emailLink","PhraseSetPickerModal","phraseSetState","canClose","current","setPhraseSet","actions","selectedLanguage","setSelectedLanguage","selectedPhraseKey","setSelectedPhraseKey","langs","languages","language","sets","byLanguage","lang","Intl","DisplayNames","of","charAt","toLocaleUpperCase","toUpperCase","getNameForLanguage","ps","display_name","PhraseSetSelectorButton","Tooltip","FirstNRecordingsModal","handleContinue","AccountModal","logInLink","selectedPill","showAccountModal","showFirstTenRecordingsModal","showThousandRecordingsModal","showReportModal","showShareModal","showPhraseSetPickerModal","canvasRef","startWaving","wave","idle","toggleShareModal","isTTSPlaying","SHARE_RECORDING","BUTTON_USAGE","trackAppUsage","SPEAK_BTN_SHARE","setState","handleKeyDown","isSubmitted","locale","onSubmit","shortcutsDisabled","ctrlKey","altKey","shiftKey","metaKey","isEnter","isDone","isSpaceWasPressed","shortcut","shortcuts","find","keyString","toLowerCase","RERECORD_CLIP","SHORTCUT_USAGE","SPEAK_KEY_RECORD_STOP","SKIP_SENTENCE","SPEAK_KEY_SKIP","Number","GO_TO_RECORDING","SPEAK_KEY_RERECORD","action","preventDefault","localStorage","setItem","user","client_id","closeThousandRecordingsModal","flags","showAccountConversionModal","JSON","parse","getItem","recordings","getClipsCount","modalSeen","phrase_set","showAccountModalDefault","Image","prevProps","skipAutoReset","activeIndex","stringify","play","skip_submission_feedback","shouldShowThousandRecordingsModal","sentences","isLoaded","errorContent","onSkip","reportModalProps","onBack","areClipsUploading","selectPill","getHasSeenFirstNRecordingsModal","setHasSeenFirstNRecordingsModal","isRegisteredUser","bold","renderClipCount","arrow","renderContent","clips_count","instruction","isFirstSubmit","pills","primaryButtons","playTTSButton","actionType","sentence","activeSentenceIndex","SET_COUNT","isActive","text","transform","dir","opacity","margin","taxonomy","blank","TARGET_SEGMENT_INFO","span","pill","isOpen","num","onShare","min","display","ref","icon","stringKey","stringKeyToDisplay","SPEAK_BTN_SKIP","open","defaultProps","mapDispatchToProps","openable","stopPropagation","clip","onReplay","onRerecord","onPlayingStatusUpdate","setIsPlaying","showSentenceTooltip","setShowSentenceTooltip","audioRef","toggleIsPlaying","nextIsPlaying","pause","currentTime","preload","onEnded","recording","theme","onMouseEnter","onMouseLeave","API_PATH","location","origin","fetch","headers","body","err","message","additionalInfo","level","ERROR","makeRequest","INFO","DEBUG","WARNING","NavigationPrompt","default","RecordingError","TTSError","NO_VOICE_SYNTHESIS_ERRORS","UnsupportedInfo","NoSentencesAvailable","initialState","clips","clipsPhraseSetKey","registeredUserFirstUpload","recordingStatus","utterThis","rerecordIndex","showPrivacyModal","showDiscardModal","voices","useFixForIOSDevice","isAudioPillPlaying","isUnsupportedPlatform","maxVolume","recordingStartTime","recordingStopTime","handleKeyUprerecording","code","discardRecording","releaseMicrophone","hidden","isRecording","saveRecording","release","processRecording","info","getRecordingError","getRecordingIndex","currentClip","sentence_text","device_data","newClips","TOO_SHORT","TOO_LONG","TOO_QUIET","audioBlob","size","NO_AUDIO","updateVolume","volume","rerecord","SPEAK_BTN_RERECORD","SPEAK_BTN_REPLAY","REPLAY_CLIP","getVoicesForLanguage","normalize","wanted","voice","startsWith","sort","voiceA","voiceB","localeCompare","playSentence","deviceMetadata","textToPlay","languageVoices","installed_voice_languages","NO_VOICE","synth","speechSynthesis","SpeechSynthesisUtterance","pitch","rate","browser_name","notChildVoice","errMsg","log","synthesis_error","speak","resumePlaySentence","resume","stopPlayingSentence","handlePlayTTS","handleRecordClick","init","startRecording","setTimeout","cancelReRecord","handleSkip","removeSentences","revokeObjectURL","upload","hasAgreed","addAchievement","addNotification","addUploads","tallyRecording","refreshUser","saveAccount","privacyAgreed","c","retries","showFirstContributionToast","hasEarnedSessionToast","showFirstStreakToast","challengeEnded","uploadClip","sessionStorage","removeItem","setInviteContributeAchievement","errorMsg","msgToDisplay","confirm","resetState","callback","agreeToTerms","updateUser","toggleDiscardModal","displayError","isLoading","returnSpeakError","resetAndGoHome","history","toLocaleRoute","ROOT","scrollTo","top","behavior","isSamePhraseSet","sentenceTexts","unusedSentences","s","shift","setVolumeCallback","isMicrophoneSupported","isAudioRecordingSupported","recordingIndex","when","onConfirm","onCancel","recordIcon","stopIcon","recordTally","addPill","add","remove","refresh","update","selectors","localeSentences"],"mappings":"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","file":"3.js?id=ee5236a30b4be72a0f5e","sourcesContent":["import * as React from 'react';\nimport { Localized } from '@fluent/react';\n\nimport URLS from '../../urls';\nimport { LocaleLink } from '../locale-helpers';\nimport { getTrackClass } from '../../services/tracker';\nimport { MicIcon, OldPlayIcon, StopIcon, PlayBlueIcon, PauseBlueIcon } from '../ui/icons';\n\nimport './primary-buttons.css';\n\nexport const PrimaryButton = ({\n  className,\n  to,\n  trackClass,\n  ...props\n}: { to?: string; trackClass?: string } & React.ButtonHTMLAttributes<any>) => (\n  <div\n    className={[\n      'primary-button',\n      className,\n      props.disabled ? 'disabled' : '',\n      trackClass ? getTrackClass('fs', trackClass) : '',\n    ].join(' ')}>\n    {to ? (\n      <LocaleLink to={to} {...props} />\n    ) : (\n      <button type=\"button\" {...props} />\n    )}\n    <div className=\"background\" />\n  </div>\n);\n\nexport type RecordingStatus = null | 'waiting' | 'recording';\nexport type PlayTTSStatus = null | 'playing' | 'listen';\n\nexport const PlayTTSButton = ({\n  status,\n  disabled,\n  ...props\n}: {\n  status: PlayTTSStatus;\n  disabled: boolean;\n} & React.ButtonHTMLAttributes<any>) => {\n  const className = `play-tts-button ${status === 'playing' ? 'pause' : 'listen'} ${disabled ? 'disabled' : ''}`;\n\n  return (\n    <>\n      {\n        status === 'playing' && (\n          <button {...props} disabled={disabled} className={className}>\n            <PauseBlueIcon />\n            <Localized id=\"pause-tts\">\n              <span />\n            </Localized>\n          </button>\n        )\n      }\n      {\n        status === 'listen' && (\n          <button {...props} disabled={disabled} className={className}>\n            <PlayBlueIcon />\n            <Localized id=\"play-tts\">\n              <span />\n            </Localized>\n          </button>\n        )\n      }\n    </>\n  )\n}\n\nexport const RecordButton = ({\n  status,\n  disabled,\n  trackClass,\n  ...props\n}: {\n  status: RecordingStatus;\n  disabled: boolean;\n  trackClass?: string;\n} & React.ButtonHTMLAttributes<any>) => (\n  <PrimaryButton\n    className={status === null ? 'stop' : 'record'}\n    {...props}\n    trackClass={trackClass}\n    disabled={status === 'waiting' || disabled}>\n    {status === null && <MicIcon />}\n    {status === 'recording' && <StopIcon />}\n  </PrimaryButton>\n);\n\nexport const RecordLink = (props: any) => (\n  <PrimaryButton className=\"stop\" to={URLS.SPEAK} {...props}>\n    <MicIcon />\n  </PrimaryButton>\n);\n\nexport const Voice = (props: any) => (\n  <PrimaryButton className=\"stop\" {...props} />\n);\n\nexport const PlayButton = ({\n  isPlaying,\n  trackClass,\n  ...props\n}: { isPlaying: boolean; trackClass?: string } & React.ButtonHTMLAttributes<\n  any\n>) => (\n  <PrimaryButton\n    className={isPlaying ? 'stop' : 'play'}\n    trackClass={trackClass}\n    {...props}>\n    {isPlaying ? <StopIcon /> : <OldPlayIcon />}\n  </PrimaryButton>\n);\n","import { getDeviceMetadata } from '../../../../utility';\n\nexport const shouldUseFixForIOSDevice = (): boolean => {\n  const metadata = getDeviceMetadata();\n\n  return metadata.os_name === 'iOS';\n}\n","import { getRecordingDevice, getAudioFormat } from '../../../../utility';\n// import AudioRecorder from 'audio-recorder-polyfill';\nconst AudioRecorder = require('../../../../audio-recorder-polyfill/index.cjs');\n\ninterface BlobEvent extends Event {\n  data: Blob;\n}\n\nexport enum AudioError {\n  NOT_ALLOWED = 'NOT_ALLOWED',\n  NO_MIC = 'NO_MIC',\n  NO_SUPPORT = 'NO_SUPPORT',\n}\n\nexport interface AudioInfo {\n  url: string;\n  blob: Blob;\n  device_default_input_label: string;\n  device_default_input_is_bluetooth: boolean;\n  created_at: string;\n}\n\nexport default class AudioWeb {\n  microphone: MediaStream;\n  analyzerNode: AnalyserNode;\n  audioContext: AudioContext;\n  recorder: any;\n  chunks: any[];\n  frequencyBins: Uint8Array;\n  volumeCallback: Function;\n  jsNode: any;\n  recorderListeners: {\n    start: Function | null;\n    dataavailable: Function | null;\n    stop: Function | null;\n  };\n\n  constructor() {\n    this.recorderListeners = {\n      start: null,\n      dataavailable: null,\n      stop: null,\n    };\n    this.analyze = this.analyze.bind(this);\n  }\n\n  private isReady(): boolean {\n    return !!this.microphone;\n  }\n\n  private getMicrophone(): Promise<MediaStream> {\n    return new Promise(function (res: Function, rej: Function) {\n      function deny(error: MediaStreamError) {\n        rej(\n          ({\n            NotAllowedError: AudioError.NOT_ALLOWED,\n            NotFoundError: AudioError.NO_MIC,\n          } as { [errorName: string]: AudioError })[error.name] || error\n        );\n      }\n      function resolve(stream: MediaStream) {\n        res(stream);\n      }\n\n      if (navigator.mediaDevices?.getUserMedia) {\n        navigator.mediaDevices\n          .getUserMedia({ audio: true })\n          .then(resolve, deny);\n      } else if (navigator.getUserMedia) {\n        navigator.getUserMedia({ audio: true }, resolve, deny);\n      } else if (navigator.webkitGetUserMedia) {\n        navigator.webkitGetUserMedia({ audio: true }, resolve, deny);\n      } else if (navigator.mozGetUserMedia) {\n        navigator.mozGetUserMedia({ audio: true }, resolve, deny);\n      } else {\n        // Browser does not support getUserMedia\n        rej(AudioError.NO_SUPPORT);\n      }\n    });\n  }\n\n  // Check all the browser prefixes for microhpone support.\n  isMicrophoneSupported() {\n    return (\n      navigator.mediaDevices?.getUserMedia ||\n      navigator.getUserMedia ||\n      navigator.webkitGetUserMedia ||\n      navigator.mozGetUserMedia\n    );\n  }\n\n  // Check if audio recording is supported\n  isAudioRecordingSupported() {\n    return (\n      typeof window.MediaRecorder !== 'undefined' &&\n      !window.MediaRecorder.notSupported\n    );\n  }\n\n  private analyze() {\n    this.analyzerNode.getByteFrequencyData(this.frequencyBins);\n\n    if (this.volumeCallback) {\n      this.volumeCallback(Math.max(...this.frequencyBins));\n    }\n  }\n\n  setVolumeCallback(cb: Function) {\n    this.volumeCallback = cb;\n  }\n\n  /**\n   * Initialize the recorder, opening the microphone media stream.\n   *\n   * If microphone access is currently denied, the user is asked to grant\n   * access. Since these permission changes take effect only after a reload,\n   * the page is reloaded if the user decides to do so.\n   *\n   */\n  async init(bufferSize: number = 0) {\n    if (this.isReady()) {\n      return;\n    }\n\n    const microphone = await this.getMicrophone();\n\n    this.microphone = microphone;\n    const audioContext = new (window.AudioContext ||\n      window.webkitAudioContext)();\n    const sourceNode = audioContext.createMediaStreamSource(microphone);\n    const volumeNode = audioContext.createGain();\n    const analyzerNode = audioContext.createAnalyser();\n    const outputNode = audioContext.createMediaStreamDestination();\n\n    // Make sure we're doing mono everywhere.\n    sourceNode.channelCount = 1;\n    volumeNode.channelCount = 1;\n    analyzerNode.channelCount = 1;\n    outputNode.channelCount = 1;\n\n    // Connect the nodes together\n    sourceNode.connect(volumeNode);\n    volumeNode.connect(analyzerNode);\n    analyzerNode.connect(outputNode);\n\n    // and set up the recorder.\n    AudioRecorder.prototype.mimeType = 'audio/wav'\n    this.recorder = new window.MediaRecorder(outputNode.stream);\n\n    // Set up the analyzer node, and allocate an array for its data\n    // FFT size 64 gives us 32 bins. But those bins hold frequencies up to\n    // 22kHz or more, and we only care about lower frequencies which is where\n    // most human voice lies, so we use fewer bins.\n    analyzerNode.fftSize = 128;\n    analyzerNode.smoothingTimeConstant = 0.96;\n    this.frequencyBins = new Uint8Array(analyzerNode.frequencyBinCount);\n\n    // Setup jsNode for audio analysis callbacks.\n    // TODO: `createScriptProcessor` is deprecated, and is a heavy solution for\n    //       what it’s doing (checking recording volume). It should be replaced\n    //       with something lighter, or AudioWorklets once they become more\n    //       widely adopted.\n    this.jsNode = audioContext.createScriptProcessor(bufferSize, 1, 1);\n    this.jsNode.connect(audioContext.destination);\n\n    this.analyzerNode = analyzerNode;\n    this.audioContext = audioContext;\n  }\n\n  start(audioContext: AudioContext, processor: ScriptProcessorNode): Promise<void> {\n    if (!this.isReady()) {\n      console.error('Cannot record audio before microhphone is ready.');\n      return Promise.resolve();\n    }\n\n    return new Promise<void>((res: Function, _rej: Function) => {\n      this.chunks = [];\n      // Remove the old listeners.\n      this.recorder.removeEventListener('start', this.recorderListeners.start);\n      this.recorder.removeEventListener(\n        'dataavailable',\n        this.recorderListeners.dataavailable\n      );\n\n      // Update the stored listeners.\n      this.recorderListeners.start = (_e: Event) => res();\n      this.recorderListeners.dataavailable = (e: BlobEvent) => {\n        this.chunks.push(e.data);\n      };\n\n      // Add the new listeners.\n      this.recorder.addEventListener('start', this.recorderListeners.start);\n      this.recorder.addEventListener(\n        'dataavailable',\n        this.recorderListeners.dataavailable\n      );\n\n      // Finally, start it up.\n      // We want to be able to record up to 60s of audio in a single blob.\n      // Without this argument to start(), Chrome will call dataavailable\n      // very frequently.\n      this.jsNode.onaudioprocess = this.analyze;\n      this.recorder.start(20000, audioContext, processor);\n    });\n  }\n\n  stop(): Promise<AudioInfo> {\n    if (!this.isReady()) {\n      console.error('Cannot stop audio before microphone is ready.');\n      return Promise.reject();\n    }\n\n    return new Promise((res: Function, _rej: Function) => {\n      this.jsNode.onaudioprocess = undefined;\n      this.recorder.removeEventListener('stop', this.recorderListeners.stop);\n      this.recorderListeners.stop = async (_e: Event) => {\n        const blob = new Blob(this.chunks, { type: getAudioFormat() });\n        \n        const recordingDevice = await getRecordingDevice();\n        const device_default_input_label = recordingDevice && recordingDevice.label\n        const device_default_input_is_bluetooth = recordingDevice?.label.toLocaleLowerCase().includes('bluetooth')\n\n        res({\n          url: URL.createObjectURL(blob),\n          blob: blob,\n          device_default_input_label,\n          device_default_input_is_bluetooth,\n          created_at: new Date().toISOString()\n        });\n      };\n      this.recorder.addEventListener('stop', this.recorderListeners.stop);\n      this.recorder.stop();\n    });\n  }\n\n  release() {\n    if (this.microphone) {\n      for (const track of this.microphone.getTracks()) {\n        track.stop();\n      }\n    }\n    this.microphone = null;\n  }\n}\n","let waveEncoder = require('./wave-encoder/index.cjs')\n\nlet AudioContext = window.AudioContext || window.webkitAudioContext\n\nlet createWorker = fn => {\n  let js = fn\n    .toString()\n    .replace(/^(\\(\\)\\s*=>|function\\s*\\(\\))\\s*{/, '')\n    .replace(/}$/, '')\n  let blob = new Blob([js])\n  return new Worker(URL.createObjectURL(blob))\n}\n\nlet error = method => {\n  let event = new Event('error')\n  event.data = new Error('Wrong state for ' + method)\n  return event\n}\n\nlet context\n\n/**\n * Audio Recorder with MediaRecorder API.\n *\n * @example\n * navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {\n *   let recorder = new MediaRecorder(stream)\n * })\n */\nclass MediaRecorder {\n  /**\n   * @param {MediaStream} stream The audio stream to record.\n   */\n  constructor (stream, config = null) {\n    /**\n     * The `MediaStream` passed into the constructor.\n     * @type {MediaStream}\n     */\n    this.stream = stream\n    this.config = config\n    /**\n     * The current state of recording process.\n     * @type {\"inactive\"|\"recording\"|\"paused\"}\n     */\n    this.state = 'inactive'\n\n    this.em = document.createDocumentFragment()\n    this.encoder = createWorker(MediaRecorder.encoder)\n\n    let recorder = this\n    this.encoder.addEventListener('message', e => {\n      let event = new Event('dataavailable')\n      event.data = new Blob([e.data], { type: recorder.mimeType })\n      recorder.em.dispatchEvent(event)\n      if (recorder.state === 'inactive') {\n        recorder.em.dispatchEvent(new Event('stop'))\n      }\n    })\n  }\n\n  /**\n   * Begins recording media.\n   *\n   * @param {number} [timeslice] The milliseconds to record into each `Blob`.\n   *                             If this parameter isn’t included, single `Blob`\n   *                             will be recorded.\n   *\n   * @param {object} [contextExternal]   ios/safari only - Audio context created in user click ctx.\n   * @param {object} [processorExternal] ios/safari only - Script processor created in user click ctx.\n   *\n   * @return {undefined}\n   *\n   * @example\n   * recordButton.addEventListener('click', () => {\n   *   recorder.start()\n   * })\n   */\n  start (timeslice, contextExternal, processorExternal) {\n    // trying to fix no_wav_data_length error using this example https://github.com/ai/audio-recorder-polyfill/compare/main...kaliatech:issue-4-ios-safari-hack?expand=1\n    // from this thread https://github.com/ai/audio-recorder-polyfill/issues/4\n    if (this.state !== 'inactive') {\n      return this.em.dispatchEvent(error('start'))\n    }\n\n    this.state = 'recording'\n\n    if (contextExternal) {\n      context = contextExternal\n    } else {\n      context = new AudioContext(this.config)\n    }\n\n    this.clone = this.stream.clone()\n    this.input = context.createMediaStreamSource(this.clone)\n\n    if (processorExternal) {\n      this.processor = processorExternal\n    } else {\n      this.processor = context.createScriptProcessor(2048, 1, 1)\n    }\n\n    this.encoder.postMessage(['init', context.sampleRate])\n\n    this.processor.onaudioprocess = e => {\n      if (this.state === 'recording') {\n        this.encoder.postMessage(['encode', e.inputBuffer.getChannelData(0)])\n      }\n    }\n\n    this.input.connect(this.processor)\n    this.processor.connect(context.destination)\n\n    this.em.dispatchEvent(new Event('start'))\n\n    if (timeslice) {\n      this.slicing = setInterval(() => {\n        if (this.state === 'recording') this.requestData()\n      }, timeslice)\n    }\n\n    return undefined\n  }\n\n  /**\n   * Stop media capture and raise `dataavailable` event with recorded data.\n   *\n   * @return {undefined}\n   *\n   * @example\n   * finishButton.addEventListener('click', () => {\n   *   recorder.stop()\n   * })\n   */\n  stop () {\n    if (this.state === 'inactive') {\n      return this.em.dispatchEvent(error('stop'))\n    }\n\n    this.requestData()\n    this.state = 'inactive'\n    this.clone.getTracks().forEach(track => {\n      track.stop()\n    })\n    this.processor.disconnect()\n    this.input.disconnect()\n    return clearInterval(this.slicing)\n  }\n\n  /**\n   * Pauses recording of media streams.\n   *\n   * @return {undefined}\n   *\n   * @example\n   * pauseButton.addEventListener('click', () => {\n   *   recorder.pause()\n   * })\n   */\n  pause () {\n    if (this.state !== 'recording') {\n      return this.em.dispatchEvent(error('pause'))\n    }\n\n    this.state = 'paused'\n    return this.em.dispatchEvent(new Event('pause'))\n  }\n\n  /**\n   * Resumes media recording when it has been previously paused.\n   *\n   * @return {undefined}\n   *\n   * @example\n   * resumeButton.addEventListener('click', () => {\n   *   recorder.resume()\n   * })\n   */\n  resume () {\n    if (this.state !== 'paused') {\n      return this.em.dispatchEvent(error('resume'))\n    }\n\n    this.state = 'recording'\n    return this.em.dispatchEvent(new Event('resume'))\n  }\n\n  /**\n   * Raise a `dataavailable` event containing the captured media.\n   *\n   * @return {undefined}\n   *\n   * @example\n   * this.on('nextData', () => {\n   *   recorder.requestData()\n   * })\n   */\n  requestData () {\n    if (this.state === 'inactive') {\n      return this.em.dispatchEvent(error('requestData'))\n    }\n\n    return this.encoder.postMessage(['dump', context.sampleRate])\n  }\n\n  /**\n   * Add listener for specified event type.\n   *\n   * @param {\"start\"|\"stop\"|\"pause\"|\"resume\"|\"dataavailable\"|\"error\"}\n   * type Event type.\n   * @param {function} listener The listener function.\n   *\n   * @return {undefined}\n   *\n   * @example\n   * recorder.addEventListener('dataavailable', e => {\n   *   audio.src = URL.createObjectURL(e.data)\n   * })\n   */\n  addEventListener (...args) {\n    this.em.addEventListener(...args)\n  }\n\n  /**\n   * Remove event listener.\n   *\n   * @param {\"start\"|\"stop\"|\"pause\"|\"resume\"|\"dataavailable\"|\"error\"}\n   * type Event type.\n   * @param {function} listener The same function used in `addEventListener`.\n   *\n   * @return {undefined}\n   */\n  removeEventListener (...args) {\n    this.em.removeEventListener(...args)\n  }\n\n  /**\n   * Calls each of the listeners registered for a given event.\n   *\n   * @param {Event} event The event object.\n   *\n   * @return {boolean} Is event was no canceled by any listener.\n   */\n  dispatchEvent (...args) {\n    this.em.dispatchEvent(...args)\n  }\n}\n\n/**\n * The MIME type that is being used for recording.\n * @type {string}\n */\nMediaRecorder.prototype.mimeType = 'audio/wav'\n\n/**\n * Returns `true` if the MIME type specified is one the polyfill can record.\n *\n * This polyfill supports `audio/wav` and `audio/mpeg`.\n *\n * @param {string} mimeType The mimeType to check.\n *\n * @return {boolean} `true` on `audio/wav` and `audio/mpeg` MIME type.\n */\nMediaRecorder.isTypeSupported = mimeType => {\n  return MediaRecorder.prototype.mimeType === mimeType\n}\n\n/**\n * `true` if MediaRecorder can not be polyfilled in the current browser.\n * @type {boolean}\n *\n * @example\n * if (MediaRecorder.notSupported) {\n *   showWarning('Audio recording is not supported in this browser')\n * }\n */\nMediaRecorder.notSupported = !navigator.mediaDevices || !AudioContext\n\n/**\n * Converts RAW audio buffer to compressed audio files.\n * It will be loaded to Web Worker.\n * By default, WAVE encoder will be used.\n * @type {function}\n *\n * @example\n * MediaRecorder.prototype.mimeType = 'audio/ogg'\n * MediaRecorder.encoder = oggEncoder\n */\nMediaRecorder.encoder = waveEncoder\n\nmodule.exports = MediaRecorder\n","// Copied from https://github.com/chris-rudmin/Recorderjs\n\nmodule.exports = () => {\n  let BYTES_PER_SAMPLE = 2\n\n  let recorded = []\n\n  function encode (buffer) {\n    let length = buffer.length\n    let data = new Uint8Array(length * BYTES_PER_SAMPLE)\n    for (let i = 0; i < length; i++) {\n      let index = i * BYTES_PER_SAMPLE\n      let sample = buffer[i]\n      if (sample > 1) {\n        sample = 1\n      } else if (sample < -1) {\n        sample = -1\n      }\n      sample = sample * 32768\n      data[index] = sample\n      data[index + 1] = sample >> 8\n    }\n    recorded.push(data)\n  }\n\n  function dump (sampleRate) {\n    let bufferLength = recorded.length ? recorded[0].length : 0\n    let length = recorded.length * bufferLength\n    let wav = new Uint8Array(44 + length)\n    let view = new DataView(wav.buffer)\n\n    // RIFF identifier 'RIFF'\n    view.setUint32(0, 1380533830, false)\n    // file length minus RIFF identifier length and file description length\n    view.setUint32(4, 36 + length, true)\n    // RIFF type 'WAVE'\n    view.setUint32(8, 1463899717, false)\n    // format chunk identifier 'fmt '\n    view.setUint32(12, 1718449184, false)\n    // format chunk length\n    view.setUint32(16, 16, true)\n    // sample format (raw)\n    view.setUint16(20, 1, true)\n    // channel count\n    view.setUint16(22, 1, true)\n    // sample rate\n    view.setUint32(24, sampleRate, true)\n    // byte rate (sample rate * block align)\n    view.setUint32(28, sampleRate * BYTES_PER_SAMPLE, true)\n    // block align (channel count * bytes per sample)\n    view.setUint16(32, BYTES_PER_SAMPLE, true)\n    // bits per sample\n    view.setUint16(34, 8 * BYTES_PER_SAMPLE, true)\n    // data chunk identifier 'data'\n    view.setUint32(36, 1684108385, false)\n    // data chunk length\n    view.setUint32(40, length, true)\n\n    // eslint-disable-next-line unicorn/no-for-loop\n    for (let i = 0; i < recorded.length; i++) {\n      wav.set(recorded[i], i * bufferLength + 44)\n    }\n\n    recorded = []\n    postMessage(wav.buffer, [wav.buffer])\n  }\n\n  onmessage = e => {\n    if (e.data[0] === 'encode') {\n      encode(e.data[1])\n    } else if (e.data[0] === 'dump') {\n      dump(e.data[1])\n    }\n  }\n}\n","module.exports = __webpack_public_path__ + \"f13444f746149609e3841d85502eeb75.svg\";","module.exports = __webpack_public_path__ + \"93c40243e5dfa5d2315e217e32beea14.svg\";","module.exports = __webpack_public_path__ + \"387704371ed51b5c8bfea8617355457d.svg\";","module.exports = __webpack_public_path__ + \"564b24d39e22c24825ae3b2f2de0a6b1.svg\";","module.exports = __webpack_public_path__ + \"489ffff270061d6591e47bf88c37056f.svg\";","module.exports = __webpack_public_path__ + \"c398dfac2509f7d315c1b2dadb0d0205.svg\";","import * as React from 'react';\nimport {\n  Localized,\n  withLocalization,\n  WithLocalizationProps,\n} from '@fluent/react';\nimport URLS from '../urls';\nimport Modal from './modal/modal';\nimport './terms-modal.css';\n\nconst TermsModal = ({\n  getString,\n  onAgree,\n  onDisagree,\n}: { onAgree: () => any; onDisagree: () => any } & WithLocalizationProps) => (\n  <Localized\n    id=\"review-terms\"\n    elems={{\n      termsLink: <a href={URLS.TERMS} target=\"_blank\" />,\n      privacyLink: <a href={URLS.PRIVACY} target=\"_blank\" />\n    }}>\n    <Modal\n      innerClassName=\"terms-modal\"\n      titleClassName=\"title\"\n      contentClassName=\"content\"\n      onRequestClose={onDisagree}\n      buttons={{\n        [getString('terms-agree')]: {\n          handler: onAgree,\n          variant: 'primary'\n        },\n        [getString('terms-disagree')]: {\n          handler: onDisagree,\n          variant: 'secondary'\n        },\n      }}\n    />\n  </Localized>\n);\nexport default withLocalization(TermsModal);\n","import * as React from 'react';\n\nimport ShareButtons from '../share-buttons/share-buttons';\nimport { LinkedInIcon } from '../ui/icons';\nimport URLS from '../../urls';\n\ninterface Props {\n  shareTextId?: string;\n  reverse?: boolean\n  color?: string\n}\n\nimport './share-buttons-extended.css'\n\nfunction ShareButtonsExtended({ shareTextId, reverse, color }: Props) {\n  return (\n    <div className=\"share-buttons-container\">\n      <a\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        href={URLS.LINKEDIN + encodeURIComponent(URLS.HTTP_ROOT)}\n      >\n        <LinkedInIcon color={color} />\n      </a>\n\n      <ShareButtons\n        {...{ shareTextId, reverse, color }}\n      />\n    </div>\n  )\n}\n\nexport default ShareButtonsExtended\n","import { Localized } from '@fluent/react';\nimport * as React from 'react';\nimport Modal from '../modal/modal';\nimport ShareButtonsExtended from '../share-buttons-extended/share-buttons-extended';\n\nimport './share-modal.css';\n\nexport default ({\n  shareTextId,\n  ...props\n}: {\n  onRequestClose: () => any;\n  shareTextId?: string;\n}) => (\n  <Modal {...props} innerClassName=\"share-modal-container\">\n    <div className=\"share-modal\">\n      <div className=\"header\">\n        <img\n          src={require('./notifications-yubi.svg')}\n          alt=\"Notifications yubi\"\n        />\n      </div>\n\n      <div className=\"content\">\n        <Localized id=\"good-fit-for-ensemble\">\n          <h1 />\n        </Localized>\n        <Localized id=\"share-with-family\">\n          <p />\n        </Localized>\n\n        <div className=\"share-buttons-wrapper\">\n          <ShareButtonsExtended {...{ shareTextId }} />\n        </div>\n      </div>\n    </div>\n  </Modal>\n);\n","import { Localized } from '@fluent/react';\nimport * as React from 'react';\nimport { useState } from 'react';\nimport { useAPI } from '../../../../hooks/store-hooks';\nimport Modal, { ModalProps } from '../../../modal/modal';\nimport { ArrowLeft } from '../../../ui/icons';\nimport { Button, Checkbox } from '../../../ui/ui';\n\nimport './report.css';\n\nexport interface ReportModalProps extends ModalProps {\n  kind: 'clip' | 'sentence';\n  id: string;\n  reasons: string[];\n  onSubmitted: () => any;\n}\n\nconst CheckboxRow = ({ children, title, ...props }: any) => (\n  <div className=\"checkbox-row\">\n    <label>\n      <Localized id={title}>\n        <div className=\"reason-title\" />\n      </Localized>\n      <Checkbox {...(props as any)} />\n    </label>\n    <div className=\"detail\">{children}</div>\n  </div>\n);\n\nexport function ReportModal({\n  kind,\n  id,\n  reasons,\n  onSubmitted,\n  ...props\n}: ReportModalProps) {\n  const api = useAPI();\n  const [selectedReasons, setSelectedReasons] = useState<{\n    [key: string]: boolean;\n  }>({});\n  const [otherText, setOtherText] = useState<string>(null);\n  const [submitStatus, setSubmitStatus] = useState<\n    null | 'submitting' | 'submitted'\n  >(null);\n\n  if (submitStatus == 'submitted') {\n    return (\n      <Modal {...props} innerClassName=\"report-success-modal\">\n        <img className=\"check\" src={require('./success.svg')} alt=\"Success\" />\n        <Localized id=\"success\">\n          <h1 />\n        </Localized>\n        <Localized id=\"report-success\">\n          <h2 />\n        </Localized>\n        <Localized id=\"continue\">\n          <Button outline rounded onClick={props.onRequestClose} />\n        </Localized>\n      </Modal>\n    );\n  }\n\n  return (\n    <Modal {...props} innerClassName=\"report-modal\">\n      <Localized id=\"report-title\">\n        <h1 />\n      </Localized>\n      <Localized id=\"report-ask\">\n        <h2 />\n      </Localized>\n      <div>\n        {reasons.map(reason => (\n          <CheckboxRow\n            key={reason}\n            title={'report-' + reason}\n            value={selectedReasons[reason]}\n            onChange={(e: any) =>\n              setSelectedReasons({\n                ...selectedReasons,\n                [reason]: e.target.checked,\n              })\n            }>\n            <Localized id={['report', reason, 'detail'].join('-')}>\n              <p />\n            </Localized>\n          </CheckboxRow>\n        ))}\n        <CheckboxRow\n          title=\"other\"\n          onChange={(e: any) => setOtherText(e.target.checked ? '' : null)}\n          value={otherText != null}>\n          {otherText != null && (\n            <Localized id=\"report-other-comment\" attrs={{ placeholder: true }}>\n              <textarea\n                value={otherText}\n                onChange={(e: any) => setOtherText(e.target.value)}\n                style={{ marginTop: 8 }}\n              />\n            </Localized>\n          )}\n        </CheckboxRow>\n      </div>\n      <Button\n        rounded\n        disabled={\n          submitStatus == 'submitting' ||\n          (otherText && otherText.trim() == '') ||\n          (!Object.values(selectedReasons).some(Boolean) && otherText == null)\n        }\n        onClick={() => {\n          api.report({\n            kind,\n            id,\n            reasons: Object.entries(selectedReasons)\n              .filter(([_key, value]) => value)\n              .map(([key]) => key)\n              .concat(otherText || []),\n          });\n          setSubmitStatus('submitted');\n          onSubmitted();\n        }}>\n        <Localized id=\"report\">\n          <span />\n        </Localized>\n        <ArrowLeft />\n      </Button>\n    </Modal>\n  );\n}\n\nexport const ReportButton = (props: React.HTMLProps<HTMLButtonElement>) => (\n  <Button outline rounded className=\"open-report-button\" {...props}>\n    <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n      <defs>\n        <path\n          id=\"flag-a\"\n          d=\"M20.4 2.1c-.4-.2-.8-.1-1.1.2 0 0-.9.7-3.3.7-1.3 0-2.4-.5-3.6-.9C11.1 1.5 9.7 1 8 1 4.8 1 3.5 2.1 3.3 2.3c-.2.2-.3.4-.3.7v19c0 .6.4 1 1 1s1-.4 1-1v-6.5c.4-.2 1.4-.5 3-.5 1.3 0 2.4.5 3.6.9 1.3.5 2.7 1.1 4.4 1.1 3.2 0 4.5-1.1 4.7-1.3.2-.2.3-.4.3-.7V3c0-.4-.2-.7-.6-.9zM19 14.5c-.4.2-1.4.5-3 .5-1.3 0-2.4-.5-3.6-.9C11.1 13.5 9.7 13 8 13c-1.3 0-2.3.2-3 .4V3.5c.4-.2 1.4-.5 3-.5 1.3 0 2.4.5 3.6.9C12.9 4.5 14.3 5 16 5c1.3 0 2.3-.2 3-.4v9.9z\"\n        />\n      </defs>\n      <g fill=\"none\" fillRule=\"evenodd\">\n        <mask id=\"flag-b\" fill=\"#fff\">\n          <use xlinkHref=\"#flag-a\" />\n        </mask>\n        <use fill=\"#000\" fillRule=\"nonzero\" xlinkHref=\"#flag-a\" />\n        <g fill=\"#4A4A4A\" mask=\"url(#flag-b)\">\n          <path d=\"M0 0h24v24H0z\" />\n        </g>\n      </g>\n    </svg>\n\n    <Localized id=\"report\">\n      <span />\n    </Localized>\n  </Button>\n);\n","import {\n  Localized,\n  withLocalization,\n  WithLocalizationProps,\n} from '@fluent/react';\nimport { authPath } from '../../../utility';\nimport * as React from 'react';\nimport { useAccount } from '../../../hooks/store-hooks';\nimport { RouteComponentProps, withRouter } from 'react-router';\n\nimport './success.css';\n\nfunction Success({\n  onReset,\n  getString,\n}: {\n  type: 'speak' | 'listen';\n  onReset: () => any;\n} & WithLocalizationProps &\n  RouteComponentProps) {\n  const account = useAccount();\n\n  const hasAccount = Boolean(account);\n\n  const handleAddMoreSpeech = () => {\n    onReset();\n  };\n\n  const handleCreateAccount = () => {\n    const link = document.createElement('a');\n    link.href = authPath('/signup');\n    link.click();\n  };\n\n  return (\n    <div className=\"contribution-success\">\n      <img className=\"image\" src={require('./images/users_celebrating.svg')} />\n\n      <div className=\"success-title-container\">\n        <Localized id=\"clips-submitted-title\">\n          <h2 className=\"title\" />\n        </Localized>\n      </div>\n\n      <div className=\"main\">\n        <button className=\"btn btn-primary\" onClick={handleAddMoreSpeech}>\n          {getString(hasAccount ? 'continue-recording' : 'add-more-speech')}\n        </button>\n\n        {!hasAccount && (\n          <div className=\"register-container\">\n            <Localized id=\"clips-submitted-register\">\n              <p className=\"regular-text\" />\n            </Localized>\n            <button className=\"btn btn-secondary\" onClick={handleCreateAccount}>\n              {getString('create-account')}\n            </button>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n\nexport default withRouter(withLocalization(Success));\n","const RATIO = window.devicePixelRatio;\nconst WIDTH = RATIO * 320;\nconst HEIGHT = RATIO * 100;\nconst IDLE_AMPLITUDE = 0.1;\nconst PLAY_AMPLITUDE = 0.6;\n\nconst LOW_FPS = 30;\nconst DISABLE_ANIMATION_LOW_FPS_THRESHOLD = 3;\n\nclass Curve {\n  baseAmplitude: number;\n\n  private amplitude: number;\n  private color: number[];\n  private ctx: CanvasRenderingContext2D;\n  private openClass: number;\n  private speed: number;\n  private seed: number;\n  private tick: number;\n\n  constructor(args: {\n    color: number[];\n    ctx: CanvasRenderingContext2D;\n    speed: number;\n    baseAmplitude: number;\n  }) {\n    Object.assign(this, args);\n    this.tick = 0;\n\n    this.respawn();\n  }\n\n  respawn() {\n    this.amplitude = 0.3 + Math.random() * 0.7;\n    this.seed = Math.random();\n    this.openClass = (5 + Math.random() * 4) | 0;\n  }\n\n  private equation(i: number) {\n    const y =\n      ((-1 *\n        Math.abs(Math.sin(this.tick)) *\n        this.baseAmplitude *\n        this.amplitude *\n        HEIGHT) /\n        2) *\n      (1 / (1 + this.openClass * i ** 2) ** 2);\n    if (Math.abs(y) < 0.001) {\n      this.respawn();\n    }\n    return y;\n  }\n\n  private _draw(direction: 1 | -1) {\n    this.tick += this.speed * (1 - 0.5 * Math.sin(this.seed * Math.PI));\n\n    const ctx = this.ctx;\n    ctx.beginPath();\n\n    const xBase = WIDTH / 2 + (-WIDTH / 4 + this.seed * (WIDTH / 2));\n    const yBase = HEIGHT / 2;\n\n    let x;\n    let y;\n    let xInit;\n\n    let i = -3;\n    while (i <= 3) {\n      x = xBase + (i * WIDTH) / 4;\n      y = yBase + direction * this.equation(i);\n      xInit = xInit || x;\n      ctx.lineTo(x, y);\n      i += 0.01;\n    }\n\n    const h = Math.abs(this.equation(0));\n    const gradient = ctx.createRadialGradient(\n      xBase,\n      yBase,\n      h * 2,\n      xBase,\n      yBase,\n      h * 0.3\n    );\n    gradient.addColorStop(0, `rgba(${this.color.join(',')},0.1)`);\n    gradient.addColorStop(1, `rgba(${this.color.join(',')},0.05)`);\n\n    ctx.fillStyle = gradient;\n\n    ctx.lineTo(xInit, yBase);\n    ctx.closePath();\n\n    ctx.fill();\n  }\n\n  draw() {\n    this._draw(-1);\n    this._draw(1);\n  }\n}\n\nlet lastFPSCheckAt = 0;\nlet lowFPSCount = 0;\nlet framesInLastSecond: number[] = [];\n\nexport default class Wave {\n  private amplitude = IDLE_AMPLITUDE;\n  private colors = [\n    [255, 148, 124],\n    [255, 212, 82],\n    [103, 214, 211],\n    [47, 132, 211]\n  ];\n  private ctx: CanvasRenderingContext2D;\n  private curves: Curve[];\n  private shouldDraw = false;\n  private speed: number;\n\n  constructor(canvas: HTMLCanvasElement) {\n    this.speed = 0.1;\n\n    canvas.width = WIDTH;\n    canvas.height = HEIGHT;\n    canvas.style.width = `${WIDTH / RATIO}px`;\n    canvas.style.height = `${HEIGHT / RATIO}px`;\n\n    this.ctx = canvas.getContext('2d');\n\n    this.curves = this.colors\n      .reduce((arr, color) => [...arr, color, color], [])\n      .map(\n        color =>\n          new Curve({\n            color: color as any,\n            ctx: this.ctx,\n            speed: this.speed,\n            baseAmplitude: 2 * IDLE_AMPLITUDE,\n          })\n      );\n\n    this.draw();\n  }\n\n  private clear() {\n    this.ctx.globalCompositeOperation = 'destination-out';\n    this.ctx.fillRect(0, 0, WIDTH, HEIGHT);\n    this.ctx.globalCompositeOperation = 'lighter';\n  }\n\n  private draw() {\n    if (lowFPSCount >= DISABLE_ANIMATION_LOW_FPS_THRESHOLD) {\n      return;\n    }\n\n    this.clear();\n\n    const baseAmplitude =\n      this.curves[0].baseAmplitude * 0.9 + this.amplitude * 0.1;\n    for (const curve of this.curves) {\n      curve.baseAmplitude = baseAmplitude;\n      curve.draw();\n    }\n\n    if (this.shouldDraw || Math.abs(baseAmplitude - this.amplitude) > 0.01) {\n      requestAnimationFrame(this.draw.bind(this));\n    }\n\n    const now = performance.now();\n    framesInLastSecond.push(now);\n    if (now - lastFPSCheckAt < 1000) return;\n    lastFPSCheckAt = now;\n    const index = framesInLastSecond\n      .slice()\n      .reverse()\n      .findIndex(t => now - t > 1000);\n    if (index === -1) {\n      return;\n    }\n\n    framesInLastSecond = framesInLastSecond.slice(\n      framesInLastSecond.length - index - 1\n    );\n    if (framesInLastSecond.length < LOW_FPS) {\n      lowFPSCount++;\n    }\n  }\n\n  play() {\n    this.amplitude = PLAY_AMPLITUDE;\n    this.shouldDraw = true;\n    this.draw();\n  }\n\n  idle() {\n    this.shouldDraw = false;\n    this.amplitude = IDLE_AMPLITUDE;\n    framesInLastSecond = [];\n  }\n}\n","import { Localized } from '@fluent/react';\nimport * as React from 'react';\nimport Modal from '../modal/modal';\nimport { RECORDINGS_GOAL } from '../../constants';\nimport ShareButtonsExtended from '../share-buttons-extended/share-buttons-extended';\n\nimport './main.css';\n\nconst EMAIL = 'ensemble@voiceitt.com';\n\nexport default ({\n  shareTextId,\n  ...props\n}: {\n  onRequestClose: () => any;\n  shareTextId?: string;\n}) => (\n  <Modal {...props} innerClassName=\"modal-container\">\n    <div className=\"modal\">\n      <div className=\"header\">\n        <Localized id=\"thousand-modal-congratulations\">\n          <div />\n        </Localized>\n        <div>\n          <img src={require('./award.svg')} alt=\"Award\" />\n        </div>\n      </div>\n\n      <div className=\"content\">\n        <Localized\n          id=\"thousand-modal-content-header\"\n          vars={{ count: RECORDINGS_GOAL }}>\n          <h1 />\n        </Localized>\n        <Localized id=\"thousand-modal-content-middle\">\n          <p />\n        </Localized>\n        <Localized\n          id=\"thousand-modal-content-middle-second-line\"\n          vars={{ email: EMAIL }}\n          elems={{\n            emailLink: (\n              <a className=\"email\" target=\"_blank\" href={`mailto:${EMAIL}`} />\n            ),\n          }}>\n          <p />\n        </Localized>\n        <Localized id=\"thousand-modal-content-bottom\">\n          <p />\n        </Localized>\n        <div>\n          <ShareButtonsExtended {...{ shareTextId, color: 'blue' }} />\n        </div>\n      </div>\n    </div>\n  </Modal>\n);\n","import * as React from 'react';\nimport {\n  Localized,\n  withLocalization,\n  WithLocalizationProps,\n} from '@fluent/react';\n\nimport { useAction, usePhraseSet } from '../../../../hooks/store-hooks';\nimport { PhraseSet } from '../../../../stores/phrase_set';\nimport { Button } from '../../../ui/ui';\nimport Modal, { ModalProps } from '../../../modal/modal';\n\nimport './style.css';\nimport { useEffect, useState } from 'react';\n\nexport interface ReportModalProps extends ModalProps {}\n\nfunction PhraseSetPicker({ onRequestClose, ...props }: ReportModalProps) {\n  const phraseSetState = usePhraseSet();\n\n  const canClose = !!phraseSetState.current;\n\n  const setPhraseSet = useAction(PhraseSet.actions.set);\n\n  // Local state for selection\n  const [selectedLanguage, setSelectedLanguage] = useState<string | null>(null);\n  const [selectedPhraseKey, setSelectedPhraseKey] = useState<string | null>(\n    null\n  );\n\n  // Autoselect language / phrase set\n  useEffect(() => {\n    const langs = phraseSetState.languages;\n    if (!langs || langs.length === 0) return;\n\n    // Only one language -> select it\n    if (langs.length === 1) {\n      setSelectedLanguage(langs[0]);\n    }\n\n    // Restore from current?.key\n    if (phraseSetState.current?.key) {\n      setSelectedLanguage(phraseSetState.current.language);\n      setSelectedPhraseKey(phraseSetState.current.key);\n    }\n  }, [phraseSetState.languages, phraseSetState.current]);\n\n  // Auto-select phrase if only one option for the language\n  useEffect(() => {\n    if (selectedLanguage) {\n      const sets = phraseSetState.byLanguage[selectedLanguage] || [];\n      if (sets.length === 1) {\n        setSelectedPhraseKey(sets[0].key);\n      }\n    }\n  }, [selectedLanguage, phraseSetState.byLanguage]);\n\n  const handleSave = async () => {\n    if (selectedPhraseKey) {\n      await setPhraseSet(selectedPhraseKey);\n      onRequestClose?.();\n    }\n  };\n\n  // Each language is named in itself, not in the current UI locale. CLDR keeps\n  // the endonym's own orthography (\"espanol\", \"francais\" are lowercase), which\n  // reads as a typo in a list, so the first letter is raised for display.\n  // Scripts without case are unaffected.\n  const getNameForLanguage = (lang: string) => {\n    const displayNames = new (Intl as any).DisplayNames([lang], {\n      type: 'language',\n    });\n    const name = displayNames.of(lang);\n    if (!name) return lang.toUpperCase();\n    return name.charAt(0).toLocaleUpperCase(lang) + name.slice(1);\n  };\n\n  return (\n    <Modal\n      onRequestClose={canClose ? onRequestClose : undefined}\n      innerClassName=\"select-phrase-set-modal\"\n      {...props}>\n      <div className=\"select-phrase-set-modal-content\">\n        <Localized id=\"select-phrase-set\">\n          <h2 className=\"modal-title\" />\n        </Localized>\n\n        {/* Language selector */}\n        {phraseSetState.languages.length > 1 && (\n          <div className=\"language-list\">\n            {phraseSetState.languages.map(lang => (\n              <button\n                key={lang}\n                className={`language-item ${\n                  selectedLanguage === lang ? 'selected' : ''\n                }`}\n                onClick={() => {\n                  setSelectedLanguage(lang);\n                  setSelectedPhraseKey(null); // reset phrase selection\n                }}>\n                {getNameForLanguage(lang)}\n              </button>\n            ))}\n          </div>\n        )}\n\n        {/* Phrase set selector */}\n        {selectedLanguage && (\n          <div className=\"phrase-set-list\">\n            {(phraseSetState.byLanguage[selectedLanguage] || []).map(ps => (\n              <button\n                key={ps.key}\n                className={`phrase-set-item ${\n                  selectedPhraseKey === ps.key ? 'selected' : ''\n                }`}\n                onClick={() => setSelectedPhraseKey(ps.key)}>\n                {ps.display_name || ps.name}\n              </button>\n            ))}\n          </div>\n        )}\n\n        {/* Save button */}\n        <Localized id=\"select-phrase-set-save\">\n          <Button\n            rounded\n            disabled={!selectedPhraseKey}\n            onClick={handleSave}\n            className=\"save-phrase-set-button\"\n          />\n        </Localized>\n      </div>\n    </Modal>\n  );\n}\n\nexport const PhraseSetPickerModal = PhraseSetPicker;\n\ninterface PhraseSetSelectorButtonProps\n  extends React.HTMLProps<HTMLButtonElement> {\n  title?: string;\n}\n\nconst PhraseSetSelector = ({\n  title,\n  lang,\n  getString,\n  ...props\n}: PhraseSetSelectorButtonProps & WithLocalizationProps) => (\n  <Button outline rounded className=\"phrase-set-select-button\" {...props}>\n    <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" className=\"phrase-set-icon\">\n      <g>\n        <path\n          fill=\"#000\"\n          d=\"M 12 0 C 5.371094 0 0 5.371094 0 12 C 0 18.625 5.371094 24 12 24 C 18.628906 24 24 18.625 24 12 C 23.992188 5.375 18.625 0.0078125 12 0 Z M 23.011719 11.511719 L 18.101562 11.511719 C 18.070312 9.726562 17.734375 7.957031 17.113281 6.28125 C 18.148438 5.792969 19.113281 5.15625 19.96875 4.394531 C 21.8125 6.316406 22.894531 8.847656 23.011719 11.511719 Z M 12.488281 1.402344 C 13.226562 2.011719 13.890625 2.710938 14.460938 3.480469 C 14.992188 4.191406 15.449219 4.957031 15.820312 5.765625 C 14.75 6.160156 13.628906 6.386719 12.488281 6.4375 Z M 13.636719 1.097656 C 15.726562 1.410156 17.679688 2.320312 19.261719 3.714844 C 18.5 4.386719 17.652344 4.945312 16.734375 5.382812 C 15.996094 3.761719 14.945312 2.304688 13.636719 1.097656 Z M 9.585938 3.480469 C 10.144531 2.726562 10.792969 2.042969 11.511719 1.441406 L 11.511719 6.4375 C 10.386719 6.390625 9.277344 6.167969 8.222656 5.78125 C 8.59375 4.96875 9.054688 4.199219 9.585938 3.480469 Z M 7.304688 5.402344 C 6.375 4.964844 5.507812 4.394531 4.734375 3.71875 C 6.335938 2.308594 8.308594 1.394531 10.417969 1.089844 C 9.105469 2.304688 8.046875 3.773438 7.304688 5.402344 Z M 11.511719 7.414062 L 11.511719 11.511719 L 6.921875 11.511719 C 6.960938 9.863281 7.269531 8.230469 7.84375 6.683594 C 9.023438 7.117188 10.257812 7.363281 11.511719 7.414062 Z M 11.511719 12.488281 L 11.511719 16.519531 C 10.289062 16.566406 9.078125 16.800781 7.925781 17.214844 C 7.316406 15.710938 6.976562 14.109375 6.921875 12.488281 Z M 11.511719 17.496094 L 11.511719 22.457031 C 10.203125 21.207031 9.121094 19.734375 8.328125 18.109375 C 9.351562 17.746094 10.425781 17.539062 11.511719 17.492188 Z M 10.582031 22.929688 C 8.394531 22.648438 6.347656 21.714844 4.699219 20.25 C 5.507812 19.53125 6.417969 18.9375 7.398438 18.484375 C 8.195312 20.136719 9.273438 21.640625 10.582031 22.929688 Z M 12.488281 22.503906 L 12.488281 17.492188 C 13.589844 17.539062 14.679688 17.753906 15.714844 18.125 C 14.910156 19.765625 13.816406 21.246094 12.488281 22.503906 Z M 16.640625 18.503906 C 17.605469 18.953125 18.503906 19.542969 19.300781 20.25 C 17.667969 21.699219 15.640625 22.632812 13.476562 22.921875 C 14.777344 21.640625 15.847656 20.148438 16.640625 18.503906 Z M 12.488281 16.519531 L 12.488281 12.488281 L 17.125 12.488281 C 17.074219 14.117188 16.730469 15.722656 16.117188 17.230469 C 14.949219 16.808594 13.726562 16.566406 12.488281 16.519531 Z M 12.488281 11.511719 L 12.488281 7.414062 C 13.757812 7.363281 15.007812 7.113281 16.195312 6.667969 C 16.773438 8.21875 17.089844 9.855469 17.125 11.511719 Z M 4.03125 4.394531 C 4.898438 5.167969 5.875 5.8125 6.929688 6.304688 C 6.3125 7.972656 5.980469 9.734375 5.945312 11.511719 L 0.988281 11.511719 C 1.105469 8.847656 2.1875 6.320312 4.03125 4.394531 Z M 0.988281 12.488281 L 5.945312 12.488281 C 5.996094 14.238281 6.355469 15.964844 7.007812 17.589844 C 5.90625 18.09375 4.894531 18.761719 3.996094 19.570312 C 2.171875 17.648438 1.105469 15.132812 0.988281 12.488281 Z M 20.003906 19.570312 C 19.117188 18.773438 18.117188 18.109375 17.035156 17.609375 C 17.691406 15.980469 18.054688 14.246094 18.101562 12.488281 L 23.011719 12.488281 C 22.894531 15.132812 21.828125 17.648438 20.003906 19.570312 Z M 20.003906 19.570312 \"\n        />\n      </g>\n    </svg>\n\n    <span className=\"phrase-set-title\">\n      {lang && title\n        ? `${lang.toUpperCase()}: ${title}`\n        : getString('no-phrase-set')}\n    </span>\n  </Button>\n);\n\nexport const PhraseSetSelectorButton = withLocalization(PhraseSetSelector);\n","import {\n  Localized,\n  withLocalization,\n  WithLocalizationProps,\n} from '@fluent/react';\nimport * as React from 'react';\nimport { connect } from 'react-redux';\nimport { isMobile, isTablet } from 'react-device-detect';\nconst { Tooltip } = require('react-tippy');\nimport { Flags } from '../../../stores/flags';\nimport { Locale } from '../../../stores/locale';\nimport StateTree from '../../../stores/tree';\nimport { User } from '../../../stores/user';\nimport { Sentence } from 'common';\nimport {\n  trackListening,\n  trackRecording,\n  getTrackClass,\n} from '../../../services/tracker';\nimport URLS from '../../../urls';\nimport Modal, { ModalProps } from '../../modal/modal';\nimport {\n  ArrowLeft,\n  CheckIcon,\n  KeyboardIcon,\n  ShareIcon,\n  SkipIcon,\n  ExternalLinkIcon,\n} from '../../ui/icons';\nimport { Button, StyledLink } from '../../ui/ui';\nimport { PrimaryButton } from '../../primary-buttons/primary-buttons';\nimport ShareModal from '../../share-modal/share-modal';\nimport { ReportButton, ReportModal, ReportModalProps } from './report/report';\nimport Success from './success';\nimport Wave from './wave';\nimport { isSpaceBarPressed, authPath } from '../../../utility';\nimport {\n  trackGoogleAnalyticsEvent,\n  EVENT_CATEGORIES,\n  EVENT_NAMES,\n} from '../../../services/ga-event-tracker';\n\nimport './contribution.css';\nimport {\n  USAGE_TRACKING_ACTIONS,\n  UsageTrackingActionId,\n  RECORDINGS_GOAL,\n} from '../../../constants';\nimport ThousandRecordingsModal from '../../thousand-recordings-modal/thousand-recordings-modal';\nimport { PhraseSet } from '../../../stores/phrase_set';\nimport {\n  PhraseSetPickerModal,\n  PhraseSetSelectorButton,\n} from './phrase-set-picker';\n\nconst HAS_SEEN_ACCOUNT_MODAL_KEY = 'hasSeenAccountModal2';\nconst HAS_SEEN_THOUSAND_RECORDINGS_MODAL_KEY =\n  'hasSeenThousandRecordingsModal2';\nconst SHORTCUT_RECORD_TOGGLE = '[space-bar]';\nconst SHORTCUT_SUBMIT = 'Enter ↩';\nconst FIRST_N_RECORDINGS_TO_TRIGGER_MODAL = 10;\nconst FIRST_N_RECORDINGS_KEY = 'seenFirstTenRecordingsModal';\n\nconst FirstNRecordingsModal = (\n  props: ModalProps & { handleContinue: Function }\n) => {\n  return (\n    <Modal {...props} innerClassName=\"n-recordings-modal\">\n      <div className=\"n-recordings-modal \">\n        <div className=\"header\" />\n\n        <div className=\"content\">\n          <Localized id=\"thank-you\">\n            <h1 className=\"mb-24\" />\n          </Localized>\n\n          <Localized id=\"our-team\">\n            <p className=\"mb-24\" />\n          </Localized>\n\n          <Localized id=\"continue\">\n            <button\n              className=\"btn btn-primary\"\n              onClick={() => props.handleContinue()}\n            />\n          </Localized>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nconst AccountModal = (props: ModalProps) => {\n  const handleSignIn = () => {\n    const link = document.createElement('a');\n    link.href = '/login';\n    link.click();\n  };\n\n  const handleSignUp = () => {\n    const link = document.createElement('a');\n    link.href = authPath('/signup');\n    link.click();\n  };\n\n  return (\n    <Modal {...props} innerClassName=\"account-modal\">\n      <div className=\"account-modal\">\n        <div className=\"header\" />\n\n        <div className=\"content\">\n          <div>\n            <Localized id=\"get-rewarded-for-your-recordings\">\n              <h1 className=\"mb-24\" />\n            </Localized>\n            <Localized id=\"create-account-to-be-eligible\">\n              <p />\n            </Localized>\n          </div>\n\n          <div>\n            <Localized id=\"sign-up\">\n              <button\n                className=\"btn btn-primary mb-24\"\n                onClick={handleSignUp}\n              />\n            </Localized>\n\n            <Localized\n              id=\"already-have-an-account\"\n              elems={{\n                logInLink: <a className=\"ml-8\" onClick={handleSignIn} />,\n              }}>\n              <p />\n            </Localized>\n          </div>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nexport const SET_COUNT = 5;\n\nexport interface ContributionPillProps {\n  isOpen: boolean;\n  key: any;\n  num: number;\n  onClick: () => any;\n  onShare: () => any;\n  style?: any;\n}\n\ninterface PropsFromState {\n  flags: Flags.State;\n  locale: Locale.State;\n  user: User.State;\n  phrase_set: PhraseSet.State;\n}\n\ninterface PropsFromDispatch {\n  setPhraseSet: typeof PhraseSet.actions.set;\n}\n\ninterface Props\n  extends WithLocalizationProps,\n    PropsFromState,\n    PropsFromDispatch {\n  areClipsUploading?: boolean;\n  skipAutoReset?: boolean;\n  activeIndex: number;\n  errorContent?: any;\n  reportModalProps: Omit<ReportModalProps, 'onSubmitted'>;\n  instruction: (props: {\n    vars: { actionType: string };\n    children: any;\n  }) => React.ReactNode;\n  isFirstSubmit?: boolean;\n  isPlaying: boolean;\n  isTTSPlaying: boolean;\n  isSubmitted: boolean;\n  onBack?: () => any;\n  onReset: () => any;\n  onSkip: () => any;\n  onSubmit?: () => any;\n  primaryButtons: React.ReactNode;\n  playTTSButton: React.ReactNode;\n  pills: ((props: ContributionPillProps) => React.ReactNode)[];\n  sentences: Sentence[];\n  shortcuts: {\n    key: string;\n    label: string;\n    action: () => any;\n    icon?: React.ReactNode;\n  }[];\n  shortcutsDisabled: boolean;\n  type: 'speak' | 'listen';\n  trackAppUsage?: (action: UsageTrackingActionId) => any;\n}\n\ninterface State {\n  selectedPill: number;\n  showAccountModal: boolean;\n  showFirstTenRecordingsModal: boolean;\n  showThousandRecordingsModal: boolean;\n  showReportModal: boolean;\n  showShareModal: boolean;\n  showPhraseSetPickerModal: boolean;\n}\n\nclass ContributionPage extends React.Component<Props, State> {\n  static defaultProps = {\n    isFirstSubmit: false,\n    trackAppUsage: () => {\n      // this is intentional\n    },\n  };\n\n  state: State = {\n    selectedPill: null,\n    showAccountModal: false,\n    showFirstTenRecordingsModal: false,\n    showThousandRecordingsModal: false,\n    showReportModal: false,\n    showShareModal: false,\n    showPhraseSetPickerModal: false,\n  };\n\n  private canvasRef: { current: HTMLCanvasElement | null } = React.createRef();\n  private wave: Wave;\n\n  private get showAccountModalDefault() {\n    const { flags, user } = this.props;\n    return (\n      flags.showAccountConversionModal &&\n      !user.account &&\n      !JSON.parse(localStorage.getItem(HAS_SEEN_ACCOUNT_MODAL_KEY))\n    );\n  }\n\n  private get shouldShowThousandRecordingsModal() {\n    const recordings = this.getClipsCount();\n\n    const modalSeen =\n      localStorage.getItem(\n        `${this.props.user?.account?.client_id}:${HAS_SEEN_THOUSAND_RECORDINGS_MODAL_KEY}`\n      ) === 'true';\n\n    return recordings >= RECORDINGS_GOAL && !modalSeen;\n  }\n\n  componentDidMount() {\n    const phraseSet = this.props.phrase_set.current;\n    if (!phraseSet) {\n      this.setState({ showPhraseSetPickerModal: true });\n    }\n\n    // this.startWaving();\n    window.addEventListener('keydown', this.handleKeyDown);\n\n    // preload account modal images to prevent layout shifting\n    if (this.showAccountModalDefault) {\n      new Image().src = require('./waves.svg');\n      new Image().src = require('./yubi-with-trophy.svg');\n    }\n  }\n\n  componentDidUpdate(prevProps: Props) {\n    this.startWaving();\n\n    const {\n      skipAutoReset,\n      activeIndex,\n      isPlaying,\n      isSubmitted,\n      onReset,\n      user,\n    } = this.props;\n\n    if (activeIndex == 1 && prevProps.activeIndex != activeIndex) {\n      const showAccountModal = this.showAccountModalDefault;\n      this.setState({ showAccountModal });\n      if (showAccountModal) {\n        localStorage.setItem(HAS_SEEN_ACCOUNT_MODAL_KEY, JSON.stringify(true));\n      }\n    }\n\n    if (this.wave) {\n      isPlaying ? this.wave.play() : this.wave.idle();\n    }\n\n    if (\n      isSubmitted &&\n      user.account?.skip_submission_feedback &&\n      !skipAutoReset\n    ) {\n      onReset();\n    }\n\n    if (this.shouldShowThousandRecordingsModal) {\n      this.showThousandRecordingsModal();\n    }\n  }\n\n  componentWillUnmount() {\n    window.removeEventListener('keydown', this.handleKeyDown);\n    if (this.wave) this.wave.idle();\n  }\n\n  private get isLoaded() {\n    return this.props.sentences.length > 0;\n  }\n\n  private get isRegisteredUser() {\n    return Boolean(this.props.user.account);\n  }\n\n  private get isDone() {\n    return this.isLoaded && this.props.activeIndex === -1;\n  }\n\n  private get shortcuts() {\n    return this.props.shortcuts;\n  }\n\n  private startWaving = () => {\n    const canvas = this.canvasRef.current;\n\n    if (this.wave) {\n      if (!canvas) {\n        this.wave.idle();\n        this.wave = null;\n      }\n      return;\n    }\n\n    if (canvas) {\n      this.wave = new Wave(canvas);\n    }\n  };\n\n  private selectPill(i: number) {\n    this.setState({ selectedPill: i });\n  }\n\n  private toggleShareModal = () => {\n    if (this.props.isTTSPlaying) return;\n\n    trackGoogleAnalyticsEvent(\n      EVENT_NAMES.SHARE_RECORDING,\n      EVENT_CATEGORIES.BUTTON_USAGE,\n      'Click on Share button on Speak page'\n    );\n    this.props.trackAppUsage(USAGE_TRACKING_ACTIONS.SPEAK_BTN_SHARE);\n    this.setState({ showShareModal: !this.state.showShareModal });\n  };\n\n  private handleKeyDown = (event: any) => {\n    const {\n      getString,\n      isSubmitted,\n      locale,\n      onReset,\n      onSubmit,\n      type,\n    } = this.props;\n\n    if (this.props.shortcutsDisabled) {\n      return;\n    }\n\n    if (\n      event.ctrlKey ||\n      event.altKey ||\n      event.shiftKey ||\n      event.metaKey ||\n      this.state.showReportModal\n    ) {\n      return;\n    }\n\n    const isEnter = event.key === 'Enter';\n    if (isSubmitted && isEnter) {\n      onReset();\n      return;\n    }\n    if (this.isDone) {\n      if (isEnter && onSubmit) onSubmit();\n      return;\n    }\n\n    const isSpaceWasPressed = isSpaceBarPressed(event);\n    const shortcut = this.shortcuts.find(({ key }) => {\n      const keyString = getString(key).toLowerCase();\n\n      return isSpaceWasPressed\n        ? keyString === SHORTCUT_RECORD_TOGGLE\n        : keyString === event.key;\n    });\n\n    switch (shortcut?.key) {\n      case 'shortcut-record-toggle':\n        trackGoogleAnalyticsEvent(\n          EVENT_NAMES.RERECORD_CLIP,\n          EVENT_CATEGORIES.SHORTCUT_USAGE,\n          'Use record shortcut'\n        );\n        this.props.trackAppUsage(USAGE_TRACKING_ACTIONS.SPEAK_KEY_RECORD_STOP);\n        break;\n      case 'shortcut-skip':\n        trackGoogleAnalyticsEvent(\n          EVENT_NAMES.SKIP_SENTENCE,\n          EVENT_CATEGORIES.SHORTCUT_USAGE,\n          'Use skip shortcut'\n        );\n        this.props.trackAppUsage(USAGE_TRACKING_ACTIONS.SPEAK_KEY_SKIP);\n        break;\n    }\n\n    if (Number(event.key) >= 1 && Number(event.key) <= 5) {\n      trackGoogleAnalyticsEvent(\n        EVENT_NAMES.GO_TO_RECORDING,\n        EVENT_CATEGORIES.SHORTCUT_USAGE,\n        `Use rerecord shortcut #${event.key}`\n      );\n      this.props.trackAppUsage(USAGE_TRACKING_ACTIONS.SPEAK_KEY_RERECORD);\n    }\n\n    if (!shortcut) return;\n\n    shortcut.action();\n    ((type === 'listen' ? trackListening : trackRecording) as any)(\n      'shortcut',\n      locale\n    );\n    event.preventDefault();\n  };\n\n  private showThousandRecordingsModal = () => {\n    localStorage.setItem(\n      `${this.props.user?.account?.client_id}:${HAS_SEEN_THOUSAND_RECORDINGS_MODAL_KEY}`,\n      'true'\n    );\n    this.setState({ showThousandRecordingsModal: true });\n  };\n\n  private closeThousandRecordingsModal = () => {\n    this.setState({ showThousandRecordingsModal: false });\n  };\n\n  render() {\n    const {\n      errorContent,\n      getString,\n      isSubmitted,\n      onSkip,\n      reportModalProps,\n      type,\n      onBack,\n      areClipsUploading,\n    } = this.props;\n    const {\n      showAccountModal,\n      showReportModal,\n      showShareModal,\n      showThousandRecordingsModal,\n      showPhraseSetPickerModal,\n    } = this.state;\n\n    return (\n      <div\n        className=\"contribution-wrapper\"\n        onClick={() => this.selectPill(null)}>\n        {showThousandRecordingsModal && (\n          <ThousandRecordingsModal\n            onRequestClose={this.closeThousandRecordingsModal}\n            shareTextId=\"thousand-modal-share-text\"\n          />\n        )}\n        {showShareModal && (\n          <ShareModal onRequestClose={this.toggleShareModal} />\n        )}\n        {showReportModal && (\n          <ReportModal\n            onRequestClose={() => this.setState({ showReportModal: false })}\n            onSubmitted={onSkip}\n            {...reportModalProps}\n          />\n        )}\n        {showAccountModal && (\n          <AccountModal\n            onRequestClose={() => this.setState({ showAccountModal: false })}\n          />\n        )}\n        {showPhraseSetPickerModal && (\n          <PhraseSetPickerModal\n            onRequestClose={() =>\n              this.setState({ showPhraseSetPickerModal: false })\n            }\n          />\n        )}\n        {this.getClipsCount() === FIRST_N_RECORDINGS_TO_TRIGGER_MODAL &&\n          !this.getHasSeenFirstNRecordingsModal() && (\n            <FirstNRecordingsModal\n              onRequestClose={() => this.setHasSeenFirstNRecordingsModal(true)}\n              handleContinue={() => this.setHasSeenFirstNRecordingsModal(true)}\n            />\n          )}\n        <div\n          className={[\n            'contribution',\n            type,\n            this.isDone ? 'submittable' : '',\n          ].join(' ')}>\n          <div className=\"top\">\n            <div onClick={() => onBack()} className=\"back\">\n              <ArrowLeft />\n            </div>\n            <div className=\"phrase-set-selector-button\">\n              <PhraseSetSelectorButton\n                title={\n                  this.props.phrase_set.current?.display_name ||\n                  this.props.phrase_set.current?.name\n                }\n                lang={this.props.phrase_set.current?.language}\n                onClick={() =>\n                  this.setState({ showPhraseSetPickerModal: true })\n                }\n              />\n            </div>\n\n            {areClipsUploading && (\n              <div className=\"progress\">\n                <Localized id=\"uploading-clips\">\n                  <span />\n                </Localized>\n              </div>\n            )}\n\n            {this.isRegisteredUser && this.isLoaded && !errorContent ? (\n              <div className={'counter ' + (isSubmitted ? 'done' : '')}>\n                {isSubmitted && <CheckIcon />}\n                <Localized\n                  id=\"clips-with-count-pluralized\"\n                  elems={{ bold: <b /> }}\n                  vars={{ count: this.renderClipCount() }}>\n                  <span className=\"text\" />\n                </Localized>\n              </div>\n            ) : (\n              <div />\n            )}\n            {isSubmitted && (\n              <Tooltip arrow title={getString('share-common-voice')}>\n                <button className=\"open-share\" onClick={this.toggleShareModal}>\n                  <ShareIcon />\n                </button>\n              </Tooltip>\n            )}\n          </div>\n\n          {this.renderContent()}\n        </div>\n      </div>\n    );\n  }\n\n  private getClipsCount(): number {\n    const { user } = this.props;\n\n    return user?.account?.clips_count || 0;\n  }\n\n  private setHasSeenFirstNRecordingsModal(value: boolean): void {\n    const { user } = this.props;\n    const key = `${FIRST_N_RECORDINGS_KEY}:${user?.account?.client_id}`;\n\n    localStorage.setItem(key, JSON.stringify(value));\n  }\n\n  private getHasSeenFirstNRecordingsModal(): boolean {\n    const { user } = this.props;\n\n    const key = `${FIRST_N_RECORDINGS_KEY}:${user?.account?.client_id}`;\n\n    return localStorage.getItem(key) === 'true';\n  }\n\n  renderClipCount() {\n    const clipCount = this.getClipsCount();\n\n    return clipCount.toString();\n  }\n\n  renderContent() {\n    const {\n      activeIndex,\n      errorContent,\n      getString,\n      instruction,\n      isFirstSubmit,\n      isSubmitted,\n      onReset,\n      onSkip,\n      onSubmit,\n      pills,\n      primaryButtons,\n      playTTSButton,\n      sentences,\n      type,\n    } = this.props;\n    const { selectedPill } = this.state;\n\n    const handleSkip = () => {\n      this.props.trackAppUsage(USAGE_TRACKING_ACTIONS.SPEAK_BTN_SKIP);\n      onSkip();\n    };\n\n    return isSubmitted ? (\n      <Success onReset={onReset} type={type} />\n    ) : (\n      errorContent ||\n        (this.isLoaded && (\n          <>\n            <div className=\"cards-and-pills\">\n              <div />\n\n              <div className=\"cards-and-instruction\">\n                {instruction({\n                  vars: { actionType: getString('action-click') },\n                  children: <div className=\"instruction hidden-sm-down\" />,\n                }) || <div className=\"instruction hidden-sm-down\" />}\n\n                <div className=\"cards\">\n                  {sentences.map((sentence, i) => {\n                    const activeSentenceIndex = this.isDone\n                      ? SET_COUNT - 1\n                      : activeIndex;\n                    const isActive = i === activeSentenceIndex;\n                    return (\n                      <div\n                        key={sentence ? sentence.text : i}\n                        className={\n                          'card card-dimensions ' + (isActive ? '' : 'inactive')\n                        }\n                        style={{\n                          transform: [\n                            `scale(${isActive ? 1 : 0.9})`,\n                            `translateX(${\n                              (document.dir == 'rtl' ? -1 : 1) *\n                              (i - activeSentenceIndex) *\n                              -130\n                            }%)`,\n                          ].join(' '),\n                          opacity: i < activeSentenceIndex ? 0 : 1,\n                        }}>\n                        <div className=\"tts-button-container\">\n                          {playTTSButton}\n                        </div>\n                        <div style={{ margin: 'auto', width: '100%' }}>\n                          {sentence?.text}\n                          {sentence?.taxonomy ? (\n                            <div className=\"sentence-taxonomy\">\n                              <Localized id=\"target-segment-first-card\">\n                                <span className=\"taxonomy-message\" />\n                              </Localized>\n                              <StyledLink\n                                className=\"taxonomy-link\"\n                                blank\n                                href={URLS.TARGET_SEGMENT_INFO}>\n                                <ExternalLinkIcon />\n                                <Localized id=\"target-segment-learn-more\">\n                                  <span />\n                                </Localized>\n                              </StyledLink>\n                            </div>\n                          ) : null}\n                        </div>\n                      </div>\n                    );\n                  })}\n                </div>\n              </div>\n\n              <div className=\"pills\">\n                <div className=\"inner\">\n                  {!errorContent && this.isRegisteredUser && (\n                    <div className=\"counter\">\n                      <Localized\n                        id=\"clips-with-count-pluralized\"\n                        elems={{ bold: <b />, span: <span className=\"text\" /> }}\n                        vars={{ count: this.renderClipCount() }}>\n                        <span className=\"text\" />\n                      </Localized>\n                    </div>\n                  )}\n                  {this.isDone && (\n                    <div className=\"review-instructions\">\n                      <Localized id=\"review-instruction\">\n                        <span />\n                      </Localized>\n                    </div>\n                  )}\n                  {pills.map((pill, i) =>\n                    pill({\n                      isOpen: this.isDone || selectedPill === i,\n                      key: i,\n                      num: i + 1,\n                      onClick: () => this.selectPill(i),\n                      onShare: this.toggleShareModal,\n                      style:\n                        selectedPill !== null &&\n                        Math.abs(\n                          Math.min(\n                            Math.max(selectedPill, 1),\n                            pills.length - 2\n                          ) - i\n                        ) > 1\n                          ? { display: 'none' }\n                          : {},\n                    })\n                  )}\n                </div>\n              </div>\n            </div>\n\n            {instruction({\n              vars: { actionType: getString('action-tap') },\n              children: <div className=\"instruction hidden-md-up\" />,\n            }) || <div className=\"instruction hidden-md-up\" />}\n\n            <div className=\"primary-buttons\">\n              {!this.props.isTTSPlaying && <canvas ref={this.canvasRef} />}\n              {primaryButtons}\n            </div>\n\n            <div className=\"buttons\">\n              <div className=\"extra-button\">\n                <ReportButton\n                  disabled={this.props.isTTSPlaying}\n                  onClick={() => this.setState({ showReportModal: true })}\n                />\n              </div>\n\n              {!(isMobile || isTablet) && (\n                <div className=\"shortcuts-container\">\n                  <div className=\"description\">\n                    <KeyboardIcon />\n                    <Localized id=\"shortcuts\">\n                      <span />\n                    </Localized>\n                  </div>\n\n                  <div className=\"shortcuts\">\n                    {this.shortcuts.map(({ key, label, icon }) => {\n                      const stringKey = getString(key);\n                      const stringKeyToDisplay =\n                        stringKey === SHORTCUT_RECORD_TOGGLE ||\n                        stringKey === SHORTCUT_SUBMIT\n                          ? stringKey\n                          : stringKey.toUpperCase();\n\n                      return (\n                        <div key={key} className=\"shortcut\">\n                          {icon ? (\n                            <div className=\"icon\">{icon}</div>\n                          ) : (\n                            <kbd>{stringKeyToDisplay}</kbd>\n                          )}\n                          <div className=\"label\">{getString(label)}</div>\n                        </div>\n                      );\n                    })}\n                  </div>\n                </div>\n              )}\n\n              <div>\n                <Button\n                  rounded\n                  outline\n                  className={[\n                    'skip',\n                    getTrackClass('fs', `skip-${type}`),\n                    'fs-ignore-rage-clicks',\n                  ].join(' ')}\n                  disabled={!this.isLoaded || this.props.isTTSPlaying}\n                  onClick={handleSkip}>\n                  <Localized id=\"skip\">\n                    <span />\n                  </Localized>{' '}\n                  <SkipIcon />\n                </Button>\n                {onSubmit && (\n                  <Tooltip\n                    arrow\n                    disabled={!this.isDone}\n                    open={isFirstSubmit || undefined}\n                    title={getString('record-submit-tooltip', {\n                      actionType: getString('action-tap'),\n                    })}>\n                    <Localized id=\"submit-form-action\">\n                      <PrimaryButton\n                        className={[\n                          'submit',\n                          getTrackClass('fs', `submit-${type}`),\n                        ].join(' ')}\n                        disabled={!this.isDone || this.props.isTTSPlaying}\n                        onClick={onSubmit}\n                        type=\"submit\"\n                      />\n                    </Localized>\n                  </Tooltip>\n                )}\n              </div>\n            </div>\n          </>\n        ))\n    );\n  }\n}\n\nconst mapStateToProps = (state: StateTree) => {\n  return {\n    flags: state.flags,\n    locale: state.locale,\n    user: state.user,\n    phrase_set: state.phrase_set,\n  };\n};\n\nconst mapDispatchToProps = {\n  setPhraseSet: PhraseSet.actions.set,\n};\n\nexport default connect<PropsFromState, PropsFromDispatch>(\n  mapStateToProps,\n  mapDispatchToProps\n)(withLocalization(ContributionPage));\n","import * as React from 'react';\n\nimport './pill.css';\n\nexport type PillStatus = 'active' | 'done' | 'pending';\n\nexport default ({\n  className = '',\n  children,\n  isOpen,\n  num,\n  onClick,\n  openable = false,\n  status,\n  style,\n}: {\n  className?: string;\n  children: React.ReactNode;\n  isOpen: boolean;\n  num: number;\n  openable?: boolean;\n  onClick: () => any;\n  status: PillStatus;\n  style?: any;\n}) => (\n  <div\n    className={['pill', isOpen ? 'open' : '', status, className].join(' ')}\n    onClick={\n      openable\n        ? event => {\n            if (status === 'pending') return;\n            event.stopPropagation();\n            if (isOpen) return;\n            onClick();\n          }\n        : null\n    }\n    style={style}>\n    <div className=\"contents\">{children}</div>\n    <div className=\"num\">{num}</div>\n  </div>\n);\n","import {\n  Localized,\n  withLocalization,\n  WithLocalizationProps,\n} from '@fluent/react';\nimport * as React from 'react';\nimport { useRef, useState } from 'react';\nimport { trackRecording } from '../../../../services/tracker';\nimport { useLocale } from '../../../locale-helpers';\nimport {\n  PlayOutlineIcon,\n  RedoIcon,\n  ShareIcon,\n  StopIcon,\n} from '../../../ui/icons';\nimport { getAudioFormat } from '../../../../utility';\nimport { ContributionPillProps } from '../contribution';\nimport Pill, { PillStatus } from '../pill';\nimport { SentenceRecording } from './sentence-recording';\n\nimport './recording-pill.css';\n\nconst { Tooltip } = require('react-tippy');\n\ninterface Props extends ContributionPillProps, WithLocalizationProps {\n  children?: React.ReactNode;\n  clip: SentenceRecording;\n  onReplay?: () => any;\n  onRerecord: () => any;\n  status: PillStatus;\n  isTTSPlaying: boolean;\n  onPlayingStatusUpdate: (playing: boolean) => any;\n}\n\nfunction RecordingPill({\n  children,\n  clip,\n  getString,\n  onReplay,\n  onRerecord,\n  onShare,\n  isTTSPlaying,\n  status,\n  onPlayingStatusUpdate,\n  ...props\n}: Props) {\n  const [locale] = useLocale();\n  const [isPlaying, setIsPlaying] = useState(false);\n  const [showSentenceTooltip, setShowSentenceTooltip] = useState(false);\n  const audioRef = useRef(null);\n\n  const toggleIsPlaying = () => {\n    const { current: audio } = audioRef;\n    let nextIsPlaying = !isPlaying;\n    if (nextIsPlaying) {\n      trackRecording('listen', locale);\n      onReplay();\n      audio.play();\n    } else {\n      audio.pause();\n      audio.currentTime = 0;\n    }\n    setIsPlaying(nextIsPlaying);\n    onPlayingStatusUpdate(nextIsPlaying)\n  };\n\n  return (\n    <Pill {...props} className=\"recording\" openable status={status}>\n      {children}\n\n      {!children && status === 'active' && (\n        <Localized id=\"record-cta\">\n          <div className=\"text\" />\n        </Localized>\n      )}\n\n      {!children && status === 'done' && (\n        <>\n          <audio preload=\"auto\" onEnded={toggleIsPlaying} ref={audioRef}>\n            <source src={clip.recording.url} type={getAudioFormat()} />\n          </audio>\n          <Tooltip\n            arrow\n            open={isPlaying || showSentenceTooltip}\n            disabled={isTTSPlaying}\n            theme=\"grey-tooltip\"\n            title={clip.sentence.text}>\n            <button\n              className=\"play\"\n              disabled={isTTSPlaying}\n              type=\"button\"\n              onClick={toggleIsPlaying}\n              onMouseEnter={() => setShowSentenceTooltip(true)}\n              onMouseLeave={() => setShowSentenceTooltip(false)}>\n              <span className=\"padder\">\n                {isPlaying ? <StopIcon /> : <PlayOutlineIcon />}\n              </span>\n            </button>\n          </Tooltip>\n          {isPlaying ? (\n            <div className=\"placeholder\" />\n          ) : (\n            <>\n              <Tooltip\n                arrow\n                title={getString('review-tooltip')}\n                disabled={isTTSPlaying}\n              >\n                <button\n                  className=\"redo\"\n                  type=\"button\"\n                  onClick={onRerecord}\n                  disabled={isTTSPlaying}\n                >\n                  <span className=\"padder\">\n                    <RedoIcon />\n                  </span>\n                </button>\n              </Tooltip>\n              <Tooltip\n                arrow\n                title={getString('share-clip')}\n                disabled={isTTSPlaying}\n              >\n                <button\n                  className=\"share\"\n                  type=\"button\"\n                  onClick={onShare}\n                  disabled={isTTSPlaying}\n                >\n                  <span className=\"padder\">\n                    <ShareIcon />\n                  </span>\n                </button>\n              </Tooltip>\n            </>\n          )}\n        </>\n      )}\n    </Pill>\n  );\n}\n\nexport default withLocalization(RecordingPill);\n","import { LOG_LEVELS } from 'common';\n\nconst API_PATH = location.origin + '/logs-api/v1';\n\nclass Logger {\n  private async makeRequest(data: any) {\n    const url = `${API_PATH}/logs`;\n\n    try {\n      await fetch(url, {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json; charset=utf-8',\n        },\n        body: JSON.stringify(data)\n      });\n    } catch(err) {\n      console.error(`Failed to send log`, err);\n    }\n  }\n\n  public error(message: string, additionalInfo: any = null) {\n    const data = {\n      level: LOG_LEVELS.ERROR,\n      message,\n      data: additionalInfo\n    };\n\n    return this.makeRequest(data);\n  }\n\n  public info(message: string, additionalInfo: any = null) {\n    const data = {\n      level: LOG_LEVELS.INFO,\n      message,\n      data: additionalInfo\n    };\n\n    return this.makeRequest(data);\n  }\n\n  public debug(message: string, additionalInfo: any = null) {\n    const data = {\n      level: LOG_LEVELS.DEBUG,\n      message,\n      data: additionalInfo\n    };\n\n    return this.makeRequest(data);\n  }\n\n  public warning(message: string, additionalInfo: any = null) {\n    const data = {\n      level: LOG_LEVELS.WARNING,\n      message,\n      data: additionalInfo\n    };\n\n    return this.makeRequest(data);\n  }\n}\n\nconst logger = new Logger();\n\nexport default logger;\n","import {\n  Localized,\n  WithLocalizationProps,\n  withLocalization,\n} from '@fluent/react';\nimport * as React from 'react';\nimport BalanceText from 'react-balance-text';\nimport { connect } from 'react-redux';\nimport { RouteComponentProps, withRouter } from 'react-router';\nconst NavigationPrompt = require('react-router-navigation-prompt').default;\n\nimport { Locale } from '../../../../stores/locale';\nimport { Notifications } from '../../../../stores/notifications';\nimport { Sentences } from '../../../../stores/sentences';\nimport { Sentence as SentenceType, CLIP_ERROR_STATUSES } from 'common';\nimport StateTree from '../../../../stores/tree';\nimport { Uploads } from '../../../../stores/uploads';\nimport { User } from '../../../../stores/user';\nimport API from '../../../../services/api';\nimport { trackRecording, getTrackClass } from '../../../../services/tracker';\nimport URLS from '../../../../urls';\nimport { localeConnector, LocalePropsFromState } from '../../../locale-helpers';\nimport Modal, { ModalButtons } from '../../../modal/modal';\nimport TermsModal from '../../../terms-modal';\nimport { shouldUseFixForIOSDevice } from './utility';\nimport {\n  CheckIcon,\n  MicIcon,\n  StopIcon,\n  FirefoxColor,\n  ChromeColor,\n  SafariColor,\n} from '../../../ui/icons';\nimport { Button, TextButton } from '../../../ui/ui';\nimport {\n  isIOS,\n  isMobileSafari,\n  getDeviceMetadata,\n  getVoices,\n} from '../../../../utility';\nimport ContributionPage, {\n  ContributionPillProps,\n  SET_COUNT,\n} from '../contribution';\nimport {\n  RecordButton,\n  RecordingStatus,\n  PlayTTSButton,\n} from '../../../primary-buttons/primary-buttons';\nimport AudioWeb, { AudioError, AudioInfo } from './audio-web';\nimport RecordingPill from './recording-pill';\nimport { SentenceRecording } from './sentence-recording';\nimport logger from '../../../../services/logger';\n\nimport './speak.css';\nimport {\n  UsageTrackingActionId,\n  USAGE_TRACKING_ACTIONS,\n} from '../../../../constants';\nimport {\n  trackGoogleAnalyticsEvent,\n  EVENT_CATEGORIES,\n  EVENT_NAMES,\n} from '../../../../services/ga-event-tracker';\nimport * as _ from 'lodash';\n\nconst MIN_RECORDING_MS = 1000;\nconst MIN_RECORDING_MS_BENCHMARK = 500;\nconst MAX_RECORDING_MS = 15000;\nconst MIN_VOLUME = 8; // Range: [0, 255].\nconst VOICE_LANG = 'en-US';\n\nenum RecordingError {\n  TOO_SHORT = 'TOO_SHORT',\n  TOO_LONG = 'TOO_LONG',\n  TOO_QUIET = 'TOO_QUIET',\n  NO_AUDIO = 'NO_AUDIO',\n}\n\nenum TTSError {\n  NO_VOICE = 'NO_VOICE',\n}\n\n// Speech synthesis error codes that mean the system cannot speak the sentence\n// because no voice for its language is installed.\nconst NO_VOICE_SYNTHESIS_ERRORS = [\n  'language-unavailable',\n  'voice-unavailable',\n  'synthesis-unavailable',\n];\n\nconst UnsupportedInfo = () => (\n  <div className=\"empty-container\">\n    <div className=\"error-card card-dimensions unsupported\">\n      {isIOS() && !isMobileSafari() ? (\n        <>\n          <BalanceText>\n            <Localized id=\"record-platform-not-supported-ios-non-safari\" />\n          </BalanceText>\n          <SafariColor />\n        </>\n      ) : (\n        <>\n          <BalanceText>\n            <Localized id=\"record-platform-not-supported\" />\n          </BalanceText>\n          <p className=\"desktop\">\n            <Localized id=\"record-platform-not-supported-desktop\">\n              <BalanceText />\n            </Localized>\n          </p>\n          <div>\n            <a\n              rel=\"noopener noreferrer\"\n              target=\"_blank\"\n              href=\"https://www.firefox.com/\"\n              title=\"Firefox\">\n              <FirefoxColor />\n            </a>{' '}\n            <a\n              rel=\"noopener noreferrer\"\n              target=\"_blank\"\n              href=\"https://www.google.com/chrome\"\n              title=\"Chrome\">\n              <ChromeColor />\n            </a>\n          </div>\n        </>\n      )}\n    </div>\n  </div>\n);\n\nconst NoSentencesAvailable = () => (\n  <div className=\"empty-container\">\n    <div className=\"error-card card-dimensions no-sentences-available\">\n      <Localized id=\"speak-empty-state\">\n        <span />\n      </Localized>\n    </div>\n  </div>\n);\n\ninterface PropsFromState {\n  api: API;\n  locale: Locale.State;\n  sentences: SentenceType[];\n  user: User.State;\n  isLoading: boolean;\n}\n\ninterface PropsFromDispatch {\n  addUploads: typeof Uploads.actions.add;\n  addAchievement: typeof Notifications.actions.addAchievement;\n  addNotification: typeof Notifications.actions.addPill;\n  removeSentences: typeof Sentences.actions.remove;\n  tallyRecording: typeof User.actions.tallyRecording;\n  refreshUser: typeof User.actions.refresh;\n  updateUser: typeof User.actions.update;\n  saveAccount: typeof User.actions.saveAccount;\n}\n\ninterface Props\n  extends LocalePropsFromState,\n    WithLocalizationProps,\n    PropsFromState,\n    PropsFromDispatch,\n    RouteComponentProps<any, any, any> {\n  areClipsUploading: boolean;\n}\n\ninterface State {\n  clips: SentenceRecording[];\n  clipsPhraseSetKey: string | undefined;\n  isSubmitted: boolean;\n  registeredUserFirstUpload: boolean;\n  error?: RecordingError | AudioError | TTSError;\n  recordingStatus: RecordingStatus;\n  isTTSPlaying: boolean;\n  utterThis: SpeechSynthesisUtterance | null;\n  rerecordIndex?: number;\n  showPrivacyModal: boolean;\n  showDiscardModal: boolean;\n  voices: SpeechSynthesisVoice[];\n  useFixForIOSDevice: boolean;\n  isAudioPillPlaying: boolean;\n}\n\nconst initialState: State = {\n  clips: [],\n  clipsPhraseSetKey: undefined,\n  isSubmitted: false,\n  registeredUserFirstUpload: false,\n  error: null,\n  recordingStatus: null,\n  isTTSPlaying: false,\n  utterThis: null,\n  rerecordIndex: null,\n  showPrivacyModal: false,\n  showDiscardModal: false,\n  voices: [],\n  useFixForIOSDevice: false,\n  isAudioPillPlaying: false,\n};\n\nclass SpeakPage extends React.Component<Props, State> {\n  state: State = initialState;\n\n  audio: AudioWeb;\n  isUnsupportedPlatform = false;\n  maxVolume = -1;\n  recordingStartTime = 0;\n  recordingStopTime = 0;\n\n  static getDerivedStateFromProps(props: Props, state: State) {\n    const isSamePhraseSet =\n      state.clipsPhraseSetKey === props.phrase_set.current?.key;\n    if (state.clips.length > 0 && isSamePhraseSet) {\n      const sentenceTexts = state.clips\n        .map(({ sentence }) => (sentence ? sentence.text : null))\n        .filter(Boolean);\n      const unusedSentences = props.sentences.filter(\n        s => !sentenceTexts.includes(s.text)\n      );\n      return {\n        clips: state.clips.map(clip =>\n          clip.sentence\n            ? clip\n            : { recording: null, sentence: unusedSentences.shift() || null }\n        ),\n      };\n    }\n\n    if (props.sentences.length > 0) {\n      return {\n        clips: props.sentences\n          .slice(0, SET_COUNT)\n          .map(sentence => ({ recording: null, sentence })),\n        clipsPhraseSetKey: props.phrase_set.current?.key,\n      };\n    }\n\n    return null;\n  }\n\n  async componentDidMount() {\n    this.audio = new AudioWeb();\n    this.audio.setVolumeCallback(this.updateVolume.bind(this));\n\n    document.addEventListener('visibilitychange', this.releaseMicrophone);\n    document.addEventListener('keyup', this.handleKeyUprerecording);\n\n    if (\n      !this.audio.isMicrophoneSupported() ||\n      !this.audio.isAudioRecordingSupported()\n    ) {\n      this.isUnsupportedPlatform = true;\n    }\n\n    const voices = await getVoices();\n    const useFixForIOSDevice = shouldUseFixForIOSDevice();\n\n    this.setState({ voices, useFixForIOSDevice });\n  }\n\n  async componentDidUpdate(prevProps: Props) {\n    const { user, toLocaleRoute, history } = this.props;\n\n    if (!Boolean(user?.account)) {\n      history.replace(toLocaleRoute(URLS.ROOT));\n    }\n  }\n\n  async componentWillUnmount() {\n    document.removeEventListener('keyup', this.handleKeyUprerecording);\n\n    document.removeEventListener('visibilitychange', this.releaseMicrophone);\n    if (!this.isRecording) return;\n    await this.audio.stop();\n  }\n\n  private get isRecording() {\n    return this.state.recordingStatus === 'recording';\n  }\n\n  private get isTTSPlaying() {\n    return this.state.isTTSPlaying;\n  }\n\n  private handleKeyUprerecording = async (event: any) => {\n    if (this.isTTSPlaying) return;\n\n    let index = null;\n    //for both sets of number keys on a keyboard with shift key\n    if (event.code === 'Digit1' || event.code === 'Numpad1') {\n      index = 0;\n    } else if (event.code === 'Digit2' || event.code === 'Numpad2') {\n      index = 1;\n    } else if (event.code === 'Digit3' || event.code === 'Numpad3') {\n      index = 2;\n    } else if (event.code === 'Digit4' || event.code === 'Numpad4') {\n      index = 3;\n    } else if (event.code === 'Digit5' || event.code === 'Numpad5') {\n      index = 4;\n    }\n\n    if (index !== null) {\n      trackRecording('rerecord', this.props.locale);\n      await this.discardRecording();\n      this.setState({\n        rerecordIndex: index,\n      });\n    }\n  };\n\n  private getRecordingIndex() {\n    return (\n      this.state.rerecordIndex ??\n      this.state.clips.findIndex(({ recording }) => !recording)\n    );\n  }\n\n  private releaseMicrophone = () => {\n    if (!document.hidden) {\n      return;\n    }\n\n    if (this.isRecording) {\n      this.saveRecording();\n    }\n    this.audio.release();\n  };\n\n  private processRecording = (info: AudioInfo) => {\n    const error = this.getRecordingError(info);\n    if (error) {\n      const { user } = this.props;\n      const current = this.getRecordingIndex();\n      const currentClip = this.state.clips[current];\n\n      logger.error(\n        `Failed to process recording when saving recording into memory`,\n        {\n          client_id: _.get(user, 'account.client_id'),\n          email: _.get(user, 'account.email'),\n          error: error,\n          sentence_text: _.get(currentClip, 'sentence.text'),\n          device_data: getDeviceMetadata(),\n        }\n      );\n\n      return this.setState({ error, recordingStatus: null });\n    }\n\n    this.setState(({ clips }) => {\n      const newClips = [...clips];\n      newClips[this.getRecordingIndex()].recording = info;\n      return {\n        clips: newClips,\n        rerecordIndex: null,\n        recordingStatus: null,\n      };\n    });\n\n    trackRecording('record', this.props.locale);\n  };\n\n  private getRecordingError = (info: AudioInfo): RecordingError => {\n    const length = this.recordingStopTime - this.recordingStartTime;\n    const currentSentence = this.state.clips[this.getRecordingIndex()].sentence;\n    const minClipLength =\n      currentSentence.taxonomy === 'Benchmark'\n        ? MIN_RECORDING_MS_BENCHMARK\n        : MIN_RECORDING_MS;\n\n    if (length < minClipLength) {\n      return RecordingError.TOO_SHORT;\n    }\n    if (length > MAX_RECORDING_MS) {\n      return RecordingError.TOO_LONG;\n    }\n    // If updateVolume was never called, we assume there’s a problem with the\n    // AnalyserNode and skip this error.\n    if (this.maxVolume !== -1 && this.maxVolume < MIN_VOLUME) {\n      return RecordingError.TOO_QUIET;\n    }\n\n    const audioBlob = info.blob;\n    // if no blob at all or managed to capture only headers and no data\n    if (audioBlob?.size <= 44) {\n      return RecordingError.NO_AUDIO;\n    }\n    return null;\n  };\n\n  private updateVolume = (volume: number) => {\n    if (volume > this.maxVolume) {\n      this.maxVolume = volume;\n    }\n  };\n\n  private rerecord = async (i: number) => {\n    trackRecording('rerecord', this.props.locale);\n    trackGoogleAnalyticsEvent(\n      EVENT_NAMES.RERECORD_CLIP,\n      EVENT_CATEGORIES.BUTTON_USAGE,\n      'Click on Rerecord clip button on Speak page'\n    );\n    this.trackAppUsage(USAGE_TRACKING_ACTIONS.SPEAK_BTN_RERECORD);\n    await this.discardRecording();\n\n    this.setState({\n      rerecordIndex: i,\n    });\n  };\n\n  private onReplay = () => {\n    this.trackAppUsage(USAGE_TRACKING_ACTIONS.SPEAK_BTN_REPLAY);\n    trackGoogleAnalyticsEvent(\n      EVENT_NAMES.REPLAY_CLIP,\n      EVENT_CATEGORIES.BUTTON_USAGE,\n      'Click on Play again button on Speak page'\n    );\n  };\n\n  private getVoicesForLanguage = (language: string): SpeechSynthesisVoice[] => {\n    // Android reports Hebrew voices with the legacy `iw` code, so map it to `he`\n    const normalize = (lang: string) =>\n      lang\n        .replace(/_/g, '-')\n        .toLowerCase()\n        .replace(/^iw(?=-|$)/, 'he');\n    const wanted = normalize(language);\n\n    return this.state.voices\n      .filter(voice => normalize(voice.lang).startsWith(wanted))\n      .sort((voiceA, voiceB) => voiceA.name.localeCompare(voiceB.name));\n  };\n\n  private playSentence = (): void => {\n    const deviceMetadata = getDeviceMetadata();\n    // get text to play\n    const current = this.getRecordingIndex();\n    const currentClip = this.state.clips[current];\n    const textToPlay = currentClip?.sentence?.text;\n    const language = (currentClip?.sentence as any)?.language || VOICE_LANG;\n    // select only voices of the sentence language\n    const languageVoices = this.getVoicesForLanguage(language);\n\n    // the language is not installed on this system, so there is no voice that\n    // could read the sentence out loud\n    if (languageVoices.length === 0) {\n      const { user } = this.props;\n\n      logger.error('no tts voice installed for sentence language', {\n        client_id: _.get(user, 'user.account.client_id'),\n        email: _.get(user, 'user.account.email'),\n        language,\n        installed_voice_languages: this.state.voices.map(voice => voice.lang),\n        device_data: deviceMetadata,\n      });\n\n      this.setState({\n        error: TTSError.NO_VOICE,\n        isTTSPlaying: false,\n        utterThis: null,\n      });\n      return;\n    }\n\n    // set up tts\n    const synth = window.speechSynthesis;\n    const utterThis = new SpeechSynthesisUtterance(textToPlay);\n    utterThis.lang = language;\n    utterThis.pitch = 1;\n    // request from Oleg to slow down speed\n    utterThis.rate = 0.6;\n\n    // fix specifically for Edge browser\n    if (deviceMetadata.browser_name === 'Edge') {\n      // happens to be that Ana is a child voice\n      const notChildVoice = languageVoices.filter(\n        voice => !voice.name.includes('Ana')\n      );\n      utterThis.voice = _.head(notChildVoice) || _.head(languageVoices);\n    } else {\n      utterThis.voice = _.head(languageVoices);\n    }\n\n    utterThis.addEventListener('start', () => {\n      this.setState({ isTTSPlaying: true });\n    });\n\n    utterThis.addEventListener('end', () => {\n      this.setState({ isTTSPlaying: false, utterThis: null });\n    });\n\n    utterThis.addEventListener('error', err => {\n      const errMsg = 'error occurred when playing tss';\n      const { user } = this.props;\n\n      console.log(errMsg, err);\n\n      logger.error(errMsg, {\n        client_id: _.get(user, 'user.account.client_id'),\n        email: _.get(user, 'user.account.email'),\n        sentence_text: _.get(currentClip, 'sentence.text'),\n        synthesis_error: err.error,\n        device_data: getDeviceMetadata(),\n      });\n\n      this.setState(({ error }) => ({\n        isTTSPlaying: false,\n        utterThis: null,\n        error: NO_VOICE_SYNTHESIS_ERRORS.includes(err.error)\n          ? TTSError.NO_VOICE\n          : error,\n      }));\n    });\n\n    utterThis.addEventListener('pause', () => {\n      this.setState({ isTTSPlaying: false });\n    });\n\n    utterThis.addEventListener('resume', () => {\n      this.setState({ isTTSPlaying: true });\n    });\n\n    synth.speak(utterThis);\n    this.setState(({ error }) => ({\n      utterThis,\n      error: error === TTSError.NO_VOICE ? null : error,\n    }));\n  };\n\n  private resumePlaySentence = (): void => {\n    const synth = window.speechSynthesis;\n    synth.resume();\n  };\n\n  private stopPlayingSentence = (): void => {\n    const synth = window.speechSynthesis;\n    synth.pause();\n  };\n\n  private handlePlayTTS = () => {\n    const { utterThis, isTTSPlaying } = this.state;\n    if (!utterThis) {\n      this.playSentence();\n    } else if (utterThis && !isTTSPlaying) {\n      this.resumePlaySentence();\n    } else {\n      this.stopPlayingSentence();\n    }\n  };\n\n  private handleRecordClick = async () => {\n    if (this.state.recordingStatus === 'waiting' || this.state.isTTSPlaying)\n      return;\n    const isRecording = this.isRecording;\n\n    this.setState({ recordingStatus: 'waiting' });\n\n    if (isRecording) {\n      this.saveRecording();\n      return;\n    }\n\n    try {\n      const audioContext = new (window.AudioContext ||\n        window.webkitAudioContext)();\n      const processor = audioContext.createScriptProcessor(\n        this.state.useFixForIOSDevice ? 2048 : 0,\n        1,\n        1\n      );\n\n      await this.audio.init(this.state.useFixForIOSDevice ? 2048 : 0);\n      await this.startRecording(audioContext, processor);\n    } catch (err) {\n      if (err in AudioError) {\n        this.setState({ error: err });\n      } else {\n        throw err;\n      }\n    }\n  };\n\n  private startRecording = async (\n    audioContext: AudioContext,\n    processor: ScriptProcessorNode\n  ) => {\n    try {\n      await this.audio.start(audioContext, processor);\n      this.maxVolume = -1; // Initialize to -1 in case updateVolume is never called.\n      this.recordingStartTime = Date.now();\n      this.recordingStopTime = 0;\n      this.setState({\n        recordingStatus: 'recording',\n        error: null,\n      });\n    } catch (err) {\n      this.setState({\n        recordingStatus: null,\n      });\n    }\n  };\n\n  private saveRecording = () => {\n    // We noticed that some people hit the Stop button too early, cutting off\n    // the recording prematurely. To compensate, we add a short buffer to the\n    // end of each recording (issue #1648).\n    const RECORD_STOP_DELAY = 500;\n    setTimeout(async () => {\n      const info = await this.audio.stop();\n      this.processRecording(info);\n    }, RECORD_STOP_DELAY);\n    this.recordingStopTime = Date.now();\n    this.setState({\n      recordingStatus: null,\n    });\n  };\n\n  private discardRecording = async () => {\n    if (!this.isRecording) return;\n    await this.audio.stop();\n    this.setState({ recordingStatus: null });\n  };\n\n  private cancelReRecord = async () => {\n    await this.discardRecording();\n    this.setState({ rerecordIndex: null });\n  };\n\n  private handleSkip = async () => {\n    const { removeSentences } = this.props;\n    await this.discardRecording();\n    const current = this.getRecordingIndex();\n    const text = this.state.clips[current]?.sentence?.text;\n\n    // Aligning logic that takes next unused sentence instead of skipped (SpeakPage getDerivedStateFromProps() unusedSentences)\n    // with Sentences.refill that should know about used sentences (already assigned to clips in current speak session)\n    // to know if it should fetch next portion of data\n    const ignoreSentences = this.state.clips\n      .map(({ sentence }) => (sentence ? sentence.text : null))\n      .filter(Boolean);\n    removeSentences([text], ignoreSentences);\n\n    this.setState(({ clips }) => {\n      const newClips = [...clips];\n      newClips[current] = { recording: null, sentence: null };\n      if (clips[current]?.recording?.url)\n        URL.revokeObjectURL(clips[current].recording.url);\n      return {\n        clips: newClips,\n        error: null,\n      };\n    });\n\n    trackGoogleAnalyticsEvent(\n      EVENT_NAMES.SKIP_SENTENCE,\n      EVENT_CATEGORIES.BUTTON_USAGE,\n      'Click on Skip button on the Speak page'\n    );\n  };\n\n  private upload = (hasAgreed: boolean = false) => {\n    const {\n      addAchievement,\n      addNotification,\n      addUploads,\n      api,\n      locale,\n      phrase_set,\n      removeSentences,\n      tallyRecording,\n      user,\n      refreshUser,\n      saveAccount,\n    } = this.props;\n\n    if (!hasAgreed && !(user.privacyAgreed || user.account)) {\n      this.setState({ showPrivacyModal: true });\n      return false;\n    }\n\n    const clips = this.state.clips.filter(clip => clip.recording);\n\n    removeSentences(clips.map(c => c.sentence.text));\n\n    const registeredUserFirstUpload =\n      user.account && user.account.clips_count === 0;\n    this.setState({ clips: [], isSubmitted: true, registeredUserFirstUpload });\n\n    addUploads([\n      ...clips.map(({ sentence, recording }) => async () => {\n        let retries = 3;\n        while (retries) {\n          try {\n            const {\n              showFirstContributionToast = false,\n              hasEarnedSessionToast = false,\n              showFirstStreakToast = false,\n              challengeEnded = true,\n            } = await api.uploadClip(\n              recording.blob,\n              sentence.text,\n              locale,\n              phrase_set.current?.key,\n              recording.device_default_input_label,\n              recording.device_default_input_is_bluetooth,\n              recording.created_at\n            );\n            URL.revokeObjectURL(recording.url);\n            sessionStorage.setItem(\n              'challengeEnded',\n              JSON.stringify(challengeEnded)\n            );\n            sessionStorage.setItem('hasContributed', 'true');\n            if (showFirstContributionToast) {\n              addAchievement(\n                50,\n                \"You're on your way! Congrats on your first contribution.\",\n                'success'\n              );\n            }\n            if (showFirstStreakToast) {\n              addAchievement(\n                50,\n                'You completed a three-day streak! Keep it up.',\n                'success'\n              );\n            }\n            if (\n              !JSON.parse(sessionStorage.getItem('challengeEnded')) &&\n              JSON.parse(sessionStorage.getItem('hasShared')) &&\n              !hasEarnedSessionToast\n            ) {\n              addAchievement(\n                50,\n                \"You're on a roll! You sent an invite and contributed in the same session.\",\n                'success'\n              );\n              sessionStorage.removeItem('hasShared');\n              // Tell back-end user get unexpected achievement: invite + contribute in the same session\n              // Each user can only get once.\n              api.setInviteContributeAchievement();\n            }\n            if (!user.account) {\n              tallyRecording();\n            }\n            retries = 0;\n          } catch (error) {\n            const errorMsg = error.message;\n            let msgToDisplay;\n\n            if (Object.values(CLIP_ERROR_STATUSES).includes(errorMsg)) {\n              msgToDisplay = this.props.getString(\n                'upload-keeps-failing-server'\n              );\n            } else {\n              msgToDisplay = this.props.getString('upload-keeps-failing-retry');\n            }\n\n            logger.error(`Failed to upload recording`, {\n              client_id: _.get(user, 'account.client_id'),\n              email: _.get(user, 'account.email'),\n              error: errorMsg,\n              sentence_text: sentence.text,\n              device_data: getDeviceMetadata(),\n            });\n\n            retries--;\n            await new Promise(resolve => setTimeout(resolve, 1000));\n            if (retries == 0 && confirm(msgToDisplay)) {\n              retries = 3;\n            }\n          }\n        }\n      }),\n      async () => {\n        trackRecording('submit', locale);\n\n        addNotification(\n          <>\n            <CheckIcon />{' '}\n            <Localized id=\"clips-uploaded\">\n              <span className=\"signals-uploaded-notification\" />\n            </Localized>\n          </>\n        );\n\n        if (\n          registeredUserFirstUpload ||\n          (user.account && user.account.clips_count >= SET_COUNT)\n        ) {\n          saveAccount({\n            skip_submission_feedback: true,\n          });\n        }\n\n        refreshUser();\n      },\n    ]);\n\n    return true;\n  };\n\n  private resetState = (callback?: any) =>\n    this.setState(initialState, callback);\n\n  private agreeToTerms = async () => {\n    this.setState({ showPrivacyModal: false });\n    this.props.updateUser({ privacyAgreed: true });\n    this.upload(true);\n  };\n\n  private toggleDiscardModal = () => {\n    this.setState({\n      showPrivacyModal: false,\n      showDiscardModal: !this.state.showDiscardModal,\n    });\n  };\n\n  private displayError = () => {\n    return (\n      this.isUnsupportedPlatform ||\n      (!this.props.isLoading && this.state.clips.length == 0)\n    );\n  };\n\n  private returnSpeakError = () => {\n    return this.isUnsupportedPlatform ? (\n      <UnsupportedInfo />\n    ) : (\n      <NoSentencesAvailable />\n    );\n  };\n\n  private resetAndGoHome = () => {\n    const { history, toLocaleRoute } = this.props;\n    this.resetState(() => {\n      history.push(toLocaleRoute(URLS.ROOT));\n      window.scrollTo({\n        top: 0,\n        behavior: 'smooth',\n      });\n    });\n  };\n\n  private onBack = () => {\n    const isRegisteredUser = Boolean(this.props.user?.account);\n    if (isRegisteredUser && this.state.isSubmitted) {\n      this.resetState();\n    } else {\n      this.props.history.push(URLS.ROOT);\n    }\n  };\n\n  private trackAppUsage = (action: UsageTrackingActionId) => {\n    return this.props.api.trackAppUsage(action);\n  };\n\n  render() {\n    const { getString, user, areClipsUploading } = this.props;\n    const {\n      clips,\n      isSubmitted,\n      registeredUserFirstUpload,\n      error,\n      recordingStatus,\n      rerecordIndex,\n      showPrivacyModal,\n      showDiscardModal,\n    } = this.state;\n    const recordingIndex = this.getRecordingIndex();\n\n    return (\n      <>\n        <NavigationPrompt\n          when={clips.filter(clip => clip.recording).length > 0}>\n          {({ onConfirm, onCancel }: any) => (\n            <Modal innerClassName=\"record-abort\" onRequestClose={onCancel}>\n              <Localized id=\"record-abort-title\">\n                <h1 className=\"title\" />\n              </Localized>\n              <Localized id=\"record-abort-text\">\n                <p className=\"text\" />\n              </Localized>\n              <ModalButtons>\n                <Localized id=\"record-abort-submit\">\n                  <Button\n                    outline\n                    rounded\n                    className={getTrackClass('fs', 'exit-submit-clips')}\n                    onClick={() => {\n                      if (this.upload()) onConfirm();\n                    }}\n                  />\n                </Localized>\n                <Localized id=\"record-abort-continue\">\n                  <Button\n                    outline\n                    rounded\n                    className={getTrackClass('fs', 'exit-continue-recording')}\n                    onClick={onCancel}\n                  />\n                </Localized>\n              </ModalButtons>\n              <Localized id=\"record-abort-delete\">\n                <TextButton\n                  className={getTrackClass('fs', 'exit-delete-clips')}\n                  onClick={onConfirm}\n                />\n              </Localized>\n            </Modal>\n          )}\n        </NavigationPrompt>\n        {showPrivacyModal && (\n          <TermsModal\n            onAgree={this.agreeToTerms}\n            onDisagree={this.toggleDiscardModal}\n          />\n        )}\n        {showDiscardModal && (\n          <Localized id=\"review-aborted\">\n            <Modal\n              buttons={{\n                [getString('review-keep-recordings')]: {\n                  handler: this.toggleDiscardModal,\n                  variant: 'primary',\n                },\n                [getString('review-delete-recordings')]: {\n                  handler: this.resetAndGoHome,\n                  variant: 'secondary',\n                },\n              }}\n            />\n          </Localized>\n        )}\n        <ContributionPage\n          skipAutoReset={registeredUserFirstUpload}\n          areClipsUploading={areClipsUploading}\n          activeIndex={recordingIndex}\n          errorContent={this.displayError() && this.returnSpeakError()}\n          instruction={props =>\n            error ? (\n              <div className=\"error\">\n                <Localized\n                  id={\n                    {\n                      [RecordingError.TOO_SHORT]: 'record-error-too-short',\n                      [RecordingError.TOO_LONG]: 'record-error-too-long',\n                      [RecordingError.TOO_QUIET]: 'record-error-too-quiet',\n                      [RecordingError.NO_AUDIO]: 'record-error-no-audio',\n                      [AudioError.NOT_ALLOWED]: 'record-must-allow-microphone',\n                      [AudioError.NO_MIC]: 'record-no-mic-found',\n                      [AudioError.NO_SUPPORT]: 'record-platform-not-supported',\n                      [TTSError.NO_VOICE]: 'tts-error-no-voice',\n                    }[error]\n                  }\n                  {...props}\n                />\n              </div>\n            ) : (\n              <div className={`info ${this.isTTSPlaying ? 'disabled' : ''}`}>\n                <Localized\n                  id={\n                    this.isRecording\n                      ? 'record-stop-instruction'\n                      : recordingIndex === SET_COUNT - 1\n                      ? 'record-last-instruction'\n                      : ['record-instruction', 'record-again-instruction'][\n                          recordingIndex\n                        ] || 'record-again-instruction2'\n                  }\n                  elems={{\n                    recordIcon: <MicIcon />,\n                    stopIcon: <StopIcon />,\n                  }}\n                  {...props}\n                />\n              </div>\n            )\n          }\n          isFirstSubmit={user.recordTally === 0}\n          isPlaying={this.isRecording}\n          isTTSPlaying={this.isTTSPlaying}\n          isSubmitted={isSubmitted}\n          onBack={() => this.onBack()}\n          onReset={() => this.resetState()}\n          onSkip={this.handleSkip}\n          onSubmit={() => this.upload()}\n          playTTSButton={\n            <PlayTTSButton\n              status={this.isTTSPlaying ? 'playing' : 'listen'}\n              onClick={this.handlePlayTTS}\n              disabled={this.isRecording}\n            />\n          }\n          primaryButtons={\n            <RecordButton\n              trackClass=\"speak-record\"\n              status={recordingStatus}\n              onClick={this.handleRecordClick}\n              disabled={\n                this.isTTSPlaying ||\n                areClipsUploading ||\n                this.state.isAudioPillPlaying\n              }\n            />\n          }\n          pills={clips.map((clip, i) => (props: ContributionPillProps) => (\n            <RecordingPill\n              isTTSPlaying={this.state.isTTSPlaying}\n              {...props}\n              clip={clip}\n              status={\n                recordingIndex === i\n                  ? 'active'\n                  : clip.recording\n                  ? 'done'\n                  : 'pending'\n              }\n              onReplay={this.onReplay}\n              onRerecord={() => this.rerecord(i)}\n              onPlayingStatusUpdate={isPlaying =>\n                this.setState({ isAudioPillPlaying: isPlaying })\n              }>\n              {rerecordIndex === i && (\n                <Localized id=\"record-cancel\">\n                  <TextButton\n                    disabled={this.isTTSPlaying}\n                    onClick={this.cancelReRecord}\n                  />\n                </Localized>\n              )}\n            </RecordingPill>\n          ))}\n          reportModalProps={{\n            reasons: [\n              'offensive-language',\n              'grammar-or-spelling',\n              'different-language',\n              'difficult-pronounce',\n            ],\n            kind: 'sentence',\n            id:\n              recordingIndex == -1 || !clips[recordingIndex].sentence\n                ? null\n                : clips[recordingIndex].sentence.text,\n          }}\n          sentences={clips.map(({ sentence }) => sentence)}\n          shortcuts={[\n            {\n              key: 'shortcut-play-tts',\n              label: 'shortcut-play-tts-label',\n              action: this.handlePlayTTS,\n            },\n            {\n              key: 'shortcut-record-toggle',\n              label: 'shortcut-record-toggle-label',\n              action: this.handleRecordClick,\n            },\n            {\n              key: 'shortcut-enter',\n              label: 'submit-form-action',\n              action: () => {\n                // it's intended\n              },\n            },\n            {\n              key: 'shortcut-skip',\n              label: 'skip',\n              action: this.handleSkip,\n            },\n            {\n              key: 'shortcut-rerecord-toggle',\n              label: 'shortcut-rerecord-toggle-label',\n              action: this.handleRecordClick,\n            },\n          ]}\n          shortcutsDisabled={this.isTTSPlaying || this.state.isAudioPillPlaying}\n          type=\"speak\"\n          trackAppUsage={this.trackAppUsage}\n        />\n      </>\n    );\n  }\n}\n\nconst mapStateToProps = (state: StateTree) => {\n  const { sentences, isLoading } = Sentences.selectors.localeSentences(state);\n\n  return {\n    api: state.api,\n    locale: state.locale,\n    phrase_set: state.phrase_set,\n    sentences,\n    user: state.user,\n    isLoading,\n  };\n};\n\nconst mapDispatchToProps = {\n  addNotification: Notifications.actions.addPill,\n  addAchievement: Notifications.actions.addAchievement,\n  addUploads: Uploads.actions.add,\n  removeSentences: Sentences.actions.remove,\n  tallyRecording: User.actions.tallyRecording,\n  refreshUser: User.actions.refresh,\n  updateUser: User.actions.update,\n  saveAccount: User.actions.saveAccount,\n};\n\nexport default withRouter(\n  localeConnector(\n    withLocalization(\n      connect<PropsFromState, any>(\n        mapStateToProps,\n        mapDispatchToProps\n      )(SpeakPage)\n    )\n  )\n);\n"],"sourceRoot":""}