diff options
| author | Bob Zeleznik <zzzman@gmail.com> | 2019-10-09 23:30:36 -0400 |
|---|---|---|
| committer | Bob Zeleznik <zzzman@gmail.com> | 2019-10-09 23:30:36 -0400 |
| commit | 5617124a794b8d256e3d24b143b8dd2403cce993 (patch) | |
| tree | 673a288cdbc853d697c2d8294312f563ae9f4e60 /src/client/views/search | |
| parent | 2cc7ca6b267d9dd6b1683d59b6966a021339bc18 (diff) | |
| parent | 3b1345616bf2f7101a21c182e0c9719b1e31f899 (diff) | |
merged
Diffstat (limited to 'src/client/views/search')
| -rw-r--r-- | src/client/views/search/FilterBox.tsx | 6 | ||||
| -rw-r--r-- | src/client/views/search/SearchBox.scss | 9 | ||||
| -rw-r--r-- | src/client/views/search/SearchBox.tsx | 63 | ||||
| -rw-r--r-- | src/client/views/search/SearchItem.scss | 13 | ||||
| -rw-r--r-- | src/client/views/search/SearchItem.tsx | 98 |
5 files changed, 88 insertions, 101 deletions
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index c13d1d276..da733d64b 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -195,11 +195,9 @@ export class FilterBox extends React.Component { collections.push(element.props.Document); } } - //gets the selected doc's containing view - let containingView = element.props.ContainingCollectionView; //makes sure collections aren't added more than once - if (containingView && !collections.includes(containingView.props.Document)) { - collections.push(containingView.props.Document); + if (element.props.ContainingCollectionDoc && !collections.includes(element.props.ContainingCollectionDoc)) { + collections.push(element.props.ContainingCollectionDoc); } }); diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss index 5ed33a596..0dd4d3dc5 100644 --- a/src/client/views/search/SearchBox.scss +++ b/src/client/views/search/SearchBox.scss @@ -34,6 +34,9 @@ &.searchBox-filter { align-self: stretch; + } + + &.searchBox-submit { margin-left: 2px; margin-right: 2px } @@ -45,6 +48,12 @@ } } +.searchBox-quickFilter { + width: 500px; + margin-left: 25px; + margin-top: 10px; +} + .searchBox-results { margin-right: 136px; top: 300px; diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 2ad69daca..be75a29e0 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,25 +1,25 @@ -import * as React from 'react'; -import { observer } from 'mobx-react'; -import { observable, action, runInAction, flow, computed } from 'mobx'; -import "./SearchBox.scss"; -import "./FilterBox.scss"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { library } from '@fortawesome/fontawesome-svg-core'; -import { SetupDrag } from '../../util/DragManager'; -import { Docs } from '../../documents/Documents'; -import { NumCast, Cast } from '../../../new_fields/Types'; -import { Doc } from '../../../new_fields/Doc'; -import { SearchItem } from './SearchItem'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; import * as rp from 'request-promise'; +import { Doc } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; -import { SearchUtil } from '../../util/SearchUtil'; +import { Cast, NumCast } from '../../../new_fields/Types'; import { RouteStore } from '../../../server/RouteStore'; -import { FilterBox } from './FilterBox'; -import { ReadStream } from 'fs'; -import * as $ from 'jquery'; -import { MainView } from '../MainView'; import { Utils } from '../../../Utils'; +import { Docs } from '../../documents/Documents'; +import { SetupDrag } from '../../util/DragManager'; +import { SearchUtil } from '../../util/SearchUtil'; +import { MainView } from '../MainView'; +import { FilterBox } from './FilterBox'; +import "./FilterBox.scss"; +import "./SearchBox.scss"; +import { SearchItem } from './SearchItem'; +import { IconBar } from './IconBar'; +import { string } from 'prop-types'; library.add(faTimes); @@ -29,7 +29,7 @@ export class SearchBox extends React.Component { @observable private _searchString: string = ""; @observable private _resultsOpen: boolean = false; @observable private _searchbarOpen: boolean = false; - @observable private _results: [Doc, string[]][] = []; + @observable private _results: [Doc, string[], string[]][] = []; private _resultsSet = new Map<Doc, number>(); @observable private _openNoResults: boolean = false; @observable private _visibleElements: JSX.Element[] = []; @@ -141,7 +141,7 @@ export class SearchBox extends React.Component { private get filterQuery() { const types = FilterBox.Instance.filterTypes; const includeDeleted = FilterBox.Instance.getDataStatus(); - return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); + return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); } @@ -161,6 +161,8 @@ export class SearchBox extends React.Component { const highlighting = res.highlighting || {}; const highlightList = res.docs.map(doc => highlighting[doc[Id]]); + const lines = new Map<string, string[]>(); + res.docs.map((doc, i) => lines.set(doc[Id], res.lines[i])); const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc)); const highlights: typeof res.highlighting = {}; docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]); @@ -170,12 +172,14 @@ export class SearchBox extends React.Component { filteredDocs.forEach(doc => { const index = this._resultsSet.get(doc); const highlight = highlights[doc[Id]]; + const line = lines.get(doc[Id]) || []; const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []; if (index === undefined) { this._resultsSet.set(doc, this._results.length); - this._results.push([doc, hlights]); + this._results.push([doc, hlights, line]); } else { this._results[index][1].push(...hlights); + this._results[index][2].push(...line); } }); }); @@ -222,7 +226,6 @@ export class SearchBox extends React.Component { doc.width = size; doc.height = size; } - doc.zoomBasis = 1; x += 250; if (x > 1000) { x = 0; @@ -234,7 +237,7 @@ export class SearchBox extends React.Component { } @action.bound - openSearch(e: React.PointerEvent) { + openSearch(e: React.SyntheticEvent) { e.stopPropagation(); this._openNoResults = false; FilterBox.Instance.closeFilter(); @@ -299,19 +302,21 @@ export class SearchBox extends React.Component { } else { if (this._isSearch[i] !== "search") { - let result: [Doc, string[]] | undefined = undefined; + let result: [Doc, string[], string[]] | undefined = undefined; if (i >= this._results.length) { this.getResults(this._searchString); if (i < this._results.length) result = this._results[i]; if (result) { - this._visibleElements[i] = <SearchItem doc={result[0]} key={result[0][Id]} highlighting={result[1]} />; + let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; this._isSearch[i] = "search"; } } else { result = this._results[i]; if (result) { - this._visibleElements[i] = <SearchItem doc={result[0]} key={result[0][Id]} highlighting={result[1]} />; + let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; this._isSearch[i] = "search"; } } @@ -338,12 +343,16 @@ export class SearchBox extends React.Component { <FontAwesomeIcon icon="object-group" size="lg" /> </span> <input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this.inputRef} - className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} + className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} onFocus={this.openSearch} style={{ width: this._searchbarOpen ? "500px" : "100px" }} /> + <button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={FilterBox.Instance.openFilter} onPointerDown={FilterBox.Instance.stopProp}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button> <button className="searchBox-barChild searchBox-submit" onClick={this.submitSearch} onPointerDown={FilterBox.Instance.stopProp}>Submit</button> - <button className="searchBox-barChild searchBox-filter" onClick={FilterBox.Instance.openFilter} onPointerDown={FilterBox.Instance.stopProp}>Filter</button> <button className="searchBox-barChild searchBox-close" title={"Close Search Bar"} onPointerDown={MainView.Instance.toggleSearch}><FontAwesomeIcon icon={faTimes} size="lg" /></button> </div> + {(this._numTotalResults > 0 || !this._searchbarOpen) ? (null) : + (<div className="searchBox-quickFilter" onPointerDown={this.openSearch}> + <div className="filter-panel"><IconBar /></div> + </div>)} <div className="searchBox-results" onScroll={this.resultsScrolled} style={{ display: this._resultsOpen ? "flex" : "none", height: this.resFull ? "560px" : this.resultHeight, overflow: this.resFull ? "auto" : "visible" diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss index 273d49349..62715c5eb 100644 --- a/src/client/views/search/SearchItem.scss +++ b/src/client/views/search/SearchItem.scss @@ -4,7 +4,6 @@ display: flex; flex-direction: row-reverse; justify-content: flex-end; - height: 70px; z-index: 0; } @@ -15,9 +14,12 @@ border-color: $intermediate-color; border-bottom-style: solid; padding: 10px; - height: 70px; + min-height: 70px; + max-height: 150px; + height: auto; z-index: 0; display: inline-block; + overflow: auto; .main-search-info { display: flex; @@ -26,6 +28,7 @@ .search-title-container { width: 100%; + overflow: hidden; .search-title { text-transform: uppercase; @@ -181,6 +184,12 @@ background: $lighter-alt-accent; } +.search-highlighting { + overflow: hidden; + text-overflow: ellipsis; + white-space: pre; +} + .searchBox-instances { float: left; opacity: 1; diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 41fc49c2e..a7822ed46 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -4,13 +4,10 @@ import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlob import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; +import { Doc } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; -import { ObjectField } from "../../../new_fields/ObjectField"; -import { RichTextField } from "../../../new_fields/RichTextField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../../Utils"; -import { DocServer } from "../../DocServer"; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, SetupDrag } from "../../util/DragManager"; @@ -28,8 +25,9 @@ import "./SelectorContextMenu.scss"; export interface SearchItemProps { doc: Doc; - query?: string; + query: string; highlighting: string[]; + lines: string[]; } library.add(faCaretUp); @@ -63,6 +61,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> { runInAction(() => { this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc })); this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); + }); } @@ -70,12 +69,12 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> { return () => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(target.x) + NumCast(target.width) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / 2; col.panX = newPanX; col.panY = newPanY; } - CollectionDockingView.Instance.AddRightSplit(col, undefined); + CollectionDockingView.AddRightSplit(col, undefined); }; } render() { @@ -109,7 +108,7 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> { unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc); - getOnClick = (col: Doc) => () => CollectionDockingView.Instance.AddRightSplit(col, undefined); + getOnClick = (col: Doc) => () => CollectionDockingView.AddRightSplit(col, undefined); render() { return ( @@ -127,68 +126,31 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> { export class SearchItem extends React.Component<SearchItemProps> { @observable _selected: boolean = false; - private _previewDoc?: Doc; onClick = () => { // I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like DocumentManager.Instance.jumpToDocument(this.props.doc, false); - if (this.props.doc.data instanceof RichTextField) { - this.highlightTextBox(this.props.doc); - } - // CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined); } @observable _useIcons = true; @observable _displayDim = 50; - highlightTextBox = (doc: Doc) => { - if (this.props.query) { - const fieldkey = 'search_string'; - if (Object.keys(doc).indexOf(fieldkey) === -1) { - doc.search_string = this.props.query; - } - else { - doc.search_string = undefined; - } - - } - } - - fitToBox = () => { - let bounds = Doc.ComputeContentBounds([this.props.doc]); - return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / Math.max((bounds.b - bounds.y), (bounds.r - bounds.x)), this._displayDim]; + componentDidMount() { + this.props.doc.search_string = this.props.query; + this.props.doc.search_fields = this.props.highlighting.join(", "); } - componentWillUnmount() { - if (this._previewDoc) { - DocServer.DeleteDocument(this._previewDoc[Id]); - } + this.props.doc.search_string = undefined; + this.props.doc.search_fields = undefined; } - //@computed @action public DocumentIcon() { let layoutresult = StrCast(this.props.doc.type); if (!this._useIcons) { - let renderDoc = this.props.doc; - //let box: number[] = []; - if (layoutresult.indexOf(DocumentType.COL) !== -1) { - renderDoc = Doc.MakeDelegate(renderDoc); - let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { - var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); - let box = () => [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / (bounds.r - bounds.x), this._displayDim]; - } let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); let returnYDimension = () => this._displayDim; - let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); - let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt - this._previewDoc = newRenderDoc; + let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension()); const docview = <div onPointerDown={action(() => { this._useIcons = !this._useIcons; @@ -201,6 +163,7 @@ export class SearchItem extends React.Component<SearchItemProps> { Document={this.props.doc} addDocument={returnFalse} removeDocument={returnFalse} + ruleProvider={undefined} ScreenToLocalTransform={Transform.Identity} addDocTab={returnFalse} pinToPres={returnFalse} @@ -209,25 +172,18 @@ export class SearchItem extends React.Component<SearchItemProps> { PanelHeight={returnYDimension} focus={emptyFunction} backgroundColor={returnEmptyString} - selectOnLoad={false} parentActive={returnFalse} whenActiveChanged={returnFalse} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne} ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} ContentScaling={scale} /> </div>; - const data = renderDoc.data; - if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); - newRenderDoc.preview = true; - newRenderDoc.search_string = this.props.query; return docview; } - if (this._previewDoc) { - DocServer.DeleteDocument(this._previewDoc[Id]); - } let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage : layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote : @@ -269,6 +225,12 @@ export class SearchItem extends React.Component<SearchItemProps> { @action pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); } + nextHighlight = (e: React.PointerEvent) => { + e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); + let sstring = StrCast(this.props.doc.search_string); + this.props.doc.search_string = ""; + setTimeout(() => this.props.doc.search_string = sstring, 0); + } highlightDoc = (e: React.PointerEvent) => { if (this.props.doc.type === DocumentType.LINK) { if (this.props.doc.anchor1 && this.props.doc.anchor2) { @@ -279,9 +241,9 @@ export class SearchItem extends React.Component<SearchItemProps> { Doc.BrushDoc(doc2); } } else { - DocumentManager.Instance.getAllDocumentViews(this.props.doc).forEach(element => - Doc.BrushDoc(element.props.Document)); + Doc.BrushDoc(this.props.doc); } + e.stopPropagation(); } unHighlightDoc = (e: React.PointerEvent) => { @@ -294,8 +256,7 @@ export class SearchItem extends React.Component<SearchItemProps> { Doc.UnBrushDoc(doc2); } } else { - DocumentManager.Instance.getAllDocumentViews(this.props.doc). - forEach(element => Doc.UnBrushDoc(element.props.Document)); + Doc.UnBrushDoc(this.props.doc); } } @@ -315,7 +276,7 @@ export class SearchItem extends React.Component<SearchItemProps> { onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => { e.stopPropagation(); const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc; - DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc], []), e.clientX, e.clientY, { + DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, { handlers: { dragComplete: emptyFunction }, hideSource: false, }); @@ -326,13 +287,14 @@ export class SearchItem extends React.Component<SearchItemProps> { const doc2 = Cast(this.props.doc.anchor2, Doc); return ( <div className="search-overview" onPointerDown={this.pointerDown} onContextMenu={this.onContextMenu}> - <div className="search-item" onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc} id="result" - onClick={this.onClick} onPointerDown={this.pointerDown} > + <div className="search-item" onPointerDown={this.nextHighlight} onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc} id="result" + onClick={this.onClick}> <div className="main-search-info"> <div title="Drag as document" onPointerDown={this.onPointerDown} style={{ marginRight: "7px" }}> <FontAwesomeIcon icon="file" size="lg" /> </div> <div className="search-title-container"> <div className="search-title">{StrCast(this.props.doc.title)}</div> - <div className="search-highlighting">Matched fields: {this.props.highlighting.join(", ")}</div> + <div className="search-highlighting">{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? this.props.lines[0] : ""}</div> + {this.props.lines.filter((m, i) => i).map((l, i) => <div id={i.toString()} className="search-highlighting">`${l}`</div>)} </div> <div className="search-info" style={{ width: this._useIcons ? "15%" : "400px" }}> <div className={`icon-${this._useIcons ? "icons" : "live"}`}> |
