aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortschicke-brown <tyler_schicke@brown.edu>2019-07-14 23:35:46 -0400
committerGitHub <noreply@github.com>2019-07-14 23:35:46 -0400
commitd7c6f0da00d4ed56d28f679d6f7de1002684864a (patch)
tree24217f91f213e57c503d226bae1475acb84c6a1d /src
parent104cb14c140c0da5f3013203d11b2a1963ae14c1 (diff)
parenta65e1f4af074beebb6a78861cf29ca25a2c59b28 (diff)
Merge pull request #205 from browngraphicslab/search_virt
Search virtualization
Diffstat (limited to 'src')
-rw-r--r--src/client/util/SearchUtil.ts8
-rw-r--r--src/client/util/SerializationHelper.ts8
-rw-r--r--src/client/views/pdf/PDFMenu.tsx14
-rw-r--r--src/client/views/pdf/PDFViewer.tsx2
-rw-r--r--src/client/views/pdf/Page.tsx2
-rw-r--r--src/client/views/search/FilterBox.tsx5
-rw-r--r--src/client/views/search/Pager.scss47
-rw-r--r--src/client/views/search/Pager.tsx78
-rw-r--r--src/client/views/search/SearchBox.scss3
-rw-r--r--src/client/views/search/SearchBox.tsx183
-rw-r--r--src/client/views/search/SearchItem.scss307
-rw-r--r--src/client/views/search/SearchItem.tsx6
-rw-r--r--src/server/Search.ts5
-rw-r--r--src/server/index.ts8
14 files changed, 347 insertions, 329 deletions
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 338628960..674eeb1a8 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -14,11 +14,11 @@ export namespace SearchUtil {
numFound: number;
}
- export function Search(query: string, returnDocs: true): Promise<DocSearchResult>;
- export function Search(query: string, returnDocs: false): Promise<IdSearchResult>;
- export async function Search(query: string, returnDocs: boolean) {
+ export function Search(query: string, returnDocs: true, start?: number, count?: number): Promise<DocSearchResult>;
+ export function Search(query: string, returnDocs: false, start?: number, count?: number): Promise<IdSearchResult>;
+ export async function Search(query: string, returnDocs: boolean, start?: number, rows?: number) {
const result: IdSearchResult = JSON.parse(await rp.get(DocServer.prepend("/search"), {
- qs: { query }
+ qs: { query, start, rows }
}));
if (!returnDocs) {
return result;
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 17ae407c4..dca539f3b 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,5 +1,6 @@
import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr";
import { Field } from "../../new_fields/Doc";
+import { ClientUtils } from "./ClientUtils";
export namespace SerializationHelper {
let serializing: number = 0;
@@ -38,7 +39,12 @@ export namespace SerializationHelper {
serializing += 1;
if (!obj.__type) {
- throw Error("No property 'type' found in JSON.");
+ if (ClientUtils.RELEASE) {
+ console.warn("No property 'type' found in JSON.");
+ return undefined;
+ } else {
+ throw Error("No property 'type' found in JSON.");
+ }
}
if (!(obj.__type in serializationTypes)) {
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index feaa1f652..27c2a8f1a 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -18,7 +18,7 @@ export default class PDFMenu extends React.Component {
@observable private _transitionDelay: string = "";
- StartDrag: (e: PointerEvent, ele: HTMLDivElement) => void = emptyFunction;
+ StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction;
Highlight: (d: Doc | undefined, color: string | undefined) => void = emptyFunction;
Delete: () => void = emptyFunction;
Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction;
@@ -34,7 +34,7 @@ export default class PDFMenu extends React.Component {
private _offsetY: number = 0;
private _offsetX: number = 0;
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- private _commentCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _commentCont = React.createRef<HTMLButtonElement>();
private _snippetButton: React.RefObject<HTMLButtonElement> = React.createRef();
private _dragging: boolean = false;
@observable private _keyValue: string = "";
@@ -238,24 +238,24 @@ export default class PDFMenu extends React.Component {
render() {
let buttons = this.Status === "pdf" || this.Status === "snippet" ? [
- <button className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked} key="1"
+ <button key="1" className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked}
style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
<FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
</button>,
<button className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}><FontAwesomeIcon icon="comment-alt" size="lg" key="2" /></button>,
this.Status === "snippet" ? <button className="pdfMenu-button" title="Drag to Snippetize Selection" onPointerDown={this.snippetStart} ref={this._snippetButton}><FontAwesomeIcon icon="cut" size="lg" /></button> : undefined,
- <button className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} key="3"
+ <button key="3" className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin}
style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
<FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} />
</button>
] : [
- <button className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}><FontAwesomeIcon icon="trash-alt" size="lg" key="1" /></button>,
- <button className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}><FontAwesomeIcon icon="map-pin" size="lg" key="2" /></button>,
+ <button key="4" className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}><FontAwesomeIcon icon="trash-alt" size="lg" key="1" /></button>,
+ <button key="5" className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}><FontAwesomeIcon icon="map-pin" size="lg" key="2" /></button>,
<div className="pdfMenu-addTag" key="3">
<input onKeyDown={handleBackspace} onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
<input onKeyDown={handleBackspace} onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
</div>,
- <button className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}><FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" key="4" /></button>,
+ <button key="6" className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}><FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" key="4" /></button>,
];
return (
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 943454c33..01fd1c247 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -242,7 +242,7 @@ export class Viewer extends React.Component<IViewerProps> {
let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {});
mainAnnoDoc.title = "Annotation on " + StrCast(this.props.parent.Document.title);
- mainAnnoDoc.pdfDoc = this.props.parent.Document;
+ mainAnnoDoc.pdfDoc = this.props.parent.props.Document;
let minY = Number.MAX_VALUE;
this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => {
for (let anno of value) {
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 8b2c86684..3d8973414 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -154,7 +154,7 @@ export default class Page extends React.Component<IPageProps> {
* start a drag event and create or put the necessary info into the drag event.
*/
@action
- startDrag = (e: PointerEvent, ele: HTMLDivElement): void => {
+ startDrag = (e: PointerEvent, ele: HTMLElement): void => {
e.preventDefault();
e.stopPropagation();
let thisDoc = this.props.parent.Document;
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 58a873ced..435ca86e3 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -239,10 +239,13 @@ export class FilterBox extends React.Component {
@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);
}
});
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/Pager.tsx b/src/client/views/search/Pager.tsx
deleted file mode 100644
index 1c62773b1..000000000
--- a/src/client/views/search/Pager.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { faArrowCircleRight, faArrowCircleLeft } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { library } from '@fortawesome/fontawesome-svg-core';
-import "./Pager.scss";
-import { SearchBox } from './SearchBox';
-import { observable, action } from 'mobx';
-import { FilterBox } from './FilterBox';
-
-library.add(faArrowCircleRight);
-library.add(faArrowCircleLeft);
-
-@observer
-export class Pager extends React.Component {
-
- @observable _leftHover: boolean = false;
- @observable _rightHover: boolean = false;
-
- @action
- onLeftClick(e: React.PointerEvent) {
- FilterBox.Instance._pointerTime = e.timeStamp;
- if (SearchBox.Instance._pageNum > 0) {
- SearchBox.Instance._pageNum -= 1;
- }
- }
-
- @action
- onRightClick(e: React.PointerEvent) {
- FilterBox.Instance._pointerTime = e.timeStamp;
- if (SearchBox.Instance._pageNum + 1 < SearchBox.Instance._maxNum) {
- SearchBox.Instance._pageNum += 1;
- }
- }
-
- @action.bound
- mouseInLeft() {
- this._leftHover = true;
- }
-
- @action.bound
- mouseOutLeft() {
- this._leftHover = false;
- }
-
- @action.bound
- mouseInRight() {
- this._rightHover = true;
- }
-
- @action.bound
- mouseOutRight() {
- this._rightHover = false;
- }
-
- render() {
- return (
- <div className="search-pager">
- <div className="search-arrows">
- <div className="arrow"
- onPointerDown={this.onLeftClick} style={SearchBox.Instance._pageNum === 0 ? { opacity: .2 } : this._leftHover ? { opacity: 1 } : { opacity: .7 }}
- onMouseEnter={this.mouseInLeft} onMouseOut={this.mouseOutLeft}>
- <FontAwesomeIcon className="fontawesome-icon" icon={faArrowCircleLeft} />
- </div>
- <div className="pager-title">
- page {SearchBox.Instance._pageNum + 1} of {SearchBox.Instance._maxNum}
- </div>
- <div className="arrow"
- onPointerDown={this.onRightClick} style={SearchBox.Instance._pageNum === SearchBox.Instance._maxNum - 1 ? { opacity: .2 } : this._rightHover ? { opacity: 1 } : { opacity: .7 }}
- onMouseEnter={this.mouseInRight} onMouseOut={this.mouseOutRight}>
- <FontAwesomeIcon className="fontawesome-icon" icon={faArrowCircleRight} />
- </div>
- </div>
- </div>
- );
- }
-
-} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 08d3a65f1..324ba3063 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -45,7 +45,8 @@
top: 300px;
display: flex;
flex-direction: column;
- height: 560px;
+ margin-right: 72px;
+ max-height: 560px;
overflow: hidden;
overflow-y: auto;
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 121abf973..dc1d35b1c 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,6 +1,6 @@
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';
@@ -16,6 +16,7 @@ import { SearchUtil } from '../../util/SearchUtil';
import { RouteStore } from '../../../server/RouteStore';
import { FilterBox } from './FilterBox';
+
@observer
export class SearchBox extends React.Component {
@@ -23,16 +24,24 @@ export class SearchBox extends React.Component {
@observable private _resultsOpen: boolean = false;
@observable private _results: Doc[] = [];
@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,15 +58,16 @@ 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._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 {
@@ -78,38 +88,70 @@ 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._isSearch = [];
+ this._visibleElements = [];
//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._openNoResults = true;
+ this.resultsScrolled();
});
}
- @action
+ getAllResults = async (query: string) => {
+ return SearchUtil.Search(query, true, 0, 10000000);
+ }
+
+
+ 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, this._maxSearchIndex, 10).then(action((res: SearchUtil.DocSearchResult) => {
+
+ // happens at the beginning
+ if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) {
+ this._numTotalResults = res.numFound;
+ }
+
+ let filteredDocs = FilterBox.Instance.filterDocsByType(res.docs);
+ this._results.push(...filteredDocs);
+
+ 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);
@@ -142,6 +184,7 @@ export class SearchBox extends React.Component {
}
}
return Docs.Create.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` });
+
}
@action.bound
@@ -155,7 +198,6 @@ export class SearchBox extends React.Component {
@action.bound
closeSearch = () => {
- console.log("closing search");
FilterBox.Instance.closeFilter();
this.closeResults();
}
@@ -164,6 +206,84 @@ export class SearchBox extends React.Component {
closeResults() {
this._resultsOpen = false;
this._results = [];
+ 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 | 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} key={result[Id]} />;
+ this._isSearch[i] = "search";
+ }
+ }
+ else {
+ result = this._results[i];
+ if (result) {
+ this._visibleElements[i] = <SearchItem doc={result} key={result[Id]} />;
+ this._isSearch[i] = "search";
+ }
+ }
+ }
+ }
+ }
+ if (this._maxSearchIndex >= this._numTotalResults) {
+ this._visibleElements.length = this._results.length;
+ this._isSearch.length = this._results.length;
+ }
+ }
+
+ @computed
+ get resFull() {
+ console.log(this._numTotalResults)
+ return this._numTotalResults <= 8;
+ }
+
+ @computed
+ get resultHeight() {
+ return this._numTotalResults * 70;
}
render() {
@@ -179,15 +299,12 @@ export class SearchBox extends React.Component {
<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} key={result[Id]} />)
- ) :
- this._openNoResults ? (<div className="no-result">No Search Results</div>) : null}
+ <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 style={this._results.length !== 0 ? { display: "flex" } : { display: "none" }}>
- <Pager />
- </div> */}
</div>
);
}
diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss
index 827cb7567..24dd2eaa3 100644
--- a/src/client/views/search/SearchItem.scss
+++ b/src/client/views/search/SearchItem.scss
@@ -6,193 +6,202 @@
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;
- width: 100%;
+.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;
- .search-title {
- text-transform: uppercase;
- text-align: left;
- width: 100%;
- font-weight: bold;
- }
+ .main-search-info {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
- .search-info {
+ .search-title {
+ text-transform: uppercase;
+ text-align: left;
+ width: 100%;
+ font-weight: bold;
+ }
+
+ .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 {
- // 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-container.item:hover {
+ width: 70px;
+ }
- }
+ .link-container.item:hover .link-count {
+ opacity: 0;
+ }
- .link-container.item:hover {
- width: 70px;
- }
+ .link-container.item:hover .link-extended {
+ opacity: 1;
+ visibility: visible;
+ // display: inline;
+ }
- .link-container.item:hover .link-count {
- opacity: 0;
- }
+ .icon-icons {
+ width: 50px
+ }
- .link-container.item:hover .link-extended {
- opacity: 1;
- visibility: visible;
- // display: inline;
- }
+ .icon-live {
+ width: 175px;
+ }
- .icon-icons {
- width: 50px
- }
+ .icon-icons,
+ .icon-live {
+ height: 50px;
+ margin: auto;
+ overflow: hidden;
- .icon-live {
- width: 175px;
+ .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;
- }
+ img {
+ width: 100% !important;
+ height: auto !important;
}
+ }
- .search-type:hover+.search-label {
- opacity: 1;
- }
+ .search-type:hover+.search-label {
+ opacity: 1;
+ }
- .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-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;
}
+ }
- .icon-live:hover {
- height: 175px;
+ .icon-live:hover {
+ height: 175px;
- .pdfBox-cont {
- img {
- width: 100% !important;
- }
+ .pdfBox-cont {
+ img {
+ width: 100% !important;
}
}
}
-
- .search-info:hover {
- width: 60%;
- }
}
- }
- .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;
}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index b3ba4c3dd..16ad71d16 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -99,7 +99,9 @@ export class SearchItem extends React.Component<SearchItemProps> {
@observable _selected: boolean = false;
onClick = () => {
- DocumentManager.Instance.jumpToDocument(this.props.doc, false);
+ // 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);
+ CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined);
}
@observable _useIcons = true;
@observable _displayDim = 50;
@@ -240,7 +242,7 @@ 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 title="Drag as document" onPointerDown={this.onPointerDown} style={{ marginRight: "7px" }}> <FontAwesomeIcon icon="file" size="lg" /> </div>
<div className="search-title" id="result" >{StrCast(this.props.doc.title)}</div>
<div className="search-info" style={{ width: this._useIcons ? "15%" : "400px" }}>
<div className={`icon-${this._useIcons ? "icons" : "live"}`}>
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 8591f8857..98f421937 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -30,13 +30,14 @@ export class Search {
}
}
- public async search(query: string, start: number = 0) {
+ public async search(query: string, start: number = 0, rows: number = 10) {
try {
const searchResults = JSON.parse(await rp.get(this.url + "dash/select", {
qs: {
q: query,
fl: "id",
- start: start
+ start,
+ rows,
}
}));
const { docs, numFound } = searchResults.response;
diff --git a/src/server/index.ts b/src/server/index.ts
index 9cb43bf4e..58af074aa 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -144,8 +144,12 @@ app.get("/pull", (req, res) =>
// GETTERS
app.get("/search", async (req, res) => {
- let query = req.query.query || "hello";
- let results = await Search.Instance.search(query);
+ const { query, start, rows } = req.query;
+ if (query === undefined) {
+ res.send([]);
+ return;
+ }
+ let results = await Search.Instance.search(query, start, rows);
res.send(results);
});