diff --git a/src/components/FastRunMenu.tsx b/src/components/FastRunMenu.tsx index 9e76558..dfaed0c 100644 --- a/src/components/FastRunMenu.tsx +++ b/src/components/FastRunMenu.tsx @@ -1,8 +1,7 @@ -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { memo, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' -import { DefaultOptionType } from 'antd/lib/select' -import { AutoComplete } from 'antd' import { BaseSelectRef } from 'rc-select' +import { AutoComplete } from 'antd' import { join } from 'path' import { useWell } from '@asb/context' @@ -20,29 +19,18 @@ const transliterationTable = { 'z': 'я', 'x': 'ч', 'c': 'с', 'v': 'м', 'b': 'и', 'n': 'т', 'm': 'ь', ',': 'б', '.': 'ю', '<': 'б', '>': 'ю', } -const transliterateToRu = (text: string): string => { - let out = text.toLowerCase() - Object.entries(transliterationTable).map(([en, ru]) => out = out.replaceAll(en, ru)) - return out +type OptionType = { + value: string + label: ReactNode } -const transliterateToEn = (text: string): string => { - let out = text.toLowerCase() - Object.entries(transliterationTable).map(([en, ru]) => out = out.replaceAll(ru, en)) - return out -} +const transliterateToRu = (text: string) => Object.entries(transliterationTable).reduce((out, [en, ru]) => out.replaceAll(en, ru), text.toLowerCase()) +const transliterateToEn = (text: string) => Object.entries(transliterationTable).reduce((out, [en, ru]) => out.replaceAll(ru, en), text.toLowerCase()) +const applyVars = (route: string, vars?: object): string => !vars ? route : + Object.entries(vars).reduce((out, [key, value]) => out.replaceAll(`{${key}}`, value), route) -const applyVars = (route: string, vars?: Record): string => { - if (!vars) return route - let out = route - Object.entries(vars).forEach(([key, value]) => { - out = out.replaceAll(`{${key}}`, value) - }) - return out -} - -const makeOptions = (items: PrivateWellMenuItem[], vars?: Record): DefaultOptionType[] => { - const out: DefaultOptionType[] = [] +const makeOptions = (items: PrivateWellMenuItem[], vars?: object): OptionType[] => { + const out: OptionType[] = [] items.forEach((item) => { if (!hasPermission(item.permissions)) return out.push({ @@ -60,9 +48,23 @@ const makeOptions = (items: PrivateWellMenuItem[], vars?: Record): return out } +const makeFindInString = (text: string) => { + const searchText = text.toLowerCase() + const toRu = transliterateToRu(searchText) + const toEn = transliterateToEn(searchText) + + return (sourceText: string) => { + const text = sourceText.toLowerCase() + let idx = text.indexOf(searchText) + if (idx < 0 && (idx = text.indexOf(toRu)) < 0 && (idx = text.indexOf(toEn)) < 0) return false + return { from: idx, to: idx + searchText.length } + } +} + export const FastRunMenu = memo(() => { const [isOpen, setIsOpen] = useState(false) const [value, setValue] = useState() + const [results, setResults] = useState([]) const ref = useRef(null) const [well] = useWell() @@ -97,6 +99,32 @@ export const FastRunMenu = memo(() => { onClose() }, [onClose]) + const onSearch = useCallback((text: string) => { + if (text.trim() === '') { + setResults(options) + return + } + const findInString = makeFindInString(text) + const results = options.map((option) => { + const label = String(option.label) + const idx = findInString(label.toLowerCase()) + if (!idx) return findInString(option.value.toLowerCase()) ? option : false + + return { + ...option, + label: <> + {label.slice(0, idx.from)} + + {label.slice(idx.from, idx.to)} + + {label.slice(idx.to)} + + } + }).filter(Boolean) as OptionType[] + + setResults(results) + }, [options]) + useEffect(() => { const listener = (event: KeyboardEvent) => { if (event.altKey && event.code === 'KeyA') @@ -114,14 +142,7 @@ export const FastRunMenu = memo(() => { ref.current?.scrollTo(0) }, [isOpen]) - const onFilter = (text: string, option: DefaultOptionType | undefined) => { - if (!option) return false - const search = (String(option.label).replaceAll(' > ', '') + String(option.value).replaceAll('/', '')).toLowerCase() - if (search.includes(text.toLowerCase())) return true - if (search.includes(transliterateToRu(text))) return true - if (search.includes(transliterateToEn(text))) return true - return false - } + useEffect(() => onSearch(''), [onSearch]) return isOpen ? (
@@ -129,13 +150,13 @@ export const FastRunMenu = memo(() => { ref={ref} autoFocus style={{ width: '100%' }} - options={options} + options={results} onBlur={onClose} onChange={setValue} + onSearch={onSearch} placeholder={'Введите название страницы...'} onSelect={onTextChanged} value={value} - filterOption={onFilter} />
) : <> diff --git a/src/styles/fast_run_menu.less b/src/styles/fast_run_menu.less index b3eee3a..299d3b0 100644 --- a/src/styles/fast_run_menu.less +++ b/src/styles/fast_run_menu.less @@ -7,3 +7,7 @@ border-radius: 5px; width: 50vw; } + +.fast-run-menu-text-found { + background-color: yellow; +}