aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/search
diff options
context:
space:
mode:
authorab <abdullah_ahmed@brown.edu>2019-07-23 11:38:10 -0400
committerab <abdullah_ahmed@brown.edu>2019-07-23 11:38:10 -0400
commit13c016d7f7765acda7f6ce2d69c14597469f55d7 (patch)
tree6fcf409ee4f0035443aa4fb67a05d24aae09689d /src/client/views/search
parentbd841fe56540e1a9177d2872310b10fefcb4acd1 (diff)
parentd880e4b2fcb4e7bab3ee25d63209b173efcf37c0 (diff)
merged
Diffstat (limited to 'src/client/views/search')
-rw-r--r--src/client/views/search/FieldFilters.tsx2
-rw-r--r--src/client/views/search/FilterBox.tsx27
-rw-r--r--src/client/views/search/IconBar.tsx4
-rw-r--r--src/client/views/search/IconButton.tsx42
-rw-r--r--src/client/views/search/Pager.scss47
-rw-r--r--src/client/views/search/SearchBox.scss6
-rw-r--r--src/client/views/search/SearchBox.tsx225
-rw-r--r--src/client/views/search/SearchItem.scss302
-rw-r--r--src/client/views/search/SearchItem.tsx58
9 files changed, 431 insertions, 282 deletions
diff --git a/src/client/views/search/FieldFilters.tsx b/src/client/views/search/FieldFilters.tsx
index 648aac20a..7a33282d2 100644
--- a/src/client/views/search/FieldFilters.tsx
+++ b/src/client/views/search/FieldFilters.tsx
@@ -34,7 +34,7 @@ export class FieldFilters extends React.Component<FieldFilterProps> {
<div className="field-filters">
<CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.titleFieldStatus} updateStatus={this.props.updateTitleStatus} title={Keys.TITLE} />
<CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.authorFieldStatus} updateStatus={this.props.updateAuthorStatus} title={Keys.AUTHOR} />
- <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.dataFieldStatus} updateStatus={this.props.updateDataStatus} title={Keys.DATA} />
+ <CheckBox default={false} numCount={3} parent={this} originalStatus={this.props.dataFieldStatus} updateStatus={this.props.updateDataStatus} title={"Deleted Docs"} />
</div>
);
}
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 6053db595..4b1887339 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -6,7 +6,7 @@ import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-s
import { library } from '@fortawesome/fontawesome-svg-core';
import { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
-import { DocTypes } from '../../documents/Documents';
+import { DocumentType } from '../../documents/Documents';
import { Cast, StrCast } from '../../../new_fields/Types';
import * as _ from "lodash";
import { ToggleBar } from './ToggleBar';
@@ -35,7 +35,7 @@ export enum Keys {
export class FilterBox extends React.Component {
static Instance: FilterBox;
- public _allIcons: string[] = [DocTypes.AUDIO, DocTypes.COL, DocTypes.HIST, DocTypes.IMG, DocTypes.LINK, DocTypes.PDF, DocTypes.TEXT, DocTypes.VID, DocTypes.WEB];
+ public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.HIST, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB];
//if true, any keywords can be used. if false, all keywords are required.
//this also serves as an indicator if the word status filter is applied
@@ -48,6 +48,7 @@ export class FilterBox extends React.Component {
@observable private _authorFieldStatus: boolean = true;
@observable private _dataFieldStatus: boolean = true;
//this also serves as an indicator if the collection status filter is applied
+ @observable public _deletedDocsStatus: boolean = false;
@observable private _collectionStatus = false;
@observable private _collectionSelfStatus = true;
@observable private _collectionParentStatus = true;
@@ -94,6 +95,9 @@ export class FilterBox extends React.Component {
}
});
+
+ let el = acc[i] as HTMLElement;
+ el.click();
}
});
}
@@ -168,13 +172,13 @@ export class FilterBox extends React.Component {
if (this._authorFieldStatus) {
finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR);
}
- if (this._dataFieldStatus) {
+ if (this._deletedDocsStatus) {
finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA);
}
return finalQuery;
}
- get fieldFiltersApplied() { return !(this._dataFieldStatus && this._authorFieldStatus && this._titleFieldStatus); }
+ get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); }
//TODO: basically all of this
//gets all of the collections of all the docviews that are selected
@@ -244,12 +248,19 @@ export class FilterBox extends React.Component {
return "+(" + finalColString + ")" + query;
}
+ get filterTypes() {
+ return this._icons.length === 9 ? undefined : this._icons;
+ }
+
@action
filterDocsByType(docs: Doc[]) {
+ if (this._icons.length === 9) {
+ return docs;
+ }
let finalDocs: Doc[] = [];
docs.forEach(doc => {
let layoutresult = Cast(doc.type, "string");
- if (!layoutresult || this._icons.includes(layoutresult)) {
+ if (layoutresult && this._icons.includes(layoutresult)) {
finalDocs.push(doc);
}
});
@@ -339,7 +350,7 @@ export class FilterBox extends React.Component {
updateAuthorStatus(newStat: boolean) { this._authorFieldStatus = newStat; }
@action.bound
- updateDataStatus(newStat: boolean) { this._dataFieldStatus = newStat; }
+ updateDataStatus(newStat: boolean) { this._deletedDocsStatus = newStat; }
@action.bound
updateCollectionStatus(newStat: boolean) { this._collectionStatus = newStat; }
@@ -355,7 +366,7 @@ export class FilterBox extends React.Component {
getParentCollectionStatus() { return this._collectionParentStatus; }
getTitleStatus() { return this._titleFieldStatus; }
getAuthorStatus() { return this._authorFieldStatus; }
- getDataStatus() { return this._dataFieldStatus; }
+ getDataStatus() { return this._deletedDocsStatus; }
getActiveFilters() {
console.log(this._authorFieldStatus, this._titleFieldStatus, this._dataFieldStatus);
@@ -433,7 +444,7 @@ export class FilterBox extends React.Component {
<div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleFieldOpen} /></div>
</div>
<div className="filter-panel"><FieldFilters
- titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._dataFieldStatus} authorFieldStatus={this._authorFieldStatus}
+ titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._deletedDocsStatus} authorFieldStatus={this._authorFieldStatus}
updateAuthorStatus={this.updateAuthorStatus} updateDataStatus={this.updateDataStatus} updateTitleStatus={this.updateTitleStatus} /> </div>
</div>
</div>
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index 744dd898a..4712b0abc 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -4,7 +4,7 @@ import { observable, action } from 'mobx';
// import "./SearchBox.scss";
import "./IconBar.scss";
import "./IconButton.scss";
-import { DocTypes } from '../../documents/Documents';
+import { DocumentType } from '../../documents/Documents';
import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faTimesCircle, faCheckCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
@@ -63,7 +63,7 @@ export class IconBar extends React.Component {
<div className="type-outer">
<div className={"type-icon all"}
onClick={this.selectAll}>
- <FontAwesomeIcon className="fontawesome-icon" icon={faCheckCircle} />
+ <FontAwesomeIcon className="fontawesome-icon" icon={faCheckCircle} />
</div>
<div className="filter-description">Select All</div>
</div>
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index 23ab42de0..bfe2c7d0b 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -6,7 +6,7 @@ import "./IconButton.scss";
import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faVideo, faCaretDown } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library, icon } from '@fortawesome/fontawesome-svg-core';
-import { DocTypes } from '../../documents/Documents';
+import { DocumentType } from '../../documents/Documents';
import '../globalCssVariables.scss';
import * as _ from "lodash";
import { IconBar } from './IconBar';
@@ -80,25 +80,25 @@ export class IconButton extends React.Component<IconButtonProps>{
@action.bound
getIcon() {
switch (this.props.type) {
- case (DocTypes.NONE):
+ case (DocumentType.NONE):
return faBan;
- case (DocTypes.AUDIO):
+ case (DocumentType.AUDIO):
return faMusic;
- case (DocTypes.COL):
+ case (DocumentType.COL):
return faObjectGroup;
- case (DocTypes.HIST):
+ case (DocumentType.HIST):
return faChartBar;
- case (DocTypes.IMG):
+ case (DocumentType.IMG):
return faImage;
- case (DocTypes.LINK):
+ case (DocumentType.LINK):
return faLink;
- case (DocTypes.PDF):
+ case (DocumentType.PDF):
return faFilePdf;
- case (DocTypes.TEXT):
+ case (DocumentType.TEXT):
return faStickyNote;
- case (DocTypes.VID):
+ case (DocumentType.VID):
return faVideo;
- case (DocTypes.WEB):
+ case (DocumentType.WEB):
return faGlobeAsia;
default:
return faCaretDown;
@@ -149,25 +149,25 @@ export class IconButton extends React.Component<IconButtonProps>{
getFA = () => {
switch (this.props.type) {
- case (DocTypes.NONE):
+ case (DocumentType.NONE):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faBan} />);
- case (DocTypes.AUDIO):
+ case (DocumentType.AUDIO):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faMusic} />);
- case (DocTypes.COL):
+ case (DocumentType.COL):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faObjectGroup} />);
- case (DocTypes.HIST):
+ case (DocumentType.HIST):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faChartBar} />);
- case (DocTypes.IMG):
+ case (DocumentType.IMG):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faImage} />);
- case (DocTypes.LINK):
+ case (DocumentType.LINK):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faLink} />);
- case (DocTypes.PDF):
+ case (DocumentType.PDF):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faFilePdf} />);
- case (DocTypes.TEXT):
+ case (DocumentType.TEXT):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faStickyNote} />);
- case (DocTypes.VID):
+ case (DocumentType.VID):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faVideo} />);
- case (DocTypes.WEB):
+ case (DocumentType.WEB):
return (<FontAwesomeIcon className="fontawesome-icon" icon={faGlobeAsia} />);
default:
return (<FontAwesomeIcon className="fontawesome-icon" icon={faCaretDown} />);
diff --git a/src/client/views/search/Pager.scss b/src/client/views/search/Pager.scss
deleted file mode 100644
index 2b9c81b93..000000000
--- a/src/client/views/search/Pager.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-@import "../globalCssVariables";
-
-.search-pager {
- background-color: $dark-color;
- border-radius: 10px;
- width: 500px;
- display: flex;
- justify-content: center;
- // margin-left: 27px;
- float: right;
- margin-right: 74px;
- margin-left: auto;
-
- // flex-direction: column;
-
- .search-arrows {
- display: flex;
- justify-content: center;
- margin: 10px;
- width: 50%;
-
- .arrow {
- -webkit-transition: all 0.2s ease-in-out;
- -moz-transition: all 0.2s ease-in-out;
- -o-transition: all 0.2s ease-in-out;
- transition: all 0.2s ease-in-out;
-
- .fontawesome-icon {
- color: $light-color;
- width: 20px;
- height: 20px;
- margin-right: 2px;
- margin-left: 2px;
- // opacity: .7;
- }
- }
-
- .pager-title {
- text-align: center;
- // font-size: 8px;
- // margin-bottom: 10px;
- color: $light-color;
- // padding: 2px;
- width: 40%;
- }
- }
-} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 7541b328a..fcdc79220 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -41,7 +41,7 @@
}
.searchBox-results {
- margin-left: 27px;
+ margin-right: 136px;
top: 300px;
display: flex;
flex-direction: column;
@@ -50,6 +50,9 @@
height: 100%;
// overflow: hidden;
// overflow-y: auto;
+ max-height: 560px;
+ overflow: hidden;
+ overflow-y: auto;
.no-result {
width: 500px;
@@ -61,5 +64,6 @@
text-transform: uppercase;
text-align: left;
font-weight: bold;
+ margin-left: 28px;
}
} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 393a5a924..2214ac8af 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,38 +1,49 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { observable, action, runInAction } from 'mobx';
+import { observable, action, runInAction, flow, computed } from 'mobx';
import "./SearchBox.scss";
import "./FilterBox.scss";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { SetupDrag } from '../../util/DragManager';
import { Docs } from '../../documents/Documents';
-import { NumCast } from '../../../new_fields/Types';
+import { NumCast, Cast } from '../../../new_fields/Types';
import { Doc } from '../../../new_fields/Doc';
import { SearchItem } from './SearchItem';
-import { DocServer } from '../../DocServer';
import * as rp from 'request-promise';
import { Id } from '../../../new_fields/FieldSymbols';
import { SearchUtil } from '../../util/SearchUtil';
import { RouteStore } from '../../../server/RouteStore';
import { FilterBox } from './FilterBox';
+import { Utils } from '../../../Utils';
+
@observer
export class SearchBox extends React.Component {
@observable private _searchString: string = "";
@observable private _resultsOpen: boolean = false;
- @observable private _results: Doc[] = [];
+ @observable private _searchbarOpen: boolean = false;
+ @observable private _results: [Doc, string[]][] = [];
+ private _resultsSet = new Map<Doc, number>();
@observable private _openNoResults: boolean = false;
- @observable public _pageNum: number = 0;
- //temp
- @observable public _maxNum: number = 10;
+ @observable private _visibleElements: JSX.Element[] = [];
+
+ private resultsRef = React.createRef<HTMLDivElement>();
+
+ private _isSearch: ("search" | "placeholder" | undefined)[] = [];
+ private _numTotalResults = -1;
+ private _endIndex = -1;
static Instance: SearchBox;
+ private _maxSearchIndex: number = 0;
+ private _curRequest?: Promise<any> = undefined;
+
constructor(props: any) {
super(props);
SearchBox.Instance = this;
+ this.resultsScrolled = this.resultsScrolled.bind(this);
}
@action
@@ -49,19 +60,21 @@ export class SearchBox extends React.Component {
onChange(e: React.ChangeEvent<HTMLInputElement>) {
this._searchString = e.target.value;
- if (this._searchString === "") {
- this._results = [];
- this._openNoResults = false;
- }
+ this._openNoResults = false;
+ this._results = [];
+ this._resultsSet.clear();
+ this._visibleElements = [];
+ this._numTotalResults = -1;
+ this._endIndex = -1;
+ this._curRequest = undefined;
+ this._maxSearchIndex = 0;
}
- enter = (e: React.KeyboardEvent) => {
- if (e.key === "Enter") { this.submitSearch(); }
- }
+ enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { this.submitSearch(); } };
public static async convertDataUri(imageUri: string, returnedFilename: string) {
try {
- let posting = DocServer.prepend(RouteStore.dataUriToImage);
+ let posting = Utils.prepend(RouteStore.dataUriToImage);
const returnedUri = await rp.post(posting, {
body: {
uri: imageUri,
@@ -78,38 +91,97 @@ export class SearchBox extends React.Component {
@action
submitSearch = async () => {
- let query = this._searchString; // searchbox gets query
- let results: Doc[];
-
+ let query = this._searchString;
query = FilterBox.Instance.getFinalQuery(query);
+ this._results = [];
+ this._resultsSet.clear();
+ this._isSearch = [];
+ this._visibleElements = [];
+ FilterBox.Instance.closeFilter();
//if there is no query there should be no result
if (query === "") {
- results = [];
+ return;
}
else {
- //gets json result into a list of documents that can be used
- //these are filtered by type
- results = await this.getResults(query);
+ this._endIndex = 12;
+ this._maxSearchIndex = 0;
+ this._numTotalResults = -1;
+ await this.getResults(query);
}
runInAction(() => {
this._resultsOpen = true;
- this._results = results;
+ this._searchbarOpen = true;
this._openNoResults = true;
+ this.resultsScrolled();
});
}
- @action
+ getAllResults = async (query: string) => {
+ return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 });
+ }
+
+ 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(" ")})` : "");
+ }
+
+
+ private lockPromise?: Promise<void>;
getResults = async (query: string) => {
- const { docs } = await SearchUtil.Search(query, true);
- return FilterBox.Instance.filterDocsByType(docs);
+ if (this.lockPromise) {
+ await this.lockPromise;
+ }
+ this.lockPromise = new Promise(async res => {
+ while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) {
+ this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: 10, hl: true, "hl.fl": "*" }).then(action(async (res: SearchUtil.DocSearchResult) => {
+
+ // happens at the beginning
+ if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) {
+ this._numTotalResults = res.numFound;
+ }
+
+ const highlighting = res.highlighting || {};
+ const highlightList = res.docs.map(doc => highlighting[doc[Id]]);
+ 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]);
+ let filteredDocs = FilterBox.Instance.filterDocsByType(docs);
+ runInAction(() => {
+ // this._results.push(...filteredDocs);
+ filteredDocs.forEach(doc => {
+ const index = this._resultsSet.get(doc);
+ const highlight = highlights[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]);
+ } else {
+ this._results[index][1].push(...hlights);
+ }
+ });
+ });
+
+ this._curRequest = undefined;
+ }));
+ this._maxSearchIndex += 10;
+
+ await this._curRequest;
+ }
+ this.resultsScrolled();
+ res();
+ });
+ return this.lockPromise;
}
collectionRef = React.createRef<HTMLSpanElement>();
startDragCollection = async () => {
- const results = await this.getResults(FilterBox.Instance.getFinalQuery(this._searchString));
- const docs = results.map(doc => {
+ let res = await this.getAllResults(FilterBox.Instance.getFinalQuery(this._searchString));
+ let filtered = FilterBox.Instance.filterDocsByType(res.docs);
+ // console.log(this._results)
+ const docs = filtered.map(doc => {
const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
if (isProto) {
return Doc.MakeDelegate(doc);
@@ -141,7 +213,8 @@ export class SearchBox extends React.Component {
y += 300;
}
}
- return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` });
+ return Docs.Create.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` });
+
}
@action.bound
@@ -150,6 +223,7 @@ export class SearchBox extends React.Component {
this._openNoResults = false;
FilterBox.Instance.closeFilter();
this._resultsOpen = true;
+ this._searchbarOpen = true;
FilterBox.Instance._pointerTime = e.timeStamp;
}
@@ -157,34 +231,107 @@ export class SearchBox extends React.Component {
closeSearch = () => {
FilterBox.Instance.closeFilter();
this.closeResults();
+ this._searchbarOpen = false;
}
@action.bound
closeResults() {
this._resultsOpen = false;
this._results = [];
+ this._resultsSet.clear();
+ this._visibleElements = [];
+ this._numTotalResults = -1;
+ this._endIndex = -1;
+ this._curRequest = undefined;
}
+ @action
+ resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => {
+ let scrollY = e ? e.currentTarget.scrollTop : this.resultsRef.current ? this.resultsRef.current.scrollTop : 0;
+ let buffer = 4;
+ let startIndex = Math.floor(Math.max(0, scrollY / 70 - buffer));
+ let endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (560 / 70) + buffer));
+
+ this._endIndex = endIndex === -1 ? 12 : endIndex;
+
+ if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) {
+ this._visibleElements = [<div className="no-result">No Search Results</div>];
+ return;
+ }
+
+ if (this._numTotalResults <= this._maxSearchIndex) {
+ this._numTotalResults = this._results.length;
+ }
+
+ // only hit right at the beginning
+ // visibleElements is all of the elements (even the ones you can't see)
+ else if (this._visibleElements.length !== this._numTotalResults) {
+ // undefined until a searchitem is put in there
+ this._visibleElements = Array<JSX.Element>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
+ // indicates if things are placeholders
+ this._isSearch = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
+ }
+
+ for (let i = 0; i < this._numTotalResults; i++) {
+ //if the index is out of the window then put a placeholder in
+ //should ones that have already been found get set to placeholders?
+ if (i < startIndex || i > endIndex) {
+ if (this._isSearch[i] !== "placeholder") {
+ this._isSearch[i] = "placeholder";
+ this._visibleElements[i] = <div className="searchBox-placeholder" key={`searchBox-placeholder-${i}`}>Loading...</div>;
+ }
+ }
+ else {
+ if (this._isSearch[i] !== "search") {
+ let result: [Doc, 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]} />;
+ 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]} />;
+ this._isSearch[i] = "search";
+ }
+ }
+ }
+ }
+ }
+ if (this._maxSearchIndex >= this._numTotalResults) {
+ this._visibleElements.length = this._results.length;
+ this._isSearch.length = this._results.length;
+ }
+ }
+
+ @computed
+ get resFull() { return this._numTotalResults <= 8; }
+
+ @computed
+ get resultHeight() { return this._numTotalResults * 70; }
+
render() {
return (
<div className="searchBox-container">
<div className="searchBox-bar">
- <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} ref={this.collectionRef}>
+ <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} ref={this.collectionRef} title="Drag Results as Collection">
<FontAwesomeIcon icon="object-group" size="lg" />
</span>
<input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..."
className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter}
- style={{ width: this._resultsOpen ? "500px" : "100px" }} />
+ style={{ width: this._searchbarOpen ? "500px" : "100px" }} />
+ <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>
</div>
- <div className="searchBox-results" style={this._resultsOpen ? { display: "flex" } : { display: "none" }}>
- {(this._results.length !== 0) ? (
- this._results.map(result => <SearchItem doc={result} query={this._searchString} key={result[Id]} />)
- ) :
- this._openNoResults ? (<div className="no-result">No Search Results</div>) : null}
- </div>
- <div style={this._results.length !== 0 ? { display: "flex" } : { display: "none" }}>
- {/* <Pager /> */}
+ <div className="searchBox-results" onScroll={this.resultsScrolled} style={{
+ display: this._resultsOpen ? "flex" : "none",
+ height: this.resFull ? "560px" : this.resultHeight, overflow: this.resFull ? "auto" : "visible"
+ }} ref={this.resultsRef}>
+ {this._visibleElements}
</div>
</div>
);
diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss
index fa4c9cb38..273d49349 100644
--- a/src/client/views/search/SearchItem.scss
+++ b/src/client/views/search/SearchItem.scss
@@ -6,20 +6,25 @@
justify-content: flex-end;
height: 70px;
z-index: 0;
+}
- .search-item {
- width: 500px;
- background: $light-color-secondary;
- border-color: $intermediate-color;
- border-bottom-style: solid;
- padding: 10px;
- height: 70px;
- z-index: 0;
- display: inline-block;
-
- .main-search-info {
- display: flex;
- flex-direction: row;
+.searchBox-placeholder,
+.search-overview .search-item {
+ width: 500px;
+ background: $light-color-secondary;
+ border-color: $intermediate-color;
+ border-bottom-style: solid;
+ padding: 10px;
+ height: 70px;
+ z-index: 0;
+ display: inline-block;
+
+ .main-search-info {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+
+ .search-title-container {
width: 100%;
.search-title {
@@ -28,160 +33,183 @@
width: 100%;
font-weight: bold;
}
+ }
- .search-info {
+ .search-info {
+ display: flex;
+ justify-content: flex-end;
+
+ .link-container.item {
+ margin-left: auto;
+ margin-right: auto;
+ height: 26px;
+ width: 26px;
+ border-radius: 13px;
+ background: $dark-color;
+ color: $light-color-secondary;
display: flex;
- justify-content: flex-end;
-
- .link-container.item {
- margin-left: auto;
- margin-right: auto;
- height: 26px;
- width: 26px;
- border-radius: 13px;
- background: $dark-color;
- color: $light-color-secondary;
- display: flex;
- justify-content: center;
- align-items: center;
- -webkit-transition: all 0.2s ease-in-out;
- -moz-transition: all 0.2s ease-in-out;
- -o-transition: all 0.2s ease-in-out;
- transition: all 0.2s ease-in-out;
- transform-origin: top right;
- overflow: hidden;
+ justify-content: center;
+ align-items: center;
+ -webkit-transition: all 0.2s ease-in-out;
+ -moz-transition: all 0.2s ease-in-out;
+ -o-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+ transform-origin: top right;
+ overflow: hidden;
+ position: relative;
+
+ .link-count {
+ opacity: 1;
+ position: absolute;
+ z-index: 1000;
+ text-align: center;
+ -webkit-transition: opacity 0.2s ease-in-out;
+ -moz-transition: opacity 0.2s ease-in-out;
+ -o-transition: opacity 0.2s ease-in-out;
+ transition: opacity 0.2s ease-in-out;
+ }
+
+ .link-extended {
+ // display: none;
+ visibility: hidden;
+ opacity: 0;
position: relative;
+ z-index: 500;
+ overflow: hidden;
+ -webkit-transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s;
+ -moz-transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s;
+ -o-transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s;
+ transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s;
+ // transition-delay: 1s;
+ }
- .link-count {
- opacity: 1;
- position: absolute;
- z-index: 1000;
- text-align: center;
- -webkit-transition: opacity 0.2s ease-in-out;
- -moz-transition: opacity 0.2s ease-in-out;
- -o-transition: opacity 0.2s ease-in-out;
- transition: opacity 0.2s ease-in-out;
- }
+ }
- .link-extended {
- opacity: 0;
- position: relative;
- z-index: 500;
- overflow: hidden;
- -webkit-transition: opacity 0.2s ease-in-out;
- -moz-transition: opacity 0.2s ease-in-out;
- -o-transition: opacity 0.2s ease-in-out;
- transition: opacity 0.2s ease-in-out;
- }
- }
+ .link-container.item:hover {
+ width: 70px;
+ }
- .link-container.item:hover {
- width: 70px;
- }
+ .link-container.item:hover .link-count {
+ opacity: 0;
+ }
- .link-container.item:hover .link-count {
- opacity: 0;
- }
+ .link-container.item:hover .link-extended {
+ opacity: 1;
+ visibility: visible;
+ // display: inline;
+ }
- .link-container.item:hover .link-extended {
- opacity: 1;
- }
-
- .icon-icons {
- width:50px
- }
- .icon-live {
- width:175px;
+ .icon-icons {
+ width: 50px
+ }
+
+ .icon-live {
+ width: 175px;
+ }
+
+ .icon-icons,
+ .icon-live {
+ height: 50px;
+ margin: auto;
+ overflow: hidden;
+
+ .search-type {
+ display: inline-block;
+ width: 100%;
+ position: absolute;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ margin-right: 5px;
}
- .icon-icons, .icon-live {
- height:50px;
- margin:auto;
+ .pdfBox-cont {
overflow: hidden;
- .search-type {
- display: inline-block;
- width:100%;
- position: absolute;
- justify-content: center;
- align-items: center;
- position: relative;
- margin-right: 5px;
- }
- .pdfBox-cont {
- overflow: hidden;
-
- img {
- width:100% !important;
- height:auto !important;
- }
- }
- .search-type:hover+.search-label {
- opacity: 1;
+ img {
+ width: 100% !important;
+ height: auto !important;
}
+ }
- .search-label {
- font-size: 10;
- position: relative;
- right: 0px;
- text-transform: capitalize;
- opacity: 0;
- -webkit-transition: opacity 0.2s ease-in-out;
- -moz-transition: opacity 0.2s ease-in-out;
- -o-transition: opacity 0.2s ease-in-out;
- transition: opacity 0.2s ease-in-out;
- }
+ .search-type:hover+.search-label {
+ opacity: 1;
}
- .icon-live:hover {
- height:175px;
- .pdfBox-cont {
- img {
- width:100% !important;
- }
- }
+ .search-label {
+ font-size: 10;
+ position: relative;
+ right: 0px;
+ text-transform: capitalize;
+ opacity: 0;
+ -webkit-transition: opacity 0.2s ease-in-out;
+ -moz-transition: opacity 0.2s ease-in-out;
+ -o-transition: opacity 0.2s ease-in-out;
+ transition: opacity 0.2s ease-in-out;
}
}
- .search-info:hover {
- width:60%;
+
+ .icon-live:hover {
+ height: 175px;
+
+ .pdfBox-cont {
+ img {
+ width: 100% !important;
+ }
+ }
}
}
- }
- .search-item:hover~.searchBox-instances,
- .searchBox-instances:hover,
- .searchBox-instances:active {
- opacity: 1;
- background: $lighter-alt-accent;
- -webkit-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
+ .search-info:hover {
+ width: 60%;
+ }
}
+}
- .search-item:hover {
- transition: all 0.2s;
- background: $lighter-alt-accent;
- }
+.search-item:hover~.searchBox-instances,
+.searchBox-instances:hover,
+.searchBox-instances:active {
+ opacity: 1;
+ background: $lighter-alt-accent;
+ -webkit-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+}
- .searchBox-instances {
- float: left;
- opacity: 1;
- width: 150px;
- transition: all 0.2s ease;
- color: black;
- transform-origin: top right;
- -webkit-transform: scale(0);
- -ms-transform: scale(0);
- transform: scale(0);
- }
+.search-item:hover {
+ transition: all 0.2s;
+ background: $lighter-alt-accent;
+}
+.searchBox-instances {
+ float: left;
+ opacity: 1;
+ width: 150px;
+ transition: all 0.2s ease;
+ color: black;
+ transform-origin: top right;
+ -webkit-transform: scale(0);
+ -ms-transform: scale(0);
+ transform: scale(0);
}
+
+
.search-overview:hover {
z-index: 1;
}
+
+.searchBox-placeholder {
+ min-height: 70px;
+ margin-left: 150px;
+ text-transform: uppercase;
+ text-align: left;
+ font-weight: bold;
+}
+
.collection {
- display:flex;
+ display: flex;
}
+
.collection-item {
- width:35px;
+ width: 35px;
} \ No newline at end of file
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 2b8d3eda3..5b0e20348 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -8,7 +8,7 @@ import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
-import { DocTypes } from "../../documents/Documents";
+import { DocumentType } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
import { SetupDrag, DragManager } from "../../util/DragManager";
import { LinkManager } from "../../util/LinkManager";
@@ -33,6 +33,7 @@ import { DocServer } from "../../DocServer";
export interface SearchItemProps {
doc: Doc;
query?: string;
+ highlighting: string[];
}
library.add(faCaretUp);
@@ -58,9 +59,9 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
async fetchDocuments() {
let aliases = (await SearchUtil.GetViewsOfDocument(this.props.doc)).filter(doc => doc !== this.props.doc);
- const { docs } = await SearchUtil.Search(`data_l:"${this.props.doc[Id]}"`, true);
+ const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.doc[Id]}"` });
const map: Map<Doc, Doc> = new Map;
- const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search(`data_l:"${doc[Id]}"`, true).then(result => result.docs)));
+ const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
docs.forEach(doc => map.delete(doc));
runInAction(() => {
@@ -145,10 +146,12 @@ export class SearchItem extends React.Component<SearchItemProps> {
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;
@@ -167,7 +170,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
}
fitToBox = () => {
- let bounds = Doc.ComputeContentBounds(this.props.doc);
+ 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];
}
@@ -185,7 +188,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
if (!this._useIcons) {
let renderDoc = this.props.doc;
//let box: number[] = [];
- if (layoutresult.indexOf(DocTypes.COL) !== -1) {
+ 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)];
@@ -210,8 +213,8 @@ export class SearchItem extends React.Component<SearchItemProps> {
onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))}
onPointerLeave={action(() => this._displayDim = 50)} >
<DocumentView
- fitToBox={this.fitToBox}
- Document={newRenderDoc}
+ fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1}
+ Document={this.props.doc}
addDocument={returnFalse}
removeDocument={returnFalse}
ScreenToLocalTransform={Transform.Identity}
@@ -239,15 +242,15 @@ export class SearchItem extends React.Component<SearchItemProps> {
if (this._previewDoc) {
DocServer.DeleteDocument(this._previewDoc[Id]);
}
- let button = layoutresult.indexOf(DocTypes.PDF) !== -1 ? faFilePdf :
- layoutresult.indexOf(DocTypes.IMG) !== -1 ? faImage :
- layoutresult.indexOf(DocTypes.TEXT) !== -1 ? faStickyNote :
- layoutresult.indexOf(DocTypes.VID) !== -1 ? faFilm :
- layoutresult.indexOf(DocTypes.COL) !== -1 ? faObjectGroup :
- layoutresult.indexOf(DocTypes.AUDIO) !== -1 ? faMusic :
- layoutresult.indexOf(DocTypes.LINK) !== -1 ? faLink :
- layoutresult.indexOf(DocTypes.HIST) !== -1 ? faChartBar :
- layoutresult.indexOf(DocTypes.WEB) !== -1 ? faGlobeAsia :
+ let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf :
+ layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage :
+ layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote :
+ layoutresult.indexOf(DocumentType.VID) !== -1 ? faFilm :
+ layoutresult.indexOf(DocumentType.COL) !== -1 ? faObjectGroup :
+ layoutresult.indexOf(DocumentType.AUDIO) !== -1 ? faMusic :
+ layoutresult.indexOf(DocumentType.LINK) !== -1 ? faLink :
+ layoutresult.indexOf(DocumentType.HIST) !== -1 ? faChartBar :
+ layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia :
faCaretUp;
return <div onPointerDown={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE); })} >
<FontAwesomeIcon icon={button} size="2x" />
@@ -281,7 +284,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); }
highlightDoc = (e: React.PointerEvent) => {
- if (this.props.doc.type === DocTypes.LINK) {
+ if (this.props.doc.type === DocumentType.LINK) {
if (this.props.doc.anchor1 && this.props.doc.anchor2) {
let doc1 = Cast(this.props.doc.anchor1, Doc, null);
@@ -298,18 +301,18 @@ export class SearchItem extends React.Component<SearchItemProps> {
}
unHighlightDoc = (e: React.PointerEvent) => {
- if (this.props.doc.type === DocTypes.LINK) {
+ if (this.props.doc.type === DocumentType.LINK) {
if (this.props.doc.anchor1 && this.props.doc.anchor2) {
let doc1 = Cast(this.props.doc.anchor1, Doc, null);
let doc2 = Cast(this.props.doc.anchor2, Doc, null);
- doc1 && (doc1.libraryBrush = false);
- doc2 && (doc2.libraryBrush = false);
+ doc1 && (doc1.libraryBrush = undefined);
+ doc2 && (doc2.libraryBrush = undefined);
}
} else {
let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
docViews.forEach(element => {
- element.props.Document.libraryBrush = false;
+ element.props.Document.libraryBrush = undefined;
});
}
}
@@ -341,12 +344,15 @@ export class SearchItem extends React.Component<SearchItemProps> {
<div className="search-item" onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc} id="result"
onClick={this.onClick} onPointerDown={this.pointerDown} >
<div className="main-search-info">
- <div title="Drag as document" onPointerDown={this.onPointerDown}> <FontAwesomeIcon icon="file" size="lg" /> </div>
- <div className="search-title" id="result" >{this.props.doc.title}</div>
+ <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>
<div className="search-info" style={{ width: this._useIcons ? "15%" : "400px" }}>
<div className={`icon-${this._useIcons ? "icons" : "live"}`}>
- <div className="search-type" >{this.DocumentIcon()}</div>
- <div className="search-label">{this.props.doc.type}</div>
+ <div className="search-type" title="Click to Preview">{this.DocumentIcon}</div>
+ <div className="search-label">{this.props.doc.type ? this.props.doc.type : "Other"}</div>
</div>
<div className="link-container item">
<div className="link-count">{this.linkCount}</div>
@@ -356,7 +362,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
</div>
</div>
<div className="searchBox-instances">
- {this.props.doc.type === DocTypes.LINK ? <LinkContextMenu doc1={Cast(this.props.doc.anchor1, Doc, new Doc())} doc2={Cast(this.props.doc.anchor2, Doc, new Doc())} /> :
+ {this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={Cast(this.props.doc.anchor1, Doc, new Doc())} doc2={Cast(this.props.doc.anchor2, Doc, new Doc())} /> :
<SelectorContextMenu {...this.props} />}
</div>
</div>