1 |
|
2 |
|
3 |
|
4 | import { InputGroup } from './inputgroup';
|
5 | import { ReactWidget } from './vdom';
|
6 | import { StringExt } from '@lumino/algorithm';
|
7 | import React, { useEffect, useState } from 'react';
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | export interface IFilterBoxProps {
|
13 | |
14 |
|
15 |
|
16 | caseSensitive?: boolean;
|
17 |
|
18 | |
19 |
|
20 |
|
21 | disabled?: boolean;
|
22 |
|
23 | |
24 |
|
25 |
|
26 | forceRefresh?: boolean;
|
27 |
|
28 | |
29 |
|
30 |
|
31 | initialQuery?: string;
|
32 |
|
33 | |
34 |
|
35 |
|
36 | inputRef?: React.RefObject<HTMLInputElement>;
|
37 |
|
38 | |
39 |
|
40 |
|
41 | placeholder?: string;
|
42 |
|
43 | |
44 |
|
45 |
|
46 | updateFilter: (
|
47 | filterFn: (item: string) => Partial<IScore> | null,
|
48 | query?: string
|
49 | ) => void;
|
50 |
|
51 | |
52 |
|
53 |
|
54 | useFuzzyFilter: boolean;
|
55 | }
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | export interface IScore {
|
61 | |
62 |
|
63 |
|
64 | score: number;
|
65 |
|
66 | |
67 |
|
68 |
|
69 | indices: number[] | null;
|
70 | }
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | export function fuzzySearch(source: string, query: string): IScore | null {
|
76 |
|
77 | let score = Infinity;
|
78 | let indices: number[] | null = null;
|
79 |
|
80 |
|
81 | const rgx = /\b\w/g;
|
82 |
|
83 | let continueSearch = true;
|
84 |
|
85 |
|
86 | while (continueSearch) {
|
87 |
|
88 | let rgxMatch = rgx.exec(source);
|
89 |
|
90 |
|
91 | if (!rgxMatch) {
|
92 | break;
|
93 | }
|
94 |
|
95 |
|
96 | let match = StringExt.matchSumOfDeltas(source, query, rgxMatch.index);
|
97 |
|
98 |
|
99 | if (!match) {
|
100 | break;
|
101 | }
|
102 |
|
103 |
|
104 | if (match && match.score <= score) {
|
105 | score = match.score;
|
106 | indices = match.indices;
|
107 | }
|
108 | }
|
109 |
|
110 |
|
111 | if (!indices || score === Infinity) {
|
112 | return null;
|
113 | }
|
114 |
|
115 |
|
116 | return {
|
117 | score,
|
118 | indices
|
119 | };
|
120 | }
|
121 |
|
122 | export const updateFilterFunction = (
|
123 | value: string,
|
124 | useFuzzyFilter: boolean,
|
125 | caseSensitive?: boolean
|
126 | ) => {
|
127 | return (item: string): Partial<IScore> | null => {
|
128 | if (useFuzzyFilter) {
|
129 |
|
130 | const query = value.toLowerCase();
|
131 |
|
132 | return fuzzySearch(item, query);
|
133 | }
|
134 | if (!caseSensitive) {
|
135 | item = item.toLocaleLowerCase();
|
136 | value = value.toLocaleLowerCase();
|
137 | }
|
138 | const i = item.indexOf(value);
|
139 | if (i === -1) {
|
140 | return null;
|
141 | }
|
142 | return {
|
143 | indices: [...Array(item.length).keys()].map(x => x + 1)
|
144 | };
|
145 | };
|
146 | };
|
147 |
|
148 | export const FilterBox = (props: IFilterBoxProps): JSX.Element => {
|
149 | const [filter, setFilter] = useState(props.initialQuery ?? '');
|
150 |
|
151 | if (props.forceRefresh) {
|
152 | useEffect(() => {
|
153 | props.updateFilter((item: string) => {
|
154 | return {};
|
155 | });
|
156 | }, []);
|
157 | }
|
158 |
|
159 | useEffect(() => {
|
160 |
|
161 | if (props.initialQuery !== undefined) {
|
162 | props.updateFilter(
|
163 | updateFilterFunction(
|
164 | props.initialQuery,
|
165 | props.useFuzzyFilter,
|
166 | props.caseSensitive
|
167 | ),
|
168 | props.initialQuery
|
169 | );
|
170 | }
|
171 | }, []);
|
172 |
|
173 | |
174 |
|
175 |
|
176 | const handleChange = (e: React.FormEvent<HTMLElement>) => {
|
177 | const target = e.target as HTMLInputElement;
|
178 | setFilter(target.value);
|
179 | props.updateFilter(
|
180 | updateFilterFunction(
|
181 | target.value,
|
182 | props.useFuzzyFilter,
|
183 | props.caseSensitive
|
184 | ),
|
185 | target.value
|
186 | );
|
187 | };
|
188 |
|
189 | return (
|
190 | <InputGroup
|
191 | className="jp-FilterBox"
|
192 | inputRef={props.inputRef}
|
193 | type="text"
|
194 | disabled={props.disabled}
|
195 | rightIcon="ui-components:search"
|
196 | placeholder={props.placeholder}
|
197 | onChange={handleChange}
|
198 | value={filter}
|
199 | />
|
200 | );
|
201 | };
|
202 |
|
203 |
|
204 |
|
205 |
|
206 | export const FilenameSearcher = (props: IFilterBoxProps): ReactWidget => {
|
207 | return ReactWidget.create(
|
208 | <FilterBox
|
209 | updateFilter={props.updateFilter}
|
210 | useFuzzyFilter={props.useFuzzyFilter}
|
211 | placeholder={props.placeholder}
|
212 | forceRefresh={props.forceRefresh}
|
213 | caseSensitive={props.caseSensitive}
|
214 | />
|
215 | );
|
216 | };
|