diff options
Diffstat (limited to 'src')
25 files changed, 557 insertions, 422 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 0678eaf5a..323908302 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -58,15 +58,21 @@ export function SetupDrag( return onItemDown; } +function moveLinkedDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { + const document = SelectionManager.SelectedDocuments()[0]; + document.props.removeDocument && document.props.removeDocument(doc); + addDocument(doc); + return true; +} + export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: number, linkDoc: Doc, sourceDoc: Doc) { let draggeddoc = LinkManager.Instance.getOppositeAnchor(linkDoc, sourceDoc); - if (draggeddoc) { let moddrag = await Cast(draggeddoc.annotationOn, Doc); let dragdocs = moddrag ? [moddrag] : [draggeddoc]; let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs); - dragData.dropAction = "alias" as dropActionType; - DragManager.StartLinkedDocumentDrag([dragEle], sourceDoc, dragData, x, y, { + dragData.moveDocument = moveLinkedDocument; + DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, { handlers: { dragComplete: action(emptyFunction), }, @@ -82,16 +88,10 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n if (srcTarg) { let linkDocs = LinkManager.Instance.getAllRelatedLinks(srcTarg); if (linkDocs) { - linkDocs.forEach((doc) => { - let opp = LinkManager.Instance.getOppositeAnchor(doc, sourceDoc); - if (opp) { - draggedDocs.push(opp); - } - } - ); - // draggedDocs = linkDocs.map(link => { - // return LinkManager.Instance.getOppositeAnchor(link, sourceDoc); - // }); + draggedDocs = linkDocs.map(link => { + let opp = LinkManager.Instance.getOppositeAnchor(link, sourceDoc); + if (opp) return opp; + }) as Doc[]; } } if (draggedDocs.length) { @@ -102,7 +102,8 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n } let dragdocs = moddrag.length ? moddrag : draggedDocs; let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs); - DragManager.StartLinkedDocumentDrag([dragEle], sourceDoc, dragData, x, y, { + dragData.moveDocument = moveLinkedDocument; + DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, { handlers: { dragComplete: action(emptyFunction), }, @@ -111,6 +112,7 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n } } + export namespace DragManager { export function Root() { const root = document.getElementById("root"); @@ -236,15 +238,14 @@ export namespace DragManager { }); } - export function StartLinkedDocumentDrag(eles: HTMLElement[], sourceDoc: Doc, dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { + export function StartLinkedDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { + dragData.moveDocument = moveLinkedDocument; runInAction(() => StartDragFunctions.map(func => func())); StartDrag(eles, dragData, downX, downY, options, (dropData: { [id: string]: any }) => { - // dropData.droppedDocuments = let droppedDocuments: Doc[] = dragData.draggedDocuments.reduce((droppedDocs: Doc[], d) => { let dvs = DocumentManager.Instance.getDocumentViews(d); - if (dvs.length) { let inContext = dvs.filter(dv => dv.props.ContainingCollectionView === SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView); if (inContext.length) { diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 338628960..674eeb1a8 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -14,11 +14,11 @@ export namespace SearchUtil { numFound: number; } - export function Search(query: string, returnDocs: true): Promise<DocSearchResult>; - export function Search(query: string, returnDocs: false): Promise<IdSearchResult>; - export async function Search(query: string, returnDocs: boolean) { + export function Search(query: string, returnDocs: true, start?: number, count?: number): Promise<DocSearchResult>; + export function Search(query: string, returnDocs: false, start?: number, count?: number): Promise<IdSearchResult>; + export async function Search(query: string, returnDocs: boolean, start?: number, rows?: number) { const result: IdSearchResult = JSON.parse(await rp.get(DocServer.prepend("/search"), { - qs: { query } + qs: { query, start, rows } })); if (!returnDocs) { return result; diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 17ae407c4..dca539f3b 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -1,5 +1,6 @@ import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr"; import { Field } from "../../new_fields/Doc"; +import { ClientUtils } from "./ClientUtils"; export namespace SerializationHelper { let serializing: number = 0; @@ -38,7 +39,12 @@ export namespace SerializationHelper { serializing += 1; if (!obj.__type) { - throw Error("No property 'type' found in JSON."); + if (ClientUtils.RELEASE) { + console.warn("No property 'type' found in JSON."); + return undefined; + } else { + throw Error("No property 'type' found in JSON."); + } } if (!(obj.__type in serializationTypes)) { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3bed62ef8..398974cb6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -453,7 +453,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) { document.removeEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp); - DragLinksAsDocuments(this._linkButton.current, e.x, e.y, SelectionManager.SelectedDocuments()[0].props.Document); } e.stopPropagation(); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index d3c689571..f378b6c0c 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -107,15 +107,25 @@ export default class KeyManager { }; }); - private ctrl = action((keyname: string) => { + private ctrl = action((keyname: string, e: KeyboardEvent) => { let stopPropagation = true; let preventDefault = true; switch (keyname) { case "arrowright": + if (document.activeElement) { + if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") { + return { stopPropagation: false, preventDefault: false }; + } + } MainView.Instance.mainFreeform && CollectionDockingView.Instance.AddRightSplit(MainView.Instance.mainFreeform, undefined); break; case "arrowleft": + if (document.activeElement) { + if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") { + return { stopPropagation: false, preventDefault: false }; + } + } MainView.Instance.mainFreeform && CollectionDockingView.Instance.CloseRightSplit(MainView.Instance.mainFreeform); break; case "f": diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 614b9cce7..76cc7e6d3 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -336,7 +336,7 @@ export class MainView extends React.Component { if (!(sidebar instanceof Doc)) return (null); return <div> <div className="mainView-libraryHandle" - style={{ left: `${this.flyoutWidth - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }} + style={{ cursor: "ew-resize", left: `${this.flyoutWidth - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }} onPointerDown={this.onPointerDown}> <span title="library View Dragger" style={{ width: "100%", height: "100%", position: "absolute" }} /> </div> diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index ef68c4489..e7a5475ed 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -33,10 +33,14 @@ export class PreviewCursor extends React.Component<{}> { onKeyPress = (e: KeyboardEvent) => { // Mixing events between React and Native is finicky. In FormattedTextBox, we set the // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore - // the keyPress here. + // the keyPress here. 112- //if not these keys, make a textbox if preview cursor is active! - if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && + if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && e.key !== "CapsLock" && e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" && + e.key !== "Insert" && e.key !== "Home" && e.key !== "End" && e.key !== "PageUp" && e.key !== "PageDown" && + e.key !== "NumLock" && + (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys + !e.key.startsWith("Arrow") && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { if (!e.ctrlKey && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e); diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index c97443785..8ab360984 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -30,13 +30,13 @@ export class CollectionPDFView extends React.Component<FieldViewProps> { () => NumCast(this.props.Document.scrollY), () => { // let transform = this.props.ScreenToLocalTransform(); - if (this._buttonTray.current) { - // console.log(this._buttonTray.current.offsetHeight); - // console.log(NumCast(this.props.Document.scrollY)); - let scale = this.nativeWidth() / this.props.Document[WidthSym](); - this.props.Document.panY = NumCast(this.props.Document.scrollY); - // console.log(scale); - } + // if (this._buttonTray.current) { + // console.log(this._buttonTray.current.offsetHeight); + // console.log(NumCast(this.props.Document.scrollY)); + let scale = this.nativeWidth() / this.props.Document[WidthSym](); + this.props.Document.panY = NumCast(this.props.Document.scrollY); + // console.log(scale); + // } // console.log(this.props.Document[HeightSym]()); }, { fireImmediately: true } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fcb38487d..2c1813482 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -421,6 +421,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { + if (de.data instanceof DragManager.AnnotationDragData) { + e.stopPropagation(); + let annotationDoc = de.data.annotationDocument; + annotationDoc.linkedToDoc = true; + let targetDoc = this.props.Document; + let annotations = await DocListCastAsync(annotationDoc.annotations); + if (annotations) { + annotations.forEach(anno => { + anno.target = targetDoc; + }); + } + } if (de.data instanceof DragManager.LinkDragData) { let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.Document; diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss index 3c49c2212..fc5f2410c 100644 --- a/src/client/views/nodes/LinkEditor.scss +++ b/src/client/views/nodes/LinkEditor.scss @@ -47,7 +47,7 @@ border-radius: 3px; .linkEditor-group-row { - // display: flex; + display: flex; margin-bottom: 3px; .linkEditor-group-row-label { @@ -108,6 +108,28 @@ &:hover { background-color: lightgray; } + + &.onDown { + background-color: gray; + } + } +} + +.linkEditor-typeButton { + background-color: transparent; + color: $dark-color; + width: 100%; + height: 20px; + padding: 0 3px; + padding-bottom: 2px; + text-align: left; + text-transform: none; + letter-spacing: normal; + font-size: 12px; + font-weight: bold; + + &:hover { + background-color: $light-color; } } @@ -115,19 +137,9 @@ height: 20px; display: flex; justify-content: flex-end; + margin-top: 5px; .linkEditor-button { margin-left: 6px; } - - // .linkEditor-groupOpts { - // width: calc(20% - 3px); - // height: 20px; - // padding: 0; - // font-size: 10px; - - // &:disabled { - // background-color: gray; - // } - // } }
\ No newline at end of file diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx index 7200e5aa0..afde85b69 100644 --- a/src/client/views/nodes/LinkEditor.tsx +++ b/src/client/views/nodes/LinkEditor.tsx @@ -22,11 +22,9 @@ interface GroupTypesDropdownProps { // this dropdown could be generalized @observer class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> { - @observable private _searchTerm: string = ""; + @observable private _searchTerm: string = this.props.groupType; @observable private _groupType: string = this.props.groupType; - - @action setSearchTerm = (value: string): void => { this._searchTerm = value; }; - @action setGroupType = (value: string): void => { this._groupType = value; }; + @observable private _isEditing: boolean = false; @action createGroup = (groupType: string): void => { @@ -34,9 +32,52 @@ class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> { LinkManager.Instance.addGroupType(groupType); } + @action onChange = (val: string): void => { - this.setSearchTerm(val); - this.setGroupType(val); + this._searchTerm = val; + this._groupType = val; + this._isEditing = true; + } + + @action + onKeyDown = (e: React.KeyboardEvent): void => { + if (e.key === "Enter") { + let allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes()); + let groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); + let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()); + + if (exactFound > -1) { + let groupType = groupOptions[exactFound]; + this.props.setGroupType(groupType); + this._groupType = groupType; + } else { + this.createGroup(this._searchTerm); + this._groupType = this._searchTerm; + } + + this._searchTerm = this._groupType; + this._isEditing = false; + } + } + + @action + onOptionClick = (value: string, createNew: boolean): void => { + if (createNew) { + this.createGroup(this._searchTerm); + this._groupType = this._searchTerm; + + } else { + this.props.setGroupType(value); + this._groupType = value; + + } + this._searchTerm = this._groupType; + this._isEditing = false; + } + + @action + onButtonPointerDown = (): void => { + this._isEditing = true; } renderOptions = (): JSX.Element[] | JSX.Element => { @@ -47,29 +88,35 @@ class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> { let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()) > -1; let options = groupOptions.map(groupType => { - return <div key={groupType} className="linkEditor-option" - onClick={() => { this.props.setGroupType(groupType); this.setGroupType(groupType); this.setSearchTerm(""); }}>{groupType}</div>; + let ref = React.createRef<HTMLDivElement>(); + return <div key={groupType} ref={ref} className="linkEditor-option" + onClick={() => this.onOptionClick(groupType, false)}>{groupType}</div>; }); // if search term does not already exist as a group type, give option to create new group type if (!exactFound && this._searchTerm !== "") { - options.push(<div key={""} className="linkEditor-option" - onClick={() => { this.createGroup(this._searchTerm); this.setGroupType(this._searchTerm); this.setSearchTerm(""); }}>Define new "{this._searchTerm}" relationship</div>); + let ref = React.createRef<HTMLDivElement>(); + options.push(<div key={""} ref={ref} className="linkEditor-option" + onClick={() => this.onOptionClick(this._searchTerm, true)}>Define new "{this._searchTerm}" relationship</div>); } return options; } render() { - return ( - <div className="linkEditor-dropdown"> - <input type="text" value={this._groupType} placeholder="Search for or create a new group" - onChange={e => this.onChange(e.target.value)}></input> - <div className="linkEditor-options-wrapper"> - {this.renderOptions()} - </div> - </div > - ); + if (this._isEditing || this._groupType === "") { + return ( + <div className="linkEditor-dropdown"> + <input type="text" value={this._groupType} placeholder="Search for or create a new group" + onChange={e => this.onChange(e.target.value)} onKeyDown={this.onKeyDown} autoFocus></input> + <div className="linkEditor-options-wrapper"> + {this.renderOptions()} + </div> + </div > + ); + } else { + return <button className="linkEditor-typeButton" onClick={() => this.onButtonPointerDown()}>{this._groupType}</button>; + } } } @@ -276,7 +323,7 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> { } return ( <div className="linkEditor-group"> - <div className="linkEditor-group-row"> + <div className="linkEditor-group-row "> <p className="linkEditor-group-row-label">type:</p> <GroupTypesDropdown groupType={groupType} setGroupType={this.setGroupType} /> </div> diff --git a/src/client/views/nodes/LinkMenuGroup.tsx b/src/client/views/nodes/LinkMenuGroup.tsx index 767f2250b..ae97bed2f 100644 --- a/src/client/views/nodes/LinkMenuGroup.tsx +++ b/src/client/views/nodes/LinkMenuGroup.tsx @@ -12,6 +12,8 @@ import { DragLinksAsDocuments, DragManager, SetupDrag } from "../../util/DragMan import { emptyFunction } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { UndoManager } from "../../util/UndoManager"; +import { StrCast } from "../../../new_fields/Types"; interface LinkMenuGroupProps { sourceDoc: Doc; @@ -40,29 +42,27 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { e.stopPropagation(); } + onLinkButtonMoved = async (e: PointerEvent) => { - if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) { - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); + UndoManager.RunInBatch(() => { + if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) { + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); - let draggedDocs: Doc[] = []; - this.props.group.forEach( - (doc: Doc) => { - let opp = LinkManager.Instance.getOppositeAnchor(doc, this.props.sourceDoc); - if (opp) { - draggedDocs.push(opp); - } - } - ); - let dragData = new DragManager.DocumentDragData(draggedDocs, draggedDocs.map(d => undefined)); + let draggedDocs = this.props.group.map(linkDoc => { + let opp = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc); + if (opp) return opp; + }) as Doc[]; + let dragData = new DragManager.DocumentDragData(draggedDocs, draggedDocs.map(d => undefined)); - DragManager.StartLinkedDocumentDrag([this._drag.current], this.props.sourceDoc, dragData, e.x, e.y, { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } + DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, { + handlers: { + dragComplete: action(emptyFunction), + }, + hideSource: false + }); + } + }, "drag links"); e.stopPropagation(); } @@ -80,7 +80,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { render() { let groupItems = this.props.group.map(linkDoc => { let destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc); - if (destination) { + if (destination && this.props.sourceDoc) { return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]} groupType={this.props.groupType} linkDoc={linkDoc} sourceDoc={this.props.sourceDoc} destinationDoc={destination} showEditor={this.props.showEditor} />; } diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss index a4624b1f6..b06d19c53 100644 --- a/src/client/views/pdf/PDFMenu.scss +++ b/src/client/views/pdf/PDFMenu.scss @@ -21,6 +21,10 @@ .pdfMenu-dragger { height: 100%; transition: width .2s; + background-image: url("https://logodix.com/logo/1020374.png"); + background-size: 90% 100%; + background-repeat: no-repeat; + background-position: left center; } .pdfMenu-addTag { diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index feaa1f652..27c2a8f1a 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -18,7 +18,7 @@ export default class PDFMenu extends React.Component { @observable private _transitionDelay: string = ""; - StartDrag: (e: PointerEvent, ele: HTMLDivElement) => void = emptyFunction; + StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction; Highlight: (d: Doc | undefined, color: string | undefined) => void = emptyFunction; Delete: () => void = emptyFunction; Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction; @@ -34,7 +34,7 @@ export default class PDFMenu extends React.Component { private _offsetY: number = 0; private _offsetX: number = 0; private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); - private _commentCont: React.RefObject<HTMLDivElement> = React.createRef(); + private _commentCont = React.createRef<HTMLButtonElement>(); private _snippetButton: React.RefObject<HTMLButtonElement> = React.createRef(); private _dragging: boolean = false; @observable private _keyValue: string = ""; @@ -238,24 +238,24 @@ export default class PDFMenu extends React.Component { render() { let buttons = this.Status === "pdf" || this.Status === "snippet" ? [ - <button className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked} key="1" + <button key="1" className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}> <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /> </button>, <button className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}><FontAwesomeIcon icon="comment-alt" size="lg" key="2" /></button>, this.Status === "snippet" ? <button className="pdfMenu-button" title="Drag to Snippetize Selection" onPointerDown={this.snippetStart} ref={this._snippetButton}><FontAwesomeIcon icon="cut" size="lg" /></button> : undefined, - <button className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} key="3" + <button key="3" className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}> <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> </button> ] : [ - <button className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}><FontAwesomeIcon icon="trash-alt" size="lg" key="1" /></button>, - <button className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}><FontAwesomeIcon icon="map-pin" size="lg" key="2" /></button>, + <button key="4" className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}><FontAwesomeIcon icon="trash-alt" size="lg" key="1" /></button>, + <button key="5" className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}><FontAwesomeIcon icon="map-pin" size="lg" key="2" /></button>, <div className="pdfMenu-addTag" key="3"> <input onKeyDown={handleBackspace} onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} /> <input onKeyDown={handleBackspace} onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} /> </div>, - <button className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}><FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" key="4" /></button>, + <button key="6" className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}><FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" key="4" /></button>, ]; return ( diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 8f5a356c8..01fd1c247 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -236,12 +236,13 @@ export class Viewer extends React.Component<IViewerProps> { } } + @action makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string): Doc => { let annoDocs: Doc[] = []; let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {}); mainAnnoDoc.title = "Annotation on " + StrCast(this.props.parent.Document.title); - mainAnnoDoc.pdfDoc = this.props.parent.Document; + mainAnnoDoc.pdfDoc = this.props.parent.props.Document; let minY = Number.MAX_VALUE; this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => { for (let anno of value) { @@ -449,10 +450,6 @@ export class Viewer extends React.Component<IViewerProps> { @action search = (searchString: string) => { - if (searchString.length === 0) { - return; - } - if (this._pdfViewer._pageViewsReady) { this._pdfFindController.executeCommand('find', { diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 5ff39c867..3d8973414 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -2,7 +2,7 @@ import { observer } from "mobx-react"; import React = require("react"); import { observable, action, runInAction, IReactionDisposer, reaction } from "mobx"; import * as Pdfjs from "pdfjs-dist"; -import { Opt, Doc, FieldResult, Field, DocListCast, WidthSym, HeightSym } from "../../../new_fields/Doc"; +import { Opt, Doc, FieldResult, Field, DocListCast, WidthSym, HeightSym, DocListCastAsync } from "../../../new_fields/Doc"; import "./PDFViewer.scss"; import "pdfjs-dist/web/pdf_viewer.css"; import { PDFBox } from "../nodes/PDFBox"; @@ -10,7 +10,7 @@ import { DragManager } from "../../util/DragManager"; import { Docs, DocUtils } from "../../documents/Documents"; import { List } from "../../../new_fields/List"; import { emptyFunction } from "../../../Utils"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; import { listSpec } from "../../../new_fields/Schema"; import { menuBar } from "prosemirror-menu"; import { AnnotationTypes, PDFViewer, scale } from "./PDFViewer"; @@ -55,6 +55,8 @@ export default class Page extends React.Component<IPageProps> { // private _curly: React.RefObject<HTMLImageElement>; private _marqueeing: boolean = false; private _reactionDisposer?: IReactionDisposer; + private _startY: number = 0; + private _startX: number = 0; constructor(props: IPageProps) { super(props); @@ -152,20 +154,30 @@ export default class Page extends React.Component<IPageProps> { * start a drag event and create or put the necessary info into the drag event. */ @action - startDrag = (e: PointerEvent, ele: HTMLDivElement): void => { + startDrag = (e: PointerEvent, ele: HTMLElement): void => { e.preventDefault(); e.stopPropagation(); let thisDoc = this.props.parent.Document; // document that this annotation is linked to let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" }); targetDoc.targetPage = this.props.page; - let annotationDoc = this.highlight(targetDoc, "red"); + let annotationDoc = this.highlight(undefined, "red"); + annotationDoc.linkedToDoc = false; // create dragData and star tdrag let dragData = new DragManager.AnnotationDragData(thisDoc, annotationDoc, targetDoc); if (this._textLayer.current) { DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { handlers: { - dragComplete: emptyFunction, + dragComplete: async () => { + if (!(await annotationDoc.linkedToDoc)) { + let annotations = await DocListCastAsync(annotationDoc.annotations); + if (annotations) { + annotations.forEach(anno => { + anno.target = targetDoc; + }); + } + } + } }, hideSource: false }); @@ -220,8 +232,8 @@ export default class Page extends React.Component<IPageProps> { let current = this._textLayer.current; if (current) { let boundingRect = current.getBoundingClientRect(); - this._marqueeX = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width); - this._marqueeY = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height); + this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width); + this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height); } this._marqueeing = true; if (this._marquee.current) this._marquee.current.style.opacity = "0.2"; @@ -244,8 +256,12 @@ export default class Page extends React.Component<IPageProps> { if (current) { // transform positions and find the width and height to set the marquee to let boundingRect = current.getBoundingClientRect(); - this._marqueeWidth = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width) - this._marqueeX; - this._marqueeHeight = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height) - this._marqueeY; + this._marqueeWidth = ((e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width)) - this._startX; + this._marqueeHeight = ((e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height)) - this._startY; + this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth); + this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight); + this._marqueeWidth = Math.abs(this._marqueeWidth); + this._marqueeHeight = Math.abs(this._marqueeHeight); let { background, opacity, transform: transform } = this.getCurlyTransform(); if (this._marquee.current /*&& this._curly.current*/) { this._marquee.current.style.background = background; diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index c6c18f9b4..435ca86e3 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -239,10 +239,13 @@ export class FilterBox extends React.Component { @action filterDocsByType(docs: Doc[]) { + if (this._icons.length === 9) { + return docs; + } let finalDocs: Doc[] = []; docs.forEach(doc => { let layoutresult = Cast(doc.type, "string"); - if (!layoutresult || this._icons.includes(layoutresult)) { + if (layoutresult && this._icons.includes(layoutresult)) { finalDocs.push(doc); } }); @@ -260,7 +263,7 @@ export class FilterBox extends React.Component { @action.bound handleWordQueryChange = () => { this._basicWordStatus = !this._basicWordStatus; } - @action + @action.bound getBasicWordStatus() { return this._basicWordStatus; } @action.bound diff --git a/src/client/views/search/Pager.scss b/src/client/views/search/Pager.scss deleted file mode 100644 index 2b9c81b93..000000000 --- a/src/client/views/search/Pager.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import "../globalCssVariables"; - -.search-pager { - background-color: $dark-color; - border-radius: 10px; - width: 500px; - display: flex; - justify-content: center; - // margin-left: 27px; - float: right; - margin-right: 74px; - margin-left: auto; - - // flex-direction: column; - - .search-arrows { - display: flex; - justify-content: center; - margin: 10px; - width: 50%; - - .arrow { - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; - -o-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; - - .fontawesome-icon { - color: $light-color; - width: 20px; - height: 20px; - margin-right: 2px; - margin-left: 2px; - // opacity: .7; - } - } - - .pager-title { - text-align: center; - // font-size: 8px; - // margin-bottom: 10px; - color: $light-color; - // padding: 2px; - width: 40%; - } - } -}
\ No newline at end of file diff --git a/src/client/views/search/Pager.tsx b/src/client/views/search/Pager.tsx deleted file mode 100644 index 1c62773b1..000000000 --- a/src/client/views/search/Pager.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react'; -import { observer } from 'mobx-react'; -import { faArrowCircleRight, faArrowCircleLeft } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import "./Pager.scss"; -import { SearchBox } from './SearchBox'; -import { observable, action } from 'mobx'; -import { FilterBox } from './FilterBox'; - -library.add(faArrowCircleRight); -library.add(faArrowCircleLeft); - -@observer -export class Pager extends React.Component { - - @observable _leftHover: boolean = false; - @observable _rightHover: boolean = false; - - @action - onLeftClick(e: React.PointerEvent) { - FilterBox.Instance._pointerTime = e.timeStamp; - if (SearchBox.Instance._pageNum > 0) { - SearchBox.Instance._pageNum -= 1; - } - } - - @action - onRightClick(e: React.PointerEvent) { - FilterBox.Instance._pointerTime = e.timeStamp; - if (SearchBox.Instance._pageNum + 1 < SearchBox.Instance._maxNum) { - SearchBox.Instance._pageNum += 1; - } - } - - @action.bound - mouseInLeft() { - this._leftHover = true; - } - - @action.bound - mouseOutLeft() { - this._leftHover = false; - } - - @action.bound - mouseInRight() { - this._rightHover = true; - } - - @action.bound - mouseOutRight() { - this._rightHover = false; - } - - render() { - return ( - <div className="search-pager"> - <div className="search-arrows"> - <div className="arrow" - onPointerDown={this.onLeftClick} style={SearchBox.Instance._pageNum === 0 ? { opacity: .2 } : this._leftHover ? { opacity: 1 } : { opacity: .7 }} - onMouseEnter={this.mouseInLeft} onMouseOut={this.mouseOutLeft}> - <FontAwesomeIcon className="fontawesome-icon" icon={faArrowCircleLeft} /> - </div> - <div className="pager-title"> - page {SearchBox.Instance._pageNum + 1} of {SearchBox.Instance._maxNum} - </div> - <div className="arrow" - onPointerDown={this.onRightClick} style={SearchBox.Instance._pageNum === SearchBox.Instance._maxNum - 1 ? { opacity: .2 } : this._rightHover ? { opacity: 1 } : { opacity: .7 }} - onMouseEnter={this.mouseInRight} onMouseOut={this.mouseOutRight}> - <FontAwesomeIcon className="fontawesome-icon" icon={faArrowCircleRight} /> - </div> - </div> - </div> - ); - } - -}
\ No newline at end of file diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss index 2a27bbe62..324ba3063 100644 --- a/src/client/views/search/SearchBox.scss +++ b/src/client/views/search/SearchBox.scss @@ -41,12 +41,12 @@ } .searchBox-results { - margin-left: 27px; + margin-right: 142px; top: 300px; display: flex; flex-direction: column; margin-right: 72px; - height: 560px; + max-height: 560px; overflow: hidden; overflow-y: auto; diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 7dcfbe1ef..dc1d35b1c 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { observer } from 'mobx-react'; -import { observable, action, runInAction } from 'mobx'; +import { observable, action, runInAction, flow, computed } from 'mobx'; import "./SearchBox.scss"; import "./FilterBox.scss"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -16,6 +16,7 @@ import { SearchUtil } from '../../util/SearchUtil'; import { RouteStore } from '../../../server/RouteStore'; import { FilterBox } from './FilterBox'; + @observer export class SearchBox extends React.Component { @@ -23,16 +24,24 @@ export class SearchBox extends React.Component { @observable private _resultsOpen: boolean = false; @observable private _results: Doc[] = []; @observable private _openNoResults: boolean = false; - @observable public _pageNum: number = 0; - //temp - @observable public _maxNum: number = 10; + @observable private _visibleElements: JSX.Element[] = []; + + private resultsRef = React.createRef<HTMLDivElement>(); + + private _isSearch: ("search" | "placeholder" | undefined)[] = []; + private _numTotalResults = -1; + private _endIndex = -1; static Instance: SearchBox; + private _maxSearchIndex: number = 0; + private _curRequest?: Promise<any> = undefined; + constructor(props: any) { super(props); SearchBox.Instance = this; + this.resultsScrolled = this.resultsScrolled.bind(this); } @action @@ -49,15 +58,16 @@ export class SearchBox extends React.Component { onChange(e: React.ChangeEvent<HTMLInputElement>) { this._searchString = e.target.value; - if (this._searchString === "") { - this._results = []; - this._openNoResults = false; - } + this._openNoResults = false; + this._results = []; + this._visibleElements = []; + this._numTotalResults = -1; + this._endIndex = -1; + this._curRequest = undefined; + this._maxSearchIndex = 0; } - enter = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { this.submitSearch(); } - } + enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { this.submitSearch(); } } public static async convertDataUri(imageUri: string, returnedFilename: string) { try { @@ -78,38 +88,70 @@ export class SearchBox extends React.Component { @action submitSearch = async () => { - let query = this._searchString; // searchbox gets query - let results: Doc[]; - + let query = this._searchString; query = FilterBox.Instance.getFinalQuery(query); + this._results = []; + this._isSearch = []; + this._visibleElements = []; //if there is no query there should be no result if (query === "") { - results = []; + return; } else { - //gets json result into a list of documents that can be used - //these are filtered by type - results = await this.getResults(query); + this._endIndex = 12; + this._maxSearchIndex = 0; + this._numTotalResults = -1; + await this.getResults(query); } runInAction(() => { this._resultsOpen = true; - this._results = results; this._openNoResults = true; + this.resultsScrolled(); }); } - @action + getAllResults = async (query: string) => { + return SearchUtil.Search(query, true, 0, 10000000); + } + + + private lockPromise?: Promise<void>; getResults = async (query: string) => { - const { docs } = await SearchUtil.Search(query, true); - return FilterBox.Instance.filterDocsByType(docs); + 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, this._maxSearchIndex, 10).then(action((res: SearchUtil.DocSearchResult) => { + + // happens at the beginning + if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) { + this._numTotalResults = res.numFound; + } + + let filteredDocs = FilterBox.Instance.filterDocsByType(res.docs); + this._results.push(...filteredDocs); + + this._curRequest = undefined; + })); + this._maxSearchIndex += 10; + + await this._curRequest; + } + this.resultsScrolled(); + res(); + }); + return this.lockPromise; } collectionRef = React.createRef<HTMLSpanElement>(); startDragCollection = async () => { - const results = await this.getResults(FilterBox.Instance.getFinalQuery(this._searchString)); - const docs = results.map(doc => { + let res = await this.getAllResults(FilterBox.Instance.getFinalQuery(this._searchString)); + let filtered = FilterBox.Instance.filterDocsByType(res.docs); + // console.log(this._results) + const docs = filtered.map(doc => { const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); if (isProto) { return Doc.MakeDelegate(doc); @@ -142,6 +184,7 @@ export class SearchBox extends React.Component { } } return Docs.Create.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` }); + } @action.bound @@ -155,7 +198,6 @@ export class SearchBox extends React.Component { @action.bound closeSearch = () => { - console.log("closing search"); FilterBox.Instance.closeFilter(); this.closeResults(); } @@ -164,29 +206,105 @@ export class SearchBox extends React.Component { closeResults() { this._resultsOpen = false; this._results = []; + this._visibleElements = []; + this._numTotalResults = -1; + this._endIndex = -1; + this._curRequest = undefined; + } + + @action + resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => { + let scrollY = e ? e.currentTarget.scrollTop : this.resultsRef.current ? this.resultsRef.current.scrollTop : 0; + let buffer = 4; + let startIndex = Math.floor(Math.max(0, scrollY / 70 - buffer)); + let endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (560 / 70) + buffer)); + + this._endIndex = endIndex === -1 ? 12 : endIndex; + + if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { + this._visibleElements = [<div className="no-result">No Search Results</div>]; + return; + } + + if (this._numTotalResults <= this._maxSearchIndex) { + this._numTotalResults = this._results.length; + } + + // only hit right at the beginning + // visibleElements is all of the elements (even the ones you can't see) + 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._isSearch = 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._visibleElements[i] = <div className="searchBox-placeholder" key={`searchBox-placeholder-${i}`}>Loading...</div>; + } + } + else { + if (this._isSearch[i] !== "search") { + let result: Doc | undefined = undefined; + if (i >= this._results.length) { + this.getResults(this._searchString); + if (i < this._results.length) result = this._results[i]; + if (result) { + this._visibleElements[i] = <SearchItem doc={result} key={result[Id]} />; + this._isSearch[i] = "search"; + } + } + else { + result = this._results[i]; + if (result) { + this._visibleElements[i] = <SearchItem doc={result} key={result[Id]} />; + this._isSearch[i] = "search"; + } + } + } + } + } + if (this._maxSearchIndex >= this._numTotalResults) { + this._visibleElements.length = this._results.length; + this._isSearch.length = this._results.length; + } + } + + @computed + get resFull() { + console.log(this._numTotalResults) + return this._numTotalResults <= 8; + } + + @computed + get resultHeight() { + return this._numTotalResults * 70; } render() { return ( <div className="searchBox-container"> <div className="searchBox-bar"> - <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} ref={this.collectionRef}> + <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} 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..." className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} style={{ width: this._resultsOpen ? "500px" : "100px" }} /> + <button className="searchBox-barChild searchBox-submit" onClick={this.submitSearch} onPointerDown={FilterBox.Instance.stopProp}>Submit</button> <button className="searchBox-barChild searchBox-filter" onClick={FilterBox.Instance.openFilter} onPointerDown={FilterBox.Instance.stopProp}>Filter</button> </div> - <div className="searchBox-results" style={this._resultsOpen ? { display: "flex" } : { display: "none" }}> - {(this._results.length !== 0) ? ( - this._results.map(result => <SearchItem doc={result} key={result[Id]} />) - ) : - this._openNoResults ? (<div className="no-result">No Search Results</div>) : null} + <div className="searchBox-results" onScroll={this.resultsScrolled} style={{ + display: this._resultsOpen ? "flex" : "none", + height: this.resFull ? "560px" : this.resultHeight, overflow: this.resFull ? "auto" : "visible" + }} ref={this.resultsRef}> + {this._visibleElements} </div> - {/* <div style={this._results.length !== 0 ? { display: "flex" } : { display: "none" }}> - <Pager /> - </div> */} </div> ); } diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss index fa4c9cb38..24dd2eaa3 100644 --- a/src/client/views/search/SearchItem.scss +++ b/src/client/views/search/SearchItem.scss @@ -6,182 +6,206 @@ justify-content: flex-end; height: 70px; z-index: 0; +} - .search-item { - width: 500px; - background: $light-color-secondary; - border-color: $intermediate-color; - border-bottom-style: solid; - padding: 10px; - height: 70px; - z-index: 0; - display: inline-block; - - .main-search-info { - display: flex; - flex-direction: row; +.searchBox-placeholder, +.search-overview .search-item { + width: 500px; + background: $light-color-secondary; + border-color: $intermediate-color; + border-bottom-style: solid; + padding: 10px; + height: 70px; + z-index: 0; + display: inline-block; + + .main-search-info { + display: flex; + flex-direction: row; + width: 100%; + + .search-title { + text-transform: uppercase; + text-align: left; width: 100%; + font-weight: bold; + } + + .search-info { + display: flex; + justify-content: flex-end; + + .link-container.item { + margin-left: auto; + margin-right: auto; + height: 26px; + width: 26px; + border-radius: 13px; + background: $dark-color; + color: $light-color-secondary; + display: flex; + justify-content: center; + align-items: center; + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + transform-origin: top right; + overflow: hidden; + position: relative; + + .link-count { + opacity: 1; + position: absolute; + z-index: 1000; + text-align: center; + -webkit-transition: opacity 0.2s ease-in-out; + -moz-transition: opacity 0.2s ease-in-out; + -o-transition: opacity 0.2s ease-in-out; + transition: opacity 0.2s ease-in-out; + } + + .link-extended { + // display: none; + visibility: hidden; + opacity: 0; + position: relative; + z-index: 500; + overflow: hidden; + -webkit-transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s; + -moz-transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s; + -o-transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s; + transition: opacity 0.2s ease-in-out .2s, visibility 0s linear 0s; + // transition-delay: 1s; + } - .search-title { - text-transform: uppercase; - text-align: left; - width: 100%; - font-weight: bold; } - .search-info { - display: flex; - justify-content: flex-end; - - .link-container.item { - margin-left: auto; - margin-right: auto; - height: 26px; - width: 26px; - border-radius: 13px; - background: $dark-color; - color: $light-color-secondary; - display: flex; + .link-container.item:hover { + width: 70px; + } + + .link-container.item:hover .link-count { + opacity: 0; + } + + .link-container.item:hover .link-extended { + opacity: 1; + visibility: visible; + // display: inline; + } + + .icon-icons { + width: 50px + } + + .icon-live { + width: 175px; + } + + .icon-icons, + .icon-live { + height: 50px; + margin: auto; + overflow: hidden; + + .search-type { + display: inline-block; + width: 100%; + position: absolute; justify-content: center; align-items: center; - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; - -o-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; - transform-origin: top right; - overflow: hidden; position: relative; + margin-right: 5px; + } - .link-count { - opacity: 1; - position: absolute; - z-index: 1000; - text-align: center; - -webkit-transition: opacity 0.2s ease-in-out; - -moz-transition: opacity 0.2s ease-in-out; - -o-transition: opacity 0.2s ease-in-out; - transition: opacity 0.2s ease-in-out; - } + .pdfBox-cont { + overflow: hidden; - .link-extended { - opacity: 0; - position: relative; - z-index: 500; - overflow: hidden; - -webkit-transition: opacity 0.2s ease-in-out; - -moz-transition: opacity 0.2s ease-in-out; - -o-transition: opacity 0.2s ease-in-out; - transition: opacity 0.2s ease-in-out; + img { + width: 100% !important; + height: auto !important; } } - .link-container.item:hover { - width: 70px; + .search-type:hover+.search-label { + opacity: 1; } - .link-container.item:hover .link-count { + .search-label { + font-size: 10; + position: relative; + right: 0px; + text-transform: capitalize; opacity: 0; + -webkit-transition: opacity 0.2s ease-in-out; + -moz-transition: opacity 0.2s ease-in-out; + -o-transition: opacity 0.2s ease-in-out; + transition: opacity 0.2s ease-in-out; } + } - .link-container.item:hover .link-extended { - opacity: 1; - } - - .icon-icons { - width:50px - } - .icon-live { - width:175px; - } - - .icon-icons, .icon-live { - height:50px; - margin:auto; - overflow: hidden; - .search-type { - display: inline-block; - width:100%; - position: absolute; - justify-content: center; - align-items: center; - position: relative; - margin-right: 5px; - } - - .pdfBox-cont { - overflow: hidden; - - img { - width:100% !important; - height:auto !important; - } - } - .search-type:hover+.search-label { - opacity: 1; - } - - .search-label { - font-size: 10; - position: relative; - right: 0px; - text-transform: capitalize; - opacity: 0; - -webkit-transition: opacity 0.2s ease-in-out; - -moz-transition: opacity 0.2s ease-in-out; - -o-transition: opacity 0.2s ease-in-out; - transition: opacity 0.2s ease-in-out; - } - } + .icon-live:hover { + height: 175px; - .icon-live:hover { - height:175px; - .pdfBox-cont { - img { - width:100% !important; - } + .pdfBox-cont { + img { + width: 100% !important; } } } - .search-info:hover { - width:60%; - } } - } - .search-item:hover~.searchBox-instances, - .searchBox-instances:hover, - .searchBox-instances:active { - opacity: 1; - background: $lighter-alt-accent; - -webkit-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); + .search-info:hover { + width: 60%; + } } +} - .search-item:hover { - transition: all 0.2s; - background: $lighter-alt-accent; - } +.search-item:hover~.searchBox-instances, +.searchBox-instances:hover, +.searchBox-instances:active { + opacity: 1; + background: $lighter-alt-accent; + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); +} - .searchBox-instances { - float: left; - opacity: 1; - width: 150px; - transition: all 0.2s ease; - color: black; - transform-origin: top right; - -webkit-transform: scale(0); - -ms-transform: scale(0); - transform: scale(0); - } +.search-item:hover { + transition: all 0.2s; + background: $lighter-alt-accent; +} +.searchBox-instances { + float: left; + opacity: 1; + width: 150px; + transition: all 0.2s ease; + color: black; + transform-origin: top right; + -webkit-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); } + + .search-overview:hover { z-index: 1; } + +.searchBox-placeholder { + min-height: 70px; + margin-left: 150px; + text-transform: uppercase; + text-align: left; + font-weight: bold; +} + .collection { - display:flex; + display: flex; } + .collection-item { - width:35px; + width: 35px; }
\ No newline at end of file diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index cd7e31b20..16ad71d16 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -99,7 +99,9 @@ export class SearchItem extends React.Component<SearchItemProps> { @observable _selected: boolean = false; onClick = () => { - DocumentManager.Instance.jumpToDocument(this.props.doc, false); + // I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like + // DocumentManager.Instance.jumpToDocument(this.props.doc, false); + CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined); } @observable _useIcons = true; @observable _displayDim = 50; @@ -240,12 +242,12 @@ export class SearchItem extends React.Component<SearchItemProps> { <div className="search-item" onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc} id="result" onClick={this.onClick} onPointerDown={this.pointerDown} > <div className="main-search-info"> - <div title="Drag as document" onPointerDown={this.onPointerDown}> <FontAwesomeIcon icon="file" size="lg" /> </div> + <div title="Drag as document" onPointerDown={this.onPointerDown} style={{ marginRight: "7px" }}> <FontAwesomeIcon icon="file" size="lg" /> </div> <div className="search-title" id="result" >{StrCast(this.props.doc.title)}</div> <div className="search-info" style={{ width: this._useIcons ? "15%" : "400px" }}> <div className={`icon-${this._useIcons ? "icons" : "live"}`}> - <div className="search-type" >{this.DocumentIcon}</div> - <div className="search-label">{this.props.doc.type}</div> + <div className="search-type" title="Click to Preview">{this.DocumentIcon}</div> + <div className="search-label">{this.props.doc.type ? this.props.doc.type : "Other"}</div> </div> <div className="link-container item"> <div className="link-count">{this.linkCount}</div> diff --git a/src/server/Search.ts b/src/server/Search.ts index 8591f8857..98f421937 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -30,13 +30,14 @@ export class Search { } } - public async search(query: string, start: number = 0) { + public async search(query: string, start: number = 0, rows: number = 10) { try { const searchResults = JSON.parse(await rp.get(this.url + "dash/select", { qs: { q: query, fl: "id", - start: start + start, + rows, } })); const { docs, numFound } = searchResults.response; diff --git a/src/server/index.ts b/src/server/index.ts index 9cb43bf4e..58af074aa 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -144,8 +144,12 @@ app.get("/pull", (req, res) => // GETTERS app.get("/search", async (req, res) => { - let query = req.query.query || "hello"; - let results = await Search.Instance.search(query); + const { query, start, rows } = req.query; + if (query === undefined) { + res.send([]); + return; + } + let results = await Search.Instance.search(query, start, rows); res.send(results); }); |