aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/search
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2020-04-07 13:46:15 -0700
committerSam Wilkins <samwilkins333@gmail.com>2020-04-07 13:46:15 -0700
commit52ad8b3874419a76e6953c3bd698d9e68a3158a6 (patch)
treea20156fa4453c817aa52d8d807dbc705066c5f6e /src/client/views/search
parentedd96f6d78ff066f0a14efefaa92a9710caff9bd (diff)
parent9ad73769dd2f3a7c6598041f123dfd5cb7ef05d5 (diff)
pulled
Diffstat (limited to 'src/client/views/search')
-rw-r--r--src/client/views/search/CheckBox.tsx146
-rw-r--r--src/client/views/search/FilterBox.scss9
-rw-r--r--src/client/views/search/FilterBox.tsx2
-rw-r--r--src/client/views/search/IconBar.scss7
-rw-r--r--src/client/views/search/IconBar.tsx20
-rw-r--r--src/client/views/search/IconButton.scss1
-rw-r--r--src/client/views/search/IconButton.tsx16
-rw-r--r--src/client/views/search/SearchBox.scss263
-rw-r--r--src/client/views/search/SearchBox.tsx418
-rw-r--r--src/client/views/search/SearchItem.tsx3
10 files changed, 725 insertions, 160 deletions
diff --git a/src/client/views/search/CheckBox.tsx b/src/client/views/search/CheckBox.tsx
index a9d48219a..8c97d5dbc 100644
--- a/src/client/views/search/CheckBox.tsx
+++ b/src/client/views/search/CheckBox.tsx
@@ -17,8 +17,8 @@ interface CheckBoxProps {
export class CheckBox extends React.Component<CheckBoxProps>{
// true = checked, false = unchecked
@observable private _status: boolean;
- @observable private uncheckTimeline: anime.AnimeTimelineInstance;
- @observable private checkTimeline: anime.AnimeTimelineInstance;
+ // @observable private uncheckTimeline: anime.AnimeTimelineInstance;
+ // @observable private checkTimeline: anime.AnimeTimelineInstance;
@observable private checkRef: any;
@observable private _resetReaction?: IReactionDisposer;
@@ -28,87 +28,87 @@ export class CheckBox extends React.Component<CheckBoxProps>{
this._status = this.props.originalStatus;
this.checkRef = React.createRef();
- this.checkTimeline = anime.timeline({
- loop: false,
- autoplay: false,
- direction: "normal",
- }); this.uncheckTimeline = anime.timeline({
- loop: false,
- autoplay: false,
- direction: "normal",
- });
+ // this.checkTimeline = anime.timeline({
+ // loop: false,
+ // autoplay: false,
+ // direction: "normal",
+ // }); this.uncheckTimeline = anime.timeline({
+ // loop: false,
+ // autoplay: false,
+ // direction: "normal",
+ // });
}
- componentDidMount = () => {
- this.uncheckTimeline.add({
- targets: this.checkRef.current,
- easing: "easeInOutQuad",
- duration: 500,
- opacity: 0,
- });
- this.checkTimeline.add({
- targets: this.checkRef.current,
- easing: "easeInOutQuad",
- duration: 500,
- strokeDashoffset: [anime.setDashoffset, 0],
- opacity: 1
- });
+ // componentDidMount = () => {
+ // this.uncheckTimeline.add({
+ // targets: this.checkRef.current,
+ // easing: "easeInOutQuad",
+ // duration: 500,
+ // opacity: 0,
+ // });
+ // this.checkTimeline.add({
+ // targets: this.checkRef.current,
+ // easing: "easeInOutQuad",
+ // duration: 500,
+ // strokeDashoffset: [anime.setDashoffset, 0],
+ // opacity: 1
+ // });
- if (this.props.originalStatus) {
- this.checkTimeline.play();
- this.checkTimeline.restart();
- }
+ // if (this.props.originalStatus) {
+ // this.checkTimeline.play();
+ // this.checkTimeline.restart();
+ // }
- this._resetReaction = reaction(
- () => this.props.parent._resetBoolean,
- () => {
- if (this.props.parent._resetBoolean) {
- runInAction(() => {
- this.reset();
- this.props.parent._resetCounter++;
- if (this.props.parent._resetCounter === this.props.numCount) {
- this.props.parent._resetCounter = 0;
- this.props.parent._resetBoolean = false;
- }
- });
- }
- },
- );
- }
+ // this._resetReaction = reaction(
+ // () => this.props.parent._resetBoolean,
+ // () => {
+ // if (this.props.parent._resetBoolean) {
+ // runInAction(() => {
+ // this.reset();
+ // this.props.parent._resetCounter++;
+ // if (this.props.parent._resetCounter === this.props.numCount) {
+ // this.props.parent._resetCounter = 0;
+ // this.props.parent._resetBoolean = false;
+ // }
+ // });
+ // }
+ // },
+ // );
+ // }
- @action.bound
- reset() {
- if (this.props.default) {
- if (!this._status) {
- this._status = true;
- this.checkTimeline.play();
- this.checkTimeline.restart();
- }
- }
- else {
- if (this._status) {
- this._status = false;
- this.uncheckTimeline.play();
- this.uncheckTimeline.restart();
- }
- }
+ // @action.bound
+ // reset() {
+ // if (this.props.default) {
+ // if (!this._status) {
+ // this._status = true;
+ // this.checkTimeline.play();
+ // this.checkTimeline.restart();
+ // }
+ // }
+ // else {
+ // if (this._status) {
+ // this._status = false;
+ // this.uncheckTimeline.play();
+ // this.uncheckTimeline.restart();
+ // }
+ // }
- this.props.updateStatus(this.props.default);
- }
+ // this.props.updateStatus(this.props.default);
+ // }
@action.bound
onClick = () => {
- if (this._status) {
- this.uncheckTimeline.play();
- this.uncheckTimeline.restart();
- }
- else {
- this.checkTimeline.play();
- this.checkTimeline.restart();
+ // if (this._status) {
+ // this.uncheckTimeline.play();
+ // this.uncheckTimeline.restart();
+ // }
+ // else {
+ // this.checkTimeline.play();
+ // this.checkTimeline.restart();
- }
- this._status = !this._status;
- this.props.updateStatus(this._status);
+ // }
+ // this._status = !this._status;
+ // this.props.updateStatus(this._status);
}
diff --git a/src/client/views/search/FilterBox.scss b/src/client/views/search/FilterBox.scss
index ebb39460d..094ea9cc5 100644
--- a/src/client/views/search/FilterBox.scss
+++ b/src/client/views/search/FilterBox.scss
@@ -4,7 +4,6 @@
.filter-form {
padding: 25px;
width: 440px;
- background: whitesmoke;
position: relative;
right: 1px;
color: grey;
@@ -12,9 +11,7 @@
display: inline-block;
transform-origin: top;
overflow: auto;
- border-radius: 15px;
- box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
- border: solid #BBBBBBBB 1px;
+ border-bottom: solid black 3px;
.top-filter-header {
@@ -124,7 +121,7 @@
max-width: 40px;
flex: initial;
- &.icon{
+ &.icon {
width: 40px;
text-align: center;
margin-bottom: 5px;
@@ -150,7 +147,7 @@
transition: all 0.2s ease-in-out;
}
- &.icon:hover + .description {
+ &.icon:hover+.description {
opacity: 1;
}
}
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index d4c9e67fb..1c05ff864 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -387,7 +387,7 @@ export class FilterBox extends React.Component {
{/* {this.getActiveFilters()} */}
</div>
{this._filterOpen ? (
- <div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex" } : { display: "none" }}>
+ <div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex", background: "black" } : { display: "none" }}>
<div className="top-filter-header" style={{ display: "flex", width: "100%" }}>
<div id="header">Filter Search Results</div>
<div style={{ marginLeft: "auto" }}></div>
diff --git a/src/client/views/search/IconBar.scss b/src/client/views/search/IconBar.scss
index 2555ad271..013dcd57e 100644
--- a/src/client/views/search/IconBar.scss
+++ b/src/client/views/search/IconBar.scss
@@ -2,10 +2,9 @@
.icon-bar {
display: flex;
+ flex-wrap: wrap;
justify-content: space-evenly;
- align-items: center;
- height: 35px;
+ height: auto;
width: 100%;
- flex-wrap: wrap;
- margin-bottom: 10px;
+ flex-direction: row-reverse;
} \ No newline at end of file
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index cff397407..46c109934 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -9,7 +9,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
import * as _ from "lodash";
import { IconButton } from './IconButton';
-import { FilterBox } from './FilterBox';
+import { DocumentType } from "../../documents/DocumentTypes";
+
library.add(faSearch);
library.add(faObjectGroup);
@@ -25,6 +26,9 @@ library.add(faBan);
@observer
export class IconBar extends React.Component {
+ public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB];
+
+ @observable private _icons: string[] = this._allIcons;
static Instance: IconBar;
@@ -33,16 +37,22 @@ export class IconBar extends React.Component {
@observable public _reset: number = 0;
@observable public _select: number = 0;
+ @action.bound
+ updateIcon(newArray: string[]) { this._icons = newArray; }
+
+ @action.bound
+ getIcons(): string[] { return this._icons; }
+
constructor(props: any) {
super(props);
IconBar.Instance = this;
}
@action.bound
- getList(): string[] { return FilterBox.Instance.getIcons(); }
+ getList(): string[] { return this.getIcons(); }
@action.bound
- updateList(newList: string[]) { FilterBox.Instance.updateIcon(newList); }
+ updateList(newList: string[]) { this.updateIcon(newList); }
@action.bound
resetSelf = () => {
@@ -53,13 +63,13 @@ export class IconBar extends React.Component {
@action.bound
selectAll = () => {
this._selectAllClicked = true;
- this.updateList(FilterBox.Instance._allIcons);
+ this.updateList(this._allIcons);
}
render() {
return (
<div className="icon-bar">
- {FilterBox.Instance._allIcons.map((type: string) =>
+ {this._allIcons.map((type: string) =>
<IconButton key={type.toString()} type={type} />
)}
</div>
diff --git a/src/client/views/search/IconButton.scss b/src/client/views/search/IconButton.scss
index 4a3107676..4ec03c7c9 100644
--- a/src/client/views/search/IconButton.scss
+++ b/src/client/views/search/IconButton.scss
@@ -5,7 +5,6 @@
flex-direction: column;
align-items: center;
width: 30px;
- height: 60px;
.type-icon {
height: 30px;
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index f01508141..4f94139d9 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -11,7 +11,6 @@ import '../globalCssVariables.scss';
import * as _ from "lodash";
import { IconBar } from './IconBar';
import { props } from 'bluebird';
-import { FilterBox } from './FilterBox';
import { Search } from '../../../server/Search';
import { gravity } from 'sharp';
@@ -34,7 +33,7 @@ interface IconButtonProps {
@observer
export class IconButton extends React.Component<IconButtonProps>{
- @observable private _isSelected: boolean = FilterBox.Instance.getIcons().indexOf(this.props.type) !== -1;
+ @observable private _isSelected: boolean = IconBar.Instance.getIcons().indexOf(this.props.type) !== -1;
@observable private _hover = false;
private _resetReaction?: IReactionDisposer;
private _selectAllReaction?: IReactionDisposer;
@@ -108,7 +107,7 @@ export class IconButton extends React.Component<IconButtonProps>{
@action.bound
onClick = () => {
- const newList: string[] = FilterBox.Instance.getIcons();
+ const newList: string[] = IconBar.Instance.getIcons();
if (!this._isSelected) {
this._isSelected = true;
@@ -119,21 +118,24 @@ export class IconButton extends React.Component<IconButtonProps>{
_.pull(newList, this.props.type);
}
- FilterBox.Instance.updateIcon(newList);
+ IconBar.Instance.updateIcon(newList);
}
selected = {
opacity: 1,
- backgroundColor: "rgb(128, 128, 128)"
+ backgroundColor: "#121721",
+ //backgroundColor: "rgb(128, 128, 128)"
};
notSelected = {
opacity: 0.2,
+ backgroundColor: "#121721",
};
hoverStyle = {
opacity: 1,
- backgroundColor: "rgb(178, 206, 248)" //$darker-alt-accent
+ backgroundColor: "rgb(128, 128, 128)"
+ //backgroundColor: "rgb(178, 206, 248)" //$darker-alt-accent
};
@action.bound
@@ -186,7 +188,7 @@ export class IconButton extends React.Component<IconButtonProps>{
>
{this.getFA()}
</div>
- <div className="filter-description">{this.props.type}</div>
+ {/* <div className="filter-description">{this.props.type}</div> */}
</div>
);
}
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index f492ea773..f0223ca76 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -4,20 +4,21 @@
.searchBox-container {
display: flex;
flex-direction: column;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
position: absolute;
font-size: 10px;
line-height: 1;
- overflow: hidden;
+ overflow: auto;
+ background: lightgrey,
}
+
.searchBox-bar {
height: 32px;
display: flex;
justify-content: flex-end;
align-items: center;
padding-left: 2px;
- padding-right: 2px;
.searchBox-barChild {
@@ -33,8 +34,7 @@
-webkit-transition: width 0.4s;
transition: width 0.4s;
align-self: stretch;
- margin-left: 2px;
- margin-right: 2px
+
}
.searchBox-input:focus {
@@ -44,6 +44,10 @@
&.searchBox-filter {
align-self: stretch;
+ button:hover{
+ transform:scale(1.0);
+ background:"#121721";
+ }
}
&.searchBox-submit {
@@ -65,7 +69,7 @@
}
.searchBox-results {
- display:flex;
+ display: flex;
flex-direction: column;
top: 300px;
display: flex;
@@ -83,6 +87,249 @@
text-transform: uppercase;
text-align: left;
font-weight: bold;
- margin-left: 28px;
+ }
+}
+
+.filter-form {
+ position: relative;
+ background: #121721;
+ flex-direction: column;
+ transform-origin: top;
+ transition: height 0.3s ease, display 0.6s ease;
+ height:0px;
+ overflow:hidden;
+
+
+ .filter-header {
+ display: flex;
+ position: relative;
+ flex-wrap:wrap;
+ right: 1px;
+ color: grey;
+ flex-direction: row-reverse;
+ transform-origin: top;
+ justify-content: space-evenly;
+ margin-bottom: 5px;
+ overflow:hidden;
+ transition:height 0.3s ease-out;
+
+
+
+ .filter-item {
+ position: relative;
+ border:1px solid grey;
+ border-radius: 16px;
+
+ }
+ }
+
+ .filter-body {
+ position: relative;
+ right: 1px;
+ color: grey;
+ transform-origin: top;
+ border-top: 0px;
+ //padding-top: 5px;
+ margin-left: 10px;
+ margin-right: 10px;
+ overflow:hidden;
+ transition:height 0.3s ease-out;
+ height:0px;
+
+ }
+ .filter-key {
+ position: relative;
+ right: 1px;
+ color: grey;
+ transform-origin: top;
+ border-top: 0px;
+ //padding-top: 5px;
+ margin-left: 10px;
+ margin-right: 10px;
+ overflow:hidden;
+ transition:height 0.3s ease-out;
+ height:0px;
+ .filter-keybar {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ height: auto;
+ width: 100%;
+ flex-direction: row-reverse;
+ margin-top:5px;
+
+ .filter-item {
+ position: relative;
+ border:1px solid grey;
+ border-radius: 16px;
+
+ }
+ }
+
+
+ }
+}
+
+// .top-filter-header {
+
+// #header {
+// text-transform: uppercase;
+// letter-spacing: 2px;
+// font-size: 13;
+// width: 80%;
+// }
+
+// .close-icon {
+// width: 20%;
+// opacity: .6;
+// position: relative;
+// display: block;
+
+// .line {
+// display: block;
+// background: $alt-accent;
+// width: 20;
+// height: 3;
+// position: absolute;
+// right: 0;
+// border-radius: ($height-line / 2);
+
+// &.line-1 {
+// transform: rotate(45deg);
+// top: 45%;
+// }
+
+// &.line-2 {
+// transform: rotate(-45deg);
+// top: 45%;
+// }
+// }
+// }
+
+// .close-icon:hover {
+// opacity: 1;
+// }
+
+// }
+
+// .filter-options {
+
+// .filter-div {
+// margin-top: 10px;
+// margin-bottom: 10px;
+// display: inline-block;
+// width: 100%;
+// border-color: rgba(178, 206, 248, .2); // $darker-alt-accent
+// border-top-style: solid;
+
+// .filter-header {
+// display: flex;
+// align-items: center;
+// margin-bottom: 10px;
+// letter-spacing: 2px;
+
+// .filter-title {
+// font-size: 13;
+// text-transform: uppercase;
+// margin-top: 10px;
+// margin-bottom: 10px;
+// -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;
+// }
+// }
+
+// .filter-header:hover .filter-title {
+// transform: scale(1.05);
+// }
+
+// .filter-panel {
+// max-height: 0px;
+// width: 100%;
+// overflow: hidden;
+// opacity: 0;
+// transform-origin: top;
+// -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;
+// text-align: center;
+// }
+// }
+// }
+
+// .filter-buttons {
+// border-color: rgba(178, 206, 248, .2); // $darker-alt-accent
+// border-top-style: solid;
+// padding-top: 10px;
+// }
+
+
+.active-filters {
+ display: flex;
+ flex-direction: row-reverse;
+ justify-content: flex-end;
+ width: 100%;
+ margin-right: 30px;
+ position: relative;
+
+ .active-icon {
+ max-width: 40px;
+ flex: initial;
+
+ &.icon {
+ width: 40px;
+ text-align: center;
+ margin-bottom: 5px;
+ position: absolute;
+ }
+
+ &.container {
+ display: flex;
+ flex-direction: column;
+ width: 40px;
+ }
+
+ &.description {
+ text-align: center;
+ top: 40px;
+ position: absolute;
+ width: 40px;
+ font-size: 9px;
+ opacity: 0;
+ -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;
+ }
+
+ &.icon:hover+.description {
+ opacity: 1;
+ }
+ }
+
+ .col-icon {
+ height: 35px;
+ margin-left: 5px;
+ width: 35px;
+ background-color: black;
+ color: white;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .save-filter,
+ .reset-filter,
+ .all-filter {
+ background-color: gray;
+ }
+
+ .save-filter:hover,
+ .reset-filter:hover,
+ .all-filter:hover {
+ background-color: $darker-alt-accent;
+ }
}
} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 586365f6e..67af661c9 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,37 +1,51 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, observable, runInAction, IReactionDisposer, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as rp from 'request-promise';
import { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
-import { Cast, NumCast } from '../../../new_fields/Types';
+import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
import { Utils } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { SetupDrag } from '../../util/DragManager';
import { SearchUtil } from '../../util/SearchUtil';
-import { FilterBox } from './FilterBox';
-import "./FilterBox.scss";
import "./SearchBox.scss";
import { SearchItem } from './SearchItem';
import { IconBar } from './IconBar';
+import { FieldView } from '../nodes/FieldView';
+import { DocumentType } from "../../documents/DocumentTypes";
+import { DocumentView } from '../nodes/DocumentView';
+import { SelectionManager } from '../../util/SelectionManager';
library.add(faTimes);
+export interface SearchProps {
+ id: string;
+ searchQuery?: string;
+ filterQquery?: string;
+}
+
+export enum Keys {
+ TITLE = "title",
+ AUTHOR = "author",
+ DATA = "data"
+}
+
@observer
-export class SearchBox extends React.Component {
+export class SearchBox extends React.Component<SearchProps> {
@observable private _searchString: string = "";
@observable private _resultsOpen: boolean = false;
@observable private _searchbarOpen: boolean = false;
@observable private _results: [Doc, string[], string[]][] = [];
- private _resultsSet = new Map<Doc, number>();
@observable private _openNoResults: boolean = false;
@observable private _visibleElements: JSX.Element[] = [];
- private resultsRef = React.createRef<HTMLDivElement>();
+ private _resultsSet = new Map<Doc, number>();
+ private _resultsRef = React.createRef<HTMLDivElement>();
public inputRef = React.createRef<HTMLInputElement>();
private _isSearch: ("search" | "placeholder" | undefined)[] = [];
@@ -42,10 +56,18 @@ export class SearchBox extends React.Component {
private _maxSearchIndex: number = 0;
private _curRequest?: Promise<any> = undefined;
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); }
+
+
+ //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
+ @observable private _basicWordStatus: boolean = false;
+ @observable private _nodeStatus: boolean = false;
+ @observable private _keyStatus: boolean = false;
+
constructor(props: any) {
super(props);
-
SearchBox.Instance = this;
this.resultsScrolled = this.resultsScrolled.bind(this);
}
@@ -53,21 +75,21 @@ export class SearchBox extends React.Component {
componentDidMount = () => {
if (this.inputRef.current) {
this.inputRef.current.focus();
+ runInAction(() => this._searchbarOpen = true);
+ }
+ if (this.props.searchQuery && this.props.filterQquery) {
+ console.log(this.props.searchQuery);
+ const sq = this.props.searchQuery;
runInAction(() => {
- this._searchbarOpen = true;
+ this._searchString = sq;
+ this.submitSearch();
});
}
}
+
@action
- getViews = async (doc: Doc) => {
- const results = await SearchUtil.GetViewsOfDocument(doc);
- let toReturn: Doc[] = [];
- await runInAction(() => {
- toReturn = results;
- });
- return toReturn;
- }
+ getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc)
@action.bound
onChange(e: React.ChangeEvent<HTMLInputElement>) {
@@ -106,33 +128,186 @@ export class SearchBox extends React.Component {
}
}
+ public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, 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
+ @observable private _filterOpen: boolean = false;
+ //if icons = all icons, then no icon filter is applied
+ @observable private _icons: string[] = this._allIcons;
+ //if all of these are true, no key filter is applied
+ @observable private _titleFieldStatus: boolean = true;
+ @observable private _authorFieldStatus: boolean = true;
+ //this also serves as an indicator if the collection status filter is applied
+ @observable public _deletedDocsStatus: boolean = false;
+ @observable private _collectionStatus = false;
+
+
+ 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;
+ }
+
+ basicRequireWords(query: string): string {
+ const oldWords = query.split(" ");
+ const newWords: string[] = [];
+ oldWords.forEach(word => {
+ const newWrd = "+" + word;
+ newWords.push(newWrd);
+ });
+ query = newWords.join(" ");
+
+ return query;
+ }
+
+ @action
+ filterDocsByType(docs: Doc[]) {
+ if (this._icons.length === this._allIcons.length) {
+ return docs;
+ }
+ const finalDocs: Doc[] = [];
+ docs.forEach(doc => {
+ const layoutresult = Cast(doc.type, "string");
+ if (layoutresult && this._icons.includes(layoutresult)) {
+ finalDocs.push(doc);
+ }
+ });
+ return finalDocs;
+ }
+
+ addCollectionFilter(query: string): string {
+ const collections: Doc[] = this.getCurCollections();
+ const oldWords = query.split(" ");
+
+ const collectionString: string[] = [];
+ collections.forEach(doc => {
+ const proto = doc.proto;
+ const protoId = (proto || doc)[Id];
+ const colString: string = "{!join from=data_l to=id}id:" + protoId + " ";
+ collectionString.push(colString);
+ });
+
+ let finalColString = collectionString.join(" ");
+ finalColString = finalColString.trim();
+ return "+(" + finalColString + ")" + query;
+ }
+
+ get filterTypes() {
+ return this._icons.length === this._allIcons.length ? undefined : this._icons;
+ }
+
+ @action.bound
+ updateIcon(newArray: string[]) { this._icons = newArray; }
+
+ @action.bound
+ getIcons(): string[] { return this._icons; }
+
+ //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[] {
+ const selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments();
+ const collections: Doc[] = [];
+
+ selectedDocs.forEach(async element => {
+ const layout: string = StrCast(element.props.Document.layout);
+ //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);
+ }
+ }
+ //makes sure collections aren't added more than once
+ if (element.props.ContainingCollectionDoc && !collections.includes(element.props.ContainingCollectionDoc)) {
+ collections.push(element.props.ContainingCollectionDoc);
+ }
+ });
+
+ return collections;
+ }
+
+
+ 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._deletedDocsStatus) {
+ finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA);
+ }
+ return finalQuery;
+ }
+
+ basicFieldFilters(query: string, type: string): string {
+ const oldWords = query.split(" ");
+ let mod = "";
+
+ if (type === Keys.AUTHOR) {
+ mod = " author_t:";
+ } if (type === Keys.DATA) {
+ //TODO
+ } if (type === Keys.TITLE) {
+ mod = " title_t:";
+ }
+
+ const newWords: string[] = [];
+ oldWords.forEach(word => {
+ const newWrd = mod + word;
+ newWords.push(newWrd);
+ });
+
+ query = newWords.join(" ");
+
+ return query;
+ }
+
+ get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); }
+
+
@action
submitSearch = async () => {
- let query = this._searchString;
- query = FilterBox.Instance.getFinalQuery(query);
+ const query = this._searchString;
+ this.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 === "") {
- return;
- }
- else {
+ if (query !== "") {
this._endIndex = 12;
this._maxSearchIndex = 0;
this._numTotalResults = -1;
await this.getResults(query);
- }
- runInAction(() => {
- this._resultsOpen = true;
- this._searchbarOpen = true;
- this._openNoResults = true;
- this.resultsScrolled();
- });
+ runInAction(() => {
+ this._resultsOpen = true;
+ this._searchbarOpen = true;
+ this._openNoResults = true;
+ this.resultsScrolled();
+ });
+ }
}
getAllResults = async (query: string) => {
@@ -140,11 +315,14 @@ export class SearchBox extends React.Component {
}
private get filterQuery() {
- const types = FilterBox.Instance.filterTypes;
- const includeDeleted = FilterBox.Instance.getDataStatus();
- return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}" OR type_t:"extension"`).join(" ")})` : "");
+ const types = this.filterTypes;
+ const includeDeleted = this.getDataStatus() ? "" : " AND NOT deleted_b:true";
+ const includeIcons = this.getDataStatus() ? "" : " AND NOT type_t:fonticonbox";
+ return "NOT baseProto_b:true" + includeDeleted + includeIcons + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
}
+ getDataStatus() { return this._deletedDocsStatus; }
+
private NumResults = 25;
private lockPromise?: Promise<void>;
@@ -155,7 +333,6 @@ export class SearchBox extends React.Component {
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: this.NumResults, 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;
@@ -168,9 +345,9 @@ export class SearchBox extends React.Component {
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]);
- const filteredDocs = FilterBox.Instance.filterDocsByType(docs);
+ const filteredDocs = this.filterDocsByType(docs);
runInAction(() => {
- // this._results.push(...filteredDocs);
+ //this._results.push(...filteredDocs);
filteredDocs.forEach(doc => {
const index = this._resultsSet.get(doc);
const highlight = highlights[doc[Id]];
@@ -200,9 +377,8 @@ export class SearchBox extends React.Component {
collectionRef = React.createRef<HTMLSpanElement>();
startDragCollection = async () => {
- const res = await this.getAllResults(FilterBox.Instance.getFinalQuery(this._searchString));
- const filtered = FilterBox.Instance.filterDocsByType(res.docs);
- // console.log(this._results)
+ const res = await this.getAllResults(this.getFinalQuery(this._searchString));
+ const filtered = this.filterDocsByType(res.docs);
const docs = filtered.map(doc => {
const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
if (isProto) {
@@ -234,22 +410,19 @@ export class SearchBox extends React.Component {
y += 300;
}
}
- return Docs.Create.TreeDocument(docs, { _width: 200, _height: 400, title: `Search Docs: "${this._searchString}"` });
+ return Docs.Create.QueryDocument({ _autoHeight: true, title: this._searchString, filterQuery: this.filterQuery, searchQuery: this._searchString });
}
@action.bound
openSearch(e: React.SyntheticEvent) {
e.stopPropagation();
this._openNoResults = false;
- FilterBox.Instance.closeFilter();
this._resultsOpen = true;
this._searchbarOpen = true;
- FilterBox.Instance._pointerTime = e.timeStamp;
}
@action.bound
closeSearch = () => {
- FilterBox.Instance.closeFilter();
this.closeResults();
this._searchbarOpen = false;
}
@@ -267,11 +440,11 @@ export class SearchBox extends React.Component {
@action
resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => {
- if (!this.resultsRef.current) return;
- const scrollY = e ? e.currentTarget.scrollTop : this.resultsRef.current ? this.resultsRef.current.scrollTop : 0;
+ if (!this._resultsRef.current) return;
+ const scrollY = e ? e.currentTarget.scrollTop : this._resultsRef.current ? this._resultsRef.current.scrollTop : 0;
const itemHght = 53;
const startIndex = Math.floor(Math.max(0, scrollY / itemHght));
- const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this.resultsRef.current.getBoundingClientRect().height / itemHght)));
+ const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this._resultsRef.current.getBoundingClientRect().height / itemHght)));
this._endIndex = endIndex === -1 ? 12 : endIndex;
@@ -337,9 +510,131 @@ export class SearchBox extends React.Component {
@computed
get resultHeight() { return this._numTotalResults * 70; }
+ //if true, any keywords can be used. if false, all keywords are required.
+ @action.bound
+ handleWordQueryChange = () => {
+ this._basicWordStatus = !this._basicWordStatus;
+ }
+
+ @action.bound
+ handleNodeChange = () => {
+ this._nodeStatus = !this._nodeStatus;
+ if (this._nodeStatus) {
+ this.expandSection(`node${this.props.id}`);
+ }
+ else {
+ this.collapseSection(`node${this.props.id}`);
+ }
+ }
+
+ @action.bound
+ handleKeyChange = () => {
+ this._keyStatus = !this._keyStatus;
+ if (this._keyStatus) {
+ this.expandSection(`key${this.props.id}`);
+ }
+ else {
+ this.collapseSection(`key${this.props.id}`);
+ }
+ }
+
+ @action.bound
+ handleFilterChange = () => {
+ this._filterOpen = !this._filterOpen;
+ if (this._filterOpen) {
+ this.expandSection(`filterhead${this.props.id}`);
+ document.getElementById(`filterhead${this.props.id}`)!.style.padding = "5";
+ }
+ else {
+ this.collapseSection(`filterhead${this.props.id}`);
+
+
+ }
+ }
+
+ @computed
+ get menuHeight() {
+ return document.getElementById("hi")?.clientHeight;
+ }
+
+
+ collapseSection(thing: string) {
+ const id = this.props.id;
+ const element = document.getElementById(thing)!;
+ // get the height of the element's inner content, regardless of its actual size
+ const sectionHeight = element.scrollHeight;
+
+ // temporarily disable all css transitions
+ const elementTransition = element.style.transition;
+ element.style.transition = '';
+
+ // on the next frame (as soon as the previous style change has taken effect),
+ // explicitly set the element's height to its current pixel height, so we
+ // aren't transitioning out of 'auto'
+ requestAnimationFrame(function () {
+ element.style.height = sectionHeight + 'px';
+ element.style.transition = elementTransition;
+
+ // on the next frame (as soon as the previous style change has taken effect),
+ // have the element transition to height: 0
+ requestAnimationFrame(function () {
+ element.style.height = 0 + 'px';
+ thing === `filterhead${id}` ? document.getElementById(`filterhead${id}`)!.style.padding = "0" : null;
+ });
+ });
+
+ // mark the section as "currently collapsed"
+ element.setAttribute('data-collapsed', 'true');
+ }
+
+ expandSection(thing: string) {
+ console.log("expand");
+ const element = document.getElementById(thing)!;
+ // get the height of the element's inner content, regardless of its actual size
+ const sectionHeight = element.scrollHeight;
+
+ // have the element transition to the height of its inner content
+ element.style.height = sectionHeight + 'px';
+
+ // when the next css transition finishes (which should be the one we just triggered)
+ element.addEventListener('transitionend', function handler(e) {
+ // remove this event listener so it only gets triggered once
+ console.log("autoset");
+ element.removeEventListener('transitionend', handler);
+
+ // remove "height" from the element's inline styles, so it can return to its initial value
+ element.style.height = "auto";
+ //element.style.height = undefined;
+ });
+
+ // mark the section as "currently not collapsed"
+ element.setAttribute('data-collapsed', 'false');
+
+ }
+
+ autoset(thing: string) {
+ const element = document.getElementById(thing)!;
+ console.log("autoset");
+ element.removeEventListener('transitionend', function (e) { });
+
+ // remove "height" from the element's inline styles, so it can return to its initial value
+ element.style.height = "auto";
+ //element.style.height = undefined;
+ }
+
+ @action.bound
+ updateTitleStatus() { this._titleFieldStatus = !this._titleFieldStatus; }
+
+ @action.bound
+ updateAuthorStatus() { this._authorFieldStatus = !this._authorFieldStatus; }
+
+ @action.bound
+ updateDataStatus() { this._deletedDocsStatus = !this._deletedDocsStatus; }
+
render() {
+
return (
- <div className="searchBox-container" onPointerDown={e => { e.stopPropagation(); e.preventDefault(); }}>
+ <div className="searchBox-container">
<div className="searchBox-bar">
<span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, () => this._searchString ? this.startDragCollection() : undefined)} ref={this.collectionRef} title="Drag Results as Collection">
<FontAwesomeIcon icon="object-group" size="lg" />
@@ -347,16 +642,31 @@ export class SearchBox extends React.Component {
<input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this.inputRef}
className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} onFocus={this.openSearch}
style={{ width: this._searchbarOpen ? "500px" : "100px" }} />
- <button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={() => { }} onPointerDown={FilterBox.Instance.stopProp}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button>
+ <button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={() => this.handleFilterChange()}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button>
</div>
- <div className="searchBox-quickFilter" onPointerDown={this.openSearch}>
- <div className="filter-panel"><IconBar /></div>
+
+ <div id={`filterhead${this.props.id}`} className="filter-form" >
+ <div id={`filterhead2${this.props.id}`} className="filter-header" style={this._filterOpen ? {} : {}}>
+ <button className="filter-item" style={this._basicWordStatus ? { background: "#aaaaa3", } : {}} onClick={this.handleWordQueryChange}>Keywords</button>
+ <button className="filter-item" style={this._keyStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleKeyChange}>Keys</button>
+ <button className="filter-item" style={this._nodeStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleNodeChange}>Nodes</button>
+ </div>
+ <div id={`node${this.props.id}`} className="filter-body" style={this._nodeStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}>
+ <IconBar />
+ </div>
+ <div className="filter-key" id={`key${this.props.id}`} style={this._keyStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}>
+ <div className="filter-keybar">
+ <button className="filter-item" style={this._titleFieldStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateTitleStatus}>Title</button>
+ <button className="filter-item" style={this._deletedDocsStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateDataStatus}>Deleted Docs</button>
+ <button className="filter-item" style={this._authorFieldStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateAuthorStatus}>Author</button>
+ </div>
+ </div>
</div>
<div className="searchBox-results" onScroll={this.resultsScrolled} style={{
display: this._resultsOpen ? "flex" : "none",
height: this.resFull ? "auto" : this.resultHeight,
overflow: "visibile" // this.resFull ? "auto" : "visible"
- }} ref={this.resultsRef}>
+ }} ref={this._resultsRef}>
{this._visibleElements}
</div>
</div>
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 63cef5101..0d77026ad 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
import { Doc } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, emptyPath, returnFalse, Utils } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue } from "../../../Utils";
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, SetupDrag } from "../../util/DragManager";
@@ -158,6 +158,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
<ContentFittingDocumentView
Document={this.props.doc}
LibraryPath={emptyPath}
+ rootSelected={returnFalse}
fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1}
addDocument={returnFalse}
removeDocument={returnFalse}