diff options
| author | Andy Rickert <andrew_rickert@brown.edu> | 2020-07-03 13:37:12 -0400 |
|---|---|---|
| committer | Andy Rickert <andrew_rickert@brown.edu> | 2020-07-03 13:37:12 -0400 |
| commit | b058ca45c5d8f3168fdb3a9e8733152765afdb0b (patch) | |
| tree | 5d66db927370f2d7cd6a016e5bc77f202e37e65b /src/client/views/search/SearchBox.tsx | |
| parent | 552e340758ae187459786d742c5e9d2487446f1b (diff) | |
| parent | 435995b1fb8ef52d2f76b89ca161e4372fb79cfd (diff) | |
merge w search_doc
Diffstat (limited to 'src/client/views/search/SearchBox.tsx')
| -rw-r--r-- | src/client/views/search/SearchBox.tsx | 818 |
1 files changed, 720 insertions, 98 deletions
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index c9d29e485..5960a0502 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -8,29 +8,45 @@ import * as rp from 'request-promise'; import { Doc } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; -import { Utils } from '../../../Utils'; -import { Docs } from '../../documents/Documents'; -import { SetupDrag } from '../../util/DragManager'; +import { Utils, returnTrue, emptyFunction, returnFalse, emptyPath, returnOne, returnEmptyString, returnEmptyFilter } from '../../../Utils'; +import { Docs, DocumentOptions } from '../../documents/Documents'; +import { SetupDrag, DragManager } 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 { FieldView, FieldViewProps } from '../nodes/FieldView'; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentView } from '../nodes/DocumentView'; import { SelectionManager } from '../../util/SelectionManager'; +import { FilterQuery } from 'mongodb'; +import { CollectionLinearView } from '../collections/CollectionLinearView'; +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; + +import { CollectionDockingView } from '../collections/CollectionDockingView'; +import { ScriptField } from '../../../fields/ScriptField'; +import { PrefetchProxy } from '../../../fields/Proxy'; +import { List } from '../../../fields/List'; +import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faVideo, faCaretDown } from '@fortawesome/free-solid-svg-icons'; +import { Transform } from '../../util/Transform'; +import { MainView } from "../MainView"; +import { Scripting, _scriptingGlobals } from '../../util/Scripting'; +import { CollectionView, CollectionViewType } from '../collections/CollectionView'; +import { ViewBoxBaseComponent } from "../DocComponent"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { makeInterface, createSchema } from '../../../fields/Schema'; import { listSpec } from '../../../fields/Schema'; +import * as _ from "lodash"; +import { checkIfStateModificationsAreAllowed } from 'mobx/lib/internal'; + library.add(faTimes); -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", @@ -38,22 +54,37 @@ export enum Keys { DATA = "data" } +export interface filterData { + deletedDocsStatus: boolean; + authorFieldStatus: boolean; + titleFieldStatus: boolean; + basicWordStatus: boolean; + icons: string[]; +} + +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); } + @computed 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 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,30 +94,89 @@ 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; + if (!_scriptingGlobals.hasOwnProperty("handleNodeChange")) { + Scripting.addGlobal(this.handleNodeChange); + } + if (!_scriptingGlobals.hasOwnProperty("handleKeyChange")) { + Scripting.addGlobal(this.handleKeyChange); + } + if (!_scriptingGlobals.hasOwnProperty("handleWordQueryChange")) { + Scripting.addGlobal(this.handleWordQueryChange); + } + if (!_scriptingGlobals.hasOwnProperty("updateIcon")) { + Scripting.addGlobal(this.updateIcon); + } + if (!_scriptingGlobals.hasOwnProperty("updateTitleStatus")) { + Scripting.addGlobal(this.updateTitleStatus); + } + if (!_scriptingGlobals.hasOwnProperty("updateAuthorStatus")) { + Scripting.addGlobal(this.updateAuthorStatus); + } + if (!_scriptingGlobals.hasOwnProperty("updateDeletedStatus")) { + Scripting.addGlobal(this.updateDeletedStatus); + } + + this.resultsScrolled = this.resultsScrolled.bind(this); - } - componentDidMount = action(() => { + new PrefetchProxy(Docs.Create.SearchItemBoxDocument({ + title: "search item template", + backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data" + })); + + + if (!this.searchItemTemplate) { // create exactly one presElmentBox template to use by any and all presentations. + Doc.UserDoc().searchItemTemplate = new PrefetchProxy(Docs.Create.SearchItemBoxDocument({ title: "search item template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data" })); + // this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement + // this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent + // the preselement docs from being part of multiple presentations since they would all have the same field, or we'd have to keep per-presentation data + // stored on each pres element. + (this.searchItemTemplate as Doc).lookupField = ScriptField.MakeFunction("lookupSearchBoxField(container, field, data)", + { field: "string", data: Doc.name, container: Doc.name }); + } + } + @observable setupButtons = false; + componentDidMount = () => { + if (this.setupButtons == false) { + this.setupDocTypeButtons(); + this.setupKeyButtons(); + this.setupDefaultButtons(); + 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 @@ -94,7 +184,7 @@ export class SearchBox extends React.Component<SearchProps> { @action.bound onChange(e: React.ChangeEvent<HTMLInputElement>) { - this._searchString = e.target.value; + this.layoutDoc._searchString = e.target.value; this._openNoResults = false; this._results = []; @@ -108,6 +198,9 @@ export class SearchBox extends React.Component<SearchProps> { enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { + if (this._icons !== this._allIcons) { + runInAction(() => { this.expandedBucket = false }); + } this.submitSearch(); } } @@ -134,10 +227,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 +256,12 @@ 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(); + console.log(query) + } return query; } @@ -176,14 +272,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; @@ -191,6 +287,8 @@ export class SearchBox extends React.Component<SearchProps> { addCollectionFilter(query: string): string { const collections: Doc[] = this.getCurCollections(); + + console.log(collections); const oldWords = query.split(" "); const collectionString: string[] = []; @@ -216,9 +314,10 @@ export class SearchBox extends React.Component<SearchProps> { getCurCollections(): Doc[] { const selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments(); const collections: Doc[] = []; - + console.log(selectedDocs); selectedDocs.forEach(async element => { const layout: string = StrCast(element.props.Document.layout); + console.log(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 @@ -277,28 +376,133 @@ export class SearchBox extends React.Component<SearchProps> { get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); } + @observable expandedBucket: boolean = false; @action - submitSearch = async () => { - const query = this._searchString; + submitSearch = async (reset?: boolean) => { + this.checkIcons(); + if (reset) { + this.layoutDoc._searchString = ""; + } + this.dataDoc[this.fieldKey] = new List<Doc>([]); + this.buckets = []; + this.new_buckets = {}; + const query = StrCast(this.layoutDoc._searchString); this.getFinalQuery(query); 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"); + this.submitSearch(); + }, 60000); + } + 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(); + }); } } + @observable _timeout: any = undefined; + + @observable firststring: string = ""; + @observable secondstring: string = ""; + + @observable bucketcount: number[] = []; + + @action private makenewbuckets() { + console.log("new"); + let highcount = 0; + let secondcount = 0; + this.firststring = ""; + this.secondstring = ""; + this.buckets = []; + this.bucketcount = []; + this.dataDoc[this.fieldKey] = new List<Doc>([]); + for (var key in this.new_buckets) { + if (this.new_buckets[key] > highcount) { + secondcount === highcount; + this.secondstring = this.firststring; + highcount = this.new_buckets[key]; + this.firststring = key; + } + else if (this.new_buckets[key] > secondcount) { + secondcount = this.new_buckets[key]; + this.secondstring = key; + } + } + + let bucket = Docs.Create.StackingDocument([], { _viewType: CollectionViewType.Stacking, ignoreClick: true, forceActive: true, lockedPosition: true, title: `default bucket` }); + bucket._viewType === CollectionViewType.Stacking; + bucket._height = 185; + bucket.bucketfield = "results"; + bucket.isBucket = true; + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, bucket); + this.buckets!.push(bucket); + this.bucketcount[0] = 0; + + if (this.firststring !== "") { + let firstbucket = Docs.Create.StackingDocument([], { _viewType: CollectionViewType.Stacking, ignoreClick: true, forceActive: true, lockedPosition: true, title: this.firststring }); + firstbucket._height = 185; + + firstbucket._viewType === CollectionViewType.Stacking; + firstbucket.bucketfield = this.firststring; + firstbucket.isBucket = true; + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, firstbucket); + this.buckets!.push(firstbucket); + this.bucketcount[1] = 0; + + } + + if (this.secondstring !== "") { + let secondbucket = Docs.Create.StackingDocument([], { _viewType: CollectionViewType.Stacking, ignoreClick: true, forceActive: true, lockedPosition: true, title: this.secondstring }); + secondbucket._height = 185; + secondbucket._viewType === CollectionViewType.Stacking; + secondbucket.bucketfield = this.secondstring; + secondbucket.isBucket = true; + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, secondbucket); + this.buckets!.push(secondbucket); + this.bucketcount[2] = 0; + } + + let webbucket = Docs.Create.StackingDocument([], { _viewType: CollectionViewType.Stacking, childDropAction: "alias", ignoreClick: true, lockedPosition: true, title: this.secondstring }); + webbucket._height = 185; + webbucket._viewType === CollectionViewType.Stacking; + webbucket.bucketfield = "webs"; + webbucket.isBucket = true; + let old = Cast(this.props.Document.webbucket, Doc) as Doc; + let old2 = Cast(this.props.Document.bing, Doc) as Doc; + if (old) { + console.log("Cleanup"); + Doc.RemoveDocFromList(old, this.props.fieldKey, old2); + } + const textDoc = Docs.Create.WebDocument(`https://bing.com/search?q=${this.layoutDoc._searchString}`, { + _width: 200, _nativeHeight: 962, _nativeWidth: 800, isAnnotating: false, + title: "bing", UseCors: true + }); + this.props.Document.bing = textDoc; + this.props.Document.webbucket = webbucket; + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, webbucket); + Doc.AddDocToList(webbucket, this.props.fieldKey, textDoc); + + + } + + + @observable buckets: Doc[] | undefined; getAllResults = async (query: string) => { return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 }); @@ -309,7 +513,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; @@ -321,17 +525,17 @@ export class SearchBox extends React.Component<SearchProps> { 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 +544,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,6 +581,9 @@ export class SearchBox extends React.Component<SearchProps> { await this._curRequest; } + if (this._numTotalResults > 3 && this.expandedBucket === false) { + this.makenewbuckets(); + } this.resultsScrolled(); res(); }); @@ -371,7 +592,7 @@ export class SearchBox extends React.Component<SearchProps> { 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 +625,14 @@ export class SearchBox extends React.Component<SearchProps> { y += 300; } } - return Docs.Create.QueryDocument({ _autoHeight: true, title: this._searchString, filterQuery: this.filterQuery, searchQuery: this._searchString }); + const filter: filterData = { + deletedDocsStatus: this._deletedDocsStatus, + authorFieldStatus: this._authorFieldStatus, + titleFieldStatus: this._titleFieldStatus, + basicWordStatus: this._basicWordStatus, + icons: this._icons, + } + return Docs.Create.SearchDocument({ _autoHeight: true, _viewType: CollectionViewType.Stacking, title: StrCast(this.layoutDoc._searchString), searchQuery: StrCast(this.layoutDoc._searchString) }); } @action.bound @@ -427,6 +655,7 @@ 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; @@ -435,15 +664,20 @@ export class SearchBox extends React.Component<SearchProps> { @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; 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))); + const endIndex = 30; this._endIndex = endIndex === -1 ? 12 : endIndex; - + this._endIndex = 30; if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { this._visibleElements = [<div className="no-result">No Search Results</div>]; + //this._visibleDocuments= Docs.Create. + let noResult = Docs.Create.TextDocument("", { title: "noResult" }) + noResult.isBucket = false; + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, noResult); return; } @@ -456,16 +690,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,31 +710,109 @@ 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} />; + let lines = new List<string>(result[2]); + result[0]._height = 46; + result[0].lines = lines; + result[0].highlighting = highlights.join(", "); + this._visibleDocuments[i] = result[0]; this._isSearch[i] = "search"; + if (this._numTotalResults > 3 && this.expandedBucket === false) { + let doctype = StrCast(result[0].type); + console.log(doctype); + if (doctype === this.firststring) { + if (this.bucketcount[1] < 3) { + result[0].parent = this.buckets![1]; + Doc.AddDocToList(this.buckets![1], this.props.fieldKey, result[0]); + this.bucketcount[1] += 1; + } + } + else if (doctype === this.secondstring) { + if (this.bucketcount[2] < 3) { + result[0].parent = this.buckets![2]; + Doc.AddDocToList(this.buckets![2], this.props.fieldKey, result[0]); + this.bucketcount[2] += 1; + } + } + else if (this.bucketcount[0] < 3) { + //Doc.AddDocToList(this.buckets![0], this.props.fieldKey, result[0]); + //this.bucketcount[0]+=1; + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + } + } + else { + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + } } } 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"; + let lines = new List<string>(result[2]); + result[0]._height = 46; + result[0].lines = lines; + result[0].highlighting = highlights.join(", "); + if (i < this._visibleDocuments.length) { + this._visibleDocuments[i] = result[0]; + this._isSearch[i] = "search"; + if (this._numTotalResults > 3 && this.expandedBucket === false) { + + if (StrCast(result[0].type) === this.firststring) { + if (this.bucketcount[1] < 3) { + result[0].parent = this.buckets![1]; + Doc.AddDocToList(this.buckets![1], this.props.fieldKey, result[0]); + this.bucketcount[1] += 1; + } + } + else if (StrCast(result[0].type) === this.secondstring) { + if (this.bucketcount[2] < 3) { + result[0].parent = this.buckets![2]; + Doc.AddDocToList(this.buckets![2], this.props.fieldKey, result[0]); + this.bucketcount[2] += 1; + } + } + else if (this.bucketcount[0] < 3) { + //Doc.AddDocToList(this.buckets![0], this.props.fieldKey, result[0]); + //this.bucketcount[0]+=1; + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + } + } + else { + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + } + } } } } } } + if (this._numTotalResults > 3 && this.expandedBucket === false) { + if (this.buckets![0]) { + this.buckets![0]._height = this.bucketcount[0] * 55 + 25; + } + if (this.buckets![1]) { + this.buckets![1]._height = this.bucketcount[1] * 55 + 25; + } + if (this.buckets![2]) { + this.buckets![2]._height = this.bucketcount[2] * 55 + 25; + } + } if (this._maxSearchIndex >= this._numTotalResults) { this._visibleElements.length = this._results.length; + this._visibleDocuments.length = this._results.length; this._isSearch.length = this._results.length; } } + findCommonElements(arr2: string[]) { + let arr1 = ["layout", "data"]; + return arr1.some(item => arr2.includes(item)) + } + @computed get resFull() { return this._numTotalResults <= 8; } @@ -506,29 +821,48 @@ export class SearchBox extends React.Component<SearchProps> { //if true, any keywords can be used. if false, all keywords are required. @action.bound - handleWordQueryChange = () => { - this._basicWordStatus = !this._basicWordStatus; + handleWordQueryChange = async () => { + this._collectionStatus = !this._collectionStatus; + if (this._collectionStatus) { + let doc = await Cast(this.props.Document.keywords, Doc) + doc!.backgroundColor = "grey"; + + } + else { + let doc = await Cast(this.props.Document.keywords, Doc) + doc!.backgroundColor = "black"; + } } @action.bound - handleNodeChange = () => { + handleNodeChange = async () => { this._nodeStatus = !this._nodeStatus; + if (this._nodeStatus) { - this.expandSection(`node${this.props.id}`); + this.expandSection(`node${this.props.Document[Id]}`); + let doc = await Cast(this.props.Document.nodes, Doc) + doc!.backgroundColor = "grey"; + } else { - this.collapseSection(`node${this.props.id}`); + this.collapseSection(`node${this.props.Document[Id]}`); + let doc = await Cast(this.props.Document.nodes, Doc) + doc!.backgroundColor = "black"; } } @action.bound - handleKeyChange = () => { + handleKeyChange = async () => { this._keyStatus = !this._keyStatus; if (this._keyStatus) { - this.expandSection(`key${this.props.id}`); + this.expandSection(`key${this.props.Document[Id]}`); + let doc = await Cast(this.props.Document.keys, Doc) + doc!.backgroundColor = "grey"; } else { - this.collapseSection(`key${this.props.id}`); + this.collapseSection(`key${this.props.Document[Id]}`); + let doc = await Cast(this.props.Document.keys, Doc) + doc!.backgroundColor = "black"; } } @@ -536,11 +870,12 @@ export class SearchBox extends React.Component<SearchProps> { handleFilterChange = () => { this._filterOpen = !this._filterOpen; if (this._filterOpen) { - this.expandSection(`filterhead${this.props.id}`); - document.getElementById(`filterhead${this.props.id}`)!.style.padding = "5"; + this.expandSection(`filterhead${this.props.Document[Id]}`); + document.getElementById(`filterhead${this.props.Document[Id]}`)!.style.padding = "5"; + console.log(this.props.Document[Id]) } else { - this.collapseSection(`filterhead${this.props.id}`); + this.collapseSection(`filterhead${this.props.Document[Id]}`); } @@ -553,7 +888,7 @@ export class SearchBox extends React.Component<SearchProps> { collapseSection(thing: string) { - const id = this.props.id; + const id = this.props.Document[Id]; const element = document.getElementById(thing)!; // get the height of the element's inner content, regardless of its actual size const sectionHeight = element.scrollHeight; @@ -582,7 +917,6 @@ export class SearchBox extends React.Component<SearchProps> { } 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; @@ -617,55 +951,343 @@ export class SearchBox extends React.Component<SearchProps> { } @action.bound - updateTitleStatus() { this._titleFieldStatus = !this._titleFieldStatus; } + updateTitleStatus = async () => { + this._titleFieldStatus = !this._titleFieldStatus; + if (this._titleFieldStatus) { + let doc = await Cast(this.props.Document.title, Doc) + doc!.backgroundColor = "grey"; + } + else { + let doc = await Cast(this.props.Document.title, Doc) + doc!.backgroundColor = "black"; + } + } + + @action.bound + updateAuthorStatus = async () => { + this._authorFieldStatus = !this._authorFieldStatus; + if (this._authorFieldStatus) { + let doc = await Cast(this.props.Document.author, Doc) + doc!.backgroundColor = "grey"; + } + else { + let doc = await Cast(this.props.Document.author, Doc) + doc!.backgroundColor = "black"; + } + } + + @action.bound + updateDeletedStatus = async () => { + this._deletedDocsStatus = !this._deletedDocsStatus; + if (this._deletedDocsStatus) { + let doc = await Cast(this.props.Document.deleted, Doc) + doc!.backgroundColor = "grey"; + } + else { + let doc = await Cast(this.props.Document.deleted, Doc) + doc!.backgroundColor = "black"; + } + } + + 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 docButtons() { + const nodeBtns = this.props.Document.nodeButtons; + let width = () => NumCast(this.props.Document._width); + // if (StrCast(this.props.Document.title)==="sidebar search stack"){ + width = MainView.Instance.flyoutWidthFunc; + + // } + if (nodeBtns instanceof Doc) { + return <div id="hi" style={{ height: "100px", }}> + <DocumentView + docFilters={returnEmptyFilter} + Document={nodeBtns} + DataDoc={undefined} + LibraryPath={emptyPath} + addDocument={undefined} + addDocTab={returnFalse} + rootSelected={returnTrue} + pinToPres={emptyFunction} + onClick={undefined} + removeDocument={undefined} + ScreenToLocalTransform={this.getTransform} + ContentScaling={returnOne} + PanelWidth={width} + PanelHeight={() => 100} + renderDepth={0} + backgroundColor={returnEmptyString} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + NativeHeight={() => 100} + NativeWidth={width} + /> + </div>; + } + return (null); + } + + @computed get keyButtons() { + const nodeBtns = this.props.Document.keyButtons; + let width = () => NumCast(this.props.Document._width); + // if (StrCast(this.props.Document.title)==="sidebar search stack"){ + width = MainView.Instance.flyoutWidthFunc; + // } + if (nodeBtns instanceof Doc) { + return <div id="hi" style={{ height: "35px", }}> + <DocumentView + docFilters={returnEmptyFilter} + Document={nodeBtns} + DataDoc={undefined} + LibraryPath={emptyPath} + addDocument={undefined} + addDocTab={returnFalse} + rootSelected={returnTrue} + pinToPres={emptyFunction} + onClick={undefined} + removeDocument={undefined} + ScreenToLocalTransform={this.getTransform} + ContentScaling={returnOne} + PanelWidth={width} + PanelHeight={() => 100} + renderDepth={0} + backgroundColor={returnEmptyString} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + NativeHeight={() => 100} + NativeWidth={width} + /> + </div>; + } + return (null); + } + + @computed get defaultButtons() { + const defBtns = this.props.Document.defaultButtons; + let width = () => NumCast(this.props.Document._width); + // if (StrCast(this.props.Document.title)==="sidebar search stack"){ + width = MainView.Instance.flyoutWidthFunc; + // } + if (defBtns instanceof Doc) { + return <div id="hi" style={{ height: "35px", }}> + <DocumentView + docFilters={returnEmptyFilter} + + Document={defBtns} + DataDoc={undefined} + LibraryPath={emptyPath} + addDocument={undefined} + addDocTab={returnFalse} + rootSelected={returnTrue} + pinToPres={emptyFunction} + onClick={undefined} + removeDocument={undefined} + ScreenToLocalTransform={this.getTransform} + ContentScaling={returnOne} + PanelWidth={width} + PanelHeight={() => 100} + renderDepth={0} + backgroundColor={returnEmptyString} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + NativeHeight={() => 100} + NativeWidth={width} + /> + </div>; + } + return (null); + } @action.bound - updateAuthorStatus() { this._authorFieldStatus = !this._authorFieldStatus; } + updateIcon = async (icon: string) => { + if (this._icons.includes(icon)) { + _.pull(this._icons, icon); + let cap = icon.charAt(0).toUpperCase() + icon.slice(1) + console.log(cap); + let doc = await Cast(this.props.Document[cap], Doc) + doc!.backgroundColor = "black"; + } + else { + this._icons.push(icon); + let cap = icon.charAt(0).toUpperCase() + icon.slice(1) + let doc = await Cast(this.props.Document[cap], Doc) + doc!.backgroundColor = "grey"; + } + } @action.bound - updateDataStatus() { this._deletedDocsStatus = !this._deletedDocsStatus; } + checkIcons = async () => { + for (let i = 0; i < this._allIcons.length; i++) { + + let cap = this._allIcons[i].charAt(0).toUpperCase() + this._allIcons[i].slice(1) + let doc = await Cast(this.props.Document[cap], Doc) + if (this._icons.includes(this._allIcons[i])) { + doc!.backgroundColor = "grey"; + } + else { + doc!.backgroundColor = "black"; + } + } + } + + setupDocTypeButtons() { + let doc = this.props.Document; + const ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ + ...opts, + dropAction: "alias", removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 100, _nativeHeight: 100, _width: 100, + _height: 100 + })) as any as Doc; + doc.Audio = ficon({ onClick: ScriptField.MakeScript(`updateIcon("audio")`), title: "music button", icon: "music" }); + doc.Collection = ficon({ onClick: ScriptField.MakeScript(`updateIcon("collection")`), title: "col button", icon: "object-group" }); + doc.Image = ficon({ onClick: ScriptField.MakeScript(`updateIcon("image")`), title: "image button", icon: "image" }); + doc.Link = ficon({ onClick: ScriptField.MakeScript(`updateIcon("link")`), title: "link button", icon: "link" }); + doc.Pdf = ficon({ onClick: ScriptField.MakeScript(`updateIcon("pdf")`), title: "pdf button", icon: "file-pdf" }); + doc.Rtf = ficon({ onClick: ScriptField.MakeScript(`updateIcon("rtf")`), title: "text button", icon: "sticky-note" }); + doc.Video = ficon({ onClick: ScriptField.MakeScript(`updateIcon("video")`), title: "vid button", icon: "video" }); + doc.Web = ficon({ onClick: ScriptField.MakeScript(`updateIcon("web")`), title: "web button", icon: "globe-asia" }); + + let buttons = [doc.None as Doc, doc.Audio as Doc, doc.Collection as Doc, + doc.Image as Doc, doc.Link as Doc, doc.Pdf as Doc, doc.Rtf as Doc, doc.Video as Doc, doc.Web as Doc]; + + const dragCreators = Docs.Create.MasonryDocument(buttons, { + _width: 500, backgroundColor: "#121721", _autoHeight: true, _columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), _yMargin: 5 + }); + doc.nodeButtons = dragCreators; + this.checkIcons() + } + + + setupKeyButtons() { + let doc = this.props.Document; + const button = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.ButtonDocument({ + ...opts, + _width: 35, _height: 30, + borderRounding: "16px", border: "1px solid grey", color: "white", hovercolor: "rgb(170, 170, 163)", letterSpacing: "2px", + _fontSize: 7, + })) as any as Doc; + doc.title = button({ backgroundColor: "grey", title: "Title", onClick: ScriptField.MakeScript("updateTitleStatus(self)") }); + doc.deleted = button({ title: "Deleted", onClick: ScriptField.MakeScript("updateDeletedStatus(self)") }); + doc.author = button({ backgroundColor: "grey", title: "Author", onClick: ScriptField.MakeScript("updateAuthorStatus(self)") }); + let buttons = [doc.title as Doc, doc.deleted as Doc, doc.author as Doc]; + + const dragCreators = Docs.Create.MasonryDocument(buttons, { + _width: 500, backgroundColor: "#121721", _autoHeight: true, _columnWidth: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _yMargin: 5 + //dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), + }); + doc.keyButtons = dragCreators; + } + + setupDefaultButtons() { + let doc = this.props.Document; + const button = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.ButtonDocument({ + ...opts, + _width: 35, _height: 30, + borderRounding: "16px", border: "1px solid grey", color: "white", + //hovercolor: "rgb(170, 170, 163)", + letterSpacing: "2px", + _fontSize: 7, + })) as any as Doc; + doc.keywords = button({ title: "Keywords", onClick: ScriptField.MakeScript("handleWordQueryChange(self)") }); + doc.keys = button({ title: "Keys", onClick: ScriptField.MakeScript(`handleKeyChange(self)`) }); + doc.nodes = button({ title: "Nodes", onClick: ScriptField.MakeScript("handleNodeChange(self)") }); + let buttons = [doc.keywords as Doc, doc.keys as Doc, doc.nodes as Doc]; + const dragCreators = Docs.Create.MasonryDocument(buttons, { + _width: 500, backgroundColor: "#121721", _autoHeight: true, _columnWidth: 60, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _yMargin: 5 + //dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), + }); + doc.defaultButtons = dragCreators; + } + @computed get searchItemTemplate() { return Cast(Doc.UserDoc().searchItemTemplate, Doc, null); } + + childLayoutTemplate = () => this.layoutDoc._viewType === CollectionViewType.Stacking ? this.searchItemTemplate : undefined; + getTransform = () => { + return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight + } + panelHeight = () => { + return this.props.PanelHeight() - 50; + } + selectElement = (doc: Doc) => { + //this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex)); + } + addDocument = (doc: Doc) => { + return null; + } + //Make id layour document render() { + if (this.expandedBucket === true) { + this.props.Document._gridGap = 5; + } + else { + this.props.Document._gridGap = 10; + } + this.props.Document._searchDoc = true; 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"> + <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._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} + <input value={StrCast(this.layoutDoc._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> + <button className="searchBox-barChild searchBox-filter" style={{ transform: "none" }} 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 id={`filterhead${this.props.Document[Id]}`} className="filter-form" style={this._filterOpen && this._numTotalResults > 0 ? { overflow: "visible" } : { overflow: "hidden" }}> + <div id={`filterhead2${this.props.Document[Id]}`} className="filter-header" > + {this.defaultButtons} </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 id={`node${this.props.Document[Id]}`} className="filter-body" style={this._nodeStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}> + {this.docButtons} </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 className="filter-key" id={`key${this.props.Document[Id]}`} style={this._keyStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}> + {this.keyButtons} </div> </div> + <CollectionView {...this.props} + Document={this.props.Document} + PanelHeight={this.panelHeight} + moveDocument={returnFalse} + NativeHeight={() => 400} + childLayoutTemplate={this.childLayoutTemplate} + addDocument={undefined} + removeDocument={returnFalse} + focus={this.selectElement} + ScreenToLocalTransform={Transform.Identity} /> <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> ); } -}
\ No newline at end of file +} + +Scripting.addGlobal(function lookupSearchBoxField(container: Doc, field: string, data: Doc) { + // if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data); + // if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 50 : 46; + // if (field === 'presStatus') return container.presStatus; + // if (field === '_itemIndex') return container._itemIndex; + if (field == "query") return container._searchString; + return undefined; +}); + |
