aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/search
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2019-10-09 23:30:36 -0400
committerBob Zeleznik <zzzman@gmail.com>2019-10-09 23:30:36 -0400
commit5617124a794b8d256e3d24b143b8dd2403cce993 (patch)
tree673a288cdbc853d697c2d8294312f563ae9f4e60 /src/client/views/search
parent2cc7ca6b267d9dd6b1683d59b6966a021339bc18 (diff)
parent3b1345616bf2f7101a21c182e0c9719b1e31f899 (diff)
merged
Diffstat (limited to 'src/client/views/search')
-rw-r--r--src/client/views/search/FilterBox.tsx6
-rw-r--r--src/client/views/search/SearchBox.scss9
-rw-r--r--src/client/views/search/SearchBox.tsx63
-rw-r--r--src/client/views/search/SearchItem.scss13
-rw-r--r--src/client/views/search/SearchItem.tsx98
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"}`}>