<commit_before>import React from 'react'
import uniq from 'lodash/uniq'

import Grid from '@material-ui/core/Grid'

import normalizeWordData from '../../core/normalizeWordData/normalizeWordData'
import makeCards from '../../utils/makeCards/makeCards'

import splitByWord from '../../utils/splitByWord/splitByWord'
import wordToData from '../../utils/wordToData/wordToData'
import assertUnreachable from '../../utils/assertUnreachable/assertUnreachable'
import downloadAndSaveDeck from '../../utils/downloadAndSaveDeck/downloadAndSaveDeck'
import getWordsAndCardsCount from '../../utils/getWordsAndCardsCount/getWordsAndCardsCount'

import Header from '../Header/Header'
import DownloadSection from '../DownloadSection/DownloadSection'
import ResultCards from '../ResultCards/ResultCards'
import UserWords from '../UserWords/UserWords'
import DeckName from '../DeckName/DeckName'

import {
    WordIsLoading,
    WordFetchError,
    WordFetchStatusOrCardsData
} from '../../types.d'

import './App.css'

interface State {
    words: string[]
    inputValue: string
    wordsFetchStatusOrCardsData: {
        [key: string]: WordFetchStatusOrCardsData
    }
    deckName: string
    isDeckBeingDownloaded: boolean
}

export default class App extends React.Component<{}, State> {
    state: State = {
        words: [],
        inputValue: '',
        wordsFetchStatusOrCardsData: {},
        deckName: 'English words',
        isDeckBeingDownloaded: false
    }

    componentDidMount() {
        if (localStorage.state) {
            const newState = JSON.parse(localStorage.state)
            newState.isDeckBeingDownloaded = false

            try {
                this.setState(newState, () => {
                    if (navigator.onLine) {
                        this.handleOnline()
                    }
                })
            } catch (error) {
                console.error(error)
            }
        }

        window.addEventListener('online', this.handleOnline)
    }

    componentWillUnmount() {
        window.removeEventListener('online', this.handleOnline)
    }

    handleOnline = () => {
        this.state.words.forEach(word => {
            if (
                this.state.wordsFetchStatusOrCardsData[word] ===
                WordFetchError.Offline
            ) {
                this.downloadAndSaveWordData(word)
            }
        })
    }

    componentDidUpdate() {
        localStorage.state = JSON.stringify(this.state)
    }

    handleDeckNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        this.setState({
            deckName: event.target.value
        })
    }

    handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        this.setState({
            inputValue: event.target.value
        })
    }

    handleDeleteButtonClick = (wordToDelete: string) => (
        e: React.MouseEvent
    ) => {
        this.setState(prevState => {
            const newWordsFetchStatusOrCardsData = {
                ...prevState.wordsFetchStatusOrCardsData
            }
            delete newWordsFetchStatusOrCardsData[wordToDelete]

            return {
                words: prevState.words.filter(word => word !== wordToDelete),
                wordsFetchStatusOrCardsData: newWordsFetchStatusOrCardsData
            }
        })
    }

    handleSubmit = (event: React.FormEvent) => {
        event.preventDefault()

        const newWords = splitByWord(this.state.inputValue)

        const allWords = uniq([...newWords, ...this.state.words])
        allWords.forEach(this.downloadAndSaveWordData)

        this.setState({
            words: allWords,
            inputValue: ''
        })
    }

    downloadAndSaveWordData = (word: string) => {
        const cardsAreAlreadyDownloaded = Array.isArray(
            this.state.wordsFetchStatusOrCardsData[word]
        )
        const wordIsAlreadyNotFound =
            this.state.wordsFetchStatusOrCardsData[word] ===
            WordFetchError.NotFound

        if (cardsAreAlreadyDownloaded || wordIsAlreadyNotFound) {
            return
        }

        this.setState(
            prevState => ({
                wordsFetchStatusOrCardsData: {
                    ...prevState.wordsFetchStatusOrCardsData,
                    [word]: WordIsLoading
                }
            }),
            async () => {
                const wordData = await wordToData(word)

                if (wordData.status) {
                    this.setState(prevState => ({
                        wordsFetchStatusOrCardsData: {
                            ...prevState.wordsFetchStatusOrCardsData,
                            [word]: wordData.status
                        }
                    }))
                } else if (wordData.payload) {
                    this.setState(prevState => ({
                        wordsFetchStatusOrCardsData: {
                            ...prevState.wordsFetchStatusOrCardsData,
                            [word]: normalizeWordData(wordData.payload)
                        }
                    }))
                } else {
                    assertUnreachable(wordData)
                }
            }
        )
    }

    handleDownload = (event: React.MouseEvent) => {
        this.setState({
            isDeckBeingDownloaded: true
        })

        const cards = makeCards(
            this.state.words,
            this.state.wordsFetchStatusOrCardsData
        )

        downloadAndSaveDeck(this.state.deckName, cards)
            .then(() => {
                this.setState({
                    isDeckBeingDownloaded: false
                })
            })
            .catch(console.error)
    }

    handleEnterPress = (event: React.KeyboardEvent) => {
        if (event.key === 'Enter' && event.shiftKey === false) {
            event.preventDefault()
            this.handleSubmit(event)
        }
    }

    render() {
        return (
            <div className="App">
                <Grid container spacing={16}>
                    <Grid item xs={12}>
                        <Header />
                    </Grid>

                    <Grid item xs={12}>
                        <UserWords
                            value={this.state.inputValue}
                            onChange={this.handleInputChange}
                            onKeyDown={this.handleEnterPress}
                            onSubmit={this.handleSubmit}
                        /><commit_msg>refactor: reorder methods in App<commit_after>import React from 'react'
import uniq from 'lodash/uniq'

import Grid from '@material-ui/core/Grid'

import normalizeWordData from '../../core/normalizeWordData/normalizeWordData'
import makeCards from '../../utils/makeCards/makeCards'

import splitByWord from '../../utils/splitByWord/splitByWord'
import wordToData from '../../utils/wordToData/wordToData'
import assertUnreachable from '../../utils/assertUnreachable/assertUnreachable'
import downloadAndSaveDeck from '../../utils/downloadAndSaveDeck/downloadAndSaveDeck'
import getWordsAndCardsCount from '../../utils/getWordsAndCardsCount/getWordsAndCardsCount'

import Header from '../Header/Header'
import DownloadSection from '../DownloadSection/DownloadSection'
import ResultCards from '../ResultCards/ResultCards'
import UserWords from '../UserWords/UserWords'
import DeckName from '../DeckName/DeckName'

import {
    WordIsLoading,
    WordFetchError,
    WordFetchStatusOrCardsData
} from '../../types.d'

import './App.css'

interface State {
    words: string[]
    inputValue: string
    wordsFetchStatusOrCardsData: {
        [key: string]: WordFetchStatusOrCardsData
    }
    deckName: string
    isDeckBeingDownloaded: boolean
}

export default class App extends React.Component<{}, State> {
    state: State = {
        words: [],
        inputValue: '',
        wordsFetchStatusOrCardsData: {},
        deckName: 'English words',
        isDeckBeingDownloaded: false
    }

    componentDidMount() {
        if (localStorage.state) {
            const newState = JSON.parse(localStorage.state)
            newState.isDeckBeingDownloaded = false

            try {
                this.setState(newState, () => {
                    if (navigator.onLine) {
                        this.handleOnline()
                    }
                })
            } catch (error) {
                console.error(error)
            }
        }

        window.addEventListener('online', this.handleOnline)
    }

    componentWillUnmount() {
        window.removeEventListener('online', this.handleOnline)
    }

    componentDidUpdate() {
        localStorage.state = JSON.stringify(this.state)
    }

    handleOnline = () => {
        this.state.words.forEach(word => {
            if (
                this.state.wordsFetchStatusOrCardsData[word] ===
                WordFetchError.Offline
            ) {
                this.downloadAndSaveWordData(word)
            }
        })
    }

    handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        this.setState({
            inputValue: event.target.value
        })
    }

    handleDeckNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        this.setState({
            deckName: event.target.value
        })
    }

    handleEnterPress = (event: React.KeyboardEvent) => {
        if (event.key === 'Enter' && event.shiftKey === false) {
            event.preventDefault()
            this.handleSubmit(event)
        }
    }

    handleSubmit = (event: React.FormEvent) => {
        event.preventDefault()

        const newWords = splitByWord(this.state.inputValue)
        newWords.forEach(this.downloadAndSaveWordData)

        this.setState({
            words: uniq([...newWords, ...this.state.words]),
            inputValue: ''
        })
    }

    downloadAndSaveWordData = (word: string) => {
        this.setState(
            prevState => ({
                wordsFetchStatusOrCardsData: {
                    ...prevState.wordsFetchStatusOrCardsData,
                    [word]: WordIsLoading
                }
            }),
            async () => {
                const wordData = await wordToData(word)

                if (wordData.status) {
                    this.setState(prevState => ({
                        wordsFetchStatusOrCardsData: {
                            ...prevState.wordsFetchStatusOrCardsData,
                            [word]: wordData.status
                        }
                    }))
                } else if (wordData.payload) {
                    this.setState(prevState => ({
                        wordsFetchStatusOrCardsData: {
                            ...prevState.wordsFetchStatusOrCardsData,
                            [word]: normalizeWordData(wordData.payload)
                        }
                    }))
                } else {
                    assertUnreachable(wordData)
                }
            }
        )
    }

    handleDeleteButtonClick = (wordToDelete: string) => (
        e: React.MouseEvent
    ) => {
        this.setState(prevState => {
            const newWordsFetchStatusOrCardsData = {
                ...prevState.wordsFetchStatusOrCardsData
            }
            delete newWordsFetchStatusOrCardsData[wordToDelete]

            return {
                words: prevState.words.filter(word => word !== wordToDelete),
                wordsFetchStatusOrCardsData: newWordsFetchStatusOrCardsData
            }
        })
    }

    handleDownload = (event: React.MouseEvent) => {
        this.setState({
            isDeckBeingDownloaded: true
        })

        const cards = makeCards(
            this.state.words,
            this.state.wordsFetchStatusOrCardsData
        )

        downloadAndSaveDeck(this.state.deckName, cards)
            .then(() => {
                this.setState({
                    isDeckBeingDownloaded: false
                })
            })
            .catch(console.error)
    }

    render() {
        return (
            <div className="App">
                <Grid container spacing={16}>
                    <Grid item xs={12}>
                        <Header />
                    </Grid>

                    <Grid item xs={12}>
                        <UserWords
                            value={this.state.inputValue}
                            onChange={this.handleInputChange}
                            onKeyDown={this.handleEnterPress}
                            onSubmit={this.handleSubmit}
                        />
{'fraction_non_alphanumeric': 0.10200988993459882, 'fraction_numerical': 0.000957090445047057, 'mean_word_length': 1.5805721341839885, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 34, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1176966', 'n_tokens_mistral': 3312, 'n_tokens_neox': 3159, 'n_words': 521}