diff options
Diffstat (limited to 'src/client/views')
| -rw-r--r-- | src/client/views/MainOverlayTextBox.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/MainView.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/SearchItem.tsx | 69 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.scss | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 97 | ||||
| -rw-r--r-- | src/client/views/search/FilterBox.scss | 56 | ||||
| -rw-r--r-- | src/client/views/search/FilterBox.tsx | 73 | ||||
| -rw-r--r-- | src/client/views/search/Pager.tsx | 78 | ||||
| -rw-r--r-- | src/client/views/search/SearchBox.scss | 5 | ||||
| -rw-r--r-- | src/client/views/search/SearchItem.tsx | 120 | ||||
| -rw-r--r-- | src/client/views/search/SelectorContextMenu.scss | 1 | ||||
| -rw-r--r-- | src/client/views/search/ToggleBar.tsx | 1 |
12 files changed, 488 insertions, 20 deletions
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 126efd11c..b2fb11afe 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -12,6 +12,7 @@ import "./MainOverlayTextBox.scss"; import { FormattedTextBox } from './nodes/FormattedTextBox'; interface MainOverlayTextBoxProps { + firstinstance?: boolean; } @observer @@ -141,7 +142,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc} isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} - ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} /> + ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} firstinstance={this.props.firstinstance} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} /> </div> </div> </div> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f5a6715e5..80b674092 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -447,6 +447,7 @@ export class MainView extends React.Component { @observable isSearchVisible = false; @action toggleSearch = () => { + // console.log("search toggling") this.isSearchVisible = !this.isSearchVisible; } @@ -460,7 +461,7 @@ export class MainView extends React.Component { {this.nodesMenu()} {this.miscButtons} <PDFMenu /> - <MainOverlayTextBox /> + <MainOverlayTextBox firstinstance={true} /> <OverlayView /> </div > ); diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx new file mode 100644 index 000000000..13e4b88f7 --- /dev/null +++ b/src/client/views/SearchItem.tsx @@ -0,0 +1,69 @@ +import React = require("react"); +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Doc } from "../../new_fields/Doc"; +import { DocumentManager } from "../util/DocumentManager"; +import { SetupDrag } from "../util/DragManager"; + + +export interface SearchProps { + doc: Doc; +} + +library.add(faCaretUp); +library.add(faObjectGroup); +library.add(faStickyNote); +library.add(faFilePdf); +library.add(faFilm); + +export class SearchItem extends React.Component<SearchProps> { + + onClick = () => { + DocumentManager.Instance.jumpToDocument(this.props.doc, false); + } + + //needs help + // @computed get layout(): string { const field = Cast(this.props.doc[fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; } + + + public static DocumentIcon(layout: string) { + let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : + layout.indexOf("ImageBox") !== -1 ? faImage : + layout.indexOf("Formatted") !== -1 ? faStickyNote : + layout.indexOf("Video") !== -1 ? faFilm : + layout.indexOf("Collection") !== -1 ? faObjectGroup : + faCaretUp; + return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />; + } + onPointerEnter = (e: React.PointerEvent) => { + this.props.doc.libraryBrush = true; + Doc.SetOnPrototype(this.props.doc, "protoBrush", true); + } + onPointerLeave = (e: React.PointerEvent) => { + this.props.doc.libraryBrush = false; + Doc.SetOnPrototype(this.props.doc, "protoBrush", false); + } + + collectionRef = React.createRef<HTMLDivElement>(); + startDocDrag = () => { + let doc = this.props.doc; + const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); + if (isProto) { + return Doc.MakeDelegate(doc); + } else { + return Doc.MakeAlias(doc); + } + } + render() { + return ( + <div className="search-item" ref={this.collectionRef} id="result" + onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} + onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} > + <div className="search-title" id="result" >title: {this.props.doc.title}</div> + {/* <div className="search-type" id="result" >Type: {this.props.doc.layout}</div> */} + {/* <div className="search-type" >{SearchItem.DocumentIcon(this.layout)}</div> */} + </div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index a24abb32e..247f7d1ea 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -43,7 +43,7 @@ .formattedTextBox-inner-rounded div, .formattedTextBox-inner div { - padding: 10px; + padding: 10px 10px; } .menuicon { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index f019868aa..2db0776ba 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,10 +1,11 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons'; -import { action, IReactionDisposer, observable, reaction, runInAction, computed, trace } from "mobx"; +import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; +import { Node as ProsNode } from "prosemirror-model"; import { EditorState, Plugin, Transaction, Selection } from "prosemirror-state"; import { NodeType, Slice, Node, Fragment } from 'prosemirror-model'; import { EditorView } from "prosemirror-view"; @@ -33,6 +34,7 @@ import { Templates } from '../Templates'; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); +import { For } from 'babel-types'; import { DateField } from '../../../new_fields/DateField'; import { Utils } from '../../../Utils'; @@ -48,6 +50,7 @@ export interface FormattedTextBoxProps { height?: string; color?: string; outer_div?: (domminus: HTMLElement) => void; + firstinstance?: boolean; } const richTextSchema = createSchema({ @@ -62,14 +65,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(FormattedTextBox, fieldStr); } + public static Instance: FormattedTextBox; private _ref: React.RefObject<HTMLDivElement>; private _outerdiv?: (dominus: HTMLElement) => void; private _proseRef?: HTMLDivElement; private _editorView: Opt<EditorView>; - private _toolTipTextMenu: TooltipTextMenu | undefined = undefined; + private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined; private _applyingChange: boolean = false; private _linkClicked = ""; private _reactionDisposer: Opt<IReactionDisposer>; + private _searchReactionDisposer?: Lambda; private _textReactionDisposer: Opt<IReactionDisposer>; private _proxyReactionDisposer: Opt<IReactionDisposer>; private dropDisposer?: DragManager.DragDropDisposer; @@ -99,6 +104,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return ""; } + public static getToolTip() { + return this._toolTipTextMenu; + } + @undoBatch public setFontColor(color: string) { let self = this; @@ -114,6 +123,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe constructor(props: FieldViewProps) { super(props); + //if (this.props.firstinstance) { + FormattedTextBox.Instance = this; + //} if (this.props.outer_div) { this._outerdiv = this.props.outer_div; } @@ -144,7 +156,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // tx.setSelection(new Selection(tx.)) const state = this._editorView!.state; this._editorView!.dispatch(tx); - if (this._toolTipTextMenu) { + if (FormattedTextBox._toolTipTextMenu) { // this._toolTipTextMenu.makeLinkWithState(state) } e.stopPropagation(); @@ -160,7 +172,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._editorView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); + if (state.selection.empty && FormattedTextBox._toolTipTextMenu) { + const marks = tx.storedMarks; + if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); } + } this._applyingChange = true; + const fieldkey = "preview"; + if (Object.keys(this.dataDoc).indexOf(fieldkey) !== -1) { + this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); + this.dataDoc[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n"); + } + else { + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); + Doc.GetProto(this.dataDoc)[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n"); + } if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"); if (this.extensionDoc) this.extensionDoc.lastModified = new DateField(new Date(Date.now())); this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); @@ -174,6 +199,50 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + public highlightSearchTerms = (terms: String[]) => { + if (this._editorView && (this._editorView as any).docView) { + const fieldkey = "preview"; + const doc = this._editorView.state.doc; + const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); + doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => { + if (node.isLeaf && node.isText && node.text) { + let nodeText: String = node.text; + let tokens = nodeText.split(" "); + let start = pos; + tokens.forEach((word) => { + if (terms.includes(word) && this._editorView) { + this._editorView.dispatch(this._editorView.state.tr.addMark(start, start + word.length, mark).removeStoredMark(mark)); + // else { + // this._editorView.state.tr.addMark(start, start + word.length, mark).removeStoredMark(mark); + // } + } + start += word.length + 1; + }); + } + }); + } + } + + public unhighlightSearchTerms = () => { + if (this._editorView && (this._editorView as any).docView) { + const doc = this._editorView.state.doc; + const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); + doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => { + if (node.isLeaf && node.isText && node.text) { + if (node.marks.includes(mark) && this._editorView) { + this._editorView.dispatch(this._editorView.state.tr.removeMark(pos, pos + node.nodeSize, mark)); + } + } + }); + // const fieldkey = 'search_string'; + // if (Object.keys(this.props.Document).indexOf(fieldkey) !== -1) { + // this.props.Document[fieldkey] = undefined; + // } + // else this.props.Document.proto![fieldkey] = undefined; + // } + } + } + protected createDropTarget = (ele: HTMLDivElement) => { this._proseRef = ele; if (this.dropDisposer) { @@ -276,6 +345,22 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } }, { fireImmediately: true }); this.setupEditor(config, this.dataDoc, this.props.fieldKey); + + this._searchReactionDisposer = reaction(() => { + return StrCast(this.props.Document.search_string); + }, searchString => { + const fieldkey = 'preview'; + let preview = false; + // if (!this._editorView && Object.keys(this.props.Document).indexOf(fieldkey) !== -1) { + // preview = true; + // } + if (searchString) { + this.highlightSearchTerms([searchString]); + } + else { + this.unhighlightSearchTerms(); + } + }, { fireImmediately: true }); } clipboardTextSerializer = (slice: Slice): string => { @@ -410,7 +495,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerDown = (e: React.PointerEvent): void => { if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); - if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) { + if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) { //this._toolTipTextMenu.tooltip.style.opacity = "0"; } } @@ -463,7 +548,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } onPointerUp = (e: React.PointerEvent): void => { - if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) { + if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) { //this._toolTipTextMenu.tooltip.style.opacity = "1"; } if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { @@ -504,7 +589,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe tooltipTextMenuPlugin() { let myprops = this.props; - let self = this; + let self = FormattedTextBox; return new Plugin({ view(_editorView) { return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops); diff --git a/src/client/views/search/FilterBox.scss b/src/client/views/search/FilterBox.scss index 1eb8963d7..1b73916c9 100644 --- a/src/client/views/search/FilterBox.scss +++ b/src/client/views/search/FilterBox.scss @@ -105,4 +105,60 @@ border-top-style: solid; padding-top: 10px; } +} + +.active-filters { + display: flex; + flex-direction: row-reverse; + justify-content: flex-end; + width: 100%; + margin-right: 30px; + position: relative; + + .active-icon { + max-width: 40px; + flex: initial; + + &.icon{ + width: 40px; + text-align: center; + margin-bottom: 5px; + position: absolute; + } + + &.container { + display: flex; + flex-direction: column; + width: 40px; + } + + &.description { + text-align: center; + top: 40px; + position: absolute; + width: 40px; + font-size: 9px; + opacity: 0; + -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; + } + + &.icon:hover + .description { + opacity: 1; + } + } + + .col-icon { + height: 35px; + margin-left: 5px; + width: 35px; + background-color: black; + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + } }
\ No newline at end of file diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index 706d1eb7f..4b1887339 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { observer } from 'mobx-react'; import { observable, action } from 'mobx'; import "./SearchBox.scss"; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-svg-icons'; import { library } from '@fortawesome/fontawesome-svg-core'; import { Doc } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; @@ -19,8 +19,11 @@ import { NaviconButton } from './NaviconButton'; import * as $ from 'jquery'; import "./FilterBox.scss"; import { SearchBox } from './SearchBox'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; library.add(faTimes); +library.add(faCheckCircle); +library.add(faObjectGroup); export enum Keys { TITLE = "title", @@ -35,11 +38,16 @@ export class FilterBox extends React.Component { public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.HIST, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB]; //if true, any keywords can be used. if false, all keywords are required. + //this also serves as an indicator if the word status filter is applied @observable private _basicWordStatus: boolean = true; @observable private _filterOpen: boolean = false; + //if icons = all icons, then no icon filter is applied @observable private _icons: string[] = this._allIcons; + //if all of these are true, no key filter is applied @observable private _titleFieldStatus: boolean = true; @observable private _authorFieldStatus: boolean = true; + @observable private _dataFieldStatus: boolean = true; + //this also serves as an indicator if the collection status filter is applied @observable public _deletedDocsStatus: boolean = false; @observable private _collectionStatus = false; @observable private _collectionSelfStatus = true; @@ -259,6 +267,40 @@ export class FilterBox extends React.Component { return finalDocs; } + getABCicon() { + return ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35"> + <path d="M25.4 47.9c-1.3 1.3-1.9 2.8-1.9 4.8 0 3.8 2.3 6.1 6.1 6.1 5.1 0 8-3.3 9-6.2 0.2-0.7 0.4-1.4 0.4-2.1v-6.1c-0.1 0-0.1 0-0.2 0C32.2 44.5 27.7 45.6 25.4 47.9z" /> + <path d="M64.5 28.6c-2.2 0-4.1 1.5-4.7 3.8l0 0.2c-0.1 0.3-0.1 0.7-0.1 1.1v3.3c0 0.4 0.1 0.8 0.2 1.1 0.6 2.2 2.4 3.6 4.6 3.6 3.2 0 5.2-2.6 5.2-6.7C69.5 31.8 68 28.6 64.5 28.6z" /> + <path d="M43.9 0C19.7 0 0 19.7 0 43.9s19.7 43.9 43.9 43.9 43.9-19.6 43.9-43.9S68.1 0 43.9 0zM40.1 65.5l-0.5-4c-3 3.1-7.4 4.9-12.1 4.9 -6.8 0-13.6-4.4-13.6-12.8 0-4 1.3-7.4 4-10 4.1-4.1 11.1-6.2 20.8-6.3 0-5.5-2.9-8.4-8.3-8.4 -3.6 0-7.4 1.1-10.2 2.9l-1.1 0.7 -2.4-6.9 0.7-0.4c3.7-2.4 8.9-3.8 14.1-3.8 10.9 0 16.7 6.2 16.7 17.9V54.6c0 4.1 0.2 7.2 0.7 9.7L49 65.5H40.1zM65.5 67.5c1.8 0 3-0.5 4-0.9l0.5-0.2 0.8 3.4 -0.3 0.2c-1 0.5-3 1.1-5.5 1.1 -5.8 0-9.7-4-9.7-9.9 0-6.1 4.3-10.3 10.4-10.3 2.1 0 4 0.5 4.9 1l0.3 0.2 -1 3.5 -0.5-0.3c-0.7-0.4-1.8-0.8-3.7-0.8 -3.7 0-6.1 2.6-6.1 6.6C59.5 64.8 61.9 67.5 65.5 67.5zM65 45.3c-2.5 0-4.5-0.9-5.9-2.7l-0.1 2.3h-3.8l0-0.5c0.1-1.2 0.2-3.1 0.2-4.8V16.7h4.3v10.8c1.4-1.6 3.5-2.5 6-2.5 2.2 0 4.1 0.8 5.5 2.3 1.8 1.8 2.8 4.5 2.8 7.7C73.8 42.1 69.3 45.3 65 45.3z" /> + </svg> + ); + } + + getTypeIcon() { + return ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35"> + <path d="M43.9 0C19.7 0 0 19.7 0 43.9s19.7 43.9 43.9 43.9 43.9-19.6 43.9-43.9S68.1 0 43.9 0zM43.9 12.2c4.1 0 7.5 3.4 7.5 7.5 0 4.1-3.4 7.5-7.5 7.5 -4.1 0-7.5-3.4-7.5-7.5C36.4 15.5 39.7 12.2 43.9 12.2zM11.9 50.4l7.5-13 7.5 13H11.9zM47.6 75.7h-7.5l-3.7-6.5 3.8-6.5h7.5l3.8 6.5L47.6 75.7zM70.7 70.7c-0.2 0.2-0.4 0.3-0.7 0.3s-0.5-0.1-0.7-0.3l-25.4-25.4 -25.4 25.4c-0.2 0.2-0.4 0.3-0.7 0.3s-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1 0-1.4l25.4-25.4 -25.4-25.4c-0.4-0.4-0.4-1 0-1.4s1-0.4 1.4 0l25.4 25.4 25.4-25.4c0.4-0.4 1-0.4 1.4 0s0.4 1 0 1.4l-25.4 25.4 25.4 25.4C71.1 69.7 71.1 70.3 70.7 70.7zM61.4 51.4v-15h15v15H61.4z" /> + </svg> + ); + } + + getKeyIcon() { + return ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35"> + <path d="M38.5 32.4c0 3.4-2.7 6.1-6.1 6.1 -3.4 0-6.1-2.7-6.1-6.1 0-3.4 2.8-6.1 6.1-6.1C35.8 26.3 38.5 29 38.5 32.4zM87.8 43.9c0 24.2-19.6 43.9-43.9 43.9S0 68.1 0 43.9C0 19.7 19.7 0 43.9 0S87.8 19.7 87.8 43.9zM66.8 60.3L50.2 43.7c-0.5-0.5-0.6-1.2-0.4-1.8 2.4-5.6 1.1-12.1-3.2-16.5 -5.9-5.8-15.4-5.8-21.2 0l0 0c-4.3 4.3-5.6 10.8-3.2 16.5 3.2 7.6 12 11.2 19.7 8 0.6-0.3 1.4-0.1 1.8 0.4l3.1 3.1h3.9c1.2 0 2.2 1 2.2 2.2v3.6h3.6c1.2 0 2.2 1 2.2 2.2v4l1.6 1.6h6.5V60.3z" /> + </svg> + ); + } + + getColIcon() { + return ( + <div className="col-icon"> + <FontAwesomeIcon icon={faObjectGroup} size="lg" /> + </div> + ); + } + @action.bound openFilter = () => { this._filterOpen = !this._filterOpen; @@ -326,6 +368,31 @@ export class FilterBox extends React.Component { getAuthorStatus() { return this._authorFieldStatus; } getDataStatus() { return this._deletedDocsStatus; } + getActiveFilters() { + console.log(this._authorFieldStatus, this._titleFieldStatus, this._dataFieldStatus); + return ( + <div className="active-filters"> + {!this._basicWordStatus ? <div className="active-icon container"> + <div className="active-icon icon">{this.getABCicon()}</div> + <div className="active-icon description">Required Words Applied</div> + </div> : undefined} + {!(this._icons.length === 9) ? <div className="active-icon container"> + <div className="active-icon icon">{this.getTypeIcon()}</div> + <div className="active-icon description">Type Filters Applied</div> + </div> : undefined} + {!(this._authorFieldStatus && this._dataFieldStatus && this._titleFieldStatus) ? + <div className="active-icon container"> + <div className="active-icon icon">{this.getKeyIcon()}</div> + <div className="active-icon description">Field Filters Applied</div> + </div> : undefined} + {this._collectionStatus ? <div className="active-icon container"> + <div className="active-icon icon">{this.getColIcon()}</div> + <div className="active-icon description">Collection Filters Active</div> + </div> : undefined} + </div> + ) + } + // Useful queries: // Delegates of a document: {!join from=id to=proto_i}id:{protoId} // Documents in a collection: {!join from=data_l to=id}id:{collectionProtoId} //id of collections prototype @@ -334,6 +401,7 @@ export class FilterBox extends React.Component { <div> <div style={{ display: "flex", flexDirection: "row-reverse" }}> <SearchBox /> + {this.getActiveFilters()} </div> {this._filterOpen ? ( <div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex" } : { display: "none" }}> @@ -387,7 +455,8 @@ export class FilterBox extends React.Component { <button className="reset-filter" onClick={this.resetFilters}>Reset Filters</button> </div> </div> - ) : undefined} + ) : + undefined} </div> ); } diff --git a/src/client/views/search/Pager.tsx b/src/client/views/search/Pager.tsx new file mode 100644 index 000000000..9bcfb1676 --- /dev/null +++ b/src/client/views/search/Pager.tsx @@ -0,0 +1,78 @@ +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} onMouseLeave = {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} onMouseLeave = {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 109b88ac9..fcdc79220 100644 --- a/src/client/views/search/SearchBox.scss +++ b/src/client/views/search/SearchBox.scss @@ -45,6 +45,11 @@ top: 300px; display: flex; flex-direction: column; + margin-right: 72px; + // height: 560px; + height: 100%; + // overflow: hidden; + // overflow-y: auto; max-height: 560px; overflow: hidden; overflow-y: auto; diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 562594210..bdf13b144 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -21,11 +21,18 @@ import { DocumentView } from "../nodes/DocumentView"; import { SearchBox } from "./SearchBox"; import "./SearchItem.scss"; import "./SelectorContextMenu.scss"; +import { RichTextField } from "../../../new_fields/RichTextField"; +import { FormattedTextBox } from "../nodes/FormattedTextBox"; +import { MarqueeView } from "../collections/collectionFreeForm/MarqueeView"; +import { SelectionManager } from "../../util/SelectionManager"; +import { ObjectField } from "../../../new_fields/ObjectField"; import { ContextMenu } from "../ContextMenu"; import { faFile } from '@fortawesome/free-solid-svg-icons'; +import { DocServer } from "../../DocServer"; export interface SearchItemProps { doc: Doc; + query?: string; highlighting: string[]; } @@ -86,7 +93,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> { SetupDrag(item, () => doc.col, undefined, undefined, undefined, undefined, () => SearchBox.Instance.closeSearch())}> <FontAwesomeIcon icon={faStickyNote} /> </div> - <a className="title" onClick={this.getOnClick(doc)}>{doc.col.title}</a> + <a onClick={this.getOnClick(doc)}>{doc.col.title}</a> </div>; })} </div> @@ -94,27 +101,115 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> { } } +export interface LinkMenuProps { + doc1: Doc; + doc2: Doc; +} + +@observer +export class LinkContextMenu extends React.Component<LinkMenuProps> { + + highlightDoc = (doc: Doc) => { + return () => { + doc.libraryBrush = true; + }; + } + + unHighlightDoc = (doc: Doc) => { + return () => { + doc.libraryBrush = false; + }; + } + + getOnClick(col: Doc) { + return () => { + CollectionDockingView.Instance.AddRightSplit(col, undefined); + }; + } + + render() { + return ( + <div className="parents"> + <p className="contexts">Anchors:</p> + <div className="collection"><a onMouseEnter={this.highlightDoc(this.props.doc1)} onMouseLeave={this.unHighlightDoc(this.props.doc1)} onClick={this.getOnClick(this.props.doc1)}>Doc 1: {this.props.doc2.title}</a></div> + <div><a onMouseEnter={this.highlightDoc(this.props.doc2)} onMouseLeave={this.unHighlightDoc(this.props.doc2)} onClick={this.getOnClick(this.props.doc2)}>Doc 2: {this.props.doc1.title}</a></div> + </div> + ) + } + +} + @observer export class SearchItem extends React.Component<SearchItemProps> { @observable _selected: boolean = false; + private _previewDoc?: Doc; onClick = () => { // 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); + if (this.props.doc.data instanceof RichTextField) { + this.highlightTextBox(this.props.doc); + } // CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined); } @observable _useIcons = true; @observable _displayDim = 50; - @computed - public get DocumentIcon() { + highlightTextBox = (doc: Doc) => { + if (this.props.query) { + const fieldkey = 'search_string'; + if (Object.keys(doc).indexOf(fieldkey) === -1) { + doc.search_string = this.props.query; + } + else { + doc.search_string = undefined; + } + + } + } + + fitToBox = () => { + let bounds = Doc.ComputeContentBounds([this.props.doc]); + return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / Math.max((bounds.b - bounds.y), (bounds.r - bounds.x)), this._displayDim]; + } + + componentWillUnmount() { + if (this._previewDoc) { + DocServer.DeleteDocument(this._previewDoc[Id]); + } + } + + + //@computed + @action + public DocumentIcon() { + let layoutresult = StrCast(this.props.doc.type); if (!this._useIcons) { + let renderDoc = this.props.doc; + //let box: number[] = []; + if (layoutresult.indexOf(DocumentType.COL) !== -1) { + renderDoc = Doc.MakeDelegate(renderDoc); + let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { + var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; + let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; + return { + x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) + }; + }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); + let box = () => [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / (bounds.r - bounds.x), this._displayDim]; + } let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); let returnYDimension = () => this._displayDim; - let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension()); - return <div - onPointerDown={action(() => { this._useIcons = !this._useIcons; this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); })} + let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); + let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt + this._previewDoc = newRenderDoc; + const docview = <div + onPointerDown={action(() => { + this._useIcons = !this._useIcons; + this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); + })} onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} onPointerLeave={action(() => this._displayDim = 50)} > <DocumentView @@ -138,9 +233,15 @@ export class SearchItem extends React.Component<SearchItemProps> { ContentScaling={scale} /> </div>; + const data = renderDoc.data; + if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); + newRenderDoc.preview = true; + newRenderDoc.search_string = this.props.query; + return docview; + } + if (this._previewDoc) { + DocServer.DeleteDocument(this._previewDoc[Id]); } - - let layoutresult = StrCast(this.props.doc.type); let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage : layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote : @@ -262,7 +363,8 @@ export class SearchItem extends React.Component<SearchItemProps> { </div> </div> <div className="searchBox-instances"> - <SelectorContextMenu {...this.props} /> + {this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={Cast(this.props.doc.anchor1, Doc, new Doc())} doc2={Cast(this.props.doc.anchor2, Doc, new Doc())} /> : + <SelectorContextMenu {...this.props} />} </div> </div> ); diff --git a/src/client/views/search/SelectorContextMenu.scss b/src/client/views/search/SelectorContextMenu.scss index 49f77b9bf..48cacc608 100644 --- a/src/client/views/search/SelectorContextMenu.scss +++ b/src/client/views/search/SelectorContextMenu.scss @@ -3,6 +3,7 @@ .parents { background: $lighter-alt-accent; padding: 10px; + // width: 300px; .contexts { text-transform: uppercase; diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx index 178578c5c..a30104089 100644 --- a/src/client/views/search/ToggleBar.tsx +++ b/src/client/views/search/ToggleBar.tsx @@ -59,6 +59,7 @@ export class ToggleBar extends React.Component<ToggleBarProps>{ this._forwardTimeline.play(); this._forwardTimeline.reverse(); this.props.handleChange(); + console.log(this.props.getStatus()) } @action.bound |
