diff options
Diffstat (limited to 'src/client/views/search')
| -rw-r--r-- | src/client/views/search/SearchBox.tsx | 189 |
1 files changed, 116 insertions, 73 deletions
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 0c1a419aa..384e6d654 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -6,7 +6,6 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCastAsync, Field, FieldType } from '../../../fields/Doc'; import { DirectLinks, DocData } from '../../../fields/DocSymbols'; -import { Id } from '../../../fields/FieldSymbols'; import { DocCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocUtils } from '../../documents/Documents'; @@ -16,16 +15,111 @@ import { SearchUtil } from '../../util/SearchUtil'; import { SettingsManager } from '../../util/SettingsManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; +import { ObservableReactComponent } from '../ObservableReactComponent'; import { CollectionDockingView } from '../collections/CollectionDockingView'; import { IRecommendation, Recommendation } from '../newlightbox/components'; import { fetchRecommendations } from '../newlightbox/utils'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import './SearchBox.scss'; +import { Id } from '../../../fields/FieldSymbols'; +import { ClientUtils } from '../../../ClientUtils'; const DAMPENING_FACTOR = 0.9; const MAX_ITERATIONS = 25; const ERROR = 0.03; +export interface SearchBoxItemProps { + Document: Doc; + searchString: string; + isLinkSearch: boolean; + matchedKeys: string[]; + className: string; + linkFrom: Doc | undefined; + selectItem: (doc: Doc) => void; + linkCreateAnchor?: () => Doc | undefined; + linkCreated?: (link: Doc) => void; +} +@observer +export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps> { + constructor(props: SearchBoxItemProps) { + super(props); + makeObservable(this); + } + + /** + * @param {Doc} doc - doc to be selected + * + * This method selects a doc by either jumping to it (centering/zooming in on it) + * or opening it in a new tab. + */ + selectElement = async (doc: Doc, finishFunc: () => void) => { + await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc); + }; + + /** + * @param {Doc} doc - doc of the search result that has been clicked on + * + * This method is called when the user clicks on a search result. The _selectedResult is + * updated accordingly and the doc is highlighted with the selectElement method. + */ + onResultClick = action(async (doc: Doc) => { + this._props.selectItem(doc); + this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._props.searchString, undefined, false)); + }); + + componentWillUnmount(): void { + const doc = this._props.Document; + DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true); + } + + @undoBatch + makeLink = action((linkTo: Doc) => { + const linkFrom = this._props.linkCreateAnchor?.(); + if (linkFrom) { + const link = DocUtils.MakeLink(linkFrom, linkTo, {}); + link && this._props.linkCreated?.(link); + } + }); + + render() { + // eslint-disable-next-line no-use-before-define + const formattedType = SearchBox.formatType(StrCast(this._props.Document.type), StrCast(this._props.Document.type_collection)); + const { title } = this._props.Document; + + return ( + <Tooltip placement="right" title={<div className="dash-tooltip">{title as string}</div>}> + <div + onClick={ + this._props.isLinkSearch + ? () => this.makeLink(this._props.Document) + : e => { + this.onResultClick(this._props.Document); + e.stopPropagation(); + } + } + style={{ + fontWeight: LinkManager.Links(this._props.linkFrom).find( + link => + Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) || + Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document) + ) + ? 'bold' + : '', + }} + className={this._props.className}> + <div className="searchBox-result-title">{title as string}</div> + <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}> + {formattedType} + </div> + <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}> + {this._props.matchedKeys.join(', ')} + </div> + </div> + </Tooltip> + ); + } +} + export interface SearchBoxProps extends FieldViewProps { linkSearch: boolean; linkFrom?: (() => Doc | undefined) | undefined; @@ -112,26 +206,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { }); /** - * @param {Doc} doc - doc of the search result that has been clicked on - * - * This method is called when the user clicks on a search result. The _selectedResult is - * updated accordingly and the doc is highlighted with the selectElement method. - */ - onResultClick = action(async (doc: Doc) => { - this._selectedResult = doc; - this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false)); - }); - - @undoBatch - makeLink = action((linkTo: Doc) => { - const linkFrom = this._props.linkCreateAnchor?.(); - if (linkFrom) { - const link = DocUtils.MakeLink(linkFrom, linkTo, {}); - link && this._props.linkCreated?.(link); - } - }); - - /** * @param {Doc[]} docs - docs to be searched through recursively * @param {number, Doc => void} func - function to be called on each doc * @@ -217,7 +291,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { if (doc[DocData][DirectLinks].size === 0) { this._linkedDocsOut.set(doc, new Set(this._results.keys())); - this._results.forEach((_, linkedDoc) => { + this._results.forEach((__, linkedDoc) => { this._linkedDocsIn.get(linkedDoc)?.add(doc); }); } else { @@ -251,7 +325,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { pageRankIteration(): boolean { let converged = true; const pageRankFromAll = (1 - DAMPENING_FACTOR) / this._results.size; - const nextPageRanks = new Map<Doc, number>(); this._results.forEach((_, doc) => { @@ -346,16 +419,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { }); /** - * @param {Doc} doc - doc to be selected - * - * This method selects a doc by either jumping to it (centering/zooming in on it) - * or opening it in a new tab. - */ - selectElement = async (doc: Doc, finishFunc: () => void) => { - await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc); - }; - - /** * This method returns a JSX list of the options in the select drop-down menu, which * is used to filter the types of documents that appear in the search results. */ @@ -365,7 +428,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { return selectValues.map(value => ( <option key={value} value={value}> - {SearchBox.formatType(value, '')} + {ClientUtils.cleanDocumentTypeExt(value as DocumentType)} </option> )); } @@ -374,56 +437,36 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { * This method renders the search input box, select drop-down menu, and search results. */ render() { - let validResults = 0; - const isLinkSearch: boolean = this._props.linkSearch; - const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank - const resultsJSX = [] as any[]; + const linkFrom = this._props.linkFrom?.(); - const fromDoc = this._props.linkFrom?.(); - - sortedResults.forEach(result => { + let validResults = 0; + sortedResults.forEach(([Document, matchedKeys]) => { let className = 'searchBox-results-scroll-view-result'; - if (this._selectedResult === result[0]) { + if (this._selectedResult === Document) { className += ' searchBox-results-scroll-view-result-selected'; } - const formattedType = SearchBox.formatType(StrCast(result[0].type), StrCast(result[0].type_collection)); - const { title } = result[0]; - - if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === result[0].type) { + if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === Document.type) { validResults++; resultsJSX.push( - <Tooltip key={result[0][Id]} placement="right" title={<div className="dash-tooltip">{title as string}</div>}> - <div - onClick={ - isLinkSearch - ? () => this.makeLink(result[0]) - : e => { - this.onResultClick(result[0]); - e.stopPropagation(); - } - } - style={{ - fontWeight: LinkManager.Links(fromDoc).find( - link => Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, fromDoc!), result[0] as Doc) || Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, fromDoc!)?.annotationOn), result[0] as Doc) - ) - ? 'bold' - : '', - }} - className={className}> - <div className="searchBox-result-title">{title as string}</div> - <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}> - {formattedType} - </div> - <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}> - {result[1].join(', ')} - </div> - </div> - </Tooltip> + <SearchBoxItem + key={Document[Id]} + Document={Document} + selectItem={action((doc: Doc) => { + this._selectedResult = doc; + })} + isLinkSearch={isLinkSearch} + searchString={this._searchString} + matchedKeys={matchedKeys} + linkFrom={linkFrom} + className={className} + linkCreateAnchor={this._props.linkCreateAnchor} + linkCreated={this._props.linkCreated} + /> ); } }); |
