All files / app nav.tsx

90% Statements 45/50
69.38% Branches 34/49
83.33% Functions 10/12
91.66% Lines 44/48

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158                                              334x 334x 334x 334x 334x 334x 334x   334x   9x         9x 9x 1x 1x 1x   1x   8x   9x 9x     334x 96x 96x 96x 96x 96x         334x                   57x       9x 48x       374x           48x 48x 48x 48x 48x   48x 48x                 48x   48x           130x                 1x                     2x 2x 2x                           1x                    
import {SavedSearch, SearchOff} from '@mui/icons-material'
import {AppBar, Autocomplete, Button, IconButton, Stack, TextField, Toolbar, Tooltip} from '@mui/material'
import {type SyntheticEvent, useContext, useState} from 'react'
import {InfoDrawerSetter} from './infoDrawer'
import {globToRegex, prepareRegex, special, wildcards} from './lib/utils'
import {ResourceContext} from './resources'
import {SettingsMenu} from './settingsMenu'
import {DictionaryMenu} from './dictionaryMenu'
import {extractMatches} from './processTerms'
import {isInDict, type TermTypes} from './building'
import {showTableTerm} from './table'
 
export function Nav({
  terms,
  asTable,
  setAsTable,
  add,
}: {
  terms?: readonly string[]
  asTable: boolean
  setAsTable: (asTable: boolean) => void
  add: (term: string | RegExp, type: TermTypes) => void
}) {
  const [inputTerm, setInputTerm] = useState('')
  const [highlightedTerm, setHighlightedTerm] = useState('')
  const [alreadyAdded, setAlreadyAdded] = useState(false)
  const [termSuggestions, setTermSuggestions] = useState<string[]>([])
  const [asRegEx, setAsRegEx] = useState(false)
  const updateInfoDrawerState = useContext(InfoDrawerSetter)
  const {collapsedTerms} = useContext(ResourceContext)
 
  const addTerm = (newTerm?: SyntheticEvent | string) => {
    const toAdd =
      newTerm ?
        'string' === typeof newTerm ? newTerm
        : 'innerText' in newTerm.target ? (newTerm.target.innerText as string)
        : inputTerm
      : inputTerm
    Eif (toAdd) {
      if (asRegEx) {
        let termPattern: string | RegExp = toAdd
        try {
          termPattern = new RegExp(termPattern)
        } catch {}
        add(termPattern, 'regex')
      } else {
        add(toAdd, 'fixed')
      }
      setTermSuggestions([])
      setInputTerm('')
    }
  }
  const updateTerm = (e: SyntheticEvent) => {
    const input = e.target
    if (input && 'value' in input && 'innerText' in input) {
      const term = (input['string' === typeof input.value ? 'value' : 'innerText'] as string).toLowerCase()
      setAlreadyAdded(isInDict(term))
      setInputTerm(term)
    } else E{
      setInputTerm('')
    }
  }
  return (
    <AppBar component="nav">
      <Toolbar variant="dense" sx={{justifyContent: 'space-between'}} disableGutters>
        <DictionaryMenu />
        <Stack direction="row" sx={{width: {sm: 'calc(min(500px, 50%))', xs: '100%'}}} spacing={{md: 1, sm: 0}}>
          <Autocomplete
            options={termSuggestions}
            value={inputTerm}
            onHighlightChange={(_, option) => setHighlightedTerm(option || '')}
            onKeyUp={e => {
              if (
                (e.code === 'Enter' || e.code === 'NumpadEnter') &&
                (!highlightedTerm || highlightedTerm === inputTerm)
              )
                return addTerm(inputTerm)
              Eif (highlightedTerm !== inputTerm) updateTerm(e)
            }}
            onChange={updateTerm}
            renderInput={params => (
              <TextField
                {...params}
                size="small"
                placeholder="Term Search"
                value={inputTerm}
                onChange={e => {
                  updateTerm(e)
                  const value = e.target.value
                  Eif (terms) {
                    const suggestions: string[] = []
                    Eif (value && collapsedTerms) {
                      let ex: RegExp | undefined
                      try {
                        ex = new RegExp(
                          asRegEx ? prepareRegex(value)
                          : wildcards.test(value) ? globToRegex(value)
                          : ';' + value.replace(special, '\\$&') + '[^;]*;',
                          'g',
                        )
                      } catch {
                        ex = new RegExp(';' + value.replace(special, '\\$&') + ';', 'g')
                      }
                      extractMatches('', ex, collapsedTerms, suggestions, 100)
                    }
                    setTermSuggestions(suggestions)
                  }
                }}
                color={alreadyAdded ? 'warning' : 'primary'}
              ></TextField>
            )}
            filterOptions={x => x}
            selectOnFocus
            clearOnBlur
            clearOnEscape
            handleHomeEndKeys
            fullWidth
            freeSolo
          ></Autocomplete>
          <Tooltip title={'regular expression characters are ' + (asRegEx ? 'active' : 'escaped')}>
            <IconButton aria-label="toggle regular expression" onClick={() => setAsRegEx(!asRegEx)}>
              {asRegEx ?
                <SavedSearch />
              : <SearchOff />}
            </IconButton>
          </Tooltip>
          <Button
            sx={{width: '6.7em', display: {sm: 'block', xs: 'none'}}}
            variant="outlined"
            disabled={!inputTerm}
            onClick={() => {
              Eif (inputTerm) {
                updateInfoDrawerState({type: 'add', state: {type: 'term', value: inputTerm}})
                Iif (alreadyAdded) showTableTerm(inputTerm)
              }
            }}
          >
            View
          </Button>
          <Button variant="contained" onClick={() => addTerm()} disabled={!inputTerm}>
            Add
          </Button>
        </Stack>
        <Stack direction="row">
          <Button
            variant="outlined"
            sx={{width: {md: 100, sm: 70}, p: {md: 1, sm: 0}, display: {sm: 'block', xs: 'none'}}}
            onClick={() => setAsTable(!asTable)}
          >
            {asTable ? 'Analyze' : 'Edit'}
          </Button>
          <SettingsMenu />
        </Stack>
      </Toolbar>
    </AppBar>
  )
}