diff options
| author | Melissa Zhang <mzhang19096@gmail.com> | 2020-08-07 09:40:06 -0700 |
|---|---|---|
| committer | Melissa Zhang <mzhang19096@gmail.com> | 2020-08-07 09:40:06 -0700 |
| commit | 9886ed681ff18a33f7acab8f83b475ca9ea60bf7 (patch) | |
| tree | 0b69d4b0fbfc52eac07c2e38fdbb34f161a2dc98 /src/client/views/search | |
| parent | c94a61aa594f77db4c9b08a5f91c1a7e57d5ff9d (diff) | |
| parent | b02cfed890d9d95a8f45bbc93d688bd3311dd387 (diff) | |
merge with master
Diffstat (limited to 'src/client/views/search')
| -rw-r--r-- | src/client/views/search/FieldFilters.scss | 12 | ||||
| -rw-r--r-- | src/client/views/search/FieldFilters.tsx | 41 | ||||
| -rw-r--r-- | src/client/views/search/FilterBox.scss | 178 | ||||
| -rw-r--r-- | src/client/views/search/FilterBox.tsx | 431 | ||||
| -rw-r--r-- | src/client/views/search/SearchBox.scss | 69 | ||||
| -rw-r--r-- | src/client/views/search/SearchBox.tsx | 731 | ||||
| -rw-r--r-- | src/client/views/search/SearchItem.scss | 163 | ||||
| -rw-r--r-- | src/client/views/search/SearchItem.tsx | 310 |
8 files changed, 525 insertions, 1410 deletions
diff --git a/src/client/views/search/FieldFilters.scss b/src/client/views/search/FieldFilters.scss deleted file mode 100644 index e1d0d8df5..000000000 --- a/src/client/views/search/FieldFilters.scss +++ /dev/null @@ -1,12 +0,0 @@ -.field-filters { - width: 100%; - display: grid; - // grid-template-columns: 18% 20% 60%; - grid-template-columns: 20% 25% 60%; -} - -.field-filters-required { - width: 100%; - display: grid; - grid-template-columns: 50% 50%; -}
\ No newline at end of file diff --git a/src/client/views/search/FieldFilters.tsx b/src/client/views/search/FieldFilters.tsx deleted file mode 100644 index 7a33282d2..000000000 --- a/src/client/views/search/FieldFilters.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from 'react'; -import { observable } from 'mobx'; -import { CheckBox } from './CheckBox'; -import { Keys } from './FilterBox'; -import "./FieldFilters.scss"; - -export interface FieldFilterProps { - titleFieldStatus: boolean; - dataFieldStatus: boolean; - authorFieldStatus: boolean; - updateTitleStatus(stat: boolean): void; - updateAuthorStatus(stat: boolean): void; - updateDataStatus(stat: boolean): void; -} - -export class FieldFilters extends React.Component<FieldFilterProps> { - - static Instance: FieldFilters; - - @observable public _resetBoolean = false; - @observable public _resetCounter: number = 0; - - constructor(props: FieldFilterProps) { - super(props); - FieldFilters.Instance = this; - } - - resetFieldFilters() { - this._resetBoolean = true; - } - - render() { - return ( - <div className="field-filters"> - <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.titleFieldStatus} updateStatus={this.props.updateTitleStatus} title={Keys.TITLE} /> - <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.authorFieldStatus} updateStatus={this.props.updateAuthorStatus} title={Keys.AUTHOR} /> - <CheckBox default={false} numCount={3} parent={this} originalStatus={this.props.dataFieldStatus} updateStatus={this.props.updateDataStatus} title={"Deleted Docs"} /> - </div> - ); - } -}
\ No newline at end of file diff --git a/src/client/views/search/FilterBox.scss b/src/client/views/search/FilterBox.scss deleted file mode 100644 index 094ea9cc5..000000000 --- a/src/client/views/search/FilterBox.scss +++ /dev/null @@ -1,178 +0,0 @@ -@import "../globalCssVariables"; -@import "./NaviconButton.scss"; - -.filter-form { - padding: 25px; - width: 440px; - position: relative; - right: 1px; - color: grey; - flex-direction: column; - display: inline-block; - transform-origin: top; - overflow: auto; - border-bottom: solid black 3px; - - .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/FilterBox.tsx b/src/client/views/search/FilterBox.tsx deleted file mode 100644 index eb61f9a14..000000000 --- a/src/client/views/search/FilterBox.tsx +++ /dev/null @@ -1,431 +0,0 @@ -import * as React from 'react'; -import { observer } from 'mobx-react'; -import { observable, action } from 'mobx'; -import "./SearchBox.scss"; -import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-svg-icons'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { Doc } from '../../../fields/Doc'; -import { Id } from '../../../fields/FieldSymbols'; -import { DocumentType } from "../../documents/DocumentTypes"; -import { Cast, StrCast } from '../../../fields/Types'; -import * as _ from "lodash"; -import { IconBar } from './IconBar'; -import { FieldFilters } from './FieldFilters'; -import { SelectionManager } from '../../util/SelectionManager'; -import { DocumentView } from '../nodes/DocumentView'; -import { CollectionFilters } from './CollectionFilters'; -import * as $ from 'jquery'; -import "./FilterBox.scss"; -import { SearchBox } from './SearchBox'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -library.add(faTimes); -library.add(faCheckCircle); -library.add(faObjectGroup); - -export enum Keys { - TITLE = "title", - AUTHOR = "author", - DATA = "data" -} - -@observer -export class FilterBox extends React.Component { - - static Instance: FilterBox; - public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, 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 _basicWordStatus: boolean = true; - @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 _anyKeywordStatus: boolean = true; - @observable private _allKeywordStatus: boolean = true; - @observable private _titleFieldStatus: boolean = true; - @observable private _authorFieldStatus: boolean = true; - @observable private _dataFieldStatus: boolean = true; - //this also serves as an indicator if the collection status filter is applied - @observable public _deletedDocsStatus: boolean = false; - @observable private _collectionStatus = false; - @observable private _collectionSelfStatus = true; - @observable private _collectionParentStatus = true; - @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; - } - setupAccordion() { - $('document').ready(function () { - const acc = document.getElementsByClassName('filter-header'); - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < acc.length; i++) { - acc[i].addEventListener("click", function (this: HTMLElement) { - this.classList.toggle("active"); - - const panel = this.nextElementSibling as HTMLElement; - if (panel.style.maxHeight) { - panel.style.overflow = "hidden"; - panel.style.maxHeight = ""; - panel.style.opacity = "0"; - } else { - setTimeout(() => { - panel.style.overflow = "visible"; - }, 200); - setTimeout(() => { - panel.style.opacity = "1"; - }, 50); - panel.style.maxHeight = panel.scrollHeight + "px"; - - } - }); - - const el = acc[i] as HTMLElement; - el.click(); - } - }); - } - - @action.bound - minimizeAll() { - $('document').ready(function () { - const acc = document.getElementsByClassName('filter-header'); - - // tslint:disable-next-line: prefer-for-of - for (var i = 0; i < acc.length; i++) { - const classList = acc[i].classList; - if (classList.contains("active")) { - acc[i].classList.toggle("active"); - const panel = acc[i].nextElementSibling as HTMLElement; - panel.style.overflow = "hidden"; - panel.style.maxHeight = ""; - } - } - }); - } - - @action.bound - resetFilters = () => { - this._basicWordStatus = true; - IconBar.Instance.selectAll(); - FieldFilters.Instance.resetFieldFilters(); - } - - 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; - } - - 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; - } - - 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; - } - - get fieldFiltersApplied() { return !(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[] { - 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; - } - - 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 { - 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 === 9 ? undefined : this._icons; - } - - @action - filterDocsByType(docs: Doc[]) { - if (this._icons.length === 9) { - 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; - } - - getABCicon() { - return ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35"> - <path d="M25.4 47.9c-1.3 1.3-1.9 2.8-1.9 4.8 0 3.8 2.3 6.1 6.1 6.1 5.1 0 8-3.3 9-6.2 0.2-0.7 0.4-1.4 0.4-2.1v-6.1c-0.1 0-0.1 0-0.2 0C32.2 44.5 27.7 45.6 25.4 47.9z" /> - <path d="M64.5 28.6c-2.2 0-4.1 1.5-4.7 3.8l0 0.2c-0.1 0.3-0.1 0.7-0.1 1.1v3.3c0 0.4 0.1 0.8 0.2 1.1 0.6 2.2 2.4 3.6 4.6 3.6 3.2 0 5.2-2.6 5.2-6.7C69.5 31.8 68 28.6 64.5 28.6z" /> - <path d="M43.9 0C19.7 0 0 19.7 0 43.9s19.7 43.9 43.9 43.9 43.9-19.6 43.9-43.9S68.1 0 43.9 0zM40.1 65.5l-0.5-4c-3 3.1-7.4 4.9-12.1 4.9 -6.8 0-13.6-4.4-13.6-12.8 0-4 1.3-7.4 4-10 4.1-4.1 11.1-6.2 20.8-6.3 0-5.5-2.9-8.4-8.3-8.4 -3.6 0-7.4 1.1-10.2 2.9l-1.1 0.7 -2.4-6.9 0.7-0.4c3.7-2.4 8.9-3.8 14.1-3.8 10.9 0 16.7 6.2 16.7 17.9V54.6c0 4.1 0.2 7.2 0.7 9.7L49 65.5H40.1zM65.5 67.5c1.8 0 3-0.5 4-0.9l0.5-0.2 0.8 3.4 -0.3 0.2c-1 0.5-3 1.1-5.5 1.1 -5.8 0-9.7-4-9.7-9.9 0-6.1 4.3-10.3 10.4-10.3 2.1 0 4 0.5 4.9 1l0.3 0.2 -1 3.5 -0.5-0.3c-0.7-0.4-1.8-0.8-3.7-0.8 -3.7 0-6.1 2.6-6.1 6.6C59.5 64.8 61.9 67.5 65.5 67.5zM65 45.3c-2.5 0-4.5-0.9-5.9-2.7l-0.1 2.3h-3.8l0-0.5c0.1-1.2 0.2-3.1 0.2-4.8V16.7h4.3v10.8c1.4-1.6 3.5-2.5 6-2.5 2.2 0 4.1 0.8 5.5 2.3 1.8 1.8 2.8 4.5 2.8 7.7C73.8 42.1 69.3 45.3 65 45.3z" /> - </svg> - ); - } - - getTypeIcon() { - return ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35"> - <path d="M43.9 0C19.7 0 0 19.7 0 43.9s19.7 43.9 43.9 43.9 43.9-19.6 43.9-43.9S68.1 0 43.9 0zM43.9 12.2c4.1 0 7.5 3.4 7.5 7.5 0 4.1-3.4 7.5-7.5 7.5 -4.1 0-7.5-3.4-7.5-7.5C36.4 15.5 39.7 12.2 43.9 12.2zM11.9 50.4l7.5-13 7.5 13H11.9zM47.6 75.7h-7.5l-3.7-6.5 3.8-6.5h7.5l3.8 6.5L47.6 75.7zM70.7 70.7c-0.2 0.2-0.4 0.3-0.7 0.3s-0.5-0.1-0.7-0.3l-25.4-25.4 -25.4 25.4c-0.2 0.2-0.4 0.3-0.7 0.3s-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1 0-1.4l25.4-25.4 -25.4-25.4c-0.4-0.4-0.4-1 0-1.4s1-0.4 1.4 0l25.4 25.4 25.4-25.4c0.4-0.4 1-0.4 1.4 0s0.4 1 0 1.4l-25.4 25.4 25.4 25.4C71.1 69.7 71.1 70.3 70.7 70.7zM61.4 51.4v-15h15v15H61.4z" /> - </svg> - ); - } - - getKeyIcon() { - return ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35"> - <path d="M38.5 32.4c0 3.4-2.7 6.1-6.1 6.1 -3.4 0-6.1-2.7-6.1-6.1 0-3.4 2.8-6.1 6.1-6.1C35.8 26.3 38.5 29 38.5 32.4zM87.8 43.9c0 24.2-19.6 43.9-43.9 43.9S0 68.1 0 43.9C0 19.7 19.7 0 43.9 0S87.8 19.7 87.8 43.9zM66.8 60.3L50.2 43.7c-0.5-0.5-0.6-1.2-0.4-1.8 2.4-5.6 1.1-12.1-3.2-16.5 -5.9-5.8-15.4-5.8-21.2 0l0 0c-4.3 4.3-5.6 10.8-3.2 16.5 3.2 7.6 12 11.2 19.7 8 0.6-0.3 1.4-0.1 1.8 0.4l3.1 3.1h3.9c1.2 0 2.2 1 2.2 2.2v3.6h3.6c1.2 0 2.2 1 2.2 2.2v4l1.6 1.6h6.5V60.3z" /> - </svg> - ); - } - - getColIcon() { - return ( - <div className="col-icon"> - <FontAwesomeIcon icon={faObjectGroup} size="lg" /> - </div> - ); - } - - @action.bound - openFilter = () => { - this._filterOpen = !this._filterOpen; - SearchBox.Instance.closeResults(); - this.setupAccordion(); - } - - //if true, any keywords can be used. if false, all keywords are required. - @action.bound - handleWordQueryChange = () => { - this._basicWordStatus = !this._basicWordStatus; - } - - @action.bound - updateIcon(newArray: string[]) { this._icons = newArray; } - - @action.bound - getIcons(): string[] { return this._icons; } - - stopProp = (e: React.PointerEvent) => { - e.stopPropagation(); - this._pointerTime = e.timeStamp; - } - - @action.bound - public closeFilter() { - this._filterOpen = false; - } - - @action.bound - updateAnyKeywordStatus(newStat: boolean) { this._anyKeywordStatus = newStat; } - - @action.bound - updateAllKeywordStatus(newStat: boolean) { this._allKeywordStatus = newStat; } - - @action.bound - updateTitleStatus(newStat: boolean) { this._titleFieldStatus = newStat; } - - @action.bound - updateAuthorStatus(newStat: boolean) { this._authorFieldStatus = newStat; } - - @action.bound - updateDataStatus(newStat: boolean) { this._deletedDocsStatus = 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; } - - getAnyKeywordStatus() { return this._anyKeywordStatus; } - getAllKeywordStatus() { return this._allKeywordStatus; } - getCollectionStatus() { return this._collectionStatus; } - getSelfCollectionStatus() { return this._collectionSelfStatus; } - getParentCollectionStatus() { return this._collectionParentStatus; } - getTitleStatus() { return this._titleFieldStatus; } - getAuthorStatus() { return this._authorFieldStatus; } - getDataStatus() { return this._deletedDocsStatus; } - - getActiveFilters() { - return ( - <div className="active-filters"> - {!this._basicWordStatus ? <div className="active-icon container"> - <div className="active-icon icon">{this.getABCicon()}</div> - <div className="active-icon description">Required Words Applied</div> - </div> : undefined} - {!(this._icons.length === 9) ? <div className="active-icon container"> - <div className="active-icon icon">{this.getTypeIcon()}</div> - <div className="active-icon description">Type Filters Applied</div> - </div> : undefined} - {!(this._authorFieldStatus && this._dataFieldStatus && this._titleFieldStatus) ? - <div className="active-icon container"> - <div className="active-icon icon">{this.getKeyIcon()}</div> - <div className="active-icon description">Field Filters Applied</div> - </div> : undefined} - {this._collectionStatus ? <div className="active-icon container"> - <div className="active-icon icon">{this.getColIcon()}</div> - <div className="active-icon description">Collection Filters Active</div> - </div> : undefined} - </div> - ); - } - - // 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" }}> - <SearchBox /> - {/* {this.getActiveFilters()} */} - </div> - {this._filterOpen ? ( - <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> - <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> - <div className="filter-panel" > - <button className="all-filter" onClick={this.handleWordQueryChange}>Include All Keywords</button> - </div> - </div> - <div className="filter-div"> - <div className="filter-header"> - <div className="filter-title icon">Filter by type of node</div> - </div> - <div className="filter-panel"><IconBar /></div> - </div> - <div className="filter-div"> - <div className="filter-header"> - <div className="filter-title field">Filter by Basic Keys</div> - </div> - <div className="filter-panel"><FieldFilters - titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._deletedDocsStatus} 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="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/SearchBox.scss b/src/client/views/search/SearchBox.scss index bb62113a1..3f06ba7d3 100644 --- a/src/client/views/search/SearchBox.scss +++ b/src/client/views/search/SearchBox.scss @@ -17,10 +17,9 @@ .searchBox-bar { height: 32px; display: flex; - justify-content: flex-end; + justify-content: center; align-items: center; - padding-left: 2px; - + background-color: black; .searchBox-barChild { &.searchBox-collection { @@ -30,24 +29,29 @@ } &.searchBox-input { + margin:5px; + border-radius:20px; + border:black; display: block; width: 130px; -webkit-transition: width 0.4s; transition: width 0.4s; align-self: stretch; - + outline:none; } .searchBox-input:focus { width: 500px; - outline: 3px solid lightblue; + outline:none; } &.searchBox-filter { align-self: stretch; + button{ + transform:none; + } button:hover{ - transform:scale(1.0); - background:"#121721"; + transform:none; } } @@ -81,8 +85,6 @@ .no-result { width: 500px; background: $light-color-secondary; - border-color: $intermediate-color; - border-bottom-style: solid; padding: 10px; height: 50px; text-transform: uppercase; @@ -96,20 +98,20 @@ background: #121721; flex-direction: column; transform-origin: top; - transition: height 0.3s ease, display 0.6s ease; + transition: height 0.3s ease, display 0.6s ease, overflow 0.6s ease; height:0px; overflow:hidden; .filter-header { - display: flex; + //display: flex; position: relative; - flex-wrap:wrap; + //flex-wrap:wrap; right: 1px; color: grey; - flex-direction: row-reverse; + //flex-direction: row-reverse; transform-origin: top; - justify-content: space-evenly; + //justify-content: space-evenly; margin-bottom: 5px; overflow:hidden; transition:height 0.3s ease-out; @@ -130,9 +132,7 @@ 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; @@ -144,30 +144,25 @@ 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-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; - - } - } - - + // .filter-item { + // position: relative; + // border:1px solid grey; + // border-radius: 16px; + // } + // } } } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 99fa6da21..1e44a379b 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,59 +1,66 @@ -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, IReactionDisposer, reaction } from 'mobx'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Tooltip } from '@material-ui/core'; +import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as rp from 'request-promise'; -import { Doc } from '../../../fields/Doc'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; +import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; -import { Utils } from '../../../Utils'; +import { returnFalse, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; +import { DocumentType } from "../../documents/DocumentTypes"; +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { SetupDrag } from '../../util/DragManager'; import { SearchUtil } from '../../util/SearchUtil'; -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'; -import { listSpec } from '../../../fields/Schema'; - -library.add(faTimes); +import { Transform } from '../../util/Transform'; +import { CollectionView, CollectionViewType } from '../collections/CollectionView'; +import { ViewBoxBaseComponent } from "../DocComponent"; +import { DocumentView } from '../nodes/DocumentView'; +import { FieldView, FieldViewProps } from '../nodes/FieldView'; +import "./SearchBox.scss"; -export interface SearchProps { - id: string; - searchQuery: string; - filterQquery?: string; - setSearchQuery: (q: string) => {}; - searchFileTypes: string[]; - setSearchFileTypes: (types: string[]) => {}; -} +export const searchSchema = createSchema({ + id: "string", + Document: Doc, + searchQuery: "string", +}); export enum Keys { TITLE = "title", AUTHOR = "author", - DATA = "data" + DATA = "data", + TEXT = "text" } +type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>; +const SearchBoxDocument = makeInterface(documentSchema, searchSchema); + +//React.Component<SearchProps> @observer -export class SearchBox extends React.Component<SearchProps> { +export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) { - private get _searchString() { return this.props.searchQuery; } - private set _searchString(value) { this.props.setSearchQuery(value); } + get _searchString() { return this.layoutDoc.searchQuery; } + @computed set _searchString(value) { this.layoutDoc.searchQuery = (value); } @observable private _resultsOpen: boolean = false; - @observable private _searchbarOpen: boolean = false; + @observable _searchbarOpen: boolean = false; @observable private _results: [Doc, string[], string[]][] = []; @observable private _openNoResults: boolean = false; @observable private _visibleElements: JSX.Element[] = []; + @observable private _visibleDocuments: Doc[] = []; private _resultsSet = new Map<Doc, number>(); private _resultsRef = React.createRef<HTMLDivElement>(); public inputRef = React.createRef<HTMLInputElement>(); private _isSearch: ("search" | "placeholder" | undefined)[] = []; + private _isSorted: ("sorted" | "placeholder" | undefined)[] = []; + private _numTotalResults = -1; private _endIndex = -1; @@ -63,55 +70,104 @@ export class SearchBox extends React.Component<SearchProps> { private _curRequest?: Promise<any> = undefined; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); } - + private new_buckets: { [characterName: string]: number } = {}; //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; + @observable private newAssign: boolean = true; constructor(props: any) { super(props); SearchBox.Instance = this; this.resultsScrolled = this.resultsScrolled.bind(this); + } + @observable setupButtons = false; + componentDidMount = () => { + if (this.setupButtons === false) { - componentDidMount = action(() => { + runInAction(() => this.setupButtons = true); + } if (this.inputRef.current) { this.inputRef.current.focus(); - this._searchbarOpen = true; + runInAction(() => { this._searchbarOpen = true; }); } - if (this.props.searchQuery) { // bcz: why was this here? } && this.props.filterQquery) { - this._searchString = this.props.searchQuery; - this.submitSearch(); + if (this.rootDoc.searchQuery && this.newAssign) { + const sq = this.rootDoc.searchQuery; + runInAction(() => { + + // this._deletedDocsStatus=this.props.filterQuery!.deletedDocsStatus; + // this._authorFieldStatus=this.props.filterQuery!.authorFieldStatus + // this._titleFieldStatus=this.props.filterQuery!.titleFieldStatus; + // this._basicWordStatus=this.props.filterQuery!.basicWordStatus; + // this._icons=this.props.filterQuery!.icons; + this.newAssign = false; + }); + runInAction(() => { + this.layoutDoc._searchString = StrCast(sq); + this.submitSearch(); + }); } - }); + } @action getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc) + + @observable newsearchstring: string = ""; @action.bound onChange(e: React.ChangeEvent<HTMLInputElement>) { - this._searchString = e.target.value; + this.layoutDoc._searchString = e.target.value; + this.newsearchstring = e.target.value; - this._openNoResults = false; - this._results = []; - this._resultsSet.clear(); - this._visibleElements = []; - this._numTotalResults = -1; - this._endIndex = -1; - this._curRequest = undefined; - this._maxSearchIndex = 0; + + if (e.target.value === "") { + this._results.forEach(result => { + Doc.UnBrushDoc(result[0]); + result[0].searchMatch = undefined; + }); + + this.props.Document._schemaHeaders = new List<SchemaHeaderField>([]); + if (this.currentSelectedCollection !== undefined) { + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]); + this.currentSelectedCollection = undefined; + this.props.Document.selectedDoc = undefined; + + } + runInAction(() => { this.open = false; }); + this._openNoResults = false; + this._results = []; + this._resultsSet.clear(); + this._visibleElements = []; + this._numTotalResults = -1; + this._endIndex = -1; + this._curRequest = undefined; + this._maxSearchIndex = 0; + } } enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { + this.layoutDoc._searchString = this.newsearchstring; + + if (StrCast(this.layoutDoc._searchString) !== "" || !this.searchFullDB) { + runInAction(() => this.open = true); + } + else { + runInAction(() => this.open = false); + + } this.submitSearch(); } } + @observable open: boolean = false; + + public static async convertDataUri(imageUri: string, returnedFilename: string) { try { const posting = Utils.prepend("/uploadURI"); @@ -134,10 +190,11 @@ export class SearchBox extends React.Component<SearchProps> { //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 - get _icons() { return this.props.searchFileTypes; } - set _icons(value) { - this.props.setSearchFileTypes(value); - } + // get _icons() { return this.props.searchFileTypes; } + // set _icons(value) { + // this.props.setSearchFileTypes(value); + // } + @observable _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; @@ -162,10 +219,11 @@ export class SearchBox extends React.Component<SearchProps> { query = query.replace(/\s+/g, ' ').trim(); } - //if should be searched in a specific collection + // if should be searched in a specific collection if (this._collectionStatus) { query = this.addCollectionFilter(query); query = query.replace(/\s+/g, ' ').trim(); + } return query; } @@ -176,14 +234,14 @@ export class SearchBox extends React.Component<SearchProps> { @action filterDocsByType(docs: Doc[]) { - if (this._icons.length === this._allIcons.length) { - return docs; - } const finalDocs: Doc[] = []; + const blockedTypes: string[] = ["preselement", "docholder", "collection", "search", "searchitem", "script", "fonticonbox", "button", "label"]; docs.forEach(doc => { const layoutresult = Cast(doc.type, "string"); - if (layoutresult && this._icons.includes(layoutresult)) { - finalDocs.push(doc); + if (layoutresult && !blockedTypes.includes(layoutresult)) { + if (layoutresult && this._icons.includes(layoutresult)) { + finalDocs.push(doc); + } } }); return finalDocs; @@ -216,7 +274,6 @@ export class SearchBox extends React.Component<SearchProps> { 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 @@ -236,6 +293,85 @@ export class SearchBox extends React.Component<SearchProps> { } + currentSelectedCollection: DocumentView | undefined = undefined; + docsforfilter: Doc[] = []; + + searchCollection(query: string) { + const selectedCollection: DocumentView = SelectionManager.SelectedDocuments()[0]; + query = query.toLowerCase(); + if (selectedCollection !== undefined) { + this.currentSelectedCollection = selectedCollection; + if (this.filter === true) { + this.props.Document.selectedDoc = selectedCollection.props.Document; + } + let docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]); + const found: [Doc, string[], string[]][] = []; + const docsforFilter: Doc[] = []; + let newarray: Doc[] = []; + + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + newarray.push(...DocListCast(d.data)); + } + const hlights: string[] = []; + const protos = Doc.GetAllPrototypes(d); + protos.forEach(proto => { + Object.keys(proto).forEach(key => { + if (StrCast(d[key]).toLowerCase().includes(query) && !hlights.includes(key)) { + hlights.push(key); + } + }); + }); + if (hlights.length > 0) { + found.push([d, hlights, []]); + docsforFilter.push(d); + } + }); + docs = newarray; + } + this._results = found; + this.docsforfilter = docsforFilter; + if (this.filter === true) { + selectedCollection.props.Document._searchDocs = new List<Doc>(docsforFilter); + docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(docsforFilter); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + } + this._numTotalResults = found.length; + } + else { + this.noresults = "No collection selected :("; + } + + } + + + documentKeys(doc: Doc) { + const keys: { [key: string]: boolean } = {}; + // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. + // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be + // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked. + // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu + // is displayed (unlikely) it won't show up until something else changes. + //TODO Types + Doc.GetAllPrototypes(doc).map + (proto => Object.keys(proto).forEach(key => keys[key] = false)); + return Array.from(Object.keys(keys)); + } + applyBasicFieldFilters(query: string) { let finalQuery = ""; @@ -248,26 +384,24 @@ export class SearchBox extends React.Component<SearchProps> { if (this._deletedDocsStatus) { finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA); } + if (this._deletedDocsStatus) { + finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TEXT); + } 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:"; + switch (type) { + case Keys.AUTHOR: mod = " author_t:"; break; + case Keys.DATA: break; // TODO + case Keys.TITLE: mod = " _title_t:"; break; + case Keys.TEXT: mod = " text_t:"; break; } const newWords: string[] = []; - oldWords.forEach(word => { - const newWrd = mod + word; - newWords.push(newWrd); - }); + const oldWords = query.split(" "); + oldWords.forEach(word => newWords.push(mod + word)); query = newWords.join(" "); @@ -276,30 +410,64 @@ export class SearchBox extends React.Component<SearchProps> { get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); } - @action - submitSearch = async () => { - const query = this._searchString; + submitSearch = async (reset?: boolean) => { + if (reset) { + this.layoutDoc._searchString = ""; + } + this.props.Document._docFilters = undefined; + this.noresults = ""; + + this.dataDoc[this.fieldKey] = new List<Doc>([]); + this.headercount = 0; + this.children = 0; + this.buckets = []; + this.new_buckets = {}; + const query = StrCast(this.layoutDoc._searchString); + Doc.SetSearchQuery(query); this.getFinalQuery(query); + this._results.forEach(result => { + Doc.UnBrushDoc(result[0]); + result[0].searchMatch = undefined; + }); this._results = []; this._resultsSet.clear(); this._isSearch = []; + this._isSorted = []; this._visibleElements = []; + this._visibleDocuments = []; + if (StrCast(this.props.Document.searchQuery)) { + if (this._timeout) { clearTimeout(this._timeout); this._timeout = undefined; } + this._timeout = setTimeout(() => { + console.log("Resubmitting search"); + }, 60000); + } + if (query !== "") { this._endIndex = 12; this._maxSearchIndex = 0; this._numTotalResults = -1; - await this.getResults(query); - + this.searchFullDB ? await this.getResults(query) : this.searchCollection(query); runInAction(() => { this._resultsOpen = true; this._searchbarOpen = true; this._openNoResults = true; this.resultsScrolled(); + }); } } + @observable searchFullDB = true; + + @observable _timeout: any = undefined; + + @observable firststring: string = ""; + @observable secondstring: string = ""; + + @observable bucketcount: number[] = []; + @observable buckets: Doc[] | undefined; + getAllResults = async (query: string) => { return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 }); } @@ -309,7 +477,7 @@ export class SearchBox extends React.Component<SearchProps> { const baseExpr = "NOT baseProto_b:true"; const includeDeleted = this.getDataStatus() ? "" : " NOT deleted_b:true"; const includeIcons = this.getDataStatus() ? "" : " NOT type_t:fonticonbox"; - // const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`; // this line was causing issues for me, check solr logging -syip2 + // const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`; // fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection q:text_t:hello const query = [baseExpr, includeDeleted, includeIcons].join(" AND ").replace(/AND $/, ""); return query; @@ -317,21 +485,20 @@ export class SearchBox extends React.Component<SearchProps> { getDataStatus() { return this._deletedDocsStatus; } - private NumResults = 25; private lockPromise?: Promise<void>; getResults = async (query: string) => { + console.log("Get"); if (this.lockPromise) { await this.lockPromise; } this.lockPromise = new Promise(async res => { while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) { - this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*" }).then(action(async (res: SearchUtil.DocSearchResult) => { + 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; } - const highlighting = res.highlighting || {}; const highlightList = res.docs.map(doc => highlighting[doc[Id]]); const lines = new Map<string, string[]>(); @@ -340,19 +507,33 @@ export class SearchBox extends React.Component<SearchProps> { const highlights: typeof res.highlighting = {}; docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]); const filteredDocs = this.filterDocsByType(docs); + runInAction(() => { - //this._results.push(...filteredDocs); - filteredDocs.forEach(doc => { + filteredDocs.forEach((doc, i) => { const index = this._resultsSet.get(doc); const highlight = highlights[doc[Id]]; const line = lines.get(doc[Id]) || []; const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []; - if (index === undefined) { - this._resultsSet.set(doc, this._results.length); - this._results.push([doc, hlights, line]); - } else { - this._results[index][1].push(...hlights); - this._results[index][2].push(...line); + doc ? console.log(Cast(doc.context, Doc)) : null; + if (this.findCommonElements(hlights)) { + } + else { + const layoutresult = Cast(doc.type, "string"); + if (layoutresult) { + if (this.new_buckets[layoutresult] === undefined) { + this.new_buckets[layoutresult] = 1; + } + else { + this.new_buckets[layoutresult] = this.new_buckets[layoutresult] + 1; + } + } + if (index === undefined) { + this._resultsSet.set(doc, this._results.length); + this._results.push([doc, hlights, line]); + } else { + this._results[index][1].push(...hlights); + this._results[index][2].push(...line); + } } }); }); @@ -363,15 +544,16 @@ export class SearchBox extends React.Component<SearchProps> { await this._curRequest; } + this.resultsScrolled(); res(); }); return this.lockPromise; } - + @observable noresults = ""; collectionRef = React.createRef<HTMLSpanElement>(); startDragCollection = async () => { - const res = await this.getAllResults(this.getFinalQuery(this._searchString)); + const res = await this.getAllResults(this.getFinalQuery(StrCast(this.layoutDoc._searchString))); const filtered = this.filterDocsByType(res.docs); const docs = filtered.map(doc => { const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); @@ -404,7 +586,7 @@ export class SearchBox extends React.Component<SearchProps> { y += 300; } } - return Docs.Create.QueryDocument({ _autoHeight: true, title: this._searchString, filterQuery: this.filterQuery, searchQuery: this._searchString }); + return Docs.Create.SearchDocument({ _autoHeight: true, _viewType: CollectionViewType.Schema, title: StrCast(this.layoutDoc._searchString), searchQuery: StrCast(this.layoutDoc._searchString) }); } @action.bound @@ -417,7 +599,7 @@ export class SearchBox extends React.Component<SearchProps> { @action.bound closeSearch = () => { - this.closeResults(); + //this.closeResults(); this._searchbarOpen = false; } @@ -427,23 +609,30 @@ export class SearchBox extends React.Component<SearchProps> { this._results = []; this._resultsSet.clear(); this._visibleElements = []; + this._visibleDocuments = []; this._numTotalResults = -1; this._endIndex = -1; this._curRequest = undefined; } + @observable children: number = 0; @action resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => { if (!this._resultsRef.current) return; + this.props.Document._schemaHeaders = new List<SchemaHeaderField>([]); + 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))); - - this._endIndex = endIndex === -1 ? 12 : endIndex; - + //const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this._resultsRef.current.getBoundingClientRect().height / itemHght))); + const endIndex = 30; + //this._endIndex = endIndex === -1 ? 12 : endIndex; + this._endIndex = 30; + const headers = new Set<string>(["title", "author", "lastModified", "text"]); if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { - this._visibleElements = [<div className="no-result">No Search Results</div>]; + if (this.noresults === "") { + this.noresults = "No search results :("; + } return; } @@ -456,16 +645,19 @@ export class SearchBox extends React.Component<SearchProps> { 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._visibleDocuments = Array<Doc>(this._numTotalResults === -1 ? 0 : this._numTotalResults); + // indicates if things are placeholders this._isSearch = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults); - } + this._isSorted = 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._isSorted[i] = "placeholder"; this._visibleElements[i] = <div className="searchBox-placeholder" key={`searchBox-placeholder-${i}`}>Loading...</div>; } } @@ -473,30 +665,61 @@ export class SearchBox extends React.Component<SearchProps> { if (this._isSearch[i] !== "search") { let result: [Doc, string[], string[]] | undefined = undefined; if (i >= this._results.length) { - this.getResults(this._searchString); + this.getResults(StrCast(this.layoutDoc._searchString)); if (i < this._results.length) result = this._results[i]; if (result) { const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; + const lines = new List<string>(result[2]); + result[0].lines = lines; + result[0].highlighting = highlights.join(", "); + highlights.forEach((item) => headers.add(item)); + this._visibleDocuments[i] = result[0]; this._isSearch[i] = "search"; + Doc.BrushDoc(result[0]); + result[0].searchMatch = true; + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + this.children++; } } else { result = this._results[i]; if (result) { const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; - this._isSearch[i] = "search"; + const lines = new List<string>(result[2]); + highlights.forEach((item) => headers.add(item)); + result[0].lines = lines; + result[0].highlighting = highlights.join(", "); + result[0].searchMatch = true; + if (i < this._visibleDocuments.length) { + this._visibleDocuments[i] = result[0]; + this._isSearch[i] = "search"; + Doc.BrushDoc(result[0]); + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + this.children++; + } } } } } } + const schemaheaders: SchemaHeaderField[] = []; + this.headerscale = headers.size; + headers.forEach((item) => schemaheaders.push(new SchemaHeaderField(item, "#f1efeb"))); + this.headercount = schemaheaders.length; + this.props.Document._schemaHeaders = new List<SchemaHeaderField>(schemaheaders); if (this._maxSearchIndex >= this._numTotalResults) { this._visibleElements.length = this._results.length; + this._visibleDocuments.length = this._results.length; this._isSearch.length = this._results.length; } } + @observable headercount: number = 0; + @observable headerscale: number = 0; + + findCommonElements(arr2: string[]) { + const arr1 = ["layout", "data"]; + return arr1.some(item => arr2.includes(item)); + } @computed get resFull() { return this._numTotalResults <= 8; } @@ -504,165 +727,197 @@ export class SearchBox extends React.Component<SearchProps> { @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}`); + addButtonDoc = (doc: Doc) => Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); + remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); + moveButtonDoc = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc); + @computed get searchItemTemplate() { return Cast(Doc.UserDoc().searchItemTemplate, Doc, null); } - } + getTransform = () => { + return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight } - - @computed - get menuHeight() { - return document.getElementById("hi")?.clientHeight; + panelHeight = () => { + return this.props.PanelHeight(); } - - - 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) { - 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 - 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'); - + selectElement = (doc: Doc) => { + //this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex)); } - autoset(thing: string) { - const element = document.getElementById(thing)!; - 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; + addDocument = (doc: Doc) => { + return null; } - @action.bound - updateTitleStatus() { this._titleFieldStatus = !this._titleFieldStatus; } - - @action.bound - updateAuthorStatus() { this._authorFieldStatus = !this._authorFieldStatus; } - - @action.bound - updateDataStatus() { this._deletedDocsStatus = !this._deletedDocsStatus; } + @observable filter = false; + //Make id layour document render() { - + this.props.Document._chromeStatus === "disabled"; + this.props.Document._searchDoc = true; + const cols = Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).length; + let length = 0; + cols > 5 ? length = 1076 : length = cols * 205 + 51; + let height = 0; + const rows = this.children; + rows > 8 ? height = 31 + 31 * 8 : height = 31 * rows + 31; return ( - <div className="searchBox-container"> + <div style={{ pointerEvents: "all" }} 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" /> - </span> - <input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this.inputRef} - className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} onFocus={this.openSearch} - style={{ width: this._searchbarOpen ? "500px" : "100px" }} /> - <button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={() => this.handleFilterChange()}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button> - </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 setIcons={(icons: string[]) => { - this._icons = icons; - }} /> - </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 style={{ position: "absolute", left: 15 }}>{Doc.CurrentUserEmail}</div> + <div style={{ display: "flex", alignItems: "center" }}> + <FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg" + style={{ color: "black", padding: 1, left: 35, position: "relative" }} /> + + <div style={{ cursor: "default", left: 250, position: "relative", }}> + <Tooltip title={<div className="dash-tooltip" >only display documents matching search</div>} ><div> + <FontAwesomeIcon icon={"filter"} size="lg" + style={{ padding: 1, backgroundColor: this.filter ? "white" : "lightgray", color: this.filter ? "black" : "white" }} + onPointerDown={e => { e.stopPropagation(); SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined); }} + onClick={action(() => { + const dofilter = (currentSelectedCollection: DocumentView) => { + let docs = DocListCast(currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + const newarray: Doc[] = []; + docs.filter(d => d.data !== undefined).forEach((d) => { + d._searchDocs = new List<Doc>(this.docsforfilter); + newarray.push(...DocListCast(d.data)); + }); + docs = newarray; + } + }; + this.filter = !this.filter && !this.searchFullDB; + if (this.filter === true && this.currentSelectedCollection !== undefined) { + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>(this.docsforfilter); + + dofilter(this.currentSelectedCollection); + + this.currentSelectedCollection.props.Document._docFilters = new List<string>(Cast(this.props.Document._docFilters, listSpec("string"), [])); + this.props.Document.selectedDoc = this.currentSelectedCollection.props.Document; + } + else if (this.filter === false && this.currentSelectedCollection !== undefined) { + + dofilter(this.currentSelectedCollection); + + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]); + this.currentSelectedCollection.props.Document._docFilters = undefined; + this.props.Document.selectedDoc = undefined; + } + } + )} /> + </div></Tooltip></div> + <input value={this.newsearchstring} autoComplete="off" 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={{ padding: 1, paddingLeft: 20, paddingRight: 20, color: "black", height: 20, width: 250 }} /> + <div style={{ + height: 25, + paddingLeft: "4px", + paddingRight: "4px", + border: "1px solid gray", + borderRadius: "0.3em", + borderBottom: this.open === false ? "1px solid" : "none", + }}> + <form className="beta" style={{ justifyContent: "space-evenly", display: "flex" }}> + <div style={{ display: "contents" }}> + <div className="radio" style={{ margin: 0 }}> + <label style={{ fontSize: 12, marginTop: 6 }} > + <input type="radio" style={{ marginLeft: -16, marginTop: -1 }} checked={!this.searchFullDB} onChange={() => { + runInAction(() => { + this.searchFullDB = !this.searchFullDB; + this.dataDoc[this.fieldKey] = new List<Doc>([]); + if (this.currentSelectedCollection !== undefined) { + let newarray: Doc[] = []; + let docs: Doc[] = []; + docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + this.currentSelectedCollection.props.Document._docFilters = undefined; + this.currentSelectedCollection.props.Document._searchDocs = undefined; + this.currentSelectedCollection = undefined; + } + this.submitSearch(); + }); + }} /> + Collection + </label> + </div> + <div className="radio" style={{ margin: 0 }}> + <label style={{ fontSize: 12, marginTop: 6 }} > + <input style={{ marginLeft: -16, marginTop: -1 }} type="radio" checked={this.searchFullDB} onChange={() => { + runInAction(() => { + this.searchFullDB = !this.searchFullDB; + this.dataDoc[this.fieldKey] = new List<Doc>([]); + this.filter = false; + if (this.currentSelectedCollection !== undefined) { + let newarray: Doc[] = []; + let docs: Doc[] = []; + docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + this.currentSelectedCollection.props.Document._docFilters = undefined; + this.currentSelectedCollection.props.Document._searchDocs = undefined; + this.currentSelectedCollection = undefined; + } + this.submitSearch(); + }); + }} /> + DB + </label> + </div> + </div> + </form> </div> </div> + + </div> + <div style={{ zIndex: 20000, color: "black" }}> + {this._searchbarOpen === true ? + <div style={{ display: "flex", justifyContent: "center", }}> + {this.noresults === "" ? <div style={{ display: this.open === true ? "flex" : "none", overflow: "auto", }}> + <CollectionView {...this.props} + Document={this.props.Document} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelHeight={this.open === true ? () => height : () => 0} + PanelWidth={this.open === true ? () => length : () => 0} + overflow={cols > 5 || rows > 8 ? true : false} + focus={this.selectElement} + ScreenToLocalTransform={Transform.Identity} + /> + </div> : + <div style={{ display: "flex", justifyContent: "center" }}><div style={{ height: 200, top: 54, minWidth: 400, position: "absolute", backgroundColor: "rgb(241, 239, 235)", display: "flex", justifyContent: "center", alignItems: "center", border: "black 1px solid", }}> + <div>{this.noresults}</div> + </div></div>} + </div> : undefined} </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}> - {this._visibleElements} </div> - </div> + </div > ); } -}
\ No newline at end of file +} diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss deleted file mode 100644 index 469f062b2..000000000 --- a/src/client/views/search/SearchItem.scss +++ /dev/null @@ -1,163 +0,0 @@ -@import "../globalCssVariables"; - -.searchItem-overview { - display: flex; - flex-direction: reverse; - justify-content: flex-end; - z-index: 0; -} - -.searchBox-placeholder, -.searchItem-overview .searchItem { - width: 100%; - background: $light-color-secondary; - border-color: $intermediate-color; - border-bottom-style: solid; - padding: 10px; - min-height: 50px; - max-height: 150px; - height: auto; - z-index: 0; - display: flex; - overflow: visible; - - .searchItem-body { - display: flex; - flex-direction: row; - width: 100%; - - .searchItem-title-container { - width: 100%; - overflow: hidden; - - .searchItem-title { - text-transform: uppercase; - text-align: left; - width: 100%; - font-weight: bold; - } - } - - .searchItem-info { - display: flex; - justify-content: flex-end; - - .icon-icons { - width: 50px - } - - .icon-live { - width: 175px; - height: 0px; - } - - .icon-icons { - height:auto; - } - .icon-icons, - .icon-live { - margin: auto; - overflow: visible; - - .searchItem-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; - } - } - - .searchItem-type:hover+.searchItem-label { - opacity: 1; - } - - .searchItem-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 { - .pdfBox-cont { - img { - width: 100% !important; - } - } - } - } - - .searchItem-info:hover { - width: 60%; - } - } -} - -.searchItem:hover~.searchBox-instances, -.searchBox-instances:hover, -.searchBox-instances:active { - opacity: 1; - background: $lighter-alt-accent; -} - -.searchItem:hover { - transition: all 0.2s; - background: $lighter-alt-accent; -} - -.searchItem-highlighting { - overflow: hidden; - text-overflow: ellipsis; - white-space: pre; -} - -.searchBox-instances { - opacity: 1; - width:40px; - height:40px; - background: gray; - transition: all 0.2s ease; - color: black; - overflow: hidden; - right:-100; - display:inline-block; -} - - -.searchItem-overview:hover { - z-index: 1; -} - -.searchBox-placeholder { - min-height: 50px; - margin-left: 150px; - width: calc(100% - 150px); - text-transform: uppercase; - text-align: left; - font-weight: bold; -} - -.collection { - display: flex; -} - -.collection-item { - width: 35px; -}
\ No newline at end of file diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx deleted file mode 100644 index 2436bf418..000000000 --- a/src/client/views/search/SearchItem.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import React = require("react"); -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlobeAsia, faImage, faLink, faMusic, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero, returnEmptyString, returnEmptyFilter } from "../../../Utils"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentManager } from "../../util/DocumentManager"; -import { DragManager, SetupDrag } from "../../util/DragManager"; -import { SearchUtil } from "../../util/SearchUtil"; -import { Transform } from "../../util/Transform"; -import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionViewType } from "../collections/CollectionView"; -import { ParentDocSelector } from "../collections/ParentDocumentSelector"; -import { ContextMenu } from "../ContextMenu"; -import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; -import { SearchBox } from "./SearchBox"; -import "./SearchItem.scss"; -import "./SelectorContextMenu.scss"; - -export interface SearchItemProps { - doc: Doc; - query: string; - highlighting: string[]; - lines: string[]; -} - -library.add(faCaretUp); -library.add(faObjectGroup); -library.add(faStickyNote); -library.add(faFile); -library.add(faFilePdf); -library.add(faFilm); -library.add(faMusic); -library.add(faLink); -library.add(faChartBar); -library.add(faGlobeAsia, faFingerprint); - -@observer -export class SelectorContextMenu extends React.Component<SearchItemProps> { - @observable private _docs: { col: Doc, target: Doc }[] = []; - @observable private _otherDocs: { col: Doc, target: Doc }[] = []; - - constructor(props: SearchItemProps) { - super(props); - this.fetchDocuments(); - } - - async fetchDocuments() { - const aliases = (await SearchUtil.GetViewsOfDocument(this.props.doc)).filter(doc => doc !== this.props.doc); - const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.doc[Id]}"` }); - const map: Map<Doc, Doc> = new Map; - const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs))); - allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); - docs.forEach(doc => map.delete(doc)); - runInAction(() => { - this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc })); - this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); - - }); - } - - getOnClick({ col, target }: { col: Doc, target: Doc }) { - return () => { - col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (col._viewType === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target._width) / 2; - const newPanY = NumCast(target.y) + NumCast(target._height) / 2; - col._panX = newPanX; - col._panY = newPanY; - } - CollectionDockingView.AddRightSplit(col); - }; - } - render() { - return ( - <div className="parents"> - <p className="contexts">Contexts:</p> - {[...this._docs, ...this._otherDocs].map(doc => { - const item = React.createRef<HTMLDivElement>(); - return <div className="collection" key={doc.col[Id] + doc.target[Id]} ref={item}> - <div className="collection-item" onPointerDown={ - SetupDrag(item, () => doc.col, undefined, undefined, () => SearchBox.Instance.closeSearch())}> - <FontAwesomeIcon icon={faStickyNote} /> - </div> - <a onClick={this.getOnClick(doc)}>{doc.col.title}</a> - </div>; - })} - </div> - ); - } -} - -export interface LinkMenuProps { - doc1: Doc; - doc2: Doc; -} - -@observer -export class LinkContextMenu extends React.Component<LinkMenuProps> { - - highlightDoc = (doc: Doc) => () => Doc.BrushDoc(doc); - - unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc); - - getOnClick = (col: Doc) => () => CollectionDockingView.AddRightSplit(col); - - render() { - return ( - <div className="parents"> - <p className="contexts">Anchors:</p> - <div className="collection"><a onMouseEnter={this.highlightDoc(this.props.doc1)} onMouseLeave={this.unHighlightDoc(this.props.doc1)} onClick={this.getOnClick(this.props.doc1)}>Doc 1: {this.props.doc2.title}</a></div> - <div><a onMouseEnter={this.highlightDoc(this.props.doc2)} onMouseLeave={this.unHighlightDoc(this.props.doc2)} onClick={this.getOnClick(this.props.doc2)}>Doc 2: {this.props.doc1.title}</a></div> - </div> - ); - } - -} - -@observer -export class SearchItem extends React.Component<SearchItemProps> { - - @observable _selected: boolean = false; - - onClick = () => { - // I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like - DocumentManager.Instance.jumpToDocument(this.props.doc, false); - } - @observable _useIcons = true; - @observable _displayDim = 50; - - componentDidMount() { - Doc.SetSearchQuery(this.props.query); - this.props.doc.searchMatch = true; - } - componentWillUnmount() { - this.props.doc.searchMatch = undefined; - } - - //@computed - @action - public DocumentIcon() { - const layoutresult = StrCast(this.props.doc.type); - if (!this._useIcons) { - const returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); - const returnYDimension = () => this._displayDim; - const docview = <div - onPointerDown={action(() => { - this._useIcons = !this._useIcons; - this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); - })} - onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} > - <ContentFittingDocumentView - Document={this.props.doc} - LibraryPath={emptyPath} - rootSelected={returnFalse} - fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1} - addDocument={returnFalse} - removeDocument={returnFalse} - addDocTab={returnFalse} - pinToPres={returnFalse} - docFilters={returnEmptyFilter} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} - ScreenToLocalTransform={Transform.Identity} - renderDepth={1} - PanelWidth={returnXDimension} - PanelHeight={returnYDimension} - NativeWidth={returnZero} - NativeHeight={returnZero} - focus={emptyFunction} - moveDocument={returnFalse} - parentActive={returnFalse} - whenActiveChanged={returnFalse} - bringToFront={returnFalse} - ContentScaling={returnOne} - /> - </div>; - return docview; - } - const button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : - layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage : - layoutresult.indexOf(DocumentType.RTF) !== -1 ? faStickyNote : - layoutresult.indexOf(DocumentType.VID) !== -1 ? faFilm : - layoutresult.indexOf(DocumentType.COL) !== -1 ? faObjectGroup : - layoutresult.indexOf(DocumentType.AUDIO) !== -1 ? faMusic : - layoutresult.indexOf(DocumentType.LINK) !== -1 ? faLink : - layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia : - faCaretUp; - return <div onClick={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE); })} > - <FontAwesomeIcon icon={button} size="2x" /> - </div>; - } - - collectionRef = React.createRef<HTMLDivElement>(); - - @action - pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); } - - nextHighlight = (e: React.PointerEvent) => { - e.preventDefault(); - e.button === 0 && SearchBox.Instance.openSearch(e); - this.props.doc.searchMatch = false; - setTimeout(() => this.props.doc.searchMatch = true, 0); - } - highlightDoc = (e: React.PointerEvent) => { - if (this.props.doc.type === DocumentType.LINK) { - if (this.props.doc.anchor1 && this.props.doc.anchor2) { - - const doc1 = Cast(this.props.doc.anchor1, Doc, null); - const doc2 = Cast(this.props.doc.anchor2, Doc, null); - Doc.BrushDoc(doc1); - Doc.BrushDoc(doc2); - } - } else { - Doc.BrushDoc(this.props.doc); - } - e.stopPropagation(); - } - - unHighlightDoc = (e: React.PointerEvent) => { - if (this.props.doc.type === DocumentType.LINK) { - if (this.props.doc.anchor1 && this.props.doc.anchor2) { - - const doc1 = Cast(this.props.doc.anchor1, Doc, null); - const doc2 = Cast(this.props.doc.anchor2, Doc, null); - Doc.UnBrushDoc(doc1); - Doc.UnBrushDoc(doc2); - } - } else { - Doc.UnBrushDoc(this.props.doc); - } - } - - onContextMenu = (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ - description: "Copy ID", event: () => { - Utils.CopyText(this.props.doc[Id]); - }, - icon: "fingerprint" - }); - ContextMenu.Instance.displayMenu(e.clientX, e.clientY); - } - - _downX = 0; - _downY = 0; - _target: any; - onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => { - this._downX = e.clientX; - this._downY = e.clientY; - e.stopPropagation(); - this._target = e.currentTarget; - document.removeEventListener("pointermove", this.onPointerMoved); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointermove", this.onPointerMoved); - document.addEventListener("pointerup", this.onPointerUp); - } - onPointerMoved = (e: PointerEvent) => { - if (Math.abs(e.clientX - this._downX) > Utils.DRAG_THRESHOLD || - Math.abs(e.clientY - this._downY) > Utils.DRAG_THRESHOLD) { - document.removeEventListener("pointermove", this.onPointerMoved); - document.removeEventListener("pointerup", this.onPointerUp); - const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc; - DragManager.StartDocumentDrag([this._target], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY); - } - } - onPointerUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onPointerMoved); - document.removeEventListener("pointerup", this.onPointerUp); - } - - @computed - get contextButton() { - return <ParentDocSelector Document={this.props.doc} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />; - } - - render() { - const doc1 = Cast(this.props.doc.anchor1, Doc); - const doc2 = Cast(this.props.doc.anchor2, Doc); - return <div className="searchItem-overview" onPointerDown={this.pointerDown} onContextMenu={this.onContextMenu}> - <div className="searchItem" onPointerDown={this.nextHighlight} onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc}> - <div className="searchItem-body" onClick={this.onClick}> - <div className="searchItem-title-container"> - <div className="searchItem-title">{StrCast(this.props.doc.title)}</div> - <div className="searchItem-highlighting">{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? this.props.lines[0] : ""}</div> - {this.props.lines.filter((m, i) => i).map((l, i) => <div id={i.toString()} className="searchItem-highlighting">`${l}`</div>)} - </div> - </div> - <div className="searchItem-info" style={{ width: this._useIcons ? "30px" : "100%" }}> - <div className={`icon-${this._useIcons ? "icons" : "live"}`}> - <div className="searchItem-type" title="Click to Preview" onPointerDown={this.onPointerDown}>{this.DocumentIcon()}</div> - <div className="searchItem-label">{this.props.doc.type ? this.props.doc.type : "Other"}</div> - </div> - </div> - <div className="searchItem-context" title="Drag as document"> - {(doc1 instanceof Doc && doc2 instanceof Doc) && this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={doc1} doc2={doc2} /> : - this.contextButton} - </div> - </div> - </div>; - } -}
\ No newline at end of file |
