import * as React from 'react'; import { observer } from 'mobx-react'; import { observable, action, runInAction } from 'mobx'; import "./SearchBox.scss"; import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faThList } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { library } from '@fortawesome/fontawesome-svg-core'; import * as rp from 'request-promise'; import { SearchItem } from './SearchItem'; import { DocServer } from '../../DocServer'; import { Doc } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; import { SetupDrag } from '../../util/DragManager'; import { Docs, DocTypes } from '../../documents/Documents'; import { RouteStore } from '../../../server/RouteStore'; import { NumCast, Cast } from '../../../new_fields/Types'; import { SearchUtil } from '../../util/SearchUtil'; import * as anime from 'animejs'; import { updateFunction } from '../../../new_fields/util'; import * as _ from "lodash"; import { findDOMNode } from 'react-dom'; import { ToggleBar } from './ToggleBar'; import { IconBar } from './IconBar'; import { type } from 'os'; import { CheckBox } from './CheckBox'; import { FieldFilters } from './FieldFilters'; export enum Keys { TITLE = "title", AUTHOR = "author", DATA = "data" } @observer export class SearchBox extends React.Component { static Instance: SearchBox; @observable _searchString: string = ""; //if true, any keywords can be used. if false, all keywords are required. @observable _basicWordStatus: boolean = true; @observable private _filterOpen: boolean = false; @observable private _resultsOpen: boolean = false; @observable private _results: Doc[] = []; @observable private _openNoResults: boolean = false; allIcons: string[] = [DocTypes.AUDIO, DocTypes.COL, DocTypes.HIST, DocTypes.IMG, DocTypes.LINK, DocTypes.PDF, DocTypes.TEXT, DocTypes.VID, DocTypes.WEB]; @observable _icons: string[] = this.allIcons; @observable _selectedTypes: any[] = []; @observable titleFieldStatus: boolean = true; @observable authorFieldStatus: boolean = true; @observable dataFieldStatus: boolean = true; constructor(props: Readonly<{}>) { super(props); SearchBox.Instance = this; } componentDidMount = () => { document.addEventListener("pointerdown", (e) => { if (e.timeStamp !== this._pointerTime) { this.closeSearch(); } }); //empties search query after 30 seconds of the search bar/filter box not being open if (!this._resultsOpen && !this._filterOpen) { setTimeout(this.clearSearchQuery, 30000); } } @action.bound resetFilters = () => { ToggleBar.Instance.resetToggle(); IconBar.Instance.selectAll(); } @action.bound onChange(e: React.ChangeEvent) { this._searchString = e.target.value; if (this._searchString === "") { this._results = []; this._openNoResults = false; } } @action.bound clearSearchQuery() { this._searchString = ""; this._results = []; } basicRequireWords(query: string): string { let oldWords = query.split(" "); let newWords: string[] = []; oldWords.forEach(word => { let newWrd = "+" + word; newWords.push(newWrd); }); query = newWords.join(" "); return query; } basicFieldFilters(query: string, type: string): string { let oldWords = query.split(" "); let mod = ""; if(type === Keys.AUTHOR){ mod = " author_t:"; }if(type === Keys.DATA){ //TODO }if(type === Keys.TITLE){ mod = " title_t:"; } let newWords:string[] = []; oldWords.forEach(word => { let newWrd = mod + word; newWords.push(newWrd); }); query = newWords.join(" "); return query; } applyBasicFieldFilters(query: string) { let finalQuery = ""; if(this.titleFieldStatus){ finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE); } if(this.authorFieldStatus){ finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR); } if(this.dataFieldStatus){ finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA); } return finalQuery; } get fieldFiltersApplied(){return !(this.dataFieldStatus && this.authorFieldStatus && this.titleFieldStatus);} @action submitSearch = async () => { let query = this._searchString; let results: Doc[]; //if this is true, then not all of the field boxes are checked //TODO: data if(this.fieldFiltersApplied){ query = this.applyBasicFieldFilters(query); } //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 there is no query there should be no result if (query === "") { results = []; } else { //gets json result into a list of documents that can be used results = await this.getResults(query); } runInAction(() => { this._resultsOpen = true; this._results = results; this._openNoResults = true; }); } @action getResults = async (query: string) => { let response = await rp.get(DocServer.prepend('/search'), { qs: { query } }); let res: string[] = JSON.parse(response); const fields = await DocServer.GetRefFields(res); const docs: Doc[] = []; for (const id of res) { const field = fields[id]; if (field instanceof Doc) { docs.push(field); } } return this.filterDocs(docs); } //this.icons will now include all the icons that need to be included @action filterDocs(docs: Doc[]) { let finalDocs: Doc[] = []; docs.forEach(doc => { let layoutresult = Cast(doc.type, "string", ""); if (this._icons.includes(layoutresult)) { finalDocs.push(doc); } }); return finalDocs; } public static async convertDataUri(imageUri: string, returnedFilename: string) { try { let posting = DocServer.prepend(RouteStore.dataUriToImage); const returnedUri = await rp.post(posting, { body: { uri: imageUri, name: returnedFilename }, json: true, }); return returnedUri; } catch (e) { console.log(e); } } enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { this.submitSearch(); } } @action.bound closeSearch = () => { this._filterOpen = false; this._resultsOpen = false; this._results = []; } @action openFilter = () => { this._filterOpen = true; this._resultsOpen = false; this._results = []; } collectionRef = React.createRef(); startDragCollection = async () => { const results = await this.getResults(this._searchString); const docs = results.map(doc => { const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); if (isProto) { return Doc.MakeDelegate(doc); } else { return Doc.MakeAlias(doc); } }); let x = 0; let y = 0; for (const doc of docs) { doc.x = x; doc.y = y; const size = 200; const aspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1); if (aspect > 1) { doc.height = size; doc.width = size / aspect; } else if (aspect > 0) { doc.width = size; doc.height = size * aspect; } else { doc.width = size; doc.height = size; } doc.zoomBasis = 1; x += 250; if (x > 1000) { x = 0; y += 300; } } return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` }); } @action getViews = async (doc: Doc) => { const results = await SearchUtil.GetViewsOfDocument(doc); let toReturn: Doc[] = []; await runInAction(() => { toReturn = results; }); return toReturn; } //if true, any keywords can be used. if false, all keywords are required. @action.bound handleWordQueryChange = () => { this._basicWordStatus = !this._basicWordStatus; } @action getBasicWordStatus() { return this._basicWordStatus; } @action.bound updateIcon(newArray: string[]) { this._icons = newArray; } @action.bound getIcons(): string[] { return this._icons; } private _pointerTime: number = -1; stopProp = (e: React.PointerEvent) => { e.stopPropagation(); this._pointerTime = e.timeStamp; } @action.bound openSearch(e: React.PointerEvent) { e.stopPropagation(); this._openNoResults = false; this._filterOpen = false; this._resultsOpen = true; this._pointerTime = e.timeStamp; } @action.bound updateTitleStatus(newStat: boolean) { this.titleFieldStatus = newStat; } @action.bound updateAuthorStatus(newStat: boolean) { this.authorFieldStatus = newStat; } @action.bound updateDataStatus(newStat: boolean) { this.dataFieldStatus = newStat; } // 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} render() { return (
{this._resultsOpen ? (
{(this._results.length !== 0) ? ( this._results.map(result => ) ) : this._openNoResults ? (
No Search Results
) : null}
) : undefined}
{this._filterOpen ? (
temp for filtering by collection
) : undefined}
); } }