aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/MainView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/search/FieldFilters.tsx4
-rw-r--r--src/client/views/search/FilterBox.tsx442
-rw-r--r--src/client/views/search/IconBar.tsx40
-rw-r--r--src/client/views/search/IconButton.tsx8
-rw-r--r--src/client/views/search/SearchBox.scss1
-rw-r--r--src/client/views/search/SearchBox.tsx495
-rw-r--r--src/client/views/search/SearchItem.tsx4
-rw-r--r--src/client/views/search/ToggleBar.tsx1
11 files changed, 561 insertions, 442 deletions
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f61b5290f..59bb83adb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -24,7 +24,7 @@ import "./Main.scss";
import { MainOverlayTextBox } from './MainOverlayTextBox';
import { DocumentView } from './nodes/DocumentView';
import { PreviewCursor } from './PreviewCursor';
-import { SearchBox } from './search/SearchBox';
+import { FilterBox } from './search/FilterBox';
import { SelectionManager } from '../util/SelectionManager';
import { FieldResult, Field, Doc, Opt, DocListCast } from '../../new_fields/Doc';
import { Cast, FieldValue, StrCast, PromiseValue } from '../../new_fields/Types';
@@ -336,7 +336,7 @@ export class MainView extends React.Component {
</div>
</div>
</div >,
- this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div> : null,
+ this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <FilterBox /> </div> : null,
<div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
<button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
];
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ee7a9f64f..9bfe9d7e1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -14,7 +14,7 @@ import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
import { InkingCanvas } from "../../InkingCanvas";
import { PreviewCursor } from "../../PreviewCursor";
-import { SearchBox } from "../../search/SearchBox";
+import { SearchBox } from "../../search/FilterBox";
import { Templates } from "../../Templates";
import { CollectionViewType } from "../CollectionBaseView";
import { CollectionFreeFormView } from "./CollectionFreeFormView";
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 07f4733ba..96852b9f7 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -11,7 +11,7 @@ import { PdfField } from "../../../new_fields/URLField";
import { RouteStore } from "../../../server/RouteStore";
import { DocComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
-import { SearchBox } from "../search/SearchBox";
+import { SearchBox } from "../search/FilterBox";
import { Annotation } from './Annotation';
import { PDFViewer } from "../pdf/PDFViewer";
import { positionSchema } from "./DocumentView";
diff --git a/src/client/views/search/FieldFilters.tsx b/src/client/views/search/FieldFilters.tsx
index befe5d54d..648aac20a 100644
--- a/src/client/views/search/FieldFilters.tsx
+++ b/src/client/views/search/FieldFilters.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { observable } from 'mobx';
import { CheckBox } from './CheckBox';
-import { Keys } from './SearchBox';
+import { Keys } from './FilterBox';
import "./FieldFilters.scss";
export interface FieldFilterProps {
@@ -16,7 +16,7 @@ export interface FieldFilterProps {
export class FieldFilters extends React.Component<FieldFilterProps> {
static Instance: FieldFilters;
-
+
@observable public _resetBoolean = false;
@observable public _resetCounter: number = 0;
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
new file mode 100644
index 000000000..ddc63255c
--- /dev/null
+++ b/src/client/views/search/FilterBox.tsx
@@ -0,0 +1,442 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action, runInAction } from 'mobx';
+import "./SearchBox.scss";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faTimes } from '@fortawesome/free-solid-svg-icons';
+import { library, icon } from '@fortawesome/fontawesome-svg-core';
+import * as rp from 'request-promise';
+import { SearchItem } from './SearchItem';
+import { DocServer } from '../../DocServer';
+import { Doc } from '../../../new_fields/Doc';
+import { Id } from '../../../new_fields/FieldSymbols';
+import { SetupDrag } from '../../util/DragManager';
+import { Docs, DocTypes } from '../../documents/Documents';
+import { RouteStore } from '../../../server/RouteStore';
+import { NumCast, Cast, StrCast } from '../../../new_fields/Types';
+import { SearchUtil } from '../../util/SearchUtil';
+import * as _ from "lodash";
+import { ToggleBar } from './ToggleBar';
+import { IconBar } from './IconBar';
+import { FieldFilters } from './FieldFilters';
+import { SelectionManager } from '../../util/SelectionManager';
+import { DocumentView } from '../nodes/DocumentView';
+import { CollectionFilters } from './CollectionFilters';
+import { NaviconButton } from './NaviconButton';
+import * as $ from 'jquery';
+import * as anime from 'animejs';
+import "./FilterBox.scss";
+import { SearchBox } from './SearchBox';
+
+library.add(faTimes);
+
+export enum Keys {
+ TITLE = "title",
+ AUTHOR = "author",
+ DATA = "data"
+}
+
+@observer
+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];
+
+ // @observable private _searchString: string = "";
+ //if true, any keywords can be used. if false, all keywords are required.
+ @observable private _basicWordStatus: boolean = true;
+ @observable private _filterOpen: boolean = false;
+ // @observable private _resultsOpen: boolean = false;
+ // @observable private _results: Doc[] = [];
+ // @observable private _openNoResults: boolean = false;
+ @observable private _icons: string[] = this._allIcons;
+ @observable private _titleFieldStatus: boolean = true;
+ @observable private _authorFieldStatus: boolean = true;
+ @observable private _dataFieldStatus: boolean = true;
+ @observable private _collectionStatus = false;
+ @observable private _collectionSelfStatus = true;
+ @observable private _collectionParentStatus = true;
+ @observable private _wordStatusOpen: boolean = false;
+ @observable private _typeOpen: boolean = false;
+ @observable private _colOpen: boolean = false;
+ @observable private _fieldOpen: boolean = false;
+ public _pointerTime: number = -1;
+
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ FilterBox.Instance = this;
+ }
+
+ // might need to add to search box
+ componentDidMount = () => {
+
+ document.addEventListener("pointerdown", (e) => {
+ if (e.timeStamp !== this._pointerTime) {
+ // this.closeSearch();
+ SearchBox.Instance.closeSearch();
+ }
+ });
+ }
+
+ setupAccordion() {
+ $('document').ready(function () {
+ var acc = document.getElementsByClassName('filter-header');
+
+ for (var i = 0; i < acc.length; i++) {
+ acc[i].addEventListener("click", function (this: HTMLElement) {
+ this.classList.toggle("active");
+
+ var panel = this.nextElementSibling as HTMLElement;
+ if (panel.style.maxHeight) {
+ panel.style.overflow = "hidden";
+ panel.style.maxHeight = null;
+ panel.style.opacity = "0";
+ } else {
+ setTimeout(() => {
+ panel.style.overflow = "visible";
+ }, 200);
+ setTimeout(() => {
+ panel.style.opacity = "1";
+ }, 50);
+ panel.style.maxHeight = panel.scrollHeight + "px";
+
+ }
+ });
+ }
+ });
+ }
+
+ @action.bound
+ minimizeAll() {
+ $('document').ready(function () {
+ var acc = document.getElementsByClassName('filter-header');
+
+ for (var i = 0; i < acc.length; i++) {
+ let classList = acc[i].classList;
+ if (classList.contains("active")) {
+ acc[i].classList.toggle("active");
+ var panel = acc[i].nextElementSibling as HTMLElement;
+ panel.style.overflow = "hidden";
+ panel.style.maxHeight = null;
+ }
+ }
+ });
+ }
+
+ @action.bound
+ resetFilters = () => {
+ ToggleBar.Instance.resetToggle();
+ IconBar.Instance.selectAll();
+ FieldFilters.Instance.resetFieldFilters();
+ CollectionFilters.Instance.resetCollectionFilters();
+ }
+
+ //--------------------------------------------------------------------------------------------------------------
+ // @action.bound
+ // onChange(e: React.ChangeEvent<HTMLInputElement>) {
+ // this._searchString = e.target.value;
+
+ // if (this._searchString === "") {
+ // this._results = [];
+ // this._openNoResults = false;
+ // }
+ // }
+ //--------------------------------------------------------------------------------------------------------------
+
+ basicRequireWords(query: string): string {
+ let oldWords = query.split(" ");
+ let newWords: string[] = [];
+ oldWords.forEach(word => {
+ let newWrd = "+" + word;
+ newWords.push(newWrd);
+ });
+ query = newWords.join(" ");
+
+ return query;
+ }
+
+ basicFieldFilters(query: string, type: string): string {
+ let oldWords = query.split(" ");
+ let mod = "";
+
+ if (type === Keys.AUTHOR) {
+ mod = " author_t:";
+ } if (type === Keys.DATA) {
+ //TODO
+ } if (type === Keys.TITLE) {
+ mod = " title_t:";
+ }
+
+ let newWords: string[] = [];
+ oldWords.forEach(word => {
+ let newWrd = mod + word;
+ newWords.push(newWrd);
+ });
+
+ query = newWords.join(" ");
+
+ return query;
+ }
+
+ applyBasicFieldFilters(query: string) {
+ let finalQuery = "";
+
+ if (this._titleFieldStatus) {
+ finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE);
+ }
+ if (this._authorFieldStatus) {
+ finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR);
+ }
+ if (this._dataFieldStatus) {
+ finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA);
+ }
+ return finalQuery;
+ }
+
+ get fieldFiltersApplied() { return !(this._dataFieldStatus && this._authorFieldStatus && this._titleFieldStatus); }
+
+ //TODO: basically all of this
+ //gets all of the collections of all the docviews that are selected
+ //if a collection is the only thing selected, search only in that collection (not its container)
+ getCurCollections(): Doc[] {
+ let selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments();
+ let collections: Doc[] = [];
+
+ selectedDocs.forEach(async element => {
+ let layout: string = StrCast(element.props.Document.baseLayout);
+ //checks if selected view (element) is a collection. if it is, adds to list to search through
+ if (layout.indexOf("Collection") > -1) {
+ //makes sure collections aren't added more than once
+ if (!collections.includes(element.props.Document)) {
+ 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);
+ }
+ });
+
+ return collections;
+ }
+
+ getFinalQuery(query: string): string {
+ //alters the query so it looks in the correct fields
+ //if this is true, then not all of the field boxes are checked
+ //TODO: data
+ if (this.fieldFiltersApplied) {
+ query = this.applyBasicFieldFilters(query);
+ query = query.replace(/\s+/g, ' ').trim();
+ }
+
+ //alters the query based on if all words or any words are required
+ //if this._wordstatus is false, all words are required and a + is added before each
+ if (!this._basicWordStatus) {
+ query = this.basicRequireWords(query);
+ query = query.replace(/\s+/g, ' ').trim();
+ }
+
+ //if should be searched in a specific collection
+ if (this._collectionStatus) {
+ query = this.addCollectionFilter(query);
+ query = query.replace(/\s+/g, ' ').trim();
+ }
+ return query;
+ }
+
+ addCollectionFilter(query: string): string {
+ let collections: Doc[] = this.getCurCollections();
+ let oldWords = query.split(" ");
+
+ let collectionString: string[] = [];
+ collections.forEach(doc => {
+ let proto = doc.proto;
+ let protoId = (proto || doc)[Id];
+ let colString: string = "{!join from=data_l to=id}id:" + protoId + " ";
+ collectionString.push(colString);
+ });
+
+ let finalColString = collectionString.join(" ");
+ finalColString = finalColString.trim();
+ return "+(" + finalColString + ")" + query;
+ }
+
+ @action
+ filterDocsByType(docs: Doc[]) {
+ let finalDocs: Doc[] = [];
+ docs.forEach(doc => {
+ let layoutresult = Cast(doc.type, "string", "");
+ if (this._icons.includes(layoutresult)) {
+ finalDocs.push(doc);
+ }
+ });
+ return finalDocs;
+ }
+ //--------------------------------------------------------------------------------------------------------------
+ // enter = (e: React.KeyboardEvent) => {
+ // if (e.key === "Enter") { this.submitSearch(); }
+ // }
+ //--------------------------------------------------------------------------------------------------------------
+
+ // @action.bound
+ // closeSearch = () => {
+ // this._filterOpen = false;
+ // this._resultsOpen = false;
+ // this._results = [];
+ // }
+
+ @action.bound
+ openFilter = () => {
+ this._filterOpen = !this._filterOpen;
+ // this._resultsOpen = false;
+ // this._results = [];
+ SearchBox.Instance.closeResults();
+ console.log("opening filter")
+ this.setupAccordion();
+ }
+
+ //if true, any keywords can be used. if false, all keywords are required.
+ @action.bound
+ handleWordQueryChange = () => { this._basicWordStatus = !this._basicWordStatus; }
+
+ @action
+ getBasicWordStatus() { return this._basicWordStatus; }
+
+ @action.bound
+ updateIcon(newArray: string[]) { this._icons = newArray; }
+
+ @action.bound
+ getIcons(): string[] { return this._icons; }
+
+ stopProp = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ this.setPointerTime(e.timeStamp);
+ console.log("stopping prop")
+ }
+
+ setPointerTime(time: number) {
+ this._pointerTime = time;
+ }
+
+ // @action.bound
+ // openSearch(e: React.PointerEvent) {
+ // e.stopPropagation();
+ // this._openNoResults = false;
+ // this._filterOpen = false;
+ // this._resultsOpen = true;
+ // this._pointerTime = e.timeStamp;
+ // }
+
+
+ @action.bound
+ public closeFilter() {
+ this._filterOpen = false;
+ }
+
+ @action.bound
+ toggleFieldOpen() { this._fieldOpen = !this._fieldOpen; }
+
+ @action.bound
+ toggleColOpen() { this._colOpen = !this._colOpen; }
+
+ @action.bound
+ toggleTypeOpen() { this._typeOpen = !this._typeOpen; }
+
+ @action.bound
+ toggleWordStatusOpen() { this._wordStatusOpen = !this._wordStatusOpen; }
+
+ @action.bound
+ updateTitleStatus(newStat: boolean) { this._titleFieldStatus = newStat; }
+
+ @action.bound
+ updateAuthorStatus(newStat: boolean) { this._authorFieldStatus = newStat; }
+
+ @action.bound
+ updateDataStatus(newStat: boolean) { this._dataFieldStatus = newStat; }
+
+ @action.bound
+ updateCollectionStatus(newStat: boolean) { this._collectionStatus = newStat; }
+
+ @action.bound
+ updateSelfCollectionStatus(newStat: boolean) { this._collectionSelfStatus = newStat; }
+
+ @action.bound
+ updateParentCollectionStatus(newStat: boolean) { this._collectionParentStatus = newStat; }
+
+ getCollectionStatus() { return this._collectionStatus; }
+ getSelfCollectionStatus() { return this._collectionSelfStatus; }
+ getParentCollectionStatus() { return this._collectionParentStatus; }
+ getTitleStatus() { return this._titleFieldStatus; }
+ getAuthorStatus() { return this._authorFieldStatus; }
+ getDataStatus() { return this._dataFieldStatus; }
+
+ // Useful queries:
+ // Delegates of a document: {!join from=id to=proto_i}id:{protoId}
+ // Documents in a collection: {!join from=data_l to=id}id:{collectionProtoId} //id of collections prototype
+ render() {
+ return (
+ <div>
+ <div style={{ display: "flex", flexDirection: "row-reverse" }}>
+ <button className="searchBox-barChild searchBox-filter" onClick={this.openFilter} onPointerDown={this.stopProp}>Filter</button>
+ <SearchBox />
+ </div>
+ {this._filterOpen ? (
+ <div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex" } : { display: "none" }}>
+ <div className="top-filter-header" style={{ display: "flex", width: "100%" }}>
+ <div id="header">Filter Search Results</div>
+ <div className="close-icon" onClick={this.closeFilter}>
+ <span className="line line-1"></span>
+ <span className="line line-2"></span></div>
+ </div>
+ <div className="filter-options">
+ <div className="filter-div">
+ <div className="filter-header">
+ <div className='filter-title words'>Required words</div>
+ <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleWordStatusOpen} /></div>
+ </div>
+ <div className="filter-panel" >
+ <ToggleBar handleChange={this.handleWordQueryChange} getStatus={this.getBasicWordStatus}
+ originalStatus={this._basicWordStatus} optionOne={"Include Any Keywords"} optionTwo={"Include All Keywords"} />
+ </div>
+ </div>
+ <div className="filter-div">
+ <div className="filter-header">
+ <div className="filter-title icon">Filter by type of node</div>
+ <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleTypeOpen} /></div>
+ </div>
+ <div className="filter-panel"><IconBar /></div>
+ </div>
+ <div className="filter-div">
+ <div className="filter-header">
+ <div className='filter-title collection'>Search in current collections</div>
+ <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleColOpen} /></div>
+ </div>
+ <div className="filter-panel"><CollectionFilters
+ updateCollectionStatus={this.updateCollectionStatus} updateParentCollectionStatus={this.updateParentCollectionStatus} updateSelfCollectionStatus={this.updateSelfCollectionStatus}
+ collectionStatus={this._collectionStatus} collectionParentStatus={this._collectionParentStatus} collectionSelfStatus={this._collectionSelfStatus} /></div>
+ </div>
+ <div className="filter-div">
+ <div className="filter-header">
+ <div className="filter-title field">Filter by Basic Keys</div>
+ <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleFieldOpen} /></div>
+ </div>
+ <div className="filter-panel"><FieldFilters
+ titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._dataFieldStatus} authorFieldStatus={this._authorFieldStatus}
+ updateAuthorStatus={this.updateAuthorStatus} updateDataStatus={this.updateDataStatus} updateTitleStatus={this.updateTitleStatus} /> </div>
+ </div>
+ </div>
+ <div className="filter-buttons" style={{ display: "flex", justifyContent: "space-around" }}>
+ <button className="minimize-filter" onClick={this.minimizeAll}>Minimize All</button>
+ <button className="advanced-filter" >Advanced Filters</button>
+ <button className="save-filter" >Save Filters</button>
+ <button className="reset-filter" onClick={this.resetFilters}>Reset Filters</button>
+ </div>
+ </div>
+ ) : undefined}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index c5b4d33cf..23a5458bc 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -10,7 +10,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
import * as _ from "lodash";
import { IconButton } from './IconButton';
-import { SearchBox } from './SearchBox';
+import { FilterBox } from './FilterBox';
library.add(faSearch);
library.add(faObjectGroup);
@@ -40,10 +40,10 @@ export class IconBar extends React.Component {
}
@action.bound
- getList(): string[] { return SearchBox.Instance.getIcons(); }
+ getList(): string[] { return FilterBox.Instance.getIcons(); }
@action.bound
- updateList(newList: string[]) { SearchBox.Instance.updateIcon(newList); }
+ updateList(newList: string[]) { FilterBox.Instance.updateIcon(newList); }
@action.bound
resetSelf = () => {
@@ -54,30 +54,30 @@ export class IconBar extends React.Component {
@action.bound
selectAll = () => {
this._selectAllClicked = true;
- this.updateList(SearchBox.Instance._allIcons);
+ this.updateList(FilterBox.Instance._allIcons);
}
render() {
return (
- <div className="icon-bar">
- <div className="type-outer">
- <div className={"type-icon all"}
- onClick={this.selectAll}>
- <FontAwesomeIcon className="fontawesome-icon" icon={faCheckCircle} />
- </div>
- <div className="filter-description">Select All</div>
+ <div className="icon-bar">
+ <div className="type-outer">
+ <div className={"type-icon all"}
+ onClick={this.selectAll}>
+ <FontAwesomeIcon className="fontawesome-icon" icon={faCheckCircle} />
</div>
- {SearchBox.Instance._allIcons.map((type: string) =>
- <IconButton type={type} />
- )}
- <div className="type-outer">
- <div className={"type-icon none"}
- onClick={this.resetSelf}>
- <FontAwesomeIcon className="fontawesome-icon" icon={faTimesCircle} />
- </div>
- <div className="filter-description">Clear</div>
+ <div className="filter-description">Select All</div>
+ </div>
+ {FilterBox.Instance._allIcons.map((type: string) =>
+ <IconButton type={type} />
+ )}
+ <div className="type-outer">
+ <div className={"type-icon none"}
+ onClick={this.resetSelf}>
+ <FontAwesomeIcon className="fontawesome-icon" icon={faTimesCircle} />
</div>
+ <div className="filter-description">Clear</div>
</div>
+ </div>
);
}
}
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index c40bb587f..23ab42de0 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -11,7 +11,7 @@ import '../globalCssVariables.scss';
import * as _ from "lodash";
import { IconBar } from './IconBar';
import { props } from 'bluebird';
-import { SearchBox } from './SearchBox';
+import { FilterBox } from './FilterBox';
import { Search } from '../../../server/Search';
library.add(faSearch);
@@ -33,7 +33,7 @@ interface IconButtonProps {
@observer
export class IconButton extends React.Component<IconButtonProps>{
- @observable private _isSelected: boolean = SearchBox.Instance.getIcons().indexOf(this.props.type) !== -1;
+ @observable private _isSelected: boolean = FilterBox.Instance.getIcons().indexOf(this.props.type) !== -1;
@observable private _hover = false;
private _resetReaction?: IReactionDisposer;
private _selectAllReaction?: IReactionDisposer;
@@ -107,7 +107,7 @@ export class IconButton extends React.Component<IconButtonProps>{
@action.bound
onClick = () => {
- let newList: string[] = SearchBox.Instance.getIcons();
+ let newList: string[] = FilterBox.Instance.getIcons();
if (!this._isSelected) {
this._isSelected = true;
@@ -118,7 +118,7 @@ export class IconButton extends React.Component<IconButtonProps>{
_.pull(newList, this.props.type);
}
- SearchBox.Instance.updateIcon(newList);
+ FilterBox.Instance.updateIcon(newList);
}
selected = {
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index e570484e4..a9c708975 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -19,6 +19,7 @@
}
&.searchBox-input {
+ display: block;
width: 130px;
-webkit-transition: width 0.4s;
transition: width 0.4s;
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 1fc777a8c..b371df380 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -2,133 +2,68 @@ import * as React from 'react';
import { observer } from 'mobx-react';
import { observable, action, runInAction } from 'mobx';
import "./SearchBox.scss";
+import "./FilterBox.scss";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faTimes } from '@fortawesome/free-solid-svg-icons';
-import { library, icon } from '@fortawesome/fontawesome-svg-core';
-import * as rp from 'request-promise';
+import { SetupDrag } from '../../util/DragManager';
+import { Docs } from '../../documents/Documents';
+import { NumCast } from '../../../new_fields/Types';
+import { Doc } from '../../../new_fields/Doc';
import { SearchItem } from './SearchItem';
import { DocServer } from '../../DocServer';
-import { Doc } from '../../../new_fields/Doc';
+import * as rp from 'request-promise';
import { Id } from '../../../new_fields/FieldSymbols';
-import { SetupDrag } from '../../util/DragManager';
-import { Docs, DocTypes } from '../../documents/Documents';
-import { RouteStore } from '../../../server/RouteStore';
-import { NumCast, Cast, StrCast } from '../../../new_fields/Types';
import { SearchUtil } from '../../util/SearchUtil';
-import * as _ from "lodash";
-import { ToggleBar } from './ToggleBar';
-import { IconBar } from './IconBar';
-import { FieldFilters } from './FieldFilters';
-import { SelectionManager } from '../../util/SelectionManager';
-import { DocumentView } from '../nodes/DocumentView';
-import { CollectionFilters } from './CollectionFilters';
-import { NaviconButton } from './NaviconButton';
-import * as $ from 'jquery';
-import * as anime from 'animejs';
-import "./FilterBox.scss";
-
-library.add(faTimes);
-
-export enum Keys {
- TITLE = "title",
- AUTHOR = "author",
- DATA = "data"
+import { RouteStore } from '../../../server/RouteStore';
+import { FilterBox } from './FilterBox';
+
+export interface SearchBoxProps {
+ // setUnfilteredResults(docs: Doc[]): void;
+ // getFilteredResults(): Doc[];
+ // getFinalQuery(str: string): string;
+
+ // submitSearch(): void;
}
-@observer
-export class SearchBox extends React.Component {
-
- static Instance: SearchBox;
- public _allIcons: string[] = [DocTypes.AUDIO, DocTypes.COL, DocTypes.HIST, DocTypes.IMG, DocTypes.LINK, DocTypes.PDF, DocTypes.TEXT, DocTypes.VID, DocTypes.WEB];
+export class SearchBox extends React.Component<SearchBoxProps> {
@observable private _searchString: string = "";
- //if true, any keywords can be used. if false, all keywords are required.
- @observable private _basicWordStatus: boolean = true;
- @observable private _filterOpen: boolean = false;
@observable private _resultsOpen: boolean = false;
@observable private _results: Doc[] = [];
@observable private _openNoResults: boolean = false;
- @observable private _icons: string[] = this._allIcons;
- @observable private _titleFieldStatus: boolean = true;
- @observable private _authorFieldStatus: boolean = true;
- @observable private _dataFieldStatus: boolean = true;
- @observable private _collectionStatus = false;
- @observable private _collectionSelfStatus = true;
- @observable private _collectionParentStatus = true;
- @observable private _wordStatusOpen: boolean = false;
- @observable private _typeOpen: boolean = false;
- @observable private _colOpen: boolean = false;
- @observable private _fieldOpen: boolean = false;
+ // private _pointerTime: number = -1;
+ static Instance: SearchBox;
- constructor(props: Readonly<{}>) {
+ constructor(props: SearchBoxProps){
super(props);
- SearchBox.Instance = this;
- }
-
- componentDidMount = () => {
- document.addEventListener("pointerdown", (e) => {
- if (e.timeStamp !== this._pointerTime) {
- this.closeSearch();
- }
- });
- }
-
- setupAccordion() {
- $('document').ready(function () {
- var acc = document.getElementsByClassName('filter-header');
- for (var i = 0; i < acc.length; i++) {
- acc[i].addEventListener("click", function (this: HTMLElement) {
- this.classList.toggle("active");
-
- var panel = this.nextElementSibling as HTMLElement;
- if (panel.style.maxHeight) {
- panel.style.overflow = "hidden";
- panel.style.maxHeight = null;
- panel.style.opacity = "0";
- } else {
- setTimeout(() => {
- panel.style.overflow = "visible";
- }, 200);
- setTimeout(() => {
- panel.style.opacity = "1";
- }, 50);
- panel.style.maxHeight = panel.scrollHeight + "px";
-
- }
- });
- }
- });
+ SearchBox.Instance = this;
}
- @action.bound
- minimizeAll() {
- $('document').ready(function () {
- var acc = document.getElementsByClassName('filter-header');
+ // componentDidMount = () => {
+ // document.addEventListener("pointerdown", (e) => {
+ // console.log(e.timeStamp, FilterBox.Instance._pointerTime)
+ // console.log("this is in the click for determining whether or not to close search")
+ // if (e.timeStamp !== FilterBox.Instance._pointerTime) {
+ // this.closeSearch();
+ // }
+ // });
+ // }
- for (var i = 0; i < acc.length; i++) {
- let classList = acc[i].classList;
- if (classList.contains("active")) {
- acc[i].classList.toggle("active");
- var panel = acc[i].nextElementSibling as HTMLElement;
- panel.style.overflow = "hidden";
- panel.style.maxHeight = null;
- }
- }
+ @action
+ getViews = async (doc: Doc) => {
+ const results = await SearchUtil.GetViewsOfDocument(doc);
+ let toReturn: Doc[] = [];
+ await runInAction(() => {
+ toReturn = results;
});
- }
-
- @action.bound
- resetFilters = () => {
- ToggleBar.Instance.resetToggle();
- IconBar.Instance.selectAll();
- FieldFilters.Instance.resetFieldFilters();
- CollectionFilters.Instance.resetCollectionFilters();
+ return toReturn;
}
@action.bound
onChange(e: React.ChangeEvent<HTMLInputElement>) {
this._searchString = e.target.value;
+ console.log(this._searchString)
+ e.stopPropagation();
if (this._searchString === "") {
this._results = [];
@@ -136,138 +71,37 @@ export class SearchBox extends React.Component {
}
}
- @action.bound
- clearSearchQuery() {
- this._searchString = "";
- this._results = [];
- }
-
- basicRequireWords(query: string): string {
- let oldWords = query.split(" ");
- let newWords: string[] = [];
- oldWords.forEach(word => {
- let newWrd = "+" + word;
- newWords.push(newWrd);
- });
- query = newWords.join(" ");
-
- return query;
- }
-
- basicFieldFilters(query: string, type: string): string {
- let oldWords = query.split(" ");
- let mod = "";
-
- if (type === Keys.AUTHOR) {
- mod = " author_t:";
- } if (type === Keys.DATA) {
- //TODO
- } if (type === Keys.TITLE) {
- mod = " title_t:";
- }
-
- let newWords: string[] = [];
- oldWords.forEach(word => {
- let newWrd = mod + word;
- newWords.push(newWrd);
- });
-
- query = newWords.join(" ");
-
- return query;
- }
-
- applyBasicFieldFilters(query: string) {
- let finalQuery = "";
-
- if (this._titleFieldStatus) {
- finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE);
- }
- if (this._authorFieldStatus) {
- finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR);
- }
- if (this._dataFieldStatus) {
- finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA);
- }
- return finalQuery;
+ enter = (e: React.KeyboardEvent) => {
+
+ if (e.key === "Enter") { this.submitSearch(); }
}
- get fieldFiltersApplied() { return !(this._dataFieldStatus && this._authorFieldStatus && this._titleFieldStatus); }
-
- //TODO: basically all of this
- //gets all of the collections of all the docviews that are selected
- //if a collection is the only thing selected, search only in that collection (not its container)
- getCurCollections(): Doc[] {
- let selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments();
- let collections: Doc[] = [];
-
- selectedDocs.forEach(async element => {
- let layout: string = StrCast(element.props.Document.baseLayout);
- //checks if selected view (element) is a collection. if it is, adds to list to search through
- if (layout.indexOf("Collection") > -1) {
- //makes sure collections aren't added more than once
- if (!collections.includes(element.props.Document)) {
- 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);
- }
+ public static async convertDataUri(imageUri: string, returnedFilename: string) {
+ try {
+ let posting = DocServer.prepend(RouteStore.dataUriToImage);
+ const returnedUri = await rp.post(posting, {
+ body: {
+ uri: imageUri,
+ name: returnedFilename
+ },
+ json: true,
});
+ return returnedUri;
- return collections;
- }
-
- getFinalQuery(query: string): string {
- //alters the query so it looks in the correct fields
- //if this is true, then not all of the field boxes are checked
- //TODO: data
- if (this.fieldFiltersApplied) {
- query = this.applyBasicFieldFilters(query);
- query = query.replace(/\s+/g, ' ').trim();
- }
-
- //alters the query based on if all words or any words are required
- //if this._wordstatus is false, all words are required and a + is added before each
- if (!this._basicWordStatus) {
- query = this.basicRequireWords(query);
- query = query.replace(/\s+/g, ' ').trim();
- }
-
- //if should be searched in a specific collection
- if (this._collectionStatus) {
- query = this.addCollectionFilter(query);
- query = query.replace(/\s+/g, ' ').trim();
+ } catch (e) {
+ console.log(e);
}
- return query;
- }
-
- addCollectionFilter(query: string): string {
- let collections: Doc[] = this.getCurCollections();
- let oldWords = query.split(" ");
-
- let collectionString: string[] = [];
- collections.forEach(doc => {
- let proto = doc.proto;
- let protoId = (proto || doc)[Id];
- let colString: string = "{!join from=data_l to=id}id:" + protoId + " ";
- collectionString.push(colString);
- });
-
- let finalColString = collectionString.join(" ");
- finalColString = finalColString.trim();
- return "+(" + finalColString + ")" + query;
}
@action
submitSearch = async () => {
- let query = this._searchString;
+ console.log("submitting")
+ let query = this._searchString; // searchbox gets query
+ console.log(this._searchString)
let results: Doc[];
- query = this.getFinalQuery(query);
+ // query = this.props.getFinalQuery(query); // sends to filterbox to modify and gets final query back
+ query = FilterBox.Instance.getFinalQuery(query);
//if there is no query there should be no result
if (query === "") {
@@ -275,11 +109,15 @@ export class SearchBox extends React.Component {
}
else {
//gets json result into a list of documents that can be used
+ // results = await this.props.getFilteredResults(query);
+
+ //these are filtered by type
results = await this.getResults(query);
}
runInAction(() => {
this._resultsOpen = true;
+ console.log("opening results")
this._results = results;
this._openNoResults = true;
});
@@ -301,61 +139,15 @@ export class SearchBox extends React.Component {
docs.push(field);
}
}
- return this.filterDocsByType(docs);
- }
-
- //this.icons will now include all the icons that need to be included
- @action filterDocsByType(docs: Doc[]) {
- let finalDocs: Doc[] = [];
- docs.forEach(doc => {
- let layoutresult = Cast(doc.type, "string", "");
- if (this._icons.includes(layoutresult)) {
- finalDocs.push(doc);
- }
- });
- return finalDocs;
- }
-
- public static async convertDataUri(imageUri: string, returnedFilename: string) {
- try {
- let posting = DocServer.prepend(RouteStore.dataUriToImage);
- const returnedUri = await rp.post(posting, {
- body: {
- uri: imageUri,
- name: returnedFilename
- },
- json: true,
- });
- return returnedUri;
-
- } catch (e) {
- console.log(e);
- }
- }
-
- enter = (e: React.KeyboardEvent) => {
- if (e.key === "Enter") { this.submitSearch(); }
- }
-
- @action.bound
- closeSearch = () => {
- this._filterOpen = false;
- this._resultsOpen = false;
- this._results = [];
- }
-
- @action
- openFilter = () => {
- this._filterOpen = !this._filterOpen;
- this._resultsOpen = false;
- this._results = [];
-
- this.setupAccordion();
+ // this.props.setUnfilteredResults(docs);
+ // return docs;
+ return FilterBox.Instance.filterDocsByType(docs);
}
collectionRef = React.createRef<HTMLSpanElement>();
startDragCollection = async () => {
- const results = await this.getResults(this._searchString);
+ // const results = await this.getResults(this._searchString);
+ const results = await this.getResults(FilterBox.Instance.getFinalQuery(this._searchString));
const docs = results.map(doc => {
const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
if (isProto) {
@@ -391,94 +183,35 @@ export class SearchBox extends React.Component {
return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` });
}
- @action
- getViews = async (doc: Doc) => {
- const results = await SearchUtil.GetViewsOfDocument(doc);
- let toReturn: Doc[] = [];
- await runInAction(() => {
- toReturn = results;
- });
- return toReturn;
- }
-
- //if true, any keywords can be used. if false, all keywords are required.
- @action.bound
- handleWordQueryChange = () => { this._basicWordStatus = !this._basicWordStatus; }
-
- @action
- getBasicWordStatus() { return this._basicWordStatus; }
-
- @action.bound
- updateIcon(newArray: string[]) { this._icons = newArray; }
-
- @action.bound
- getIcons(): string[] { return this._icons; }
-
- private _pointerTime: number = -1;
-
- stopProp = (e: React.PointerEvent) => {
- e.stopPropagation();
- this._pointerTime = e.timeStamp;
- }
-
@action.bound
openSearch(e: React.PointerEvent) {
+ console.log("opening search")
e.stopPropagation();
+ // FilterBox.Instance.setPointerTime(e.timeStamp);
this._openNoResults = false;
- this._filterOpen = false;
+ FilterBox.Instance.closeFilter();
this._resultsOpen = true;
- this._pointerTime = e.timeStamp;
}
-
-
- @action.bound
- closeFilter() { this._filterOpen = false; }
-
- @action.bound
- toggleFieldOpen() { this._fieldOpen = !this._fieldOpen; }
-
- @action.bound
- toggleColOpen() { this._colOpen = !this._colOpen; }
-
@action.bound
- toggleTypeOpen() { this._typeOpen = !this._typeOpen; }
-
- @action.bound
- toggleWordStatusOpen() { this._wordStatusOpen = !this._wordStatusOpen; }
-
- @action.bound
- updateTitleStatus(newStat: boolean) { this._titleFieldStatus = newStat; }
-
- @action.bound
- updateAuthorStatus(newStat: boolean) { this._authorFieldStatus = newStat; }
-
- @action.bound
- updateDataStatus(newStat: boolean) { this._dataFieldStatus = newStat; }
-
- @action.bound
- updateCollectionStatus(newStat: boolean) { this._collectionStatus = newStat; }
+ closeSearch = () => {
+ console.log("closing search")
- @action.bound
- updateSelfCollectionStatus(newStat: boolean) { this._collectionSelfStatus = newStat; }
+ FilterBox.Instance.closeFilter();
+ this.closeResults();
+ console.log(this._resultsOpen)
+ }
@action.bound
- updateParentCollectionStatus(newStat: boolean) { this._collectionParentStatus = newStat; }
-
- getCollectionStatus() { return this._collectionStatus; }
- getSelfCollectionStatus() { return this._collectionSelfStatus; }
- getParentCollectionStatus() { return this._collectionParentStatus; }
- getTitleStatus() { return this._titleFieldStatus; }
- getAuthorStatus() { return this._authorFieldStatus; }
- getDataStatus() { return this._dataFieldStatus; }
+ closeResults() {
+ console.log("closing results")
+ this._resultsOpen = false;
+ this._results = [];
+ }
- // Useful queries:
- // Delegates of a document: {!join from=id to=proto_i}id:{protoId}
- // Documents in a collection: {!join from=data_l to=id}id:{collectionProtoId} //id of collections prototype
render() {
- return (
- <div>
- <div className="searchBox-container">
+ return(
+ <div className="searchBox-container">
<div className="searchBox-bar">
<span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} ref={this.collectionRef}>
<FontAwesomeIcon icon="object-group" size="lg" />
@@ -486,72 +219,16 @@ export class SearchBox extends React.Component {
<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" }} />
- <button className="searchBox-barChild searchBox-filter" onClick={this.openFilter} onPointerDown={this.stopProp}>Filter</button>
</div>
- {this._resultsOpen ? (
- <div className="searchBox-results">
+ <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>
- ) : undefined}
</div>
- {this._filterOpen ? (
- <div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex" } : { display: "none" }}>
- <div className = "top-filter-header" style={{ display: "flex", width: "100%" }}>
- <div id="header">Filter Search Results</div>
- <div className="close-icon" onClick={this.closeFilter}>
- <span className="line line-1"></span>
- <span className="line line-2"></span></div>
- </div>
- <div className = "filter-options">
- <div className="filter-div">
- <div className="filter-header">
- <div className='filter-title words'>Required words</div>
- <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleWordStatusOpen} /></div>
- </div>
- <div className="filter-panel" >
- <ToggleBar handleChange={this.handleWordQueryChange} getStatus={this.getBasicWordStatus}
- originalStatus={this._basicWordStatus} optionOne={"Include Any Keywords"} optionTwo={"Include All Keywords"} />
- </div>
- </div>
- <div className="filter-div">
- <div className="filter-header">
- <div className="filter-title icon">Filter by type of node</div>
- <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleTypeOpen} /></div>
- </div>
- <div className="filter-panel"><IconBar /></div>
- </div>
- <div className="filter-div">
- <div className="filter-header">
- <div className='filter-title collection'>Search in current collections</div>
- <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleColOpen} /></div>
- </div>
- <div className="filter-panel"><CollectionFilters
- updateCollectionStatus={this.updateCollectionStatus} updateParentCollectionStatus={this.updateParentCollectionStatus} updateSelfCollectionStatus={this.updateSelfCollectionStatus}
- collectionStatus={this._collectionStatus} collectionParentStatus={this._collectionParentStatus} collectionSelfStatus={this._collectionSelfStatus} /></div>
- </div>
- <div className="filter-div">
- <div className="filter-header">
- <div className="filter-title field">Filter by Basic Keys</div>
- <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleFieldOpen} /></div>
- </div>
- <div className="filter-panel"><FieldFilters
- titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._dataFieldStatus} authorFieldStatus={this._authorFieldStatus}
- updateAuthorStatus={this.updateAuthorStatus} updateDataStatus={this.updateDataStatus} updateTitleStatus={this.updateTitleStatus} /> </div>
- </div>
- </div>
- <div className="filter-buttons" style={{ display: "flex", justifyContent: "space-around" }}>
- <button className="minimize-filter" onClick={this.minimizeAll}>Minimize All</button>
- <button className="advanced-filter" >Advanced Filters</button>
- <button className="save-filter" >Save Filters</button>
- <button className="reset-filter" onClick={this.resetFilters}>Reset Filters</button>
- </div>
- </div>
- ) : undefined}
- </div>
- );
+ )
}
+
} \ No newline at end of file
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 71ad9a5ee..82d0c2aae 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -15,7 +15,7 @@ import { observer } from "mobx-react";
import "./SearchItem.scss";
import { CollectionViewType } from "../collections/CollectionBaseView";
import { DocTypes } from "../../documents/Documents";
-import { SearchBox } from "./SearchBox";
+import { FilterBox } from "./FilterBox";
import { DocumentView } from "../nodes/DocumentView";
import "./SelectorContextMenu.scss";
@@ -129,7 +129,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
return num.toString() + " links";
}
- pointerDown = (e: React.PointerEvent) => { SearchBox.Instance.openSearch(e); };
+ pointerDown = (e: React.PointerEvent) => { FilterBox.Instance.openSearch(e); };
highlightDoc = (e: React.PointerEvent) => {
let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
index dbe16cbdb..8a3aacf96 100644
--- a/src/client/views/search/ToggleBar.tsx
+++ b/src/client/views/search/ToggleBar.tsx
@@ -4,7 +4,6 @@ import { observable, action, runInAction, computed } from 'mobx';
import "./SearchBox.scss";
import "./ToggleBar.scss";
import * as anime from 'animejs';
-import { SearchBox } from './SearchBox';
export interface ToggleBarProps {
originalStatus: boolean;