diff --git a/web/src/componentTable.js b/web/src/componentTable.js index 712017942ec..072792ed274 100644 --- a/web/src/componentTable.js +++ b/web/src/componentTable.js @@ -618,6 +618,11 @@ class CategoryFilter extends React.Component { categories: {}, allCategories: false, searchString: "", + searchFulltextRegex: false, + searchFulltextRegexError: null, + searchMfrString: "", + searchMfrRegex: false, + searchMfrRegexError: null, abort: () => null } } @@ -643,7 +648,7 @@ class CategoryFilter extends React.Component { this.state.abort(); let query; if (this.state.allCategories) { - if (this.state.searchString.length < 3) { // prevent high ram usage + if (this.state.searchString.length < 3 && this.state.searchMfrString.length < 3) { // prevent high ram usage return []; } query = db.components; @@ -651,17 +656,57 @@ class CategoryFilter extends React.Component { else query = db.components.where("category").anyOf(this.collectActiveCategories()); - if (this.state.searchString.length !== 0) { - const words = this.state.searchString.split(/\s+/) - .filter(x => x.length > 0) - .map(x => x.toLocaleLowerCase()); - if (words.length > 0) { + const prepareWords = text => text.split(/\s+/) + .map(x => x.trim().toLocaleLowerCase()) + .filter(x => x.length > 0); + + const prepareRegex = (text, onError) => { + text = text.trim(); + if(!text) return; + + try { + return new RegExp(text, "i"); + }catch(e) { + onError(e.message.replace('Invalid regular expression: ', '')); + } + } + + this.state.searchFulltextRegexError = null; + if(this.state.searchFulltextRegex) { + const regex = prepareRegex(this.state.searchString, err => this.state.searchFulltextRegexError = err); + if(regex) { + query = query.filter(component => { + const text = componentText(component); + return regex.test(text); + }); + } + }else{ + const words = prepareWords(this.state.searchString); + if(words.length) { query = query.filter(component => { const text = componentText(component); return words.every(word => text.includes(word)); }); } } + + this.state.searchMfrRegexError = null; + if(this.state.searchMfrRegex) { + const regex = prepareRegex(this.state.searchMfrString, err => this.state.searchMfrRegexError = err); + if(regex) { + query = query.filter(component => { + return regex.test(component.mfr); + }); + } + }else{ + const words = prepareWords(this.state.searchMfrString); + if(words.length) { + query = query.filter(component => { + const text = component.mfr; + return words.every(word => text.includes(word)); + }); + } + } let aborted = false; this.setState({abort: () => aborted = true}); @@ -699,9 +744,9 @@ class CategoryFilter extends React.Component { this.setState(produce(this.state, this.selectNone), this.notifyParent); } - handleFulltextChange = e => { + handleInputChange = (recipe) => { this.setState(produce(this.state, draft => { - draft.searchString = e.target.value; + recipe(draft); if (!draft.allCategories && this.collectActiveCategories().length === 0) this.selectAll(draft); }), () => { @@ -710,9 +755,38 @@ class CategoryFilter extends React.Component { }); } + handleFulltextChange = e => { + this.handleInputChange(draft => { + draft.searchString = e.target.value; + }); + } + + handleFulltextRegexCheckboxChange = e => { + this.handleInputChange(draft => { + draft.searchFulltextRegex = e.target.checked; + }); + } + + handleMfrTextChange = e => { + this.handleInputChange(draft => { + draft.searchMfrString = e.target.value; + }); + } + + handleMfrRegexCheckboxChange = e => { + this.handleInputChange(draft => { + draft.searchMfrRegex = e.target.checked; + }); + } + handleClear = () => { this.setState(produce(this.state, draft => { draft.searchString = ""; + draft.searchFulltextRegex = false; + draft.searchFulltextRegexError = null; + draft.searchMfrString = ""; + draft.searchMfrRegex = false; + draft.searchMfrRegexError = null; if (draft.allCategories) { this.selectNone(draft); } @@ -734,9 +808,14 @@ class CategoryFilter extends React.Component { + + -