diff options
-rw-r--r-- | src/client/util/SearchUtil.ts | 3 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSchemaHeaders.tsx | 305 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSchemaView.tsx | 16 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSubView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/SchemaTable.tsx | 28 | ||||
-rw-r--r-- | src/client/views/search/SearchBox.tsx | 284 | ||||
-rw-r--r-- | src/fields/Doc.ts | 1 | ||||
-rw-r--r-- | src/fields/SchemaHeaderField.ts | 2 |
9 files changed, 256 insertions, 389 deletions
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 7b2c601fe..3073da954 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -37,7 +37,8 @@ export namespace SearchUtil { export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) { query = query || "*"; //If we just have a filter query, search for * as the query const rpquery = Utils.prepend("/dashsearch"); - const gotten = await rp.get(rpquery, { qs: { ...options, q: query } }); + const replacedQuery = query.replace(/type_t:([^ )])/, (substring, arg) => `{!join from=id to=proto_i}type_t:${arg}`); + const gotten = await rp.get(rpquery, { qs: { ...options, q: replacedQuery } }); const result: IdSearchResult = gotten.startsWith("<") ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten); if (!returnDocs) { return result; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a5d2f2fa1..d1c4dd4e6 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -163,7 +163,7 @@ export class MainView extends React.Component { } } - library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, + library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar, fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight, @@ -179,7 +179,7 @@ export class MainView extends React.Component { fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper, fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp, - fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer); + fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl); this.initEventListeners(); this.initAuthenticationRouters(); } diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index 8cc91b3da..d379f1c1e 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -1,37 +1,23 @@ import React = require("react"); -import { action, observable, computed } from "mobx"; -import { observer } from "mobx-react"; -import "./CollectionSchemaView.scss"; -import { faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faSortAmountDown, faSortAmountUp, faTimes, faImage, faListUl, faCalendar } from '@fortawesome/free-solid-svg-icons'; -import { library, IconProp } from "@fortawesome/fontawesome-svg-core"; +import { IconProp, library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { ColumnType } from "./CollectionSchemaView"; -import { faFile } from "@fortawesome/free-regular-svg-icons"; -import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField"; -import { undoBatch } from "../../util/UndoManager"; -import { Transform } from '../../util/Transform'; -import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; -import { StrCast, Cast } from "../../../fields/Types"; -import { optionFocusAriaMessage } from "react-select/src/accessibility"; -import { TraceMobx } from "../../../fields/util"; -import { CollectionTreeView } from "./CollectionTreeView"; -import { returnEmptyFilter, returnFalse, emptyPath, returnZero, emptyFunction, returnOne } from "../../../Utils"; -import { RichTextField } from "../../../fields/RichTextField"; -import { Docs } from "../../documents/Documents"; -import { List } from "../../../fields/List"; +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, DocListCast } from "../../../fields/Doc"; import { listSpec } from "../../../fields/Schema"; -import { ScriptField, ComputedField } from "../../../fields/ScriptField"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { CollectionView } from "./CollectionView"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { ScriptField } from "../../../fields/ScriptField"; +import { Cast, StrCast } from "../../../fields/Types"; +import { undoBatch } from "../../util/UndoManager"; import { SearchBox } from "../search/SearchBox"; -import { createParameter } from "typescript"; +import { ColumnType } from "./CollectionSchemaView"; +import "./CollectionSchemaView.scss"; +import { CollectionView } from "./CollectionView"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; -library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile as any, faSortAmountDown, faSortAmountUp, faTimes, faImage, faListUl, faCalendar); - export interface HeaderProps { keyValue: SchemaHeaderField; possibleKeys: string[]; @@ -50,7 +36,7 @@ export interface HeaderProps { export class CollectionSchemaHeader extends React.Component<HeaderProps> { render() { const icon: IconProp = this.props.keyType === ColumnType.Number ? "hashtag" : this.props.keyType === ColumnType.String ? "font" : - this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "file" : + this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "sort-amount-down" : this.props.keyType === ColumnType.Image ? "image" : this.props.keyType === ColumnType.List ? "list-ul" : this.props.keyType === ColumnType.Date ? "calendar" : "align-justify"; return ( @@ -253,18 +239,6 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> renderContent = () => { return ( <div className="collectionSchema-header-menuOptions"> - <div className="collectionSchema-headerMenu-group"> - <label>Key:</label> - <KeysDropdown - keyValue={this.props.columnField.heading} - possibleKeys={this.props.possibleKeys} - existingKeys={this.props.existingKeys} - canAddNew={true} - addNew={this.props.addNew} - onSelect={this.props.onSelect} - setIsEditing={this.props.setIsEditing} - /> - </div> {this.props.onlyShowOptions ? <></> : <> {this.renderTypes()} @@ -301,12 +275,15 @@ export interface KeysDropdownProps { setIsEditing: (isEditing: boolean) => void; width?: string; docs?: Doc[]; - Document?: Doc; - dataDoc?: Doc; - fieldKey?: string; - ContainingCollectionDoc?: Doc; - ContainingCollectionView?: CollectionView; + Document: Doc; + dataDoc: Doc; + fieldKey: string; + ContainingCollectionDoc: Doc; + ContainingCollectionView: CollectionView; active?: (outsideReaction?: boolean) => boolean; + openHeader: (column: any, screenx: number, screeny: number) => void; + col: SchemaHeaderField; + icon: IconProp; } @observer export class KeysDropdown extends React.Component<KeysDropdownProps> { @@ -322,28 +299,10 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { @action onSelect = (key: string): void => { - if (this._searchTerm.includes(":")) { - const colpos = this._searchTerm.indexOf(":"); - const filter = this._searchTerm.slice(colpos + 1, this._searchTerm.length); - //const filter = key.slice(this._key.length - key.length); - this.props.onSelect(this._key, this._key, this.props.addNew, filter); - } - else { - this.props.onSelect(this._key, key, this.props.addNew); - this.setKey(key); - this._isOpen = false; - this.props.setIsEditing(false); - } - } - - @action - onSelectValue = (key: string): void => { - const colpos = this._searchTerm.indexOf(":"); - this.onSelect(key); - this._searchTerm = this._searchTerm.slice(0, colpos + 1) + key; + this.props.onSelect(this._key, key, this.props.addNew); + this.setKey(key); this._isOpen = false; - this.props.onSelect(this._key, this._key, this.props.addNew, key); - + this.props.setIsEditing(false); } @undoBatch @@ -354,8 +313,10 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { keyOptions = keyOptions.filter(n => !blockedkeys.includes(n)); if (keyOptions.length) { this.onSelect(keyOptions[0]); + console.log("case1"); } else if (this._searchTerm !== "" && this.props.canAddNew) { this.setSearchTerm(this._searchTerm || this._key); + console.log("case2"); this.onSelect(this._searchTerm); } } @@ -412,7 +373,9 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { }); // if search term does not already exist as a group type, give option to create new group type + if (this._key !== this._searchTerm.slice(0, this._key.length)) { + console.log("little further"); if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) { options.push(<div key={""} className="key-option" style={{ border: "1px solid lightgray", width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", @@ -436,6 +399,9 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { } return options; } + + docSafe: Doc[] = [] + @action renderFilterOptions = (): JSX.Element[] | JSX.Element => { if (!this._isOpen) { @@ -443,20 +409,27 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { return <></>; } let keyOptions: string[] = []; - const colpos = this._searchTerm.indexOf(":"); - const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length); - let docs = DocListCast(this.props.dataDoc![this.props.fieldKey!]); + if (this.docSafe.length === 0) { + this.docSafe = DocListCast(this.props.dataDoc![this.props.fieldKey!]); + } + let docs = this.docSafe; docs.forEach((doc) => { const key = StrCast(doc[this._key]); - if (keyOptions.includes(key) === false && key.includes(temp)) { + if (keyOptions.includes(key) === false) { keyOptions.push(key); } }); + let filters = Cast(this.props.Document!._docFilters, listSpec("string")); + for (let i = 0; i < (filters?.length ?? 0) - 1; i += 3) { + if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i + 1]) === false) { + keyOptions.push(filters![i + 1]); + } + } + const options = keyOptions.map(key => { //Doc.setDocFilter(this.props.Document!, this._key, key, undefined); let bool = false; - let filters = Cast(this.props.Document!._docFilters, listSpec("string")); console.log(filters); if (filters !== undefined) { bool = filters.includes(key) && filters[filters.indexOf(key) + 1] === "check"; @@ -467,7 +440,10 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", backgroundColor: "white", }} > - <input type="checkbox" onChange={(e) => { e.target.checked === true ? Doc.setDocFilter(this.props.Document!, this._key, key, "check") : Doc.setDocFilter(this.props.Document!, this._key, key, undefined); e.target.checked === true && SearchBox.Instance.filter === true ? Doc.setDocFilter(docs![0], this._key, key, "check") : Doc.setDocFilter(docs![0], this._key, key, undefined); }} + <input type="checkbox" onChange={(e) => { + e.target.checked === true ? Doc.setDocFilter(this.props.Document!, this._key, key, "check") : Doc.setDocFilter(this.props.Document!, this._key, key, undefined); + e.target.checked === true && SearchBox.Instance.filter === true ? Doc.setDocFilter(docs![0], this._key, key, "check") : Doc.setDocFilter(docs![0], this._key, key, undefined); + }} checked={bool} ></input> <span style={{ paddingLeft: 4 }}> {key} @@ -488,107 +464,12 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { } } - return options; } - - // @action - // renderFilterOptions = (): JSX.Element[] | JSX.Element => { - // this.facetClick(this._key); - // return <div> - // {this.filterView} - // </div> - // } @observable defaultMenuHeight = 0; - facetClick = (facetHeader: string) => { - facetHeader = this._key; - let newFacet: Opt<Doc>; - if (this.props.Document !== undefined) { - const facetCollection = this.props.Document; - // const found = DocListCast(facetCollection[this.props.fieldKey + "-filter"]).findIndex(doc => doc.title === facetHeader); - // if (found !== -1) { - // console.log("found not n-1"); - // (facetCollection[this.props.fieldKey + "-filter"] as List<Doc>).splice(found, 1); - // const docFilter = Cast(this.props.Document._docFilters, listSpec("string")); - // if (docFilter) { - // let index: number; - // while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) { - // docFilter.splice(index, 3); - // } - // } - // const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string")); - // if (docRangeFilters) { - // let index: number; - // while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) { - // docRangeFilters.splice(index, 3); - // } - // } - // } - - - - console.log("found is n-1"); - const allCollectionDocs = DocListCast(this.props.dataDoc![this.props.fieldKey!]); - var rtfields = 0; - const facetValues = Array.from(allCollectionDocs.reduce((set, child) => { - const field = child[facetHeader] as Field; - const fieldStr = Field.toString(field); - if (field instanceof RichTextField || (typeof (field) === "string" && fieldStr.split(" ").length > 2)) rtfields++; - return set.add(fieldStr); - }, new Set<string>())); - - let nonNumbers = 0; - let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE; - facetValues.map(val => { - const num = Number(val); - if (Number.isNaN(num)) { - nonNumbers++; - } else { - minVal = Math.min(num, minVal); - maxVal = Math.max(num, maxVal); - } - }); - if (facetHeader === "text" || rtfields / allCollectionDocs.length > 0.1) { - console.log("Case1"); - newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true }); - Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox - newFacet.target = this.props.Document; - newFacet._textBoxPadding = 4; - const scriptText = `setDocFilter(this.target, "${facetHeader}", text, "match")`; - newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" }); - } else if (nonNumbers / facetValues.length < .1) { - console.log("Case2"); - newFacet = Docs.Create.SliderDocument({ title: facetHeader, treeViewExpandedView: "layout", treeViewOpen: true }); - const newFacetField = Doc.LayoutFieldKey(newFacet); - const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader); - Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox - const extendedMinVal = minVal - Math.min(1, Math.abs(maxVal - minVal) * .05); - const extendedMaxVal = maxVal + Math.min(1, Math.abs(maxVal - minVal) * .05); - newFacet[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0]; - newFacet[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1]; - Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = extendedMinVal; - Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal; - newFacet.target = this.props.Document; - const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`; - newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" }); - Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet); - } else { - console.log("Case3"); - newFacet = new Doc(); - newFacet.title = facetHeader; - newFacet.treeViewOpen = true; - newFacet.type = DocumentType.COL; - const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.props.dataDoc! }; - this.props.Document.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, {}, capturedVariables); - // this.props.Document.data - } - //newFacet && Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet); - } - } - get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } @@ -600,82 +481,28 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { } filterBackground = () => "rgba(105, 105, 105, 0.432)"; - - // @computed get filterView() { - // TraceMobx(); - // if (this.props.Document !== undefined) { - // //const facetCollection = this.props.Document; - // // const flyout = ( - // // <div className="collectionTimeView-flyout" style={{ width: `${this.facetWidth()}`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}> - // // {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}> - // // <input type="checkbox" onChange={e => { }} checked={DocListCast(this.props.Document[this.props.fieldKey + "-filter"]).some(d => d.title === facet)} /> - // // <span className="checkmark" /> - // // {facet} - // // </label>)} - // // </div> - // // ); - // return <div className="altcollectionTimeView-treeView"> - // <div className="altcollectionTimeView-tree" key="tree"> - // <CollectionTreeView - // PanelPosition={""} - // Document={this.props.Document} - // DataDoc={this.props.Document} - // fieldKey={this.props.fieldKey!} - // CollectionView={undefined} - // docFilters={returnEmptyFilter} - // ContainingCollectionDoc={this.props.ContainingCollectionDoc!} - // ContainingCollectionView={this.props.ContainingCollectionView!} - // PanelWidth={() => 200} - // PanelHeight={() => 200} - // NativeHeight={returnZero} - // NativeWidth={returnZero} - // LibraryPath={emptyPath} - // rootSelected={returnFalse} - // renderDepth={1} - // dropAction={undefined} - // ScreenToLocalTransform={Transform.Identity} - // addDocTab={returnFalse} - // pinToPres={returnFalse} - // isSelected={returnFalse} - // select={returnFalse} - // bringToFront={emptyFunction} - // active={this.props.active!} - // whenActiveChanged={returnFalse} - // treeViewHideTitle={true} - // ContentScaling={returnOne} - // focus={returnFalse} - // treeViewHideHeaderFields={true} - // onCheckedClick={this.scriptField} - // ignoreFields={this.ignoreFields} - // annotationsKey={""} - // dontRegisterView={true} - // backgroundColor={this.filterBackground} - // moveDocument={returnFalse} - // removeDocument={returnFalse} - // addDocument={returnFalse} /> - // </div> - // </div>; - // } - // } - + @observable filterOpen: boolean | undefined = undefined; render() { + console.log(this._isOpen, this._key, this._searchTerm); return ( - <div className="keys-dropdown" style={{ zIndex: 10, width: this.props.width, maxWidth: this.props.width }}> - <input className="keys-search" style={{ width: "100%" }} - ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown} - onChange={e => this.onChange(e.target.value)} - onClick={(e) => { - //this._inputRef.current!.select(); - e.stopPropagation(); - }} onFocus={this.onFocus} onBlur={this.onBlur}></input> - <div className="keys-options-wrapper" style={{ - width: this.props.width, maxWidth: this.props.width, height: "auto", - }} - onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}> - {this._searchTerm.includes(":") ? - this.renderFilterOptions() : this.renderOptions()} - </div> - </div > + <div style={{ display: "flex" }}> + <FontAwesomeIcon onClick={e => { this.props.Document._searchDoc ? runInAction(() => { this._isOpen === undefined ? this._isOpen = true : this._isOpen = !this._isOpen }) : this.props.openHeader(this.props.col, e.clientX, e.clientY) }} icon={this.props.icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} /> + <div className="keys-dropdown" style={{ zIndex: 10, width: this.props.width, maxWidth: this.props.width }}> + <input className="keys-search" style={{ width: "100%" }} + ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown} + onChange={e => this.onChange(e.target.value)} + onClick={(e) => { + //this._inputRef.current!.select(); + e.stopPropagation(); + }} onFocus={this.onFocus} onBlur={this.onBlur}></input> + <div className="keys-options-wrapper" style={{ + width: this.props.width, maxWidth: this.props.width, height: "auto", + }} + onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}> + {this._key === this._searchTerm ? this.renderFilterOptions() : this.renderOptions()} + </div> + </div > + </div> ); } } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 3f879b489..ef7b1c4f7 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -366,17 +366,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action closeHeader = () => { this._headerOpen = false; } - renderKeysDropDown = (col: any) => { - return <KeysDropdown - keyValue={col.heading} - possibleKeys={this.possibleKeys} - existingKeys={this.columns.map(c => c.heading)} - canAddNew={true} - addNew={false} - onSelect={this.changeColumns} - setIsEditing={this.setHeaderIsEditing} - />; - } + @undoBatch @action @@ -415,10 +405,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @computed get renderMenuContent() { TraceMobx(); return <div className="collectionSchema-header-menuOptions"> - <div className="collectionSchema-headerMenu-group"> - <label>Key:</label> - {this.renderKeysDropDown(this._col)} - </div> {this.renderTypes(this._col)} {this.renderSorting(this._col)} {this.renderColors(this._col)} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 7957ded1c..a43685a5e 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -172,8 +172,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: }); return docsforFilter; } - console.log("you fool"); - console.log(childDocs); const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); return this.props.Document.dontRegisterView ? childDocs : DocUtils.FilterDocs(childDocs, this.docFilters(), docRangeFilters, viewSpecScript); } diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx index d8756aae3..4fb05733a 100644 --- a/src/client/views/collections/SchemaTable.tsx +++ b/src/client/views/collections/SchemaTable.tsx @@ -180,6 +180,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> { this.props.active const cols = this.props.columns.map(col => { + const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" : + this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" : + this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" : + this.getColumnType(col) === ColumnType.Date ? "calendar" : "align-justify"; + const keysDropdown = <KeysDropdown keyValue={col.heading} possibleKeys={possibleKeys} @@ -195,26 +200,14 @@ export class SchemaTable extends React.Component<SchemaTableProps> { ContainingCollectionDoc={this.props.ContainingCollectionDoc} ContainingCollectionView={this.props.ContainingCollectionView} active={this.props.active} - - + openHeader={this.props.openHeader} + icon={icon} + col={col} // try commenting this out width={"100%"} />; - const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" : - this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" : - this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" : - this.getColumnType(col) === ColumnType.Date ? "calendar" : "align-justify"; - const headerText = this._showTitleDropdown ? keysDropdown : <div - onClick={this.changeTitleMode} - style={{ - background: col.color, padding: "2px", - letterSpacing: "2px", - textTransform: "uppercase", - display: "flex" - }}> - {col.heading}</div>; const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up"; @@ -226,11 +219,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { background: col.color, padding: "2px", display: "flex", cursor: "default", height: "100%", }}> - <FontAwesomeIcon onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} /> - {/* <div className="keys-dropdown" - style={{ display: "inline", zIndex: 1000 }}> */} + {/* <FontAwesomeIcon onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} /> */} {keysDropdown} - {/* </div> */} <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "hand" }}> <FontAwesomeIcon icon={sortIcon} size="lg" /> diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 4a14b222c..2e2e87092 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, observable, runInAction, reaction, IReactionDisposer } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as rp from 'request-promise'; @@ -86,7 +86,19 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } @observable setupButtons = false; + private _disposers: { [name: string]: IReactionDisposer } = {}; + componentDidMount = () => { + this._disposers.filters = reaction(() => Cast(this.props.Document._docFilters, listSpec("string")), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks + newFilters => { + if (this.searchFullDB) { + runInAction(() => this._pageStart = 0); + this.submitSearch(); + // newFilters?.forEach(f => { + // console.log(f); + // }) + } + }); if (this.setupButtons === false) { runInAction(() => this.setupButtons = true); @@ -113,6 +125,10 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } } + componentWillUnmount() { + Object.values(this._disposers).forEach(disposer => disposer?.()); + } + @action getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc) @@ -123,8 +139,6 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc onChange(e: React.ChangeEvent<HTMLInputElement>) { this.layoutDoc._searchString = e.target.value; this.newsearchstring = e.target.value; - - if (e.target.value === "") { if (this.currentSelectedCollection !== undefined) { let newarray: Doc[] = []; @@ -176,6 +190,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { this.layoutDoc._searchString = this.newsearchstring; + runInAction(() => this._pageStart = 0); if (StrCast(this.layoutDoc._searchString) !== "" || !this.searchFullDB) { runInAction(() => this.open = true); @@ -228,26 +243,101 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc 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 + //if this is true, th`en not all of the field boxes are checked //TODO: data - if (this.fieldFiltersApplied) { - query = this.applyBasicFieldFilters(query); - query = query.replace(/\s+/g, ' ').trim(); + let initialfilters = Cast(this.props.Document._docFilters, listSpec("string"), []); + + let type: string[] = []; + + let filters: string[] = [] + + for (let i = 0; i < initialfilters.length; i = i + 3) { + if (initialfilters[i + 2] !== undefined) { + filters.push(initialfilters[i]); + filters.push(initialfilters[i + 1]); + filters.push(initialfilters[i + 2]); + } } - //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(); + let finalfilters: { [key: string]: string[] } = {}; + + for (let i = 0; i < filters.length; i = i + 3) { + if (finalfilters[filters[i]] !== undefined) { + finalfilters[filters[i]].push(filters[i + 1]); + } + else { + finalfilters[filters[i]] = [filters[i + 1]]; + } } - // if should be searched in a specific collection - if (this._collectionStatus) { - query = this.addCollectionFilter(query); - query = query.replace(/\s+/g, ' ').trim(); + for (var key in finalfilters) { + let values = finalfilters[key]; + if (values.length === 1) { + let mod = "_t:" + const newWords: string[] = []; + const oldWords = values[0].split(" "); + oldWords.forEach((word, i) => { + i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\"") + }); + query = `(${query}) AND (${newWords.join(" ")})`; + } + else { + for (let i = 0; i < values.length; i++) { + let mod = "_t:" + const newWords: string[] = []; + const oldWords = values[i].split(" "); + oldWords.forEach((word, i) => { + i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\"") + }); + let v = "(" + newWords.join(" ") + ")"; + if (i === 0) { + query = `(${query}) AND (${v}`; + if (values.length === 1) { + query = query + ")"; + } + } + else if (i === values.length - 1) { + query = query + " OR " + v + ")"; + } + else { + query = query + " OR " + v; + } + } + } } + + + // let limit = typepos.length + // typepos.forEach(i => { + // if (i === 0) { + // if (i + 1 === limit) { + // query = query + " && " + filters[i] + "_t:" + filters; + // } + // else if (filters[i] === filters[i + 3]) { + // query = query + " && (" + filters[i] + "_t:" + filters; + // } + // else { + // query = query + " && " + filters[i] + "_t:" + filters; + // } + + // } + // else if (i + 3 > filters.length) { + + // } + // else { + + // } + + // }); + + // query = this.applyBasicFieldFilters(query); + + + + query = query.replace(/-\s+/g, ''); + query = query.replace(/-/g, ""); + console.log(query); return query; } @@ -400,15 +490,9 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc 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); - } + finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE); + finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR); + if (this._deletedDocsStatus) { finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TEXT); } @@ -419,8 +503,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc let mod = ""; switch (type) { case Keys.AUTHOR: mod = " author_t:"; break; - case Keys.DATA: break; // TODO - case Keys.TITLE: mod = " _title_t:"; break; + case Keys.TITLE: mod = " title_t:"; break; case Keys.TEXT: mod = " text_t:"; break; } @@ -446,7 +529,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc docs.forEach((d) => { if (d.data !== undefined) { d._searchDocs = new List<Doc>(); - d._docFilters = new List(); + //d._docFilters = new List(); const newdocs = DocListCast(d.data); newdocs.forEach((newdoc) => { newarray.push(newdoc); @@ -463,7 +546,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc if (reset) { this.layoutDoc._searchString = ""; } - this.props.Document._docFilters = new List(); + //this.props.Document._docFilters = new List(); this.noresults = ""; this.dataDoc[this.fieldKey] = new List<Doc>([]); @@ -471,9 +554,9 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this.children = 0; this.buckets = []; this.new_buckets = {}; - const query = StrCast(this.layoutDoc._searchString); + let query = StrCast(this.layoutDoc._searchString); Doc.SetSearchQuery(query); - this.getFinalQuery(query); + this.searchFullDB ? query = this.getFinalQuery(query) : console.log("local"); this._results.forEach(result => { Doc.UnBrushDoc(result[0]); result[0].searchMatch = undefined; @@ -521,7 +604,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } private get filterQuery() { - const types = ["preselement", "docholder", "collection", "search", "searchitem", "script", "fonticonbox", "button", "label"]; // this.filterTypes; + const types = ["preselement", "docholder", "search", "searchitem", "script", "fonticonbox", "button", "label"]; // this.filterTypes; const baseExpr = "NOT baseProto_b:true AND NOT system_b:true"; const includeDeleted = this.getDataStatus() ? "" : " NOT deleted_b:true"; const includeIcons = this.getDataStatus() ? "" : " NOT type_t:fonticonbox"; @@ -533,7 +616,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc getDataStatus() { return this._deletedDocsStatus; } - private NumResults = 25; + private NumResults = 50; private lockPromise?: Promise<void>; getResults = async (query: string) => { console.log("Get"); @@ -542,11 +625,12 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } 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: 10000000, 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; } + console.log(res.numFound); const highlighting = res.highlighting || {}; const highlightList = res.docs.map(doc => highlighting[doc[Id]]); const lines = new Map<string, string[]>(); @@ -558,31 +642,23 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc runInAction(() => { filteredDocs.forEach((doc, i) => { + console.log(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)).filter(k => k) : []; 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); - } + // if (this.findCommonElements(hlights)) { + // } + if (index === undefined) { + this._resultsSet.set(doc, this._results.length); + this._results.push([doc, hlights, line]); + console.log(i); + } else { + this._results[index][1].push(...hlights); + this._results[index][2].push(...line); } + }); }); @@ -663,26 +739,27 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this._curRequest = undefined; } + @observable _pageStart: number = 0 + @observable _pageCount: number = 5; + @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))); const endIndex = 30; //this._endIndex = endIndex === -1 ? 12 : endIndex; this._endIndex = 30; - const headers = new Set<string>(["title", "author", "*lastModified"]); - if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { - if (this.noresults === "") { - this.noresults = "No search results :("; - } - return; - } + const headers = new Set<string>(["title", "author", "*lastModified", "type"]); + // if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { + // if (this.noresults === "") { + // this.noresults = "No search results :("; + // } + // return; + // } if (this._numTotalResults <= this._maxSearchIndex) { this._numTotalResults = this._results.length; @@ -699,62 +776,42 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this._isSorted = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults); } - for (let i = 0; i < this._numTotalResults; i++) { + let max = this._pageStart + this._pageCount; + max > this._results.length ? max = this._results.length : console.log(""); + for (let i = this._pageStart; i < max; 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>; - } - } - else { - if (this._isSearch[i] !== "search") { - let result: [Doc, string[], string[]] | undefined = undefined; - if (i >= this._results.length) { - 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())]); - 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())]); - 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++; - } - } + + if (this._isSearch[i] !== "search") { + let result: [Doc, string[], string[]] | undefined = undefined; + + result = this._results[i]; + if (result) { + const highlights = Array.from([...Array.from(new Set(result[1]).values())]); + 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 (Cast(this.props.Document._docFilters, listSpec("string"), []).length === 0) { + 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; @@ -799,6 +856,11 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc @observable filter = false; + @action newpage() { + this._pageStart += 5; + this.dataDoc[this.fieldKey] = new List<Doc>([]); + this.resultsScrolled(); + } render() { this.props.Document._chromeStatus === "disabled"; this.props.Document._searchDoc = true; @@ -807,14 +869,16 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc cols > 5 ? length = 1076 : length = cols * 205 + 51; let height = 0; const rows = this.children; - rows > 6 ? height = 31 + 31 * 6 : height = 31 * rows + 31; + height = 31 + 31 * 6; return ( <div style={{ pointerEvents: "all" }} className="searchBox-container"> <div className="searchBox-bar"> <div style={{ position: "absolute", left: 15 }}>{Doc.CurrentUserEmail}</div> <div style={{ display: "flex", alignItems: "center" }}> <Tooltip title={<div className="dash-tooltip" >drag search results as collection</div>} ><div> - <FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg" + {/* <FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg" + style={{ cursor: "hand", color: "black", padding: 1, left: 35, position: "relative" }} /> */} + <FontAwesomeIcon onPointerDown={() => { this.newpage(); }} icon={"search"} size="lg" style={{ cursor: "hand", color: "black", padding: 1, left: 35, position: "relative" }} /> </div></Tooltip> <div style={{ cursor: "default", left: 250, position: "relative", }}> diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 00ffe399e..ea53bc9a2 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1045,6 +1045,7 @@ export namespace Doc { if (docFilters[i] === key && (docFilters[i + 1] === value || modifiers === "match")) { if (docFilters[i + 2] === modifiers && modifiers && docFilters[i + 1] === value) return; docFilters.splice(i, 3); + container._docFilters = new List<string>(docFilters); break; } } diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts index 07c90f5a2..22ae454f8 100644 --- a/src/fields/SchemaHeaderField.ts +++ b/src/fields/SchemaHeaderField.ts @@ -114,7 +114,7 @@ export class SchemaHeaderField extends ObjectField { } [ToScriptString]() { - return `invalid`; + return `header(${this.heading},${this.type}})`; } [ToString]() { return `SchemaHeaderField`; |