diff options
author | Andy Rickert <andrew_rickert@brown.edu> | 2020-08-12 18:20:32 -0400 |
---|---|---|
committer | Andy Rickert <andrew_rickert@brown.edu> | 2020-08-12 18:20:32 -0400 |
commit | ee14178da93fbe18ef3ff8f7bc79f7f2b6323514 (patch) | |
tree | 419c4f3178dd4cfc3ef5d19b4097a35e1a23a981 /src | |
parent | a37cdb9d11271a28b0d9265fcb598747a6a0074a (diff) | |
parent | 92b00652421465ea5f801102dd6f182dc1bf8d81 (diff) |
final ui fixups
Diffstat (limited to 'src')
-rw-r--r-- | src/client/DocServer.ts | 126 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSchemaCells.tsx | 42 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSchemaHeaders.tsx | 45 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSchemaView.tsx | 10 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSubView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/collections/SchemaTable.tsx | 14 | ||||
-rw-r--r-- | src/client/views/nodes/FieldView.tsx | 8 | ||||
-rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 10 | ||||
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 6 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 4 | ||||
-rw-r--r-- | src/client/views/search/SearchBox.tsx | 64 |
11 files changed, 156 insertions, 179 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index a36dfaf69..63e01bc5b 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -330,72 +330,76 @@ export namespace DocServer { } } - // 2) synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string) - // fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of - // the fields have been returned from the server - const getSerializedFields: Promise<any> = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds); - - // 3) when the serialized RefFields have been received, go head and begin deserializing them into objects. - // Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all - // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. - const deserializeFields = getSerializedFields.then(async fields => { - const fieldMap: { [id: string]: RefField } = {}; - const proms: Promise<void>[] = []; - runInAction(() => { - for (const field of fields) { - if (field !== undefined && field !== null && !_cache[field.id]) { - // deserialize - const cached = _cache[field.id]; - if (!cached) { - const prom = SerializationHelper.Deserialize(field).then(deserialized => { - fieldMap[field.id] = deserialized; - - //overwrite or delete any promises (that we inserted as flags - // to indicate that the field was in the process of being fetched). Now everything - // should be an actual value within or entirely absent from the cache. - if (deserialized !== undefined) { - _cache[field.id] = deserialized; - } else { - delete _cache[field.id]; - } - return deserialized; - }); - // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) - // we set the value at the field's id to a promise that will resolve to the field. - // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). - // The mapping in the .then call ensures that when other callers await these promises, they'll - // get the resolved field - _cache[field.id] = prom; - - // adds to a list of promises that will be awaited asynchronously - proms.push(prom); - } else if (cached instanceof Promise) { - proms.push(cached as any); + if (requestedIds.length) { + + // 2) synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string) + // fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of + // the fields have been returned from the server + const getSerializedFields: Promise<any> = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds); + + // 3) when the serialized RefFields have been received, go head and begin deserializing them into objects. + // Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all + // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. + const deserializeFields = getSerializedFields.then(async fields => { + const fieldMap: { [id: string]: RefField } = {}; + const proms: Promise<void>[] = []; + runInAction(() => { + for (const field of fields) { + if (field !== undefined && field !== null && !_cache[field.id]) { + // deserialize + const cached = _cache[field.id]; + if (!cached) { + const prom = SerializationHelper.Deserialize(field).then(deserialized => { + fieldMap[field.id] = deserialized; + + //overwrite or delete any promises (that we inserted as flags + // to indicate that the field was in the process of being fetched). Now everything + // should be an actual value within or entirely absent from the cache. + if (deserialized !== undefined) { + _cache[field.id] = deserialized; + } else { + delete _cache[field.id]; + } + return deserialized; + }); + // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) + // we set the value at the field's id to a promise that will resolve to the field. + // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). + // The mapping in the .then call ensures that when other callers await these promises, they'll + // get the resolved field + _cache[field.id] = prom; + + // adds to a list of promises that will be awaited asynchronously + proms.push(prom); + } else if (cached instanceof Promise) { + proms.push(cached as any); + } + } else if (_cache[field.id] instanceof Promise) { + proms.push(_cache[field.id] as any); + (_cache[field.id] as any).then((f: any) => fieldMap[field.id] = f); + } else if (field) { + proms.push(_cache[field.id] as any); + fieldMap[field.id] = field; } - } else if (_cache[field.id] instanceof Promise) { - proms.push(_cache[field.id] as any); - (_cache[field.id] as any).then((f: any) => fieldMap[field.id] = f); - } else if (field) { - proms.push(_cache[field.id] as any); - fieldMap[field.id] = field; } - } + }); + await Promise.all(proms); + return fieldMap; }); - await Promise.all(proms); - return fieldMap; - }); - // 5) at this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose - // prototype documents, if any, have also been fetched and cached. - const fields = await deserializeFields; + // 5) at this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose + // prototype documents, if any, have also been fetched and cached. + const fields = await deserializeFields; - // 6) with this confidence, we can now go through and update the cache at the ids of the fields that - // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given - // id to the soon-to-be-returned field mapping. - requestedIds.forEach(id => { - const field = fields[id]; - map[id] = field; - }); + // 6) with this confidence, we can now go through and update the cache at the ids of the fields that + // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given + // id to the soon-to-be-returned field mapping. + requestedIds.forEach(id => { + const field = fields[id]; + map[id] = field; + }); + + } // 7) those promises we encountered in the else if of 1), which represent // other callers having already submitted a request to the server for (a) document(s) diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 20ae74b44..49d75e6de 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -166,15 +166,16 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const contents = bing(); if (positions !== undefined) { - StrCast(this.props.Document._searchString) + StrCast(this.props.Document._searchString); const length = StrCast(this.props.Document._searchString).length; + const color = contents ? "black" : "grey"; - results.push(<span style={{ color: contents ? "black" : "grey" }}>{contents ? contents.slice(0, positions[0]) : "undefined"}</span>); + results.push(<span key="-1" style={{ color }}>{contents?.slice(0, positions[0])}</span>); positions.forEach((num, cur) => { - results.push(<span style={{ backgroundColor: "#FFFF00", color: contents ? "black" : "grey" }}>{contents ? contents.slice(num, num + length) : "undefined"}</span>); + results.push(<span key={"start" + cur} style={{ backgroundColor: "#FFFF00", color }}>{contents?.slice(num, num + length)}</span>); let end = 0; cur === positions.length - 1 ? end = contents.length : end = positions[cur + 1]; - results.push(<span style={{ color: contents ? "black" : "grey" }}>{contents ? contents.slice(num + length, end) : "undefined"}</span>); + results.push(<span key={"end" + cur} style={{ color }}>{contents?.slice(num + length, end)}</span>); } ); return results; @@ -227,13 +228,12 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const onItemDown = async (e: React.PointerEvent) => { if (this.props.Document._searchDoc !== undefined) { - let doc = Doc.GetProto(this.props.rowProps.original); + const doc = Doc.GetProto(this.props.rowProps.original); const aliasdoc = await SearchUtil.GetAliasesOfDocument(doc); let targetContext = undefined; if (aliasdoc.length > 0) { targetContext = Cast(aliasdoc[0].context, Doc) as Doc; } - console.log(targetContext); DocumentManager.Instance.jumpToDocument(this.props.rowProps.original, false, undefined, targetContext); } else { @@ -289,18 +289,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const positions = []; if (StrCast(this.props.Document._searchString).toLowerCase() !== "") { const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); - let term = ""; - if (cfield !== undefined) { - if (cfield.Text !== undefined) { - term = cfield.Text; - } - else if (StrCast(cfield)) { - term = StrCast(cfield); - } - else { - term = String(NumCast(cfield)); - } - } + let term = Field.toString(cfield as Field); term = term.toLowerCase(); const search = StrCast(this.props.Document._searchString).toLowerCase(); let start = term.indexOf(search); @@ -409,22 +398,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> { : this.returnHighlights(() => { const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); - if (cfield !== undefined) { - // if (typeof(cfield)===RichTextField) - const a = cfield as RichTextField; - if (a.Text !== undefined) { - return (a.Text); - } - else if (StrCast(cfield)) { - return StrCast(cfield); - } - else { - return String(NumCast(cfield)); - } - } - else { - return ""; - } + return Field.toString(cfield as Field); }, positions) } </div > diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index 0ad1c5fa1..fa260bee1 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -3,7 +3,7 @@ import { IconProp, library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../fields/Doc"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { listSpec } from "../../../fields/Schema"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { ScriptField } from "../../../fields/ScriptField"; @@ -277,10 +277,10 @@ export interface KeysDropdownProps { width?: string; docs?: Doc[]; Document: Doc; - dataDoc: Doc; + dataDoc: Doc | undefined; fieldKey: string; - ContainingCollectionDoc: Doc; - ContainingCollectionView: CollectionView; + ContainingCollectionDoc: Doc | undefined; + ContainingCollectionView: Opt<CollectionView>; active?: (outsideReaction?: boolean) => boolean; openHeader: (column: any, screenx: number, screeny: number) => void; col: SchemaHeaderField; @@ -310,7 +310,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { onKeyDown = (e: React.KeyboardEvent): void => { if (e.key === "Enter") { let keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); - let blockedkeys = ["_scrollTop", "customTitle", "limitHeight", "proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"]; + const blockedkeys = ["_scrollTop", "customTitle", "limitHeight", "proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"]; keyOptions = keyOptions.filter(n => !blockedkeys.includes(n)); if (keyOptions.length) { this.onSelect(keyOptions[0]); @@ -362,7 +362,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { const exactFound = keyOptions.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1 || this.props.existingKeys.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1; - let blockedkeys = ["proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"]; + const blockedkeys = ["proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"]; keyOptions = keyOptions.filter(n => !blockedkeys.includes(n)); const options = keyOptions.map(key => { @@ -391,7 +391,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { } else { if (this.props.docs) { - let panesize = this.props.docs.length * 30; + const panesize = this.props.docs.length * 30; options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8; } else { @@ -401,7 +401,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { return options; } - docSafe: Doc[] = [] + docSafe: Doc[] = []; @action renderFilterOptions = (): JSX.Element[] | JSX.Element => { @@ -409,25 +409,32 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { this.defaultMenuHeight = 0; return <></>; } + let keyOptions: string[] = []; + const colpos = this._searchTerm.indexOf(":"); + const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length); if (this.docSafe.length === 0) { - this.docSafe = DocListCast(this.props.dataDoc![this.props.fieldKey!]); + this.docSafe = DocListCast(this.props.dataDoc![this.props.fieldKey]); } - let docs = this.docSafe; + const docs = this.docSafe; docs.forEach((doc) => { const key = StrCast(doc[this._key]); - if (keyOptions.includes(key) === false) { + if (keyOptions.includes(key) === false && key.includes(temp)) { keyOptions.push(key); } }); - let filters = Cast(this.props.Document!._docFilters, listSpec("string")); + const 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]); } } + if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) { + this.props.col.setColor("rgb(241, 239, 235)"); + } + const options = keyOptions.map(key => { //Doc.setDocFilter(this.props.Document!, this._key, key, undefined); let bool = false; @@ -442,8 +449,9 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { }} > <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); + 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 ? this.props.col.setColor("green") : ""; + 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 }}> @@ -457,7 +465,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { } else { if (this.props.docs) { - let panesize = this.props.docs.length * 30; + const panesize = this.props.docs.length * 30; options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8; } else { @@ -484,14 +492,13 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { @observable filterOpen: boolean | undefined = undefined; render() { - console.log(this._isOpen, this._key, this._searchTerm); return ( <div style={{ display: "flex" }}> <FontAwesomeIcon onClick={e => { 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" }} /> - <FontAwesomeIcon icon={fa.faSearchMinus} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} onClick={e => { + {/* <FontAwesomeIcon icon={fa.faSearchMinus} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} onClick={e => { runInAction(() => { this._isOpen === undefined ? this._isOpen = true : this._isOpen = !this._isOpen }) - }} /> + }} /> */} <div className="keys-dropdown" style={{ zIndex: 10, width: this.props.width, maxWidth: this.props.width }}> <input className="keys-search" style={{ width: "100%" }} @@ -505,7 +512,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { width: this.props.width, maxWidth: this.props.width, height: "auto", }} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}> - {this._key === this._searchTerm ? this.renderFilterOptions() : this.renderOptions()} + {this._searchTerm.includes(":") ? this.renderFilterOptions() : this.renderOptions()} </div> </div > </div> diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index ef7b1c4f7..1de881f6d 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -74,11 +74,11 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { let searchx = 0; let searchy = 0; if (this.props.Document._searchDoc !== undefined) { - let el = document.getElementsByClassName("collectionSchemaView-searchContainer")[0]; + const el = document.getElementsByClassName("collectionSchemaView-searchContainer")[0]; if (el !== undefined) { - let rect = el.getBoundingClientRect(); + const rect = el.getBoundingClientRect(); searchx = rect.x; - searchy = rect.y + searchy = rect.y; } } const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx; @@ -420,7 +420,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { super.CreateDropTarget(ele); } - isFocused = (doc: Doc): boolean => this.props.isSelected() && doc === this._focusedTable; + isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable; @action setFocused = (doc: Doc) => this._focusedTable = doc; @@ -631,7 +631,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { style={{ overflow: this.props.overflow === true ? "scroll" : undefined, pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined, - width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", + width: name === "collectionSchemaView-searchContainer" ? "auto" : this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", }} > <div className="collectionSchemaView-tableContainer" style={{ backgroundColor: "white", width: `calc(100% - ${this.previewWidth()}px)` }} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index a43685a5e..3f2ad47a5 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -126,7 +126,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); - let childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; let searchDocs = DocListCast(this.props.Document._searchDocs); @@ -137,7 +137,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: docsforFilter = []; const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); console.log(searchDocs); - searchDocs = DocUtils.FilterDocs(searchDocs, this.docFilters(), docRangeFilters, viewSpecScript) + searchDocs = DocUtils.FilterDocs(searchDocs, this.docFilters(), docRangeFilters, viewSpecScript); console.log(this.docFilters()); console.log(searchDocs); childDocs.forEach((d) => { @@ -240,6 +240,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document); added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse); } else added = res; + !added && alert("You don't have permission to perform this move"); + e.stopPropagation(); } else { ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData }); added = this.addDocument(docDragData.droppedDocuments); diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx index 2866bb497..8d87a3ba2 100644 --- a/src/client/views/collections/SchemaTable.tsx +++ b/src/client/views/collections/SchemaTable.tsx @@ -63,12 +63,12 @@ export interface SchemaTableProps { addDocument: (document: Doc | Doc[]) => boolean; moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; ScreenToLocalTransform: () => Transform; - active: (outsideReaction: boolean) => boolean; + active: (outsideReaction: boolean | undefined) => boolean; onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void; addDocTab: (document: Doc, where: string) => boolean; pinToPres: (document: Doc) => void; isSelected: (outsideReaction?: boolean) => boolean; - isFocused: (document: Doc) => boolean; + isFocused: (document: Doc, outsideReaction: boolean) => boolean; setFocused: (document: Doc) => void; setPreviewDoc: (document: Doc) => void; columns: SchemaHeaderField[]; @@ -155,7 +155,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); const columns: Column<Doc>[] = []; - const tableIsFocused = this.props.isFocused(this.props.Document); + const tableIsFocused = this.props.isFocused(this.props.Document, false); const focusedRow = this._focusedCell.row; const focusedCol = this._focusedCell.col; const isEditable = !this.props.headerIsEditing; @@ -177,7 +177,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } ); } - this.props.active + 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" : @@ -342,7 +342,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { addDoc: this.tableAddDoc, removeDoc: this.props.deleteDocument, rowInfo, - rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document), + rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true), textWrapRow: this.toggleTextWrapRow, rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1, dropAction: StrCast(this.props.Document.childDropAction), @@ -356,7 +356,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { const row = rowInfo.index; //@ts-ignore const col = this.columns.map(c => c.heading).indexOf(column!.id); - const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document); + const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true); // TODO: editing border doesn't work :( return { style: { @@ -376,7 +376,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { @action onKeyDown = (e: KeyboardEvent): void => { - if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document)) {// && this.props.isSelected(true)) { + if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {// && this.props.isSelected(true)) { const direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : ""; this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index fe0ea80d5..3f2a590ab 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -5,7 +5,7 @@ import { DateField } from "../../../fields/DateField"; import { Doc, FieldResult, Opt, Field } from "../../../fields/Doc"; import { List } from "../../../fields/List"; import { ScriptField } from "../../../fields/ScriptField"; -import { AudioField, VideoField } from "../../../fields/URLField"; +import { AudioField, VideoField, WebField } from "../../../fields/URLField"; import { Transform } from "../../util/Transform"; import { CollectionView } from "../collections/CollectionView"; import { AudioBox } from "./AudioBox"; @@ -135,9 +135,9 @@ export class FieldView extends React.Component<FieldViewProps> { return <div> {field.map(f => Field.toString(f)).join(", ")} </div>; } // bcz: this belongs here, but it doesn't render well so taking it out for now - // else if (field instanceof HtmlField) { - // return <WebBox {...this.props} /> - // } + else if (field instanceof WebField) { + return <p>{Field.toString(field.url.href)}</p>; + } else if (!(field instanceof Promise)) { return <p>{Field.toString(field)}</p>; } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 2fdb87e63..255a1b2d0 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -60,19 +60,19 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum if (href) { const pathCorrectionTest = /upload\_[a-z0-9]{32}.(.*)/g; const matches = pathCorrectionTest.exec(href); - console.log("\nHere's the { url } being fed into the outer regex:"); - console.log(href); - console.log("And here's the 'properPath' build from the captured filename:\n"); + // console.log("\nHere's the { url } being fed into the outer regex:"); + // console.log(href); + // console.log("And here's the 'properPath' build from the captured filename:\n"); if (matches !== null && href.startsWith(window.location.origin)) { const properPath = Utils.prepend(`/files/pdfs/${matches[0]}`); - console.log(properPath); + //console.log(properPath); if (!properPath.includes(href)) { console.log(`The two (url and proper path) were not equal`); const proto = Doc.GetProto(Document); proto[this.props.fieldKey] = new PdfField(properPath); proto[backup] = href; } else { - console.log(`The two (url and proper path) were equal`); + //console.log(`The two (url and proper path) were equal`); } } else { console.log("Outer matches was null!"); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index f37909e6e..1393e7868 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -454,9 +454,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />; } else if (field instanceof WebField) { const url = this.layoutDoc.UseCors ? Utils.CorsProxy(field.url.href) : field.url.href; - view = <iframe className="webBox-iframe" enable-annotation={true} ref={this._iframeRef} src={url} onLoad={this.iframeLoaded} />; + view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={this._iframeRef} src={url} onLoad={this.iframeLoaded} + // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page + sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />; } else { - view = <iframe className="webBox-iframe" enable-annotation={true} ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} />; + view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} />; } return view; } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index cca86adb5..f9ae78778 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -399,10 +399,10 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @action search = (searchString: string, fwd: boolean, clear: boolean = false) => { if (clear) { - this._pdfViewer.findController.executeCommand('reset', { query: "" }); + this._pdfViewer?.findController.executeCommand('reset', { query: "" }); } else if (!searchString) { fwd ? this.nextAnnotation() : this.prevAnnotation(); - } else if (this._pdfViewer.pageViewsReady) { + } else if (this._pdfViewer?.pageViewsReady) { this._pdfViewer.findController.executeCommand('findagain', { caseSensitive: false, findPrevious: !fwd, diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index da3bafa95..35b383a27 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -54,6 +54,8 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc @observable private _visibleElements: JSX.Element[] = []; @observable private _visibleDocuments: Doc[] = []; + static NUM_SEARCH_RESULTS_PER_PAGE = 25; + private _resultsSet = new Map<Doc, number>(); private _resultsRef = React.createRef<HTMLDivElement>(); public inputRef = React.createRef<HTMLInputElement>(); @@ -244,11 +246,11 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc //alters the query so it looks in the correct fields //if this is true, th`en not all of the field boxes are checked //TODO: data - let initialfilters = Cast(this.props.Document._docFilters, listSpec("string"), []); + const initialfilters = Cast(this.props.Document._docFilters, listSpec("string"), []); - let type: string[] = []; + const type: string[] = []; - let filters: string[] = [] + const filters: string[] = []; for (let i = 0; i < initialfilters.length; i = i + 3) { if (initialfilters[i + 2] !== undefined) { @@ -258,7 +260,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } } - let finalfilters: { [key: string]: string[] } = {}; + const finalfilters: { [key: string]: string[] } = {}; for (let i = 0; i < filters.length; i = i + 3) { if (finalfilters[filters[i]] !== undefined) { @@ -269,26 +271,26 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } } - for (var key in finalfilters) { - let values = finalfilters[key]; + for (const key in finalfilters) { + const values = finalfilters[key]; if (values.length === 1) { - let mod = "_t:" + const 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 + "\"") + 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 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 + "\"") + i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\""); }); - let v = "(" + newWords.join(" ") + ")"; + const v = "(" + newWords.join(" ") + ")"; if (i === 0) { query = `(${query}) AND (${v}`; if (values.length === 1) { @@ -336,7 +338,6 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc query = query.replace(/-\s+/g, ''); query = query.replace(/-/g, ""); - console.log(query); return query; } @@ -347,7 +348,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc @action filterDocsByType(docs: Doc[]) { const finalDocs: Doc[] = []; - const blockedTypes: string[] = ["preselement", "docholder", "search", "searchitem", "script", "fonticonbox", "button", "label"]; + const blockedTypes: string[] = [DocumentType.PRESELEMENT, DocumentType.DOCHOLDER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; docs.forEach(doc => { const layoutresult = Cast(doc.type, "string"); if (layoutresult && !blockedTypes.includes(layoutresult)) { @@ -618,18 +619,16 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc private NumResults = 50; 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: 10000000, 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; } - console.log(res.numFound); const highlighting = res.highlighting || {}; const highlightList = res.docs.map(doc => highlighting[doc[Id]]); const lines = new Map<string, string[]>(); @@ -641,18 +640,15 @@ 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)) { // } 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); @@ -738,21 +734,15 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this._curRequest = undefined; } - @observable _pageStart: number = 0 - @observable _pageCount: number = 5; + @observable _pageStart: number = 0; + @observable _pageCount: number = SearchBox.NUM_SEARCH_RESULTS_PER_PAGE; @observable children: number = 0; @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 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", "type"]); + const headers = new Set<string>(["title", "author", "text", "type", "data", "*lastModified", "context"]); // if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { // if (this.noresults === "") { // this.noresults = "No search results :("; @@ -775,7 +765,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this._isSorted = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults); } - let max = this._pageStart + this._pageCount; + let max = this.NumResults; 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 @@ -811,7 +801,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i); headers.forEach(header => { if (oldSchemaHeaders.includes(header) === false) { - newSchemaHeaders.push(new SchemaHeaderField(header, "#f1efeb")) + newSchemaHeaders.push(new SchemaHeaderField(header, "#f1efeb")); } }); this.headercount = newSchemaHeaders.length; @@ -846,7 +836,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc @computed get searchItemTemplate() { return Cast(Doc.UserDoc().searchItemTemplate, Doc, null); } - @computed get viewspec() { return Cast(this.props.Document._docFilters, listSpec("string"), []) } + @computed get viewspec() { return Cast(this.props.Document._docFilters, listSpec("string"), []); } getTransform = () => { return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight @@ -865,7 +855,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc @observable filter = false; @action newpage() { - this._pageStart += 5; + this._pageStart += SearchBox.NUM_SEARCH_RESULTS_PER_PAGE; this.dataDoc[this.fieldKey] = new List<Doc>([]); this.resultsScrolled(); } @@ -874,7 +864,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this.props.Document._searchDoc = true; const cols = Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).length; let length = 0; - cols > 5 ? length = 1076 : length = cols * 205 + 51; + length = cols * 205 + 51; let height = 0; const rows = this.children; height = 31 + 31 * 6; @@ -884,9 +874,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc <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" - style={{ cursor: "hand", color: "black", padding: 1, left: 35, position: "relative" }} /> */} - <FontAwesomeIcon onPointerDown={() => { this.newpage(); }} 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" }} /> </div></Tooltip> <div style={{ cursor: "default", left: 250, position: "relative", }}> @@ -1008,7 +996,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); @@ -1043,7 +1031,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc removeDocument={returnFalse} PanelHeight={this.open === true ? () => height : () => 0} PanelWidth={this.open === true ? () => length : () => 0} - overflow={cols > 5 || rows > 6 ? true : false} + overflow={length > screen.width || rows > 6 ? true : false} focus={this.selectElement} ScreenToLocalTransform={Transform.Identity} /> |