diff options
Diffstat (limited to 'src/client/views')
51 files changed, 1690 insertions, 2425 deletions
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index 86e0a568a..7467bc043 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -7,7 +7,7 @@ box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw; flex-direction: column; background: whitesmoke; - padding-top: 10px; + padding-top: 10px; padding-bottom: 10px; border-radius: 15px; border: solid #BBBBBBBB 1px; @@ -72,6 +72,7 @@ margin-left: 5px; } } + .contextMenu-description { // width: 11vw; //10vw background: whitesmoke; @@ -100,6 +101,8 @@ border-color: $intermediate-color; // rgb(187, 186, 186); border-bottom-style: solid; border-top-style: solid; + + cursor: pointer; } .contextMenu-itemSelected { diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index c2ca93900..09ae14016 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -64,9 +64,13 @@ $linkGap : 3px; text-align: center; border-radius: 50%; pointer-events: auto; - color: $dark-color; - border: $dark-color 1px solid; + background-color: $dark-color; + border: none; transition: 0.2s ease all; + + &:hover { + background-color: $main-accent; + } } .documentButtonBar-linker:hover { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index d45531b76..8748b1880 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -198,7 +198,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const isPinned = targetDoc && Doc.isDocPinned(targetDoc); return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></>}> <div className="documentButtonBar-linker" - style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }} + style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white", border: isPinned ? "black 1px solid " : "" }} onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}> <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" /> diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index f9d060681..ec3e754fb 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -6,6 +6,8 @@ import { ObjectField } from '../../fields/ObjectField'; import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import "./EditableView.scss"; import { DragManager } from '../util/DragManager'; +import { ComputedField } from '../../fields/ScriptField'; +import { FieldValue } from '../../fields/Types'; export interface EditableProps { /** @@ -52,6 +54,10 @@ export interface EditableProps { color?: string | undefined; onDrop?: any; placeholder?: string; + highlight?: boolean; + positions?: number[]; + search?: string; + bing?: () => string | undefined; } /** @@ -179,6 +185,34 @@ export class EditableView extends React.Component<EditableProps> { placeholder={this.props.placeholder} />; } + + returnHighlights() { + const results = []; + const contents = this.props.bing!(); + + if (contents !== undefined) { + if (this.props.positions !== undefined) { + const positions = this.props.positions; + const length = this.props.search!.length; + + // contents = String(this.props.contents.valueOf()); + + results.push(<span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{contents ? contents.slice(0, this.props.positions[0]) : this.props.placeholder?.valueOf()}</span>); + positions.forEach((num, cur) => { + results.push(<span style={{ backgroundColor: "#FFFF00", fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{contents ? contents.slice(num, num + length) : this.props.placeholder?.valueOf()}</span>); + let end = 0; + cur === positions.length - 1 ? end = contents.length : end = positions[cur + 1]; + results.push(<span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{contents ? contents.slice(num + length, end) : this.props.placeholder?.valueOf()}</span>); + } + ); + } + return results; + } + else { + return <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>; + } + } + render() { if (this._editing && this.props.GetValue() !== undefined) { return this.props.sizeToContent ? @@ -193,11 +227,8 @@ export class EditableView extends React.Component<EditableProps> { ref={this._ref} style={{ display: this.props.display, minHeight: "17px", whiteSpace: "nowrap", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }} onClick={this.onClick} placeholder={this.props.placeholder}> - <span style={{ - fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, - color: this.props.contents ? this.props.color ? this.props.color : "black" : "grey" - }}> - {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span> + {this.props.highlight === undefined || this.props.positions === undefined || this.props.bing === undefined ? <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span> + : this.returnHighlights()} </div> ); } diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index a2a9ceca5..97ed0a901 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -26,7 +26,7 @@ body { height: 100%; border-radius: inherit; position: inherit; - // background: inherit; + // background: inherit; } p { @@ -37,7 +37,7 @@ p { ::-webkit-scrollbar { -webkit-appearance: none; height: 8px; - width: 8px; + width: 8px; } ::-webkit-scrollbar-thumb { @@ -47,7 +47,7 @@ p { // button stuff button { - background: $dark-color; + background: black; outline: none; border: 0px; color: $light-color; diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index a57d22afd..f3fba82bc 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -21,7 +21,7 @@ // add nodes menu. Note that the + button is actually an input label, not an actual button. .mainView-docButtons { position: absolute; - bottom: 20px; + bottom: 35px; left: calc(100% + 5px); z-index: 1; } @@ -103,7 +103,8 @@ } .mainView-propertiesDragger { - background-color: rgb(140, 139, 139); + //background-color: rgb(140, 139, 139); + background-color: lightgrey; height: 55px; width: 17px; position: absolute; @@ -155,8 +156,8 @@ .mainView-menuPanel { width: 60px; - background-color: black; - height: 100%; + background-color: #121721; + height: calc(100% - 32px); //overflow-y: scroll; //overflow-x: hidden; @@ -165,6 +166,7 @@ padding: 7px; padding-left: 7px; width: 100%; + background: black; .mainView-menuPanel-button-wrap { width: 45px; @@ -213,12 +215,11 @@ .mainView-searchPanel { width: 100%; - height: 33px; + height: 32px; background-color: black; color: white; text-align: center; vertical-align: middle; - padding-top: 6px; } .mainView-mainDiv { @@ -297,6 +298,7 @@ position: absolute; z-index: 2; touch-action: none; + background-color: lightgrey; cursor: grab; .mainView-libraryHandle-icon { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7f50dda72..845e93ecb 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -60,6 +60,7 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { undoBatch } from '../util/UndoManager'; +import { SearchBox } from './search/SearchBox'; @observer export class MainView extends React.Component { @@ -79,6 +80,7 @@ export class MainView extends React.Component { @computed private get userDoc() { return Doc.UserDoc(); } @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; } @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } + @computed public get searchDoc() { return Cast(this.userDoc["search-panel"], Doc) as Doc; } @observable public sidebarContent: any = this.userDoc?.sidebar; @observable public panelContent: string = "none"; @@ -191,6 +193,20 @@ export class MainView extends React.Component { if (targets && (targets.length && targets[0].className.toString() !== "timeline-menu-desc" && targets[0].className.toString() !== "timeline-menu-item" && targets[0].className.toString() !== "timeline-menu-input")) { TimelineMenu.Instance.closeMenu(); } + if (targets && targets.length && SearchBox.Instance._searchbarOpen) { + let check = false; + const icon = "icon"; + targets.forEach((thing) => { + if (thing.className.toString() === "collectionSchemaView-table" || (thing as any)?.dataset[icon] === "filter" || thing.className.toString() === "beta" || thing.className.toString() === "collectionSchemaView-menuOptions-wrapper") { + check = true; + } + }); + if (check === false) { + SearchBox.Instance.closeSearch(); + } + } + + }); globalPointerUp = () => this.isPointerDown = false; @@ -316,6 +332,16 @@ export class MainView extends React.Component { defaultBackgroundColors = (doc: Opt<Doc>) => { if (this.panelContent === doc?.title) return "lightgrey"; + + if (doc?.type === DocumentType.COL) { + if (doc.title === "Basic Item Creators" || doc.title === "sidebar-tools" + || doc.title === "sidebar-recentlyClosed" || doc.title === "sidebar-catalog" + || doc.title === "Mobile Uploads" || doc.title === "COLLECTION_PROTO" + || doc.title === "Advanced Item Prototypes" || doc.title === "all Creators") { + return "lightgrey"; + } + return StrCast(Doc.UserDoc().defaultColor); + } if (this.darkScheme) { switch (doc?.type) { case DocumentType.FONTICON: return "white"; @@ -340,6 +366,7 @@ export class MainView extends React.Component { } } } + @computed get mainDocView() { return <DocumentView Document={this.mainContainer!} @@ -368,11 +395,12 @@ export class MainView extends React.Component { renderDepth={-1} />; } + @computed get dockingContent() { TraceMobx(); const mainContainer = this.mainContainer; const width = this.flyoutWidth + this.propertiesWidth(); - return <div className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)` }}> + return <div className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)`, height: `calc(100% - 32px)` }}> {!mainContainer ? (null) : this.mainDocView} </div>; } @@ -417,11 +445,11 @@ export class MainView extends React.Component { @computed get flyout() { if (!this.sidebarContent) return null; return <div className="mainView-libraryFlyout"> - <div className="mainView-contentArea" style={{ position: "relative", height: `100%`, width: "100%", overflow: "visible" }}> - {this.flyoutWidth > 0 ? <div className="mainView-libraryFlyout-close" + <div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - 32px)`, width: "100%", overflow: "visible" }}> + {/* {this.flyoutWidth > 0 ? <div className="mainView-libraryFlyout-close" onPointerDown={this.closeFlyout}> <FontAwesomeIcon icon="times" color="black" size="lg" /> - </div> : null} + </div> : null} */} <DocumentView Document={this.sidebarContent} @@ -449,6 +477,7 @@ export class MainView extends React.Component { ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} relative={true} + forcedBackgroundColor={() => "lightgrey"} /> </div> {this.docButtons}</div>; @@ -488,7 +517,7 @@ export class MainView extends React.Component { } - @action @undoBatch + @action closeFlyout = () => { this._lastButton && (this._lastButton.color = "white"); this._lastButton && (this._lastButton._backgroundColor = ""); @@ -499,7 +528,7 @@ export class MainView extends React.Component { get groupManager() { return GroupManager.Instance; } _lastButton: Doc | undefined; - @action @undoBatch + @action selectMenu = (button: Doc, str: string) => { this._lastButton && (this._lastButton.color = "white"); this._lastButton && (this._lastButton._backgroundColor = ""); @@ -528,7 +557,7 @@ export class MainView extends React.Component { return true; } - @action @undoBatch + @action closeProperties = () => { CurrentUserUtils.propertiesWidth = 0; } @@ -556,7 +585,8 @@ export class MainView extends React.Component { <div className="mainView-flyoutContainer" style={{ width: this.flyoutWidth }}> {this.flyoutWidth !== 0 ? <div className="mainView-libraryHandle" onPointerDown={this.onFlyoutPointerDown} - style={{ backgroundColor: 'lightgrey' }}> + //style={{ backgroundColor: '#8c8b8b' }} + > <span title="library View Dragger" style={{ width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "3vw", //height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "100vh", @@ -583,7 +613,7 @@ export class MainView extends React.Component { <div className="mainView-propertiesDragger" title="Properties View Dragger" onPointerDown={this.onPropertiesPointerDown} style={{ right: rightFlyout, top: "50%" }}> <div className="mainView-propertiesDragger-icon"> - <FontAwesomeIcon icon={this.propertiesIcon} color="white" size="sm" /> </div> + <FontAwesomeIcon icon={this.propertiesIcon} color="black" size="sm" /> </div> </div> } {this.propertiesWidth() < 10 ? (null) : @@ -593,7 +623,6 @@ export class MainView extends React.Component { } @computed get mainContent() { - //const n = (RichTextMenu.Instance?.Pinned ? 1 : 0) + (CollectionMenu.Instance?.Pinned ? 1 : 0); const n = (CollectionMenu.Instance?.Pinned ? 1 : 0); const height = `calc(100% - ${n * Number(ANTIMODEMENU_HEIGHT.replace("px", ""))}px)`; const pinned = FormatShapePane.Instance?.Pinned; @@ -716,8 +745,32 @@ export class MainView extends React.Component { @computed get search() { return <div className="mainView-searchPanel"> - <div style={{ float: "left", marginLeft: "10px" }}>{Doc.CurrentUserEmail}</div> - <div>SEARCH GOES HERE</div> + {/* <div style={{ float: "left", marginLeft: "10px" }}>{Doc.CurrentUserEmail}</div> */} + <div><DocumentView Document={this.searchDoc} + DataDoc={undefined} + LibraryPath={emptyPath} + addDocument={undefined} + addDocTab={this.addDocTabFunc} + pinToPres={emptyFunction} + rootSelected={returnTrue} + onClick={undefined} + backgroundColor={this.defaultBackgroundColors} + removeDocument={undefined} + ScreenToLocalTransform={Transform.Identity} + ContentScaling={returnOne} + NativeHeight={returnZero} + NativeWidth={returnZero} + PanelWidth={this.getPWidth} + PanelHeight={this.getPHeight} + renderDepth={0} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docFilters={returnEmptyFilter} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + /></div> </div>; } @@ -732,7 +785,7 @@ export class MainView extends React.Component { <GoogleAuthenticationManager /> <HypothesisAuthenticationManager /> <DocumentDecorations /> - {/* {this.search} */} + {this.search} <CollectionMenu /> <FormatShapePane /> <div style={{ display: "none" }}><RichTextMenu key="rich" /></div> diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx index 66ea2dbf8..19387f619 100644 --- a/src/client/views/MainViewModal.tsx +++ b/src/client/views/MainViewModal.tsx @@ -21,7 +21,9 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps> const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1; const overlayOpacity = p.overlayDisplayedOpacity || 0.4; return !p.isDisplayed ? (null) : ( - <div style={{ pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" }}> + <div style={{ + pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" + }}> <div className={"dialogue-box"} style={{ diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss index 1cba252de..6199d34d0 100644 --- a/src/client/views/PropertiesButtons.scss +++ b/src/client/views/PropertiesButtons.scss @@ -21,9 +21,8 @@ $linkGap : 3px; .propertiesButtons-linkButton-empty, .propertiesButtons-linkButton-nonempty { height: 30px; - width: 30px; - border-radius: 5px; - opacity: 0.9; + width: 32px; + border-radius: 6px; pointer-events: auto; background-color: #121721; color: #fcfbf7; @@ -36,6 +35,7 @@ $linkGap : 3px; justify-content: center; align-items: center; margin-right: 10px; + margin-left: 3.5px; &:hover { background: $main-accent; @@ -68,23 +68,35 @@ $linkGap : 3px; padding-right: 5px; width: 25px; border-radius: 5px; - margin-right: 18px; + margin-right: 22px; margin-bottom: 8px; } +.propertiesButtons-title { + background: #121721; + color: white; + font-size: 6px; + width: 40px; + padding: 3px; + height: 13px; + border-radius: 7px; + text-transform: uppercase; + text-align: center; + margin-top: -4px; +} + .propertiesButtons-linker { height: 30px; - width: 30px; + width: 32px; text-align: center; - border-radius: 5px; + border-radius: 6px; pointer-events: auto; - // color: $dark-color; - // border: $dark-color 1px solid; - background-color: #252b33; + background-color: #121721; color: #fcfbf7; transition: 0.2s ease all; margin-right: 5px; padding-top: 5px; + margin-left: 3.5px; &:hover { background: $main-accent; diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 7d8a75dda..29afd447f 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -28,7 +28,6 @@ import { GooglePhotos } from '../apis/google_docs/GooglePhotosClientUtils'; import { ImageField } from '../../fields/URLField'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { DocumentType } from '../documents/DocumentTypes'; -import { CollectionFreeFormView } from './collections/collectionFreeForm/CollectionFreeFormView'; import { InkField } from '../../fields/InkField'; import { PresBox } from './nodes/PresBox'; const higflyout = require("@hig/flyout"); @@ -129,18 +128,22 @@ export class PropertiesButtons extends React.Component<{}, {}> { const targetDoc = this.selectedDoc; const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined; const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none"; - return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div></>}> - <div - className="propertiesButtons-linker" - style={{ animation }} - onClick={async () => { - await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); - !published && runInAction(() => this.isAnimatingPulse = true); - PropertiesButtons.hasPushedHack = false; - targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1; - }}> - <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "sm" : "xs"} /> - </div></Tooltip>; + return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div>} placement="top"> + <div> + <div + className="propertiesButtons-linker" + style={{ animation }} + onClick={async () => { + await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); + !published && runInAction(() => this.isAnimatingPulse = true); + PropertiesButtons.hasPushedHack = false; + targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1; + }}> + <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "lg" : "sm"} /> + </div> + <div className="propertiesButtons-title">Google</div> + </div> + </Tooltip>; } @computed @@ -159,62 +162,76 @@ export class PropertiesButtons extends React.Component<{}, {}> { })(); return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <Tooltip - title={<><div className="dash-tooltip">{title}</div></>}> - <div className="propertiesButtons-linker" - style={{ backgroundColor: this.pullColor }} - onPointerEnter={action(e => { - if (e.altKey) { - this.openHover = UtilityButtonState.OpenExternally; - } else if (e.shiftKey) { - this.openHover = UtilityButtonState.OpenRight; - } - })} - onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)} - onClick={async e => { - const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`; - if (e.shiftKey) { - e.preventDefault(); - let googleDoc = await Cast(dataDoc.googleDoc, Doc); - if (!googleDoc) { - const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false }; - googleDoc = Docs.Create.WebDocument(googleDocUrl, options); - dataDoc.googleDoc = googleDoc; + title={<><div className="dash-tooltip">{title}</div></>} placement="top"> + <div> + <div className="propertiesButtons-linker" + style={{ backgroundColor: this.pullColor }} + onPointerEnter={action(e => { + if (e.altKey) { + this.openHover = UtilityButtonState.OpenExternally; + } else if (e.shiftKey) { + this.openHover = UtilityButtonState.OpenRight; } - CollectionDockingView.AddRightSplit(googleDoc); - } else if (e.altKey) { - e.preventDefault(); - window.open(googleDocUrl); - } else { - this.clearPullColor(); - PropertiesButtons.hasPulledHack = false; - targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1; - dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); - } - }}> - <FontAwesomeIcon className="documentdecorations-icon" size="sm" - style={{ WebkitAnimation: animation, MozAnimation: animation }} - icon={(() => { - switch (this.openHover) { - default: - case UtilityButtonState.Default: return dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; - case UtilityButtonState.OpenRight: return "arrow-alt-circle-right"; - case UtilityButtonState.OpenExternally: return "share"; + })} + onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)} + onClick={async e => { + const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`; + if (e.shiftKey) { + e.preventDefault(); + let googleDoc = await Cast(dataDoc.googleDoc, Doc); + if (!googleDoc) { + const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false }; + googleDoc = Docs.Create.WebDocument(googleDocUrl, options); + dataDoc.googleDoc = googleDoc; + } + CollectionDockingView.AddRightSplit(googleDoc); + } else if (e.altKey) { + e.preventDefault(); + window.open(googleDocUrl); + } else { + this.clearPullColor(); + PropertiesButtons.hasPulledHack = false; + targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1; + dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); } - })()} - /> - </div></Tooltip>; + }}> + <FontAwesomeIcon className="documentdecorations-icon" size="lg" + color="black" + style={{ WebkitAnimation: animation, MozAnimation: animation }} + icon={(() => { + switch (this.openHover) { + default: + case UtilityButtonState.Default: return dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; + case UtilityButtonState.OpenRight: return "arrow-alt-circle-right"; + case UtilityButtonState.OpenExternally: return "share"; + } + })()} + /> + </div> + <div className="propertiesButtons-title" style={{ backgroundColor: "white", color: "black" }}>Fetch</div> + </div> + </Tooltip>; } @computed get pinButton() { const targetDoc = this.selectedDoc; const isPinned = targetDoc && Doc.isDocPinned(targetDoc); - return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></>}> - <div className="propertiesButtons-linker" - style={{ backgroundColor: isPinned ? "" : "white", color: isPinned ? "white" : "black" }} - onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}> - <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" - /> - </div></Tooltip>; + return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top"> + <div> + <div className="propertiesButtons-linker" + style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white" }} + onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}> + <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" + /> + </div> + + <div className="propertiesButtons-title" style={{ + backgroundColor: Doc.isDocPinned(targetDoc) ? "white" : "black", + color: Doc.isDocPinned(targetDoc) ? "black" : "white" + }} + >{Doc.isDocPinned(targetDoc) ? "Unpin" : "Pin"}</div> + </div> + </Tooltip>; } @computed @@ -252,12 +269,15 @@ export class PropertiesButtons extends React.Component<{}, {}> { get metadataButton() { //const view0 = this.view0; if (this.selectedDoc) { - return <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>}> + return <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>} placement="top"> <div className="propertiesButtons-linkFlyout"> <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={<MetadataEntryMenu docs={[this.selectedDoc]} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}> - <div className={"propertiesButtons-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} > - {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />} + <div> + <div className={"propertiesButtons-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} > + {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="lg" />} + </div> + <div className="propertiesButtons-title">Metadata</div> </div> </Flyout> </div></Tooltip>; @@ -296,12 +316,15 @@ export class PropertiesButtons extends React.Component<{}, {}> { Array.from(Object.values(Templates.TemplateList)).map(template => templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean))); return !docView ? (null) : - <Tooltip title={<><div className="dash-tooltip">Customize layout</div></>}> + <Tooltip title={<><div className="dash-tooltip">Customize layout</div></>} placement="top"> <div className="propertiesButtons-linkFlyout"> <Flyout anchorPoint={anchorPoints.LEFT_TOP} //onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)} content={<TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}> - <div className={"propertiesButtons-linkButton-empty"} > - {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />} + <div> + <div className={"propertiesButtons-linkButton-empty"} > + {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="lg" />} + </div> + <div className="propertiesButtons-title">Layout</div> </div> </Flyout> </div></Tooltip>; @@ -324,12 +347,15 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get copyButton() { const targetDoc = this.selectedDoc; - return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Tap or Drag to create an alias"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - ref={this._dragRef} - onPointerDown={this.onAliasButtonDown} - onClick={this.onCopy}> - {<FontAwesomeIcon className="documentdecorations-icon" icon="copy" size="sm" />} + return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Tap or Drag to create an alias"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + ref={this._dragRef} + onPointerDown={this.onAliasButtonDown} + onClick={this.onCopy}> + {<FontAwesomeIcon className="documentdecorations-icon" icon="copy" size="lg" />} + </div> + <div className="propertiesButtons-title">Alias</div> </div> </Tooltip>; } @@ -344,11 +370,19 @@ export class PropertiesButtons extends React.Component<{}, {}> { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{this.selectedDoc?.lockedPosition ? - "Unlock Position" : "Lock Position"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={this.onLock} > - {<FontAwesomeIcon className="documentdecorations-icon" - icon={BoolCast(this.selectedDoc?.lockedPosition) ? "unlock" : "lock"} size="sm" />} + "Unlock Position" : "Lock Position"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + style={{ backgroundColor: BoolCast(this.selectedDoc?.lockedPosition) ? "white" : "" }} + onPointerDown={this.onLock} > + {<FontAwesomeIcon className="documentdecorations-icon" + color={BoolCast(this.selectedDoc?.lockedPosition) ? "black" : "white"} + icon={BoolCast(this.selectedDoc?.lockedPosition) ? "unlock" : "lock"} size="lg" />} + </div> + <div className="propertiesButtons-title" style={{ + backgroundColor: BoolCast(this.selectedDoc?.lockedPosition) ? "white" : "black", + color: BoolCast(this.selectedDoc?.lockedPosition) ? "black" : "white" + }}>Position </div> </div> </Tooltip>; } @@ -357,15 +391,18 @@ export class PropertiesButtons extends React.Component<{}, {}> { get downloadButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{"Download Document"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={async () => { - if (this.selectedDocumentView?.props.Document) { - Doc.Zip(this.selectedDocumentView?.props.Document); - } - }}> - {<FontAwesomeIcon className="propertiesButtons-icon" - icon="download" size="sm" />} + title={<><div className="dash-tooltip">{"Download Document"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + onPointerDown={async () => { + if (this.selectedDocumentView?.props.Document) { + Doc.Zip(this.selectedDocumentView?.props.Document); + } + }}> + {<FontAwesomeIcon className="propertiesButtons-icon" + icon="download" size="lg" />} + </div> + <div className="propertiesButtons-title"> downld </div> </div> </Tooltip>; } @@ -374,11 +411,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { get deleteButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{"Delete Document"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={this.deleteDocument}> - {<FontAwesomeIcon className="propertiesButtons-icon" - icon="trash-alt" size="sm" />} + title={<><div className="dash-tooltip">{"Delete Document"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + onPointerDown={this.deleteDocument}> + {<FontAwesomeIcon className="propertiesButtons-icon" + icon="trash-alt" size="lg" />} + </div> + <div className="propertiesButtons-title"> delete </div> </div> </Tooltip>; } @@ -393,15 +433,18 @@ export class PropertiesButtons extends React.Component<{}, {}> { get sharingButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{"Share Document"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={() => { - if (this.selectedDocumentView) { - SharingManager.Instance.open(this.selectedDocumentView); - } - }}> - {<FontAwesomeIcon className="propertiesButtons-icon" - icon="users" size="sm" />} + title={<><div className="dash-tooltip">{"Share Document"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + onPointerDown={() => { + if (this.selectedDocumentView) { + SharingManager.Instance.open(this.selectedDocumentView); + } + }}> + {<FontAwesomeIcon className="propertiesButtons-icon" + icon="users" size="lg" />} + </div> + <div className="propertiesButtons-title"> share </div> </div> </Tooltip>; } @@ -409,15 +452,19 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get onClickButton() { if (this.selectedDoc) { - return <Tooltip title={<><div className="dash-tooltip">Choose onClick behavior</div></>}> - <div className="propertiesButtons-linkFlyout"> - <Flyout anchorPoint={anchorPoints.LEFT_TOP} - content={this.onClickFlyout}> - <div className={"propertiesButtons-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} > - {<FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="sm" />} - </div> - </Flyout> - </div></Tooltip>; + return <Tooltip title={<><div className="dash-tooltip">Choose onClick behavior</div></>} placement="top"> + <div> + <div className="propertiesButtons-linkFlyout"> + <Flyout anchorPoint={anchorPoints.LEFT_TOP} + content={this.onClickFlyout}> + <div className={"propertiesButtons-linkButton-empty"} onPointerDown={e => e.stopPropagation()} > + {<FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="lg" />} + </div> + </Flyout> + </div> + <div className="propertiesButtons-title"> onclick </div> + </div> + </Tooltip>; } else { return null; } @@ -504,15 +551,18 @@ export class PropertiesButtons extends React.Component<{}, {}> { get googlePhotosButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{"Export to Google Photos"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={() => { - if (this.selectedDocumentView) { - GooglePhotos.Export.CollectionToAlbum({ collection: this.selectedDocumentView.Document }).then(console.log); - } - }}> - {<FontAwesomeIcon className="documentdecorations-icon" - icon="cloud-upload-alt" size="sm" />} + title={<><div className="dash-tooltip">{"Export to Google Photos"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + onPointerDown={() => { + if (this.selectedDocumentView) { + GooglePhotos.Export.CollectionToAlbum({ collection: this.selectedDocumentView.Document }).then(console.log); + } + }}> + {<FontAwesomeIcon className="documentdecorations-icon" + icon="cloud-upload-alt" size="lg" />} + </div> + <div className="propertiesButtons-title"> google </div> </div> </Tooltip>; } @@ -521,13 +571,19 @@ export class PropertiesButtons extends React.Component<{}, {}> { get clustersButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{this.selectedDoc?.useClusters ? "Stop Showing Clusters" : "Show Clusters"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - style={{ backgroundColor: this.selectedDoc?.useClusters ? "#a0a0a0" : "" }} - onPointerDown={this.changeClusters}> - {<FontAwesomeIcon className="documentdecorations-icon" - color={this.selectedDoc?.useClusters ? "black" : "white"} - icon="braille" size="sm" />} + title={<><div className="dash-tooltip">{this.selectedDoc?.useClusters ? "Stop Showing Clusters" : "Show Clusters"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + style={{ backgroundColor: this.selectedDoc?.useClusters ? "white" : "" }} + onPointerDown={this.changeClusters}> + {<FontAwesomeIcon className="documentdecorations-icon" + color={this.selectedDoc?.useClusters ? "black" : "white"} + icon="braille" size="lg" />} + </div> + <div className="propertiesButtons-title" style={{ + backgroundColor: this.selectedDoc?.useClusters ? "white" : "black", + color: this.selectedDoc?.useClusters ? "black" : "white" + }}> clusters </div> </div> </Tooltip>; } @@ -546,13 +602,19 @@ export class PropertiesButtons extends React.Component<{}, {}> { get fitContentButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{this.selectedDoc?._fitToBox ? "Stop Fitting Content" : "Fit Content"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - style={{ backgroundColor: this.selectedDoc?._fitToBox ? "#a0a0a0" : "" }} - onPointerDown={this.changeFitToBox}> - {<FontAwesomeIcon className="documentdecorations-icon" - color={this.selectedDoc?._fitToBox ? "black" : "white"} - icon="expand" size="sm" />} + title={<><div className="dash-tooltip">{this.selectedDoc?._fitToBox ? "Stop Fitting Content" : "Fit Content"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + style={{ backgroundColor: this.selectedDoc?._fitToBox ? "white" : "" }} + onPointerDown={this.changeFitToBox}> + {<FontAwesomeIcon className="documentdecorations-icon" + color={this.selectedDoc?._fitToBox ? "black" : "white"} + icon="expand" size="lg" />} + </div> + <div className="propertiesButtons-title" style={{ + backgroundColor: this.selectedDoc?._fitToBox ? "white" : "black", + color: this.selectedDoc?._fitToBox ? "black" : "white" + }}> {this.selectedDoc?._fitToBox ? "unfit" : "fit"} </div> </div> </Tooltip>; } @@ -573,11 +635,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { get maskButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">Make Mask</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={this.makeMask}> - {<FontAwesomeIcon className="documentdecorations-icon" - color="white" icon="paint-brush" size="sm" />} + title={<><div className="dash-tooltip">Make Mask</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + onPointerDown={this.makeMask}> + {<FontAwesomeIcon className="documentdecorations-icon" + color="white" icon="paint-brush" size="lg" />} + </div> + <div className="propertiesButtons-title"> mask </div> </div> </Tooltip>; } @@ -585,13 +650,16 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get contextButton() { if (this.selectedDoc) { - return <Tooltip title={<><div className="dash-tooltip">Show Context</div></>}> - <div className={"propertiesButtons-linkButton-empty"}> - <ParentDocSelector Document={this.selectedDoc} addDocTab={(doc, where) => { - where === "onRight" ? CollectionDockingView.AddRightSplit(doc) : - this.selectedDocumentView?.props.addDocTab(doc, "onRight"); - return true; - }} /> + return <Tooltip title={<><div className="dash-tooltip">Show Context</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"}> + <ParentDocSelector Document={this.selectedDoc} addDocTab={(doc, where) => { + where === "onRight" ? CollectionDockingView.AddRightSplit(doc) : + this.selectedDocumentView?.props.addDocTab(doc, "onRight"); + return true; + }} /> + </div> + <div className="propertiesButtons-title"> context </div> </div> </Tooltip>; } else { @@ -657,9 +725,6 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-button"> {this.onClickButton} </div> - {/* <div className="propertiesButtons-button"> - {this.contextButton} - </div> */} <div className="propertiesButtons-button"> {this.sharingButton} </div> @@ -687,6 +752,9 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-button" style={{ display: !isInk ? "none" : "" }}> {this.maskButton} </div> + <div className="propertiesButtons-button"> + {this.contextButton} + </div> </div> </div>; } diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx deleted file mode 100644 index 084f952a3..000000000 --- a/src/client/views/SearchDocBox.tsx +++ /dev/null @@ -1,428 +0,0 @@ -import { library } from "@fortawesome/fontawesome-svg-core"; -import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -//import "./SearchBoxDoc.scss"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; -import { returnFalse, returnZero } from "../../Utils"; -import { Docs } from "../documents/Documents"; -import { SearchUtil } from "../util/SearchUtil"; -import { EditableView } from "./EditableView"; -import { ContentFittingDocumentView } from "./nodes/ContentFittingDocumentView"; -import { FieldView, FieldViewProps } from "./nodes/FieldView"; -import { FilterBox } from "./search/FilterBox"; -import { SearchItem } from "./search/SearchItem"; -import React = require("react"); - -export interface RecProps { - documents: { preview: Doc, similarity: number }[]; - node: Doc; - -} - -library.add(faBullseye, faLink); -export const keyPlaceholder = "Query"; - -@observer -export class SearchDocBox extends React.Component<FieldViewProps> { - - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchDocBox, fieldKey); } - - // @observable private _display: boolean = false; - @observable private _pageX: number = 0; - @observable private _pageY: number = 0; - @observable private _width: number = 0; - @observable private _height: number = 0; - @observable.shallow private _docViews: JSX.Element[] = []; - // @observable private _documents: { preview: Doc, score: number }[] = []; - private previewDocs: Doc[] = []; - - constructor(props: FieldViewProps) { - super(props); - this.editingMetadata = this.editingMetadata || false; - //SearchBox.Instance = this; - this.resultsScrolled = this.resultsScrolled.bind(this); - } - - - @computed - private get editingMetadata() { - return BoolCast(this.props.Document.editingMetadata); - } - - private set editingMetadata(value: boolean) { - this.props.Document.editingMetadata = value; - } - - static readonly buffer = 20; - - componentDidMount() { - runInAction(() => { - this.query = StrCast(this.props.Document.searchText); - this.content = (Docs.Create.TreeDocument(DocListCast(Doc.GetProto(this.props.Document).data), { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query })); - - }); - if (this.inputRef.current) { - this.inputRef.current.focus(); - runInAction(() => { - this._searchbarOpen = true; - }); - } - } - - @observable - private content: Doc | undefined; - - @action - updateKey = async (newKey: string) => { - this.query = newKey; - if (newKey.length > 1) { - const newdocs = await this.getAllResults(this.query); - const things = newdocs.docs; - runInAction(() => { - this.content = Docs.Create.TreeDocument(things, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query }); - }); - } - - - //this.keyRef.current && this.keyRef.current.setIsFocused(false); - //this.query.length === 0 && (this.query = keyPlaceholder); - return true; - } - - @computed - public get query() { - return StrCast(this.props.Document.query); - } - - public set query(value: string) { - this.props.Document.query = value; - } - - @observable private _searchString: string = ""; - @observable private _resultsOpen: boolean = false; - @observable private _searchbarOpen: boolean = false; - @observable private _results: [Doc, string[], string[]][] = []; - private _resultsSet = new Map<Doc, number>(); - @observable private _openNoResults: boolean = false; - @observable private _visibleElements: JSX.Element[] = []; - - private resultsRef = React.createRef<HTMLDivElement>(); - public inputRef = React.createRef<HTMLInputElement>(); - - private _isSearch: ("search" | "placeholder" | undefined)[] = []; - private _numTotalResults = -1; - private _endIndex = -1; - - - private _maxSearchIndex: number = 0; - private _curRequest?: Promise<any> = undefined; - - @action - getViews = async (doc: Doc) => { - const results = await SearchUtil.GetViewsOfDocument(doc); - let toReturn: Doc[] = []; - await runInAction(() => { - toReturn = results; - }); - return toReturn; - } - - @action.bound - onChange(e: React.ChangeEvent<HTMLInputElement>) { - this._searchString = e.target.value; - - this._openNoResults = false; - this._results = []; - this._resultsSet.clear(); - this._visibleElements = []; - this._numTotalResults = -1; - this._endIndex = -1; - this._curRequest = undefined; - this._maxSearchIndex = 0; - } - - enter = async (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - const newdocs = await this.getAllResults(this.query); - this.content = Docs.Create.TreeDocument(newdocs.docs, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs: "Results"` }); - } - } - - - @action - submitSearch = async () => { - let query = this._searchString; - query = FilterBox.Instance.getFinalQuery(query); - this._results = []; - this._resultsSet.clear(); - this._isSearch = []; - this._visibleElements = []; - FilterBox.Instance.closeFilter(); - - //if there is no query there should be no result - if (query === "") { - return; - } - else { - this._endIndex = 12; - this._maxSearchIndex = 0; - this._numTotalResults = -1; - await this.getResults(query); - } - - runInAction(() => { - this._resultsOpen = true; - this._searchbarOpen = true; - this._openNoResults = true; - this.resultsScrolled(); - }); - } - - getAllResults = async (query: string) => { - return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 }); - } - - private get filterQuery() { - const types = FilterBox.Instance.filterTypes; - const includeDeleted = FilterBox.Instance.getDataStatus(); - return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}" OR type_t:"extension"`).join(" ")})` : ""); - } - - - private NumResults = 25; - private lockPromise?: Promise<void>; - getResults = async (query: string) => { - if (this.lockPromise) { - await this.lockPromise; - } - this.lockPromise = new Promise(async res => { - while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) { - this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*" }).then(action(async (res: SearchUtil.DocSearchResult) => { - - // happens at the beginning - if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) { - this._numTotalResults = res.numFound; - } - - const highlighting = res.highlighting || {}; - const highlightList = res.docs.map(doc => highlighting[doc[Id]]); - const lines = new Map<string, string[]>(); - res.docs.map((doc, i) => lines.set(doc[Id], res.lines[i])); - const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc)); - const highlights: typeof res.highlighting = {}; - docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]); - const filteredDocs = FilterBox.Instance.filterDocsByType(docs); - runInAction(() => { - // this._results.push(...filteredDocs); - filteredDocs.forEach(doc => { - const index = this._resultsSet.get(doc); - const highlight = highlights[doc[Id]]; - const line = lines.get(doc[Id]) || []; - const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []; - if (index === undefined) { - this._resultsSet.set(doc, this._results.length); - this._results.push([doc, hlights, line]); - } else { - this._results[index][1].push(...hlights); - this._results[index][2].push(...line); - } - }); - }); - - this._curRequest = undefined; - })); - this._maxSearchIndex += this.NumResults; - - await this._curRequest; - } - this.resultsScrolled(); - res(); - }); - return this.lockPromise; - } - - collectionRef = React.createRef<HTMLSpanElement>(); - startDragCollection = async () => { - const res = await this.getAllResults(FilterBox.Instance.getFinalQuery(this._searchString)); - const filtered = FilterBox.Instance.filterDocsByType(res.docs); - const docs = filtered.map(doc => { - const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); - if (isProto) { - return Doc.MakeDelegate(doc); - } else { - return Doc.MakeAlias(doc); - } - }); - let x = 0; - let y = 0; - for (const doc of docs.map(d => Doc.Layout(d))) { - doc.x = x; - doc.y = y; - const size = 200; - const aspect = NumCast(doc._nativeHeight) / NumCast(doc._nativeWidth, 1); - if (aspect > 1) { - doc._height = size; - doc._width = size / aspect; - } else if (aspect > 0) { - doc._width = size; - doc._height = size * aspect; - } else { - doc._width = size; - doc._height = size; - } - x += 250; - if (x > 1000) { - x = 0; - y += 300; - } - } - //return Docs.Create.TreeDocument(docs, { _width: 200, _height: 400, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` }); - return Docs.Create.QueryDocument({ _width: 200, _height: 400, searchText: this._searchString, title: `Query Docs: "${this._searchString}"` }); - } - - @action.bound - openSearch(e: React.SyntheticEvent) { - e.stopPropagation(); - this._openNoResults = false; - FilterBox.Instance.closeFilter(); - this._resultsOpen = true; - this._searchbarOpen = true; - FilterBox.Instance._pointerTime = e.timeStamp; - } - - @action.bound - closeSearch = () => { - FilterBox.Instance.closeFilter(); - this.closeResults(); - this._searchbarOpen = false; - } - - @action.bound - closeResults() { - this._resultsOpen = false; - this._results = []; - this._resultsSet.clear(); - this._visibleElements = []; - this._numTotalResults = -1; - this._endIndex = -1; - this._curRequest = undefined; - } - - @action - resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => { - if (!this.resultsRef.current) return; - const scrollY = e ? e.currentTarget.scrollTop : this.resultsRef.current ? this.resultsRef.current.scrollTop : 0; - const itemHght = 53; - const startIndex = Math.floor(Math.max(0, scrollY / itemHght)); - const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this.resultsRef.current.getBoundingClientRect().height / itemHght))); - - 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, string[], string[]] | undefined = undefined; - if (i >= this._results.length) { - this.getResults(this._searchString); - if (i < this._results.length) result = this._results[i]; - if (result) { - const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; - this._isSearch[i] = "search"; - } - } - else { - result = this._results[i]; - if (result) { - const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; - this._isSearch[i] = "search"; - } - } - } - } - } - if (this._maxSearchIndex >= this._numTotalResults) { - this._visibleElements.length = this._results.length; - this._isSearch.length = this._results.length; - } - } - - @computed - get resFull() { return this._numTotalResults <= 8; } - - @computed - get resultHeight() { return this._numTotalResults * 70; } - - render() { - const isEditing = this.editingMetadata; - return !this.content ? (null) : ( - <div style={{ pointerEvents: "all" }}> - <ContentFittingDocumentView {...this.props} - Document={this.content} - rootSelected={returnFalse} - bringToFront={returnFalse} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} - NativeWidth={returnZero} - NativeHeight={returnZero} - parentActive={this.props.active} - ScreenToLocalTransform={this.props.ScreenToLocalTransform}> - </ContentFittingDocumentView> - <div - style={{ - position: "absolute", - right: 0, - width: 20, - height: 20, - background: "black", - pointerEvents: "all", - opacity: 1, - transition: "0.4s opacity ease", - zIndex: 99, - top: 0, - }} - title={"Add Metadata"} - onClick={action(() => this.editingMetadata = !this.editingMetadata)} - /> - <div className="editableclass" onKeyPress={this.enter} style={{ opacity: isEditing ? 1 : 0, pointerEvents: isEditing ? "auto" : "none", transition: "0.4s opacity ease", position: "absolute", top: 0, left: 0, height: 20, width: "-webkit-fill-available" }}> - <EditableView - contents={this.query} - SetValue={this.updateKey} - GetValue={() => ""} - /> - </div> - </div > - ); - } - -}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss index b8b72e756..f5c4299a9 100644 --- a/src/client/views/collections/CollectionLinearView.scss +++ b/src/client/views/collections/CollectionLinearView.scss @@ -2,7 +2,7 @@ @import "../_nodeModuleOverrides"; .collectionLinearView-outer { - overflow: hidden; + overflow: visible; height: 100%; .collectionLinearView { diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index a1f5ff657..9ae72ba53 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1,37 +1,35 @@ import React = require("react"); +import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, reaction, runInAction, Lambda } from "mobx"; +import { Tooltip } from "@material-ui/core"; +import { action, computed, Lambda, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, Opt, Field } from "../../../fields/Doc"; -import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types"; -import AntimodeMenu from "../AntimodeMenu"; -import "./CollectionMenu.scss"; -import { undoBatch } from "../../util/UndoManager"; -import { CollectionViewType, CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView"; -import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils"; -import { DragManager } from "../../util/DragManager"; -import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; -import { List } from "../../../fields/List"; -import { EditableView } from "../EditableView"; +import { ColorState } from "react-color"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; +import { Document } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; -import { listSpec } from "../../../fields/Schema"; -import FormatShapePane from "./collectionFreeForm/FormatShapePane"; -import { ActiveFillColor, SetActiveInkWidth, ActiveInkColor, SetActiveBezierApprox, SetActiveArrowEnd, SetActiveArrowStart, SetActiveFillColor, SetActiveInkColor } from "../InkingStroke"; -import GestureOverlay from "../GestureOverlay"; import { InkTool } from "../../../fields/InkField"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { Document } from "../../../fields/documentSchemas"; -import { SelectionManager } from "../../util/SelectionManager"; -import { DocumentView } from "../nodes/DocumentView"; -import { ColorState } from "react-color"; +import { List } from "../../../fields/List"; import { ObjectField } from "../../../fields/ObjectField"; -import RichTextMenu from "../nodes/formattedText/RichTextMenu"; import { RichTextField } from "../../../fields/RichTextField"; +import { listSpec } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { DocUtils } from "../../documents/Documents"; -import { Tooltip } from "@material-ui/core"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; +import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils"; +import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { DragManager } from "../../util/DragManager"; +import { SelectionManager } from "../../util/SelectionManager"; +import { undoBatch } from "../../util/UndoManager"; +import AntimodeMenu from "../AntimodeMenu"; +import { EditableView } from "../EditableView"; +import GestureOverlay from "../GestureOverlay"; +import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke"; +import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; +import { DocumentView } from "../nodes/DocumentView"; +import RichTextMenu from "../nodes/formattedText/RichTextMenu"; +import "./CollectionMenu.scss"; +import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView"; @observer export default class CollectionMenu extends AntimodeMenu { @@ -185,7 +183,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp initialize: (button: Doc) => { button['target-docFilters'] = this.target._docFilters instanceof ObjectField ? ObjectField.MakeCopy(this.target._docFilters as any as ObjectField) : ""; }, }; - @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } + @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } @computed get _stacking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } @computed get _masonry_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } @computed get _schema_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; } @@ -236,7 +234,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp @computed get subChrome() { switch (this.props.type) { - default: + default: return this.otherSubChrome; case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />); case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />); case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" {...this.props} />); @@ -247,6 +245,21 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp case CollectionViewType.Docking: return (<CollectionDockingChrome key="collchrome" {...this.props} />); } } + + @computed get otherSubChrome() { + const docType = this.props.docView.Document.type; + switch (docType) { + default: return (null); + case DocumentType.IMG: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />); + case DocumentType.PDF: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />); + case DocumentType.INK: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />); + case DocumentType.WEB: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />); + case DocumentType.VID: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />); + case DocumentType.RTF: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} isDoc={true} />); + } + } + + private dropDisposer?: DragManager.DragDropDisposer; protected createDropTarget = (ele: HTMLDivElement) => { this.dropDisposer?.(); @@ -392,7 +405,7 @@ export class CollectionDockingChrome extends React.Component<CollectionMenuProps } @observer -export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps & { isOverlay: boolean }> { +export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps & { isOverlay: boolean, isDoc?: boolean }> { public static Instance: CollectionFreeFormViewChrome; constructor(props: any) { super(props); @@ -590,34 +603,36 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu </div>; } + @observable viewType = this.selectedDoc?._viewType; + render() { return !this.props.docView.layoutDoc ? (null) : <div className="collectionFreeFormMenu-cont"> - {this.props.docView.props.renderDepth !== 0 || this.isText ? (null) : + {this.props.docView.props.renderDepth !== 0 || this.isText || this.props.isDoc ? (null) : <Tooltip key="map" title={<div className="dash-tooltip">Toggle Mini Map</div>} placement="bottom"> - <div className="backKeyframe" onClick={this.miniMap}> + <div className="backKeyframe" onClick={this.miniMap} style={{ marginRight: "5px" }}> <FontAwesomeIcon icon={"map"} size={"lg"} /> </div> </Tooltip> } - {!this.isText ? <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom"> + {!this.isText && !this.props.isDoc ? <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom"> <div className="backKeyframe" onClick={this.prevKeyframe}> <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> </div> </Tooltip> : null} - {!this.isText ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom"> + {!this.isText && !this.props.isDoc ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom"> <div className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }} onClick={action(() => this.document.editing = !this.document.editing)} > {NumCast(this.document.currentFrame)} </div> </Tooltip> : null} - {!this.isText ? <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom"> + {!this.isText && !this.props.isDoc ? <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom"> <div className="fwdKeyframe" onClick={this.nextKeyframe}> <FontAwesomeIcon icon={"caret-right"} size={"lg"} /> </div> </Tooltip> : null} - {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText ? (null) : + {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText || this.props.isDoc ? (null) : <Tooltip key="hypothesis" title={<div className="dash-tooltip">Use Hypothesis</div>} placement="bottom"> <button className={"antimodeMenu-button"} key="hypothesis" style={{ @@ -655,6 +670,24 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu getKeySuggestions = async (value: string): Promise<string[]> => { value = value.toLowerCase(); const docs = DocListCast(this.document[this.props.fieldKey]); + + if (Doc.UserDoc().noviceMode) { + if (docs instanceof Doc) { + const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || + key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || + (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_")); + return keys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1); + } else { + const keys = new Set<string>(); + docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); + const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || + key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 || + key.indexOf("lastModified") >= 0 || (key[0].toUpperCase() === key[0] && + key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_")); + return noviceKeys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1); + } + } + if (docs instanceof Doc) { return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value)); } else { diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index eecaf7672..d11d6a5ba 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -32,6 +32,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import { DateField } from "../../../fields/DateField"; +import { RichTextField } from "../../../fields/RichTextField"; const path = require('path'); library.add(faExpand); @@ -193,7 +194,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc); const onItemDown = (e: React.PointerEvent) => { - fieldIsDoc && SetupDrag(this._focusRef, + //fieldIsDoc && + SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, this._document[props.fieldKey] instanceof Doc ? (doc: Doc | Doc[], target: Doc | undefined, addDoc: (newDoc: Doc | Doc[]) => any) => addDoc(doc) : this.props.moveDocument, this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); @@ -240,25 +242,72 @@ export class CollectionSchemaCell extends React.Component<CellProps> { // <FontAwesomeIcon icon="expand" size="sm" /> // </div> // ); - trace(); - - - + 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)); + } + } + term = term.toLowerCase(); + const search = StrCast(this.props.Document._searchString).toLowerCase(); + let start = term.indexOf(search); + let tally = 0; + if (start !== -1) { + positions.push(start); + } + while (start < contents.length && start !== -1) { + term = term.slice(start + search.length + 1); + tally += start + search.length + 1; + start = term.indexOf(search); + positions.push(tally + start); + } + if (positions.length > 1) { + positions.pop(); + } + } return ( - <div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}> + <div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} + ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}> <div className={className} ref={this._focusRef} onPointerDown={onItemDown} tabIndex={-1}> <div className="collectionSchemaView-cellContents" ref={type === undefined || type === "document" ? this.dropRef : null} key={props.Document[Id]}> - - <EditableView + positions={positions.length > 0 ? positions : undefined} + search={StrCast(this.props.Document._searchString) ? StrCast(this.props.Document._searchString) : undefined} editing={this._isEditing} isEditingCallback={this.isEditingCallback} display={"inline"} contents={contents ? contents : type === "number" ? "0" : "undefined"} + highlight={positions.length > 0 ? true : undefined} //contents={StrCast(contents)} height={"auto"} maxHeight={Number(MAX_ROW_HEIGHT)} placeholder={"enter value"} + bing={() => { + const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); + if (cfield !== undefined) { + console.log(typeof (cfield)); + // 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)); + } + } + }} GetValue={() => { if (type === "number" && (contents === 0 || contents === "0")) { return "0"; @@ -272,6 +321,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const val = cscript !== undefined ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : Field.IsField(cfield) ? Field.toScriptString(cfield) : ""; return val; + } }} @@ -827,3 +877,69 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { ); } } + + +@observer +export class CollectionSchemaButtons extends CollectionSchemaCell { + + render() { + // const reference = React.createRef<HTMLDivElement>(); + // const onItemDown = (e: React.PointerEvent) => { + // (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined : + // SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e)); + // }; + const doc = this.props.rowProps.original; + let buttons: JSX.Element | undefined = undefined; + buttons = <div style={{ + paddingTop: 8, + paddingLeft: 3, + }}><button onClick={() => { + doc.searchMatch = false; + setTimeout(() => doc.searchMatch = true, 0); + doc.searchIndex = NumCast(doc.searchIndex); + }} style={{ padding: 2, left: 77 }}> + <FontAwesomeIcon icon="arrow-up" size="sm" /> + </button> + <button onClick={() => { + { + doc.searchMatchAlt = false; + setTimeout(() => doc.searchMatchAlt = true, 0); + doc.searchIndex = NumCast(doc.searchIndex); + } + }} style={{ padding: 2 }}> + <FontAwesomeIcon icon="arrow-down" size="sm" /> + </button></div>; + const type = StrCast(doc.type); + if (type === "pdf") { + buttons = <div><button + style={{ + position: "relative", + height: 30, + width: 28, + left: 1, + }} + + onClick={() => { + doc.searchMatch = false; + setTimeout(() => doc.searchMatch = true, 0); + doc.searchIndex = NumCast(doc.searchIndex); + }}> + <FontAwesomeIcon icon="arrow-down" size="sm" /> + </button></div >; + } + else if (type !== "rtf") { + buttons = undefined; + } + + if (BoolCast(this.props.Document._searchDoc) === true) { + + } + else { + buttons = undefined; + } + return ( + <div> {buttons}</div> + ); + } +} + diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index efff4db98..e65adcf76 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -9,6 +9,8 @@ import { ColumnType } from "./CollectionSchemaView"; import { faFile } from "@fortawesome/free-regular-svg-icons"; import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField"; import { undoBatch } from "../../util/UndoManager"; +import { Doc } from "../../../fields/Doc"; +import { StrCast } from "../../../fields/Types"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -76,14 +78,6 @@ export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHe - - - - - - - - export interface ColumnMenuProps { columnField: SchemaHeaderField; // keyValue: string; @@ -288,9 +282,10 @@ export interface KeysDropdownProps { existingKeys: string[]; canAddNew: boolean; addNew: boolean; - onSelect: (oldKey: string, newKey: string, addnew: boolean) => void; + onSelect: (oldKey: string, newKey: string, addnew: boolean, filter?: string) => void; setIsEditing: (isEditing: boolean) => void; width?: string; + docs?: Doc[]; } @observer export class KeysDropdown extends React.Component<KeysDropdownProps> { @@ -306,10 +301,23 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { @action onSelect = (key: string): void => { - this.props.onSelect(this._key, key, this.props.addNew); - this.setKey(key); + if (key.slice(0, this._key.length) === this._key && this._key !== key) { + const filter = key.slice(this._key.length - key.length); + this.props.onSelect(this._key, this._key, this.props.addNew, filter); + } + else { + this.props.onSelect(this._key, key, this.props.addNew); + this.setKey(key); + this._isOpen = false; + this.props.setIsEditing(false); + } + } + + @action + onSelect2 = (key: string): void => { + this._searchTerm = this._searchTerm.slice(0, this._key.length) + key; this._isOpen = false; - this.props.setIsEditing(false); + } @undoBatch @@ -371,22 +379,53 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { }); // if search term does not already exist as a group type, give option to create new group type - if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) { - options.push(<div key={""} className="key-option" style={{ + if (this._key !== this._searchTerm.slice(0, this._key.length)) { + if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) { + options.push(<div key={""} className="key-option" style={{ + border: "1px solid lightgray", + width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" + }} + onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}> + Create "{this._searchTerm}" key</div>); + } + } + + return options; + } + + renderFilterOptions = (): JSX.Element[] | JSX.Element => { + if (!this._isOpen) return <></>; + const keyOptions: string[] = []; + const temp = this._searchTerm.slice(this._key.length); + this.props.docs?.forEach((doc) => { + const key = StrCast(doc[this._key]); + if (keyOptions.includes(key) === false && key.includes(temp)) { + keyOptions.push(key); + } + }); + + + const options = keyOptions.map(key => { + return <div key={key} className="key-option" style={{ border: "1px solid lightgray", width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" }} - onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}> - Create "{this._searchTerm}" key</div>); - } + onPointerDown={e => e.stopPropagation()} onClick={() => { this.onSelect2(key); }}>{key}</div>; + }); return options; } + render() { return ( - <div className="keys-dropdown" style={{ width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" }}> - <input className="keys-search" //style={{ width: this.props.width, maxWidth: "1000" }} + <div className="keys-dropdown" style={{ zIndex: 10, width: this.props.width, maxWidth: this.props.width }}> + {this._key === this._searchTerm.slice(0, this._key.length) ? + <div style={{ position: "absolute", marginLeft: "4px", marginTop: "3", color: "grey", pointerEvents: "none", lineHeight: 1.15 }}> + {this._key} + </div> + : undefined} + <input className="keys-search" style={{ width: "100%" }} ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown} onChange={e => this.onChange(e.target.value)} onClick={(e) => { @@ -395,10 +434,11 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { }} onFocus={this.onFocus} onBlur={this.onBlur}></input> <div className="keys-options-wrapper" style={{ backgroundColor: "white", - width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" + width: this.props.width, maxWidth: this.props.width, }} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}> - {this.renderOptions()} + {this._key === this._searchTerm.slice(0, this._key.length) ? + this.renderFilterOptions() : this.renderOptions()} </div> </div > ); diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index b77173b25..dade4f2f2 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -209,6 +209,14 @@ export class MovableRow extends React.Component<MovableRowProps> { return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc); } + @action + onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { + console.log("yes"); + if (e.key === "Backspace" || e.key === "Delete") { + undoBatch(() => this.props.removeDoc(this.props.rowInfo.original)); + } + } + render() { const { children = null, rowInfo } = this.props; if (!rowInfo) { @@ -227,14 +235,14 @@ export class MovableRow extends React.Component<MovableRowProps> { if (this.props.rowWrapped) className += " row-wrapped"; return ( - <div className={className} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}> - <div className="collectionSchema-row-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> - <ReactTableDefaults.TrComponent> - <div className="row-dragger"> + <div className={className} onKeyPress={this.onKeyDown} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}> + <div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> + <ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown} > + {/* <div className="row-dragger"> <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div> <div className="row-option" style={{ cursor: "grab" }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div> <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, "onRight")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div> - </div> + </div> */} {children} </ReactTableDefaults.TrComponent> </div> diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index 5226a60f1..ba0a259c5 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -6,7 +6,7 @@ border-style: solid; border-radius: $border-radius; box-sizing: border-box; - position: absolute; + position: relative; top: 0; width: 100%; height: 100%; @@ -25,7 +25,6 @@ .collectionSchemaView-tableContainer { width: 100%; height: 100%; - overflow: scroll; } .collectionSchemaView-dividerDragger { @@ -59,9 +58,7 @@ } .rt-thead { - width: calc(100% - 52px); - margin-left: 50px; - + width: 100%; z-index: 100; overflow-y: visible; @@ -96,7 +93,7 @@ } .rt-tbody { - width: calc(100% - 2px); + width: 100%; direction: rtl; overflow: visible; } @@ -164,16 +161,6 @@ .collectionSchema-col { height: 100%; - - .collectionSchema-col-wrapper { - &.col-before { - border-left: 2px solid red; - } - - &.col-after { - border-right: 2px solid red; - } - } } @@ -297,7 +284,6 @@ button.add-column { background-color: white; border: 1px solid lightgray; padding: 2px 3px; - overflow-x: hidden; &:not(:first-child) { border-top: 0; @@ -525,14 +511,21 @@ button.add-column { .collectionSchemaView-table { width: 100%; height: 100%; - overflow: visible; } .reactTable-sub { padding: 10px 30px; background-color: rgb(252, 252, 252); - width: calc(100% - 50px); - margin-left: 50px; + width: 100%; + + .rt-thead { + display:none; + } + .collectionSchemaView-table{ + border: solid 1px; + overflow: hidden; + } + .row-dragger { background-color: rgb(252, 252, 252); @@ -567,7 +560,6 @@ button.add-column { text-transform: uppercase; cursor: pointer; font-size: 10.5px; - padding: 10px; margin-left: 50px; margin-top: 10px; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index f67e049fd..a003de0d3 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -4,27 +4,26 @@ import { faCog, faPlus, faSortDown, faSortUp, faTable } from '@fortawesome/free- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, untracked } from "mobx"; import { observer } from "mobx-react"; +import Measure from "react-measure"; import { Resize } from "react-table"; import "react-table/react-table.css"; import { Doc } from "../../../fields/Doc"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { Docs, DocumentOptions } from "../../documents/Documents"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { Cast, NumCast } from "../../../fields/Types"; +import { TraceMobx } from "../../../fields/util"; +import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents } from "../../../Utils"; +import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss'; import '../DocumentDecorations.scss'; +import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; import { KeysDropdown } from "./CollectionSchemaHeaders"; import "./CollectionSchemaView.scss"; import { CollectionSubView } from "./CollectionSubView"; -import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; -import { setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils"; -import { SnappingManager } from "../../util/SnappingManager"; -import Measure from "react-measure"; import { SchemaTable } from "./SchemaTable"; -import { TraceMobx } from "../../../fields/util"; library.add(faCog, faPlus, faSortUp, faSortDown); library.add(faTable); @@ -170,6 +169,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => { const columns = this.columns; + columns.forEach(col => col.setDesc(undefined)); + const index = columns.findIndex(c => c.heading === columnField.heading); const column = columns[index]; column.setDesc(descending); @@ -310,7 +311,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @undoBatch @action - changeColumns = (oldKey: string, newKey: string, addNew: boolean) => { + changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => { const columns = this.columns; if (columns === undefined) { this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]); @@ -325,6 +326,20 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { column.setHeading(newKey); columns[index] = column; this.columns = columns; + if (filter) { + Doc.setDocFilter(this.props.Document, newKey, filter, "match"); + if (this.props.Document.selectedDoc !== undefined) { + const doc = Cast(this.props.Document.selectedDoc, Doc) as Doc; + Doc.setDocFilter(doc, newKey, filter, "match"); + } + } + else { + this.props.Document._docFilters = undefined; + if (this.props.Document.selectedDoc !== undefined) { + const doc = Cast(this.props.Document.selectedDoc, Doc) as Doc; + doc._docFilters = undefined; + } + } } } } @@ -591,6 +606,10 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { } } + + + onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { + } render() { TraceMobx(); const menuContent = this.renderMenuContent; @@ -608,14 +627,15 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>} </Measure> </div>; - return <div className="collectionSchemaView-container" style={{ + overflow: this.props.overflow === true ? "auto" : undefined, pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined, - width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%" + width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", }} > <div className="collectionSchemaView-tableContainer" - style={{ width: `calc(100% - ${this.previewWidth()}px)` }} + style={{ backgroundColor: "white", width: `calc(100% - ${this.previewWidth()}px)` }} + onKeyPress={this.onKeyPress} onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index fbc4e1552..f193a9787 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -369,7 +369,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC </div> {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled' && type !== DocumentType.PRES) ? <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" - style={{ width: style.columnWidth / style.numGroupColumns }}> + style={{ width: style.columnWidth / style.numGroupColumns, marginBottom: 70 }}> <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} /> </div> : null} </div> diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 3ebc6baca..4025e25f9 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,7 +1,7 @@ import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx"; import { basename } from 'path'; import CursorField from "../../../fields/CursorField"; -import { Doc, Opt, Field } from "../../../fields/Doc"; +import { Doc, Opt, Field, DocListCast } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; @@ -112,9 +112,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: [...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])]; } @computed get childDocs() { + let rawdocs: (Doc | Promise<Doc>)[] = DocListCast(this.props.Document._searchDocs); - let rawdocs: (Doc | Promise<Doc>)[] = []; - if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; + if (rawdocs.length !== 0) { + } else if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; rawdocs = [this.dataField]; } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it. rawdocs = Cast(this.dataField, listSpec(Doc), null); @@ -126,11 +127,66 @@ 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 searchDocs = DocListCast(this.props.Document._searchDocs); + // if (searchDocs !== undefined && searchDocs.length > 0) { + // let newdocs: Doc[] = []; + // childDocs.forEach((el) => { + // searchDocs.includes(el) ? newdocs.push(el) : undefined; + // }); + // childDocs = newdocs; + // } + + let docsforFilter: Doc[] = childDocs; + if (searchDocs !== undefined && searchDocs.length > 0) { + docsforFilter = []; + // let newdocs: Doc[] = []; + // let newarray: Doc[] = []; + //while (childDocs.length > 0) { + //newarray = []; + childDocs.forEach((d) => { + if (d.data !== undefined) { + console.log(d); + let newdocs = DocListCast(d.data); + if (newdocs.length > 0) { + let vibecheck: boolean | undefined = undefined; + let newarray: Doc[] = []; + while (newdocs.length > 0) { + newarray = []; + newdocs.forEach((t) => { + if (d.data !== undefined) { + const newdocs = DocListCast(t.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + if (searchDocs.includes(t)) { + vibecheck = true; + } + }); + newdocs = newarray; + } + if (vibecheck === true) { + docsforFilter.push(d); + } + } + } + if (searchDocs.includes(d)) { + docsforFilter.push(d); + } + }); + //childDocs = newarray; + //} + } + childDocs = docsforFilter; + + const docFilters = this.docFilters(); - const viewSpecScript = ScriptCast(this.props.Document.viewSpecScript); const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); - return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, docFilters, docRangeFilters, viewSpecScript); + return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, this.docFilters(), docRangeFilters, viewSpecScript); } @action @@ -436,4 +492,3 @@ import { CollectionView, CollectionViewType } from "./CollectionView"; import { SelectionManager } from "../../util/SelectionManager"; import { OverlayView } from "../OverlayView"; import { setTimeout } from "timers"; - diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 50f0534bd..c9bf82406 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -22,7 +22,7 @@ ul { list-style: none; padding-left: 20px; - margin-bottom: 1px;// otherwise vertical scrollbars may pop up for no apparent reason.... + margin-bottom: 1px; // otherwise vertical scrollbars may pop up for no apparent reason.... } @@ -35,7 +35,7 @@ width: 15px; color: $intermediate-color; margin-top: 3px; - transform: scale(1.3, 1.3); + transform: scale(1.3, 1.3); border: #80808030 1px solid; border-radius: 4px; } @@ -67,8 +67,10 @@ margin-left: 3px; display: none; } + .collectionTreeView-keyHeader:hover { background: #797777; + cursor: pointer; } .collectionTreeView-subtitle { @@ -89,8 +91,10 @@ height: 17px; width: 15px; } + .treeViewItem-openRight:hover { background: #797777; + cursor: pointer; } .treeViewItem-border { @@ -106,10 +110,12 @@ .editableView-container-editing-oneLine { min-width: 15px; } + .documentView-node-topmost { width: unset; } - > svg { + + >svg { display: none; } @@ -119,7 +125,8 @@ .collectionTreeView-keyHeader { display: inherit; } - > svg { + + >svg { display: inherit; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 89034a0c0..6e15cb887 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -74,7 +74,7 @@ export enum CollectionViewType { Pile = "pileup" } export interface CollectionViewCustomProps { - filterAddDocument: (doc: Doc | Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) + filterAddDocument?: (doc: Doc | Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) childLayoutTemplate?: () => Opt<Doc>; // specify a layout Doc template to use for children of the collection childLayoutString?: string; // specify a layout string to use for children of the collection childOpacity?: () => number; @@ -88,6 +88,7 @@ export interface CollectionRenderProps { active: () => boolean; whenActiveChanged: (isActive: boolean) => void; PanelWidth: () => number; + PanelHeight: () => number; ChildLayoutTemplate?: () => Doc; ChildLayoutString?: string; } @@ -523,6 +524,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus </div> <div className="collectionTimeView-tree" key="tree"> <CollectionTreeView + PanelPosition={""} Document={facetCollection} DataDoc={facetCollection} fieldKey={`${this.props.fieldKey}-filter`} @@ -574,6 +576,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus active: this.active, whenActiveChanged: this.whenActiveChanged, PanelWidth: this.bodyPanelWidth, + PanelHeight: this.props.PanelHeight, ChildLayoutTemplate: this.childLayoutTemplate, ChildLayoutString: this.childLayoutString, }; @@ -582,7 +585,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus return (<div className={"collectionView"} onContextMenu={this.onContextMenu} style={{ pointerEvents: this.props.Document.isBackground ? "none" : undefined, boxShadow }}> {this.showIsTagged()} - <div className="collectionView-facetCont" style={{ width: `calc(100% - ${this.facetWidth()}px)` }}> + <div className="collectionView-facetCont" style={{ display: this.props.PanelPosition === "absolute" ? "flex" : "", justifyContent: this.props.PanelPosition === "absolute" ? "center" : "", width: `calc(100% - ${this.facetWidth()}px)` }}> {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} </div> {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d => diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx index 7e2840c2c..75d484cbe 100644 --- a/src/client/views/collections/SchemaTable.tsx +++ b/src/client/views/collections/SchemaTable.tsx @@ -11,22 +11,22 @@ import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../fields/Types"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types"; +import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils"; import { Docs, DocumentOptions } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; import { CompileScript, Transformer, ts } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss'; import { ContextMenu } from "../ContextMenu"; import '../DocumentDecorations.scss'; -import { CellProps, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDocCell, CollectionSchemaNumberCell, CollectionSchemaStringCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaDateCell } from "./CollectionSchemaCells"; +import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; +import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells"; import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders"; import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC"; import "./CollectionSchemaView.scss"; import { CollectionView } from "./CollectionView"; -import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; -import { emptyFunction, returnZero, returnOne, returnFalse, returnEmptyFilter, emptyPath } from "../../../Utils"; -import { TouchScrollableMenuItem } from "../TouchScrollableMenu"; enum ColumnType { @@ -128,7 +128,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } @computed get sorted(): SortingRule[] { return this.props.columns.reduce((sorted, shf) => { - shf.desc && sorted.push({ id: shf.heading, desc: shf.desc }); + shf.desc !== undefined && sorted.push({ id: shf.heading, desc: shf.desc }); return sorted; }, [] as SortingRule[]); } @@ -160,7 +160,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { const focusedCol = this._focusedCell.col; const isEditable = !this.props.headerIsEditing; - if (this.childDocs.reduce((found, doc) => found || doc.type === "collection", false)) { + if (this.childDocs.reduce((found, doc) => found || doc.type === DocumentType.COL, false)) { columns.push( { expander: true, @@ -177,6 +177,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } ); } + console.log(columns); const cols = this.props.columns.map(col => { @@ -188,7 +189,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { addNew={false} onSelect={this.props.changeColumns} setIsEditing={this.props.setHeaderIsEditing} - + docs={this.props.childDocs} // try commenting this out width={"100%"} />; @@ -216,21 +217,21 @@ export class SchemaTable extends React.Component<SchemaTableProps> { className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: "2px", - display: "flex" + display: "flex", cursor: "default", height: "100%", }}> - <FontAwesomeIcon icon={icon} size="lg" style={{ display: "inline", paddingLeft: "7px" }} /> - <div className="keys-dropdown" - style={{ display: "inline", zIndex: 1000 }}> - {keysDropdown} - </div> + <FontAwesomeIcon icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px" }} /> + {/* <div className="keys-dropdown" + style={{ display: "inline", zIndex: 1000 }}> */} + {keysDropdown} + {/* </div> */} <div onClick={e => this.changeSorting(col)} - style={{ paddingRight: "6px", marginLeft: "4px", display: "inline" }}> - <FontAwesomeIcon icon={sortIcon} size="sm" /> - </div> - <div onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} - style={{ float: "right", paddingRight: "6px" }}> - <FontAwesomeIcon icon={"cog"} size="sm" /> + style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit" }}> + <FontAwesomeIcon icon={sortIcon} size="lg" /> </div> + {/* <div onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} + style={{ float: "right", paddingRight: "6px", zIndex: 1, background: "inherit" }}> + <FontAwesomeIcon icon={"compass"} size="sm" /> + </div> */} </div>; return { @@ -283,13 +284,63 @@ export class SchemaTable extends React.Component<SchemaTableProps> { Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />, accessor: (doc: Doc) => 0, id: "add", - Cell: (rowProps: CellInfo) => <></>, + Cell: (rowProps: CellInfo) => { + const rowIndex = rowProps.index; + const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); + const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; + const props: CellProps = { + row: rowIndex, + col: columnIndex, + rowProps: rowProps, + isFocused: isFocused, + changeFocusedCellByIndex: this.changeFocusedCellByIndex, + CollectionView: this.props.CollectionView, + ContainingCollection: this.props.ContainingCollectionView, + Document: this.props.Document, + fieldKey: this.props.fieldKey, + renderDepth: this.props.renderDepth, + addDocTab: this.props.addDocTab, + pinToPres: this.props.pinToPres, + moveDocument: this.props.moveDocument, + setIsEditing: this.setCellIsEditing, + isEditable: isEditable, + setPreviewDoc: this.props.setPreviewDoc, + setComputed: this.setComputed, + getField: this.getField, + showDoc: this.showDoc, + }; + + return <CollectionSchemaButtons {...props} />; + }, width: 28, resizable: false }); + console.log(columns); return columns; } + + + @action + nextHighlight = (e: React.MouseEvent, doc: Doc) => { + e.preventDefault(); + e.stopPropagation(); + doc.searchMatch = false; + console.log(doc.searchMatch); + setTimeout(() => doc.searchMatch = true, 0); + console.log(doc.searchMatch); + + doc.searchIndex = NumCast(doc.searchIndex); + } + + @action + nextHighlight2 = (doc: Doc) => { + + doc.searchMatchAlt = false; + setTimeout(() => doc.searchMatchAlt = true, 0); + doc.searchIndex = NumCast(doc.searchIndex); + } + constructor(props: SchemaTableProps) { super(props); // convert old schema columns (list of strings) into new schema columns (list of schema header fields) @@ -574,7 +625,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { return <div className="collectionSchemaView-table" onPointerDown={this.props.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} > {this.reactTable} - <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div> + {StrCast(this.props.Document.type) !== "search" ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div> + : undefined} {!this._showDoc ? (null) : <div className="collectionSchemaView-documentPreview" //onClick={() => { this.onOpenClick(); }} style={{ diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 65cd28742..f2ad0ba58 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1258,7 +1258,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" }); optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" }); - appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); + optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); if (!Doc.UserDoc().noviceMode) { optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" }); diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss index 89124129b..5b41db90e 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.scss +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss @@ -41,7 +41,7 @@ font-size: 12.5px; &:hover { - cursor: pointer; + cursor: text; } } @@ -617,6 +617,7 @@ display: flex; margin-bottom: 3px; + margin-left: 4px; .arrows-head { @@ -650,7 +651,7 @@ .dashed { display: flex; - margin-left: 74px; + margin-left: 64px; margin-bottom: 6px; .dashed-title { @@ -662,4 +663,15 @@ margin-top: 2px; } } +} + +.editable-title { + border: none; + padding: 6px; + padding-bottom: 2px; + + + &:hover { + border: 0.75px solid rgb(122, 28, 28); + } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx index dcbf8e989..89f48fc65 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx @@ -29,6 +29,9 @@ import "./FormatShapePane.scss"; import { discovery_v1 } from "googleapis"; import { PresBox } from "../../nodes/PresBox"; import { DocumentManager } from "../../../util/DocumentManager"; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; interface PropertiesViewProps { @@ -74,6 +77,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @observable openPresProgressivize: boolean = true; @observable openSlideOptions: boolean = true; + @observable inActions: boolean = false; @observable _controlBtn: boolean = false; @observable _lock: boolean = false; @@ -174,7 +178,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); const rows: JSX.Element[] = []; for (const key of Object.keys(ids).slice().sort()) { - if ((key[0] === key[0].toUpperCase() && key.substring(0, 3) !== "ACL") + if ((key[0] === key[0].toUpperCase() && key.substring(0, 3) !== "ACL" && key !== "UseCors") || key[0] === "#" || key === "author" || key === "creationDate" || key.indexOf("lastModified") !== -1) { @@ -254,7 +258,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { rootSelected={returnFalse} treeViewDoc={undefined} backgroundColor={() => "lightgrey"} - fitToBox={false} + fitToBox={true} FreezeDimensions={true} NativeWidth={layoutDoc.type === StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : returnZero} @@ -277,6 +281,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { bringToFront={returnFalse} ContentScaling={returnOne} dontRegisterView={true} + dropAction={undefined} /> </div>; } else { @@ -367,20 +372,19 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { />; } - @undoBatch @action toggleCheckbox = () => { this.layoutFields = !this.layoutFields; } @computed get editableTitle() { - return <EditableView + return <div className="editable-title"><EditableView key="editableView" contents={StrCast(this.selectedDoc?.title)} height={25} fontSize={14} GetValue={() => StrCast(this.selectedDoc?.title)} - SetValue={this.setTitle} />; + SetValue={this.setTitle} /> </div>; } @undoBatch @@ -417,15 +421,14 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { var index = 0; if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { doc.rotation = Number(doc.rotation) + Number(angle); - const ink = Cast(doc.data, InkField)?.inkData; - if (ink) { - + const inks = Cast(doc.data, InkField)?.inkData; + if (inks) { const newPoints: { X: number, Y: number }[] = []; - for (var i = 0; i < ink.length; i++) { - const newX = Math.cos(angle) * (ink[i].X - _centerPoints[index].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].X; - const newY = Math.sin(angle) * (ink[i].X - _centerPoints[index].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].Y; + inks.forEach(ink => { + const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X; + const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y; newPoints.push({ X: newX, Y: newY }); - } + }); doc.data = new InkField(newPoints); const xs = newPoints.map(p => p.X); const ys = newPoints.map(p => p.Y); @@ -552,7 +555,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { getField(key: string) { //if (this.selectedDoc) { - return Field.toString(this?.[key] as Field); + return Field.toString(this.selectedDoc?.[key] as Field); // } else { // return undefined as Opt<string>; // } @@ -594,14 +597,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); } set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); } - colorButton(value: string, setter: () => {}) { - return <div className="color-button" key="color" onPointerDown={undoBatch(action(e => setter()))}> - <div className="color-button-preview" style={{ - backgroundColor: value ?? "121212", width: 15, height: 15, - display: value === "" || value === "transparent" ? "none" : "" - }} /> - {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -14, position: "fixed" }}>☒</p> : ""} - </div>; + colorButton(value: string, type: string, setter: () => {}) { + return <Flyout anchorPoint={anchorPoints.LEFT_TOP} + content={type === "fill" ? this.fillPicker : this.linePicker}> + <div className="color-button" key="color" onPointerDown={undoBatch(action(e => setter()))}> + <div className="color-button-preview" style={{ + backgroundColor: value ?? "121212", width: 15, height: 15, + display: value === "" || value === "transparent" ? "none" : "" + }} /> + {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -14, position: "fixed" }}>☒</p> : ""} + </div> + </Flyout>; + } @undoBatch @@ -627,8 +634,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { color={type === "stk" ? this.colorStk : this.colorFil} />; } - @computed get fillButton() { return this.colorButton(this.colorFil, () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); } - @computed get lineButton() { return this.colorButton(this.colorStk, () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); } + @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); } + @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); } @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); } @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); } @@ -645,8 +652,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="stroke-button">{this.lineButton}</div> </div> </div> - {this._fillBtn ? this.fillPicker : ""} - {this._lineBtn ? this.linePicker : ""} + {/* {this._fillBtn ? this.fillPicker : ""} + {this._lineBtn ? this.linePicker : ""} */} </div>; } @@ -753,201 +760,203 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return <div className="propertiesView" style={{ width: this.props.width }}> <div className="propertiesView-title" style={{ width: this.props.width }}> No Document Selected - <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> - <FontAwesomeIcon icon="times" color="black" size="xs" /> - </div> </div> </div>; - } - const novice = Doc.UserDoc().noviceMode; - - if (this.selectedDoc && !this.isPres) { - return <div className="propertiesView" style={{ width: this.props.width }} > - <div className="propertiesView-title" style={{ width: this.props.width }}> - Properties - <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> + } else { + const novice = Doc.UserDoc().noviceMode; + + if (this.selectedDoc && !this.isPres) { + return <div className="propertiesView" style={{ + width: this.props.width, + // overflowY: this.inActions ? "visible" : "scroll" + }} > + <div className="propertiesView-title" style={{ width: this.props.width }}> + Properties + {/* <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> <FontAwesomeIcon icon="times" color="black" size="sm" /> + </div> */} </div> - </div> - <div className="propertiesView-name"> - {this.editableTitle} - </div> - <div className="propertiesView-settings"> - <div className="propertiesView-settings-title" - onPointerDown={() => runInAction(() => { this.openActions = !this.openActions; })} - style={{ backgroundColor: this.openActions ? "black" : "" }}> - Actions - <div className="propertiesView-settings-title-icon"> - <FontAwesomeIcon icon={this.openActions ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-name"> + {this.editableTitle} + </div> + <div className="propertiesView-settings" onPointerEnter={() => runInAction(() => { this.inActions = true; })} + onPointerLeave={action(() => this.inActions = false)}> + <div className="propertiesView-settings-title" + onPointerDown={() => runInAction(() => { this.openActions = !this.openActions; })} + style={{ backgroundColor: this.openActions ? "black" : "" }}> + Actions + <div className="propertiesView-settings-title-icon"> + <FontAwesomeIcon icon={this.openActions ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {!this.openActions ? (null) : + <div className="propertiesView-settings-content"> + <PropertiesButtons /> + </div>} </div> - {!this.openActions ? (null) : - <div className="propertiesView-settings-content"> - <PropertiesButtons /> - </div>} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })} - style={{ backgroundColor: this.openSharing ? "black" : "" }}> - Sharing {"&"} Permissions - <div className="propertiesView-sharing-title-icon"> - <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })} + style={{ backgroundColor: this.openSharing ? "black" : "" }}> + Sharing {"&"} Permissions + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {!this.openSharing ? (null) : + <div className="propertiesView-sharing-content"> + {this.sharingTable} + </div>} </div> - {!this.openSharing ? (null) : - <div className="propertiesView-sharing-content"> - {this.sharingTable} + + {!this.isInk ? (null) : + <div className="propertiesView-appearance"> + <div className="propertiesView-appearance-title" + onPointerDown={() => runInAction(() => { this.openAppearance = !this.openAppearance; })} + style={{ backgroundColor: this.openAppearance ? "black" : "" }}> + Appearance + <div className="propertiesView-appearance-title-icon"> + <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + </div> + {!this.openAppearance ? (null) : + <div className="propertiesView-appearance-content"> + {this.appearanceEditor} + </div>} </div>} - </div> - {!this.isInk ? (null) : - <div className="propertiesView-appearance"> - <div className="propertiesView-appearance-title" - onPointerDown={() => runInAction(() => { this.openAppearance = !this.openAppearance; })} - style={{ backgroundColor: this.openAppearance ? "black" : "" }}> - Appearance - <div className="propertiesView-appearance-title-icon"> - <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" /> + {this.isInk ? <div className="propertiesView-transform"> + <div className="propertiesView-transform-title" + onPointerDown={() => runInAction(() => { this.openTransform = !this.openTransform; })} + style={{ backgroundColor: this.openTransform ? "black" : "" }}> + Transform + <div className="propertiesView-transform-title-icon"> + <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" /> </div> </div> - {!this.openAppearance ? (null) : - <div className="propertiesView-appearance-content"> - {this.appearanceEditor} - </div>} - </div>} - - {this.isInk ? <div className="propertiesView-transform"> - <div className="propertiesView-transform-title" - onPointerDown={() => runInAction(() => { this.openTransform = !this.openTransform; })} - style={{ backgroundColor: this.openTransform ? "black" : "" }}> - Transform - <div className="propertiesView-transform-title-icon"> - <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" /> - </div> - </div> - {this.openTransform ? <div className="propertiesView-transform-content"> - {this.transformEditor} + {this.openTransform ? <div className="propertiesView-transform-content"> + {this.transformEditor} + </div> : null} </div> : null} - </div> : null} - - <div className="propertiesView-fields"> - <div className="propertiesView-fields-title" - onPointerDown={() => runInAction(() => { this.openFields = !this.openFields; })} - style={{ backgroundColor: this.openFields ? "black" : "" }}> - <div className="propertiesView-fields-title-name"> - Fields {"&"} Tags - <div className="propertiesView-fields-title-icon"> - <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" /> + + <div className="propertiesView-fields"> + <div className="propertiesView-fields-title" + onPointerDown={() => runInAction(() => { this.openFields = !this.openFields; })} + style={{ backgroundColor: this.openFields ? "black" : "" }}> + <div className="propertiesView-fields-title-name"> + Fields {"&"} Tags + <div className="propertiesView-fields-title-icon"> + <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> </div> + {!novice && this.openFields ? <div className="propertiesView-fields-checkbox"> + {this.fieldsCheckbox} + <div className="propertiesView-fields-checkbox-text">Layout</div> + </div> : null} + {!this.openFields ? (null) : + <div className="propertiesView-fields-content"> + {novice ? this.noviceFields : this.expandedField} + </div>} </div> - {!novice && this.openFields ? <div className="propertiesView-fields-checkbox"> - {this.fieldsCheckbox} - <div className="propertiesView-fields-checkbox-text">Layout</div> - </div> : null} - {!this.openFields ? (null) : - <div className="propertiesView-fields-content"> - {novice ? this.noviceFields : this.expandedField} - </div>} - </div> - <div className="propertiesView-layout"> - <div className="propertiesView-layout-title" - onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })} - style={{ backgroundColor: this.openLayout ? "black" : "" }}> - Layout - <div className="propertiesView-layout-title-icon" onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}> - <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-layout"> + <div className="propertiesView-layout-title" + onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })} + style={{ backgroundColor: this.openLayout ? "black" : "" }}> + Layout + <div className="propertiesView-layout-title-icon" onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}> + <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openLayout ? <div className="propertiesView-layout-content">{this.layoutPreview}</div> : null} </div> - {this.openLayout ? <div className="propertiesView-layout-content">{this.layoutPreview}</div> : null} - </div> - </div>; - } - if (this.isPres) { - return <div className="propertiesView" style={{ width: this.props.width }} > - <div className="propertiesView-title" style={{ width: this.props.width }}> - Presentation - <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> - <FontAwesomeIcon icon="times" color="black" size="sm" /> + </div>; + } + if (this.isPres) { + return <div className="propertiesView" style={{ width: this.props.width }} > + <div className="propertiesView-title" style={{ width: this.props.width }}> + Presentation + <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> + <FontAwesomeIcon icon="times" color="black" size="sm" /> + </div> </div> - </div> - <div className="propertiesView-name"> - {this.editableTitle} - <div className="propertiesView-presSelected"> - {PresBox.Instance._selectedArray.length} selected - <div className="propertiesView-selectedList"> - {PresBox.Instance.listOfSelected} + <div className="propertiesView-name"> + {this.editableTitle} + <div className="propertiesView-presSelected"> + {PresBox.Instance._selectedArray.length} selected + <div className="propertiesView-selectedList"> + {PresBox.Instance.listOfSelected} + </div> </div> </div> - </div> - <div className="propertiesView-settings"> - <div className="propertiesView-settings-title" - onPointerDown={() => runInAction(() => { this.openAddSlide = !this.openAddSlide; })} - style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> - <FontAwesomeIcon icon={"plus"} /> Add new slide - <div className="propertiesView-settings-title-icon"> - <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-settings"> + <div className="propertiesView-settings-title" + onPointerDown={() => runInAction(() => { this.openAddSlide = !this.openAddSlide; })} + style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> + <FontAwesomeIcon icon={"plus"} /> Add new slide + <div className="propertiesView-settings-title-icon"> + <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openAddSlide ? <div className="propertiesView-settings-content"> + {PresBox.Instance.newDocumentDropdown} + </div> : null} </div> - {this.openAddSlide ? <div className="propertiesView-settings-content"> - {PresBox.Instance.newDocumentDropdown} - </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { this.openPresTransitions = !this.openPresTransitions; })} - style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> - <FontAwesomeIcon icon={"rocket"} /> Transitions - <div className="propertiesView-sharing-title-icon"> - <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openPresTransitions = !this.openPresTransitions; })} + style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> + <FontAwesomeIcon icon={"rocket"} /> Transitions + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openPresTransitions ? <div className="propertiesView-sharing-content"> + {PresBox.Instance.transitionDropdown} + </div> : null} </div> - {this.openPresTransitions ? <div className="propertiesView-sharing-content"> - {PresBox.Instance.transitionDropdown} - </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { this.openPresProgressivize = !this.openPresProgressivize; })} - style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> - <FontAwesomeIcon icon={"tasks"} /> Progressivize - <div className="propertiesView-sharing-title-icon"> - <FontAwesomeIcon icon={this.openPresProgressivize ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openPresProgressivize = !this.openPresProgressivize; })} + style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> + <FontAwesomeIcon icon={"tasks"} /> Progressivize + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openPresProgressivize ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openPresProgressivize ? <div className="propertiesView-sharing-content"> + {PresBox.Instance.progressivizeDropdown} + </div> : null} </div> - {this.openPresProgressivize ? <div className="propertiesView-sharing-content"> - {PresBox.Instance.progressivizeDropdown} - </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { this.openSlideOptions = !this.openSlideOptions; })} - style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> - <FontAwesomeIcon icon={"cog"} /> {PresBox.Instance.stringType} options - <div className="propertiesView-sharing-title-icon"> - <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openSlideOptions = !this.openSlideOptions; })} + style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> + <FontAwesomeIcon icon={"cog"} /> {PresBox.Instance.stringType} options + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openSlideOptions ? <div className="propertiesView-sharing-content"> + {PresBox.Instance.optionsDropdown} + </div> : null} </div> - {this.openSlideOptions ? <div className="propertiesView-sharing-content"> - {PresBox.Instance.optionsDropdown} - </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })} - style={{ backgroundColor: this.openSharing ? "black" : "" }}> - Sharing {"&"} Permissions - <div className="propertiesView-sharing-title-icon"> - <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })} + style={{ backgroundColor: this.openSharing ? "black" : "" }}> + Sharing {"&"} Permissions + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openSharing ? <div className="propertiesView-sharing-content"> + {this.sharingTable} + </div> : null} </div> - {this.openSharing ? <div className="propertiesView-sharing-content"> - {this.sharingTable} - </div> : null} - </div> - </div>; + </div>; + } } } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 21f77e47b..e6ac7021a 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -240,7 +240,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) { w: Math.min(w, this.numCols), // reduces width if greater than numCols static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout.lockedPosition, false) // checks if the lock position item has been selected in the context menu })) : - this.savedLayoutList.map((layout, index) => Object.assign(layout, this.unflexedPosition(index))); + this.savedLayoutList.map((layout, index) => { Object.assign(layout, this.unflexedPosition(index)); return layout; }); } /** diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss index d26b7920a..7e6999cdc 100644 --- a/src/client/views/linking/LinkEditor.scss +++ b/src/client/views/linking/LinkEditor.scss @@ -89,7 +89,7 @@ /* float: right; */ border-radius: 7px; font-size: 9px; - background-color: black; + background: black; /* padding: 3px; */ padding-top: 4px; padding-left: 7px; @@ -100,6 +100,7 @@ &:hover { cursor: pointer; + background: grey; } } } diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 660afd4b9..75fc8bf85 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -287,7 +287,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> { @observable openDropdown: boolean = false; @observable showInfo: boolean = false; @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; } - @observable private buttonColor: string = "black"; + @observable private buttonColor: string = ""; //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION"; @@ -303,7 +303,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> { if (LinkManager.currentLink) { LinkManager.currentLink.description = value; this.buttonColor = "rgb(62, 133, 55)"; - setTimeout(action(() => this.buttonColor = "black"), 750); + setTimeout(action(() => this.buttonColor = ""), 750); return true; } } @@ -345,7 +345,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> { ></input> </div> <div className="linkEditor-description-add-button" - style={{ backgroundColor: this.buttonColor }} + style={{ background: this.buttonColor }} onPointerDown={this.onDown}>Set</div> </div></div>; } diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 6feb0a398..f4aed94e7 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -191,7 +191,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { case DocumentType.AUDIO: destinationIcon = "microphone"; break; case DocumentType.BUTTON: destinationIcon = "bolt"; break; case DocumentType.PRES: destinationIcon = "tv"; break; - case DocumentType.QUERY: destinationIcon = "search"; break; case DocumentType.SCRIPTING: destinationIcon = "terminal"; break; case DocumentType.IMPORT: destinationIcon = "cloud-upload-alt"; break; case DocumentType.DOCHOLDER: destinationIcon = "expand"; break; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index e3f258b8d..2408b3906 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -25,7 +25,7 @@ import { ImageBox } from "./ImageBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; import { PresBox } from "./PresBox"; -import { QueryBox } from "./QueryBox"; +import { SearchBox } from "../search/SearchBox"; import { ColorBox } from "./ColorBox"; import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; import { LinkAnchorBox } from "./LinkAnchorBox"; @@ -192,7 +192,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { components={{ FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, MenuIconBox, LabelBox, SliderBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, - PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox, + PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox, ScreenshotBox, HTMLtag, ComparisonBox }} diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss index 97e714cd5..9328fb96b 100644 --- a/src/client/views/nodes/DocumentLinksButton.scss +++ b/src/client/views/nodes/DocumentLinksButton.scss @@ -28,7 +28,13 @@ } .documentLinksButton { - background-color: $link-color; + background-color: black; + + &:hover { + background: $main-accent; + transform: scale(1.05); + cursor: pointer; + } } .documentLinksButton-endLink { diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 025669b41..cb79e1522 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -181,7 +181,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp const linkButton = <div ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}> <div className={"documentLinksButton"} style={{ - backgroundColor: this.props.InMenu ? "black" : "", + backgroundColor: this.props.InMenu ? "" : "#add8e6", color: this.props.InMenu ? "white" : "black", width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold" }} @@ -202,8 +202,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp link : links.length} </div> - {DocumentLinksButton.StartLink && this.props.InMenu && !this.props.StartLink && - DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} + {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View ? + <div className={"documentLinksButton-endLink"} style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", backgroundColor: DocumentLinksButton.StartLink ? "" : "grey", @@ -218,7 +218,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp </div>; return (!links.length) && !this.props.AlwaysOn ? (null) : - this.props.InMenu ? + this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ? <Tooltip title={<><div className="dash-tooltip">{title}</div></>}> {linkButton} </Tooltip> : !!!DocumentLinksButton.EditLink && !this.props.InMenu ? diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e6e709621..b9e685b44 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -749,7 +749,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu moreItems.push({ description: "Download document", icon: "download", event: async () => Doc.Zip(this.props.Document) }); moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "users" }); //moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); - moreItems.push({ description: "Create an Alias", event: () => this.onCopy(), icon: "copy" }); + //moreItems.push({ description: "Create an Alias", event: () => this.onCopy(), icon: "copy" }); if (!Doc.UserDoc().noviceMode) { moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 1b4151bd8..e631ad5fe 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -49,11 +49,18 @@ export interface FieldViewProps { ignoreAutoHeight?: boolean; PanelWidth: () => number; PanelHeight: () => number; + PanelPosition?: string; + overflow?: boolean; NativeHeight: () => number; NativeWidth: () => number; setVideoBox?: (player: VideoBox) => void; ContentScaling: () => number; + ChromeHeight?: () => number; + childLayoutTemplate?: () => Opt<Doc>; + highlighting?: string[]; + lines?: string[]; + doc?: Doc; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) height?: number; width?: number; diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss index 5bdafd857..9709e1dbd 100644 --- a/src/client/views/nodes/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox.scss @@ -15,12 +15,17 @@ .menuButton-round { border-radius: 100%; + background-color: black; .fontIconBox-label { margin-left: -10px; // button padding is 10px; bottom: 0; position: absolute; } + + &:hover { + background-color: #aaaaa3; + } } .menuButton-square { diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index eff5a4160..c0eb78d98 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -63,16 +63,14 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( const color = StrCast(this.layoutDoc.color, this._foregroundColor); const backgroundColor = StrCast(this.layoutDoc._backgroundColor, StrCast(this.rootDoc.backgroundColor, this.props.backgroundColor?.(this.rootDoc))); const shape = StrCast(this.layoutDoc.iconShape, "round"); - const button = <> - <button className={`menuButton-${shape}`} ref={this._ref} onContextMenu={this.specificContextMenu} - style={{ boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined, backgroundColor }}> - <div className="menuButton-wrap"> - {<FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={StrCast(this.dataDoc.icon, "user") as any} color={color} - size={this.layoutDoc.iconShape === "square" ? "sm" : "lg"} />} - {!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>} - </div> - </button> - </>; + const button = <button className={`menuButton-${shape}`} ref={this._ref} onContextMenu={this.specificContextMenu} + style={{ boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined, backgroundColor: this.layoutDoc.iconShape === "square" ? backgroundColor : "" }}> + <div className="menuButton-wrap"> + {<FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={StrCast(this.dataDoc.icon, "user") as any} color={color} + size={this.layoutDoc.iconShape === "square" ? "sm" : "lg"} />} + {!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>} + </div> + </button>; return !this.layoutDoc.toolTip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}> {button} diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 05ba6628c..826ccd340 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -1,4 +1,4 @@ -import { action } from 'mobx'; +import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; @@ -56,16 +56,26 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument e.stopPropagation(); } } + + @observable _mouseOver = false; + @computed get backColor() { return this.clicked || this._mouseOver ? StrCast(this.layoutDoc.hovercolor) : "unset"; } + + @observable clicked = false; // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); const missingParams = params?.filter(p => !this.paramsDoc[p]); params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ... return ( - <div className="labelBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu} + <div className="labelBox-outerDiv" + onClick={action(() => this.clicked = !this.clicked)} + onMouseLeave={action(() => this._mouseOver = false)} + onMouseOver={action(() => this._mouseOver = true)} + ref={this.createDropTarget} onContextMenu={this.specificContextMenu} style={{ boxShadow: this.layoutDoc.opacity ? StrCast(this.layoutDoc.boxShadow) : "" }}> <div className="labelBox-mainButton" style={{ background: StrCast(this.layoutDoc.backgroundColor), + backgroundColor: this.backColor, color: StrCast(this.layoutDoc.color, "inherit"), fontSize: StrCast(this.layoutDoc._fontSize) || "inherit", fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit", diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx index 0fff0b57f..1b6056be6 100644 --- a/src/client/views/nodes/QueryBox.tsx +++ b/src/client/views/nodes/QueryBox.tsx @@ -1,41 +1,38 @@ -import React = require("react"); -import { IReactionDisposer } from "mobx"; -import { observer } from "mobx-react"; -import { documentSchema } from "../../../fields/documentSchemas"; -import { Id } from '../../../fields/FieldSymbols'; -import { makeInterface, listSpec } from "../../../fields/Schema"; -import { StrCast, Cast } from "../../../fields/Types"; -import { ViewBoxAnnotatableComponent } from '../DocComponent'; -import { SearchBox } from "../search/SearchBox"; -import { FieldView, FieldViewProps } from './FieldView'; -import "./QueryBox.scss"; -import { List } from "../../../fields/List"; -import { SnappingManager } from "../../util/SnappingManager"; +// import React = require("react"); +// import { IReactionDisposer } from "mobx"; +// import { observer } from "mobx-react"; +// import { documentSchema } from "../../../new_fields/documentSchemas"; +// import { Id } from '../../../new_fields/FieldSymbols'; +// import { makeInterface, listSpec } from "../../../new_fields/Schema"; +// import { StrCast, Cast } from "../../../new_fields/Types"; +// import { ViewBoxAnnotatableComponent } from '../DocComponent'; +// import { SearchBox } from "../search/SearchBox"; +// import { FieldView, FieldViewProps } from './FieldView'; +// import "./QueryBox.scss"; +// import { List } from "../../../new_fields/List"; +// import { SnappingManager } from "../../util/SnappingManager"; -type QueryDocument = makeInterface<[typeof documentSchema]>; -const QueryDocument = makeInterface(documentSchema); +// type QueryDocument = makeInterface<[typeof documentSchema]>; +// const QueryDocument = makeInterface(documentSchema); -@observer -export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); } - _docListChangedReaction: IReactionDisposer | undefined; - componentDidMount() { - } +// @observer +// export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) { +// public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); } +// _docListChangedReaction: IReactionDisposer | undefined; +// componentDidMount() { +// } - componentWillUnmount() { - this._docListChangedReaction?.(); - } +// componentWillUnmount() { +// this._docListChangedReaction?.(); +// } - render() { - const dragging = !SnappingManager.GetIsDragging() ? "" : "-dragging"; - return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} > - <SearchBox - id={this.props.Document[Id]} - setSearchQuery={q => this.dataDoc.searchQuery = q} - searchQuery={StrCast(this.dataDoc.searchQuery)} - setSearchFileTypes={q => this.dataDoc.searchFileTypes = new List<string>(q)} - searchFileTypes={Cast(this.dataDoc.searchFileTypes, listSpec("string"), [])} - filterQquery={StrCast(this.dataDoc.filterQuery)} /> - </div >; - } -}
\ No newline at end of file +// render() { +// const dragging = !SnappingManager.GetIsDragging() ? "" : "-dragging"; +// return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} > + +// <SearchBox Document={this.props.Document} /> +// </div >; +// } +// } + +// //<SearchBox id={this.props.Document[Id]} sideBar={side} Document={this.props.Document} searchQuery={StrCast(this.dataDoc.searchQuery)} filterQuery={this.dataDoc.filterQuery} /> diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 646a94aa7..3283f568a 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -536,7 +536,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum highlight = (color: string) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.Document); - const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color); + const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) ? this.makeAnnotationDocument(color) : undefined; annotationDoc && this.addDocument?.(annotationDoc); return annotationDoc ?? undefined; } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ab1de5529..af4bd77c7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -236,7 +236,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; - (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); + const lastmodified = "lastmodified"; + (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))) && (this.dataDoc[lastmodified] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) { !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize)); @@ -270,7 +271,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) { let node = this._editorView.state.doc; - while (node.firstChild) node = node.firstChild; + while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; const str = node.textContent; const titlestr = str.substr(0, Math.min(40, str.length)); this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); @@ -292,18 +293,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); } } - public highlightSearchTerms = (terms: string[]) => { + public highlightSearchTerms = (terms: string[], alt: boolean) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { + const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + const length = res[0].length; let tr = this._editorView.state.tr; const flattened: TextSelection[] = []; res.map(r => r.map(h => flattened.push(h))); + + const lastSel = Math.min(flattened.length - 1, this._searchIndex); flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); + if (alt === true) { + if (this._searchIndex > 1) { + this._searchIndex += -2; + } + else if (this._searchIndex === 1) { + this._searchIndex = length - 1; + } + else if (this._searchIndex === 0 && length !== 1) { + this._searchIndex = length - 2; + } + + } + else { + + } + const index = this._searchIndex; + + Doc.GetProto(this.dataDoc).searchIndex = index; } } @@ -314,6 +337,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); const end = this._editorView.state.doc.nodeSize - 2; this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); + } if (FormattedTextBox.PasteOnLoad) { const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfOrigin"); @@ -799,8 +823,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.setupEditor(this.config, this.props.fieldKey); + this._disposers.searchAlt = reaction(() => this.rootDoc.searchMatchAlt, + search => search ? this.highlightSearchTerms([Doc.SearchQuery()], false) : this.unhighlightSearchTerms(), + { fireImmediately: true }); this._disposers.search = reaction(() => this.rootDoc.searchMatch, - search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), + search => search ? this.highlightSearchTerms([Doc.SearchQuery()], true) : this.unhighlightSearchTerms(), { fireImmediately: this.rootDoc.searchMatch ? true : false }); this._disposers.record = reaction(() => this._recording, diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 5af07c15d..459632ec8 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -274,7 +274,7 @@ export default class RichTextMenu extends AntimodeMenu { } !activeFamilies.length && (activeFamilies.push(StrCast(this.TextView.layoutDoc._fontFamily, StrCast(Doc.UserDoc().fontFamily)))); !activeSizes.length && (activeSizes.push(StrCast(this.TextView.layoutDoc._fontSize, StrCast(Doc.UserDoc().fontSize)))); - !activeColors.length && (activeSizes.push(StrCast(this.TextView.layoutDoc.color, StrCast(Doc.UserDoc().fontColor)))); + !activeColors.length && (activeColors.push(StrCast(this.TextView.layoutDoc.color, StrCast(Doc.UserDoc().fontColor)))); } !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily))); !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize))); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 192a6300a..cfa9a1844 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -106,6 +106,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu private _scrollTopReactionDisposer?: IReactionDisposer; private _filterReactionDisposer?: IReactionDisposer; private _searchReactionDisposer?: IReactionDisposer; + private _searchReactionDisposer2?: IReactionDisposer; private _viewer: React.RefObject<HTMLDivElement> = React.createRef(); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _selectionText: string = ""; @@ -336,6 +337,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu nextAnnotation = () => { this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1); this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]); + this.Document.searchIndex = this.Index; + } @action @@ -408,6 +411,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu phraseSearch: true, query: searchString }); + this.Document.searchIndex = this.Index; } else if (this._mainCont.current) { const executeFind = () => { @@ -421,7 +425,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu }; this._mainCont.current.addEventListener("pagesloaded", executeFind); this._mainCont.current.addEventListener("pagerendered", executeFind); + this.Document.searchIndex = this.Index; } + } @action diff --git a/src/client/views/search/FieldFilters.scss b/src/client/views/search/FieldFilters.scss deleted file mode 100644 index e1d0d8df5..000000000 --- a/src/client/views/search/FieldFilters.scss +++ /dev/null @@ -1,12 +0,0 @@ -.field-filters { - width: 100%; - display: grid; - // grid-template-columns: 18% 20% 60%; - grid-template-columns: 20% 25% 60%; -} - -.field-filters-required { - width: 100%; - display: grid; - grid-template-columns: 50% 50%; -}
\ No newline at end of file diff --git a/src/client/views/search/FieldFilters.tsx b/src/client/views/search/FieldFilters.tsx deleted file mode 100644 index 7a33282d2..000000000 --- a/src/client/views/search/FieldFilters.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from 'react'; -import { observable } from 'mobx'; -import { CheckBox } from './CheckBox'; -import { Keys } from './FilterBox'; -import "./FieldFilters.scss"; - -export interface FieldFilterProps { - titleFieldStatus: boolean; - dataFieldStatus: boolean; - authorFieldStatus: boolean; - updateTitleStatus(stat: boolean): void; - updateAuthorStatus(stat: boolean): void; - updateDataStatus(stat: boolean): void; -} - -export class FieldFilters extends React.Component<FieldFilterProps> { - - static Instance: FieldFilters; - - @observable public _resetBoolean = false; - @observable public _resetCounter: number = 0; - - constructor(props: FieldFilterProps) { - super(props); - FieldFilters.Instance = this; - } - - resetFieldFilters() { - this._resetBoolean = true; - } - - render() { - return ( - <div className="field-filters"> - <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.titleFieldStatus} updateStatus={this.props.updateTitleStatus} title={Keys.TITLE} /> - <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.authorFieldStatus} updateStatus={this.props.updateAuthorStatus} title={Keys.AUTHOR} /> - <CheckBox default={false} numCount={3} parent={this} originalStatus={this.props.dataFieldStatus} updateStatus={this.props.updateDataStatus} title={"Deleted Docs"} /> - </div> - ); - } -}
\ No newline at end of file diff --git a/src/client/views/search/FilterBox.scss b/src/client/views/search/FilterBox.scss deleted file mode 100644 index 094ea9cc5..000000000 --- a/src/client/views/search/FilterBox.scss +++ /dev/null @@ -1,178 +0,0 @@ -@import "../globalCssVariables"; -@import "./NaviconButton.scss"; - -.filter-form { - padding: 25px; - width: 440px; - position: relative; - right: 1px; - color: grey; - flex-direction: column; - display: inline-block; - transform-origin: top; - overflow: auto; - border-bottom: solid black 3px; - - .top-filter-header { - - #header { - text-transform: uppercase; - letter-spacing: 2px; - font-size: 13; - width: 80%; - } - - .close-icon { - width: 20%; - opacity: .6; - position: relative; - display: block; - - .line { - display: block; - background: $alt-accent; - width: 20; - height: 3; - position: absolute; - right: 0; - border-radius: ($height-line / 2); - - &.line-1 { - transform: rotate(45deg); - top: 45%; - } - - &.line-2 { - transform: rotate(-45deg); - top: 45%; - } - } - } - - .close-icon:hover { - opacity: 1; - } - - } - - .filter-options { - - .filter-div { - margin-top: 10px; - margin-bottom: 10px; - display: inline-block; - width: 100%; - border-color: rgba(178, 206, 248, .2); // $darker-alt-accent - border-top-style: solid; - - .filter-header { - display: flex; - align-items: center; - margin-bottom: 10px; - letter-spacing: 2px; - - .filter-title { - font-size: 13; - text-transform: uppercase; - margin-top: 10px; - margin-bottom: 10px; - -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; - } - } - - .filter-header:hover .filter-title { - transform: scale(1.05); - } - - .filter-panel { - max-height: 0px; - width: 100%; - overflow: hidden; - opacity: 0; - transform-origin: top; - -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; - text-align: center; - } - } - } - - .filter-buttons { - border-color: rgba(178, 206, 248, .2); // $darker-alt-accent - 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; - - .save-filter, - .reset-filter, - .all-filter { - background-color: gray; - } - - .save-filter:hover, - .reset-filter:hover, - .all-filter:hover { - background-color: $darker-alt-accent; - } - } -}
\ No newline at end of file diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx deleted file mode 100644 index eb61f9a14..000000000 --- a/src/client/views/search/FilterBox.tsx +++ /dev/null @@ -1,431 +0,0 @@ -import * as React from 'react'; -import { observer } from 'mobx-react'; -import { observable, action } from 'mobx'; -import "./SearchBox.scss"; -import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-svg-icons'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { Doc } from '../../../fields/Doc'; -import { Id } from '../../../fields/FieldSymbols'; -import { DocumentType } from "../../documents/DocumentTypes"; -import { Cast, StrCast } from '../../../fields/Types'; -import * as _ from "lodash"; -import { IconBar } from './IconBar'; -import { FieldFilters } from './FieldFilters'; -import { SelectionManager } from '../../util/SelectionManager'; -import { DocumentView } from '../nodes/DocumentView'; -import { CollectionFilters } from './CollectionFilters'; -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", - AUTHOR = "author", - DATA = "data" -} - -@observer -export class FilterBox extends React.Component { - - static Instance: FilterBox; - public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, 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 _anyKeywordStatus: boolean = true; - @observable private _allKeywordStatus: boolean = true; - @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; - @observable private _collectionParentStatus = true; - @observable private _wordStatusOpen: boolean = false; - @observable private _typeOpen: boolean = false; - @observable private _colOpen: boolean = false; - @observable private _fieldOpen: boolean = false; - public _pointerTime: number = -1; - - constructor(props: Readonly<{}>) { - super(props); - FilterBox.Instance = this; - } - setupAccordion() { - $('document').ready(function () { - const acc = document.getElementsByClassName('filter-header'); - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < acc.length; i++) { - acc[i].addEventListener("click", function (this: HTMLElement) { - this.classList.toggle("active"); - - const panel = this.nextElementSibling as HTMLElement; - if (panel.style.maxHeight) { - panel.style.overflow = "hidden"; - panel.style.maxHeight = ""; - panel.style.opacity = "0"; - } else { - setTimeout(() => { - panel.style.overflow = "visible"; - }, 200); - setTimeout(() => { - panel.style.opacity = "1"; - }, 50); - panel.style.maxHeight = panel.scrollHeight + "px"; - - } - }); - - const el = acc[i] as HTMLElement; - el.click(); - } - }); - } - - @action.bound - minimizeAll() { - $('document').ready(function () { - const acc = document.getElementsByClassName('filter-header'); - - // tslint:disable-next-line: prefer-for-of - for (var i = 0; i < acc.length; i++) { - const classList = acc[i].classList; - if (classList.contains("active")) { - acc[i].classList.toggle("active"); - const panel = acc[i].nextElementSibling as HTMLElement; - panel.style.overflow = "hidden"; - panel.style.maxHeight = ""; - } - } - }); - } - - @action.bound - resetFilters = () => { - this._basicWordStatus = true; - IconBar.Instance.selectAll(); - FieldFilters.Instance.resetFieldFilters(); - } - - basicRequireWords(query: string): string { - const oldWords = query.split(" "); - const newWords: string[] = []; - oldWords.forEach(word => { - const newWrd = "+" + word; - newWords.push(newWrd); - }); - query = newWords.join(" "); - - return query; - } - - basicFieldFilters(query: string, type: string): string { - const oldWords = query.split(" "); - let mod = ""; - - if (type === Keys.AUTHOR) { - mod = " author_t:"; - } if (type === Keys.DATA) { - //TODO - } if (type === Keys.TITLE) { - mod = " title_t:"; - } - - const newWords: string[] = []; - oldWords.forEach(word => { - const newWrd = mod + word; - newWords.push(newWrd); - }); - - query = newWords.join(" "); - - return query; - } - - applyBasicFieldFilters(query: string) { - let finalQuery = ""; - - if (this._titleFieldStatus) { - finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE); - } - if (this._authorFieldStatus) { - finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR); - } - if (this._deletedDocsStatus) { - finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA); - } - return finalQuery; - } - - get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); } - - //TODO: basically all of this - //gets all of the collections of all the docviews that are selected - //if a collection is the only thing selected, search only in that collection (not its container) - getCurCollections(): Doc[] { - const selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments(); - const collections: Doc[] = []; - - selectedDocs.forEach(async element => { - const layout: string = StrCast(element.props.Document.layout); - //checks if selected view (element) is a collection. if it is, adds to list to search through - if (layout.indexOf("Collection") > -1) { - //makes sure collections aren't added more than once - if (!collections.includes(element.props.Document)) { - collections.push(element.props.Document); - } - } - //makes sure collections aren't added more than once - if (element.props.ContainingCollectionDoc && !collections.includes(element.props.ContainingCollectionDoc)) { - collections.push(element.props.ContainingCollectionDoc); - } - }); - - return collections; - } - - getFinalQuery(query: string): string { - //alters the query so it looks in the correct fields - //if this is true, then not all of the field boxes are checked - //TODO: data - if (this.fieldFiltersApplied) { - query = this.applyBasicFieldFilters(query); - query = query.replace(/\s+/g, ' ').trim(); - } - - //alters the query based on if all words or any words are required - //if this._wordstatus is false, all words are required and a + is added before each - if (!this._basicWordStatus) { - query = this.basicRequireWords(query); - query = query.replace(/\s+/g, ' ').trim(); - } - - //if should be searched in a specific collection - if (this._collectionStatus) { - query = this.addCollectionFilter(query); - query = query.replace(/\s+/g, ' ').trim(); - } - return query; - } - - addCollectionFilter(query: string): string { - const collections: Doc[] = this.getCurCollections(); - const oldWords = query.split(" "); - - const collectionString: string[] = []; - collections.forEach(doc => { - const proto = doc.proto; - const protoId = (proto || doc)[Id]; - const colString: string = "{!join from=data_l to=id}id:" + protoId + " "; - collectionString.push(colString); - }); - - let finalColString = collectionString.join(" "); - finalColString = finalColString.trim(); - return "+(" + finalColString + ")" + query; - } - - get filterTypes() { - return this._icons.length === 9 ? undefined : this._icons; - } - - @action - filterDocsByType(docs: Doc[]) { - if (this._icons.length === 9) { - return docs; - } - const finalDocs: Doc[] = []; - docs.forEach(doc => { - const layoutresult = Cast(doc.type, "string"); - if (layoutresult && this._icons.includes(layoutresult)) { - finalDocs.push(doc); - } - }); - 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; - SearchBox.Instance.closeResults(); - this.setupAccordion(); - } - - //if true, any keywords can be used. if false, all keywords are required. - @action.bound - handleWordQueryChange = () => { - this._basicWordStatus = !this._basicWordStatus; - } - - @action.bound - updateIcon(newArray: string[]) { this._icons = newArray; } - - @action.bound - getIcons(): string[] { return this._icons; } - - stopProp = (e: React.PointerEvent) => { - e.stopPropagation(); - this._pointerTime = e.timeStamp; - } - - @action.bound - public closeFilter() { - this._filterOpen = false; - } - - @action.bound - updateAnyKeywordStatus(newStat: boolean) { this._anyKeywordStatus = newStat; } - - @action.bound - updateAllKeywordStatus(newStat: boolean) { this._allKeywordStatus = newStat; } - - @action.bound - updateTitleStatus(newStat: boolean) { this._titleFieldStatus = newStat; } - - @action.bound - updateAuthorStatus(newStat: boolean) { this._authorFieldStatus = newStat; } - - @action.bound - updateDataStatus(newStat: boolean) { this._deletedDocsStatus = newStat; } - - @action.bound - updateCollectionStatus(newStat: boolean) { this._collectionStatus = newStat; } - - @action.bound - updateSelfCollectionStatus(newStat: boolean) { this._collectionSelfStatus = newStat; } - - @action.bound - updateParentCollectionStatus(newStat: boolean) { this._collectionParentStatus = newStat; } - - getAnyKeywordStatus() { return this._anyKeywordStatus; } - getAllKeywordStatus() { return this._allKeywordStatus; } - getCollectionStatus() { return this._collectionStatus; } - getSelfCollectionStatus() { return this._collectionSelfStatus; } - getParentCollectionStatus() { return this._collectionParentStatus; } - getTitleStatus() { return this._titleFieldStatus; } - getAuthorStatus() { return this._authorFieldStatus; } - getDataStatus() { return this._deletedDocsStatus; } - - getActiveFilters() { - 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 - render() { - return ( - <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", background: "black" } : { display: "none" }}> - <div className="top-filter-header" style={{ display: "flex", width: "100%" }}> - <div id="header">Filter Search Results</div> - <div style={{ marginLeft: "auto" }}></div> - <div className="close-icon" onClick={this.closeFilter}> - <span className="line line-1"></span> - <span className="line line-2"></span></div> - </div> - <div className="filter-options"> - <div className="filter-div"> - <div className="filter-header"> - <div className='filter-title words'>Required words</div> - </div> - <div className="filter-panel" > - <button className="all-filter" onClick={this.handleWordQueryChange}>Include All Keywords</button> - </div> - </div> - <div className="filter-div"> - <div className="filter-header"> - <div className="filter-title icon">Filter by type of node</div> - </div> - <div className="filter-panel"><IconBar /></div> - </div> - <div className="filter-div"> - <div className="filter-header"> - <div className="filter-title field">Filter by Basic Keys</div> - </div> - <div className="filter-panel"><FieldFilters - titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._deletedDocsStatus} authorFieldStatus={this._authorFieldStatus} - updateAuthorStatus={this.updateAuthorStatus} updateDataStatus={this.updateDataStatus} updateTitleStatus={this.updateTitleStatus} /> </div> - </div> - </div> - <div className="filter-buttons" style={{ display: "flex", justifyContent: "space-around" }}> - <button className="save-filter" >Save Filters</button> - <button className="reset-filter" onClick={this.resetFilters}>Reset Filters</button> - </div> - </div> - ) : - undefined} - </div> - ); - } -}
\ No newline at end of file diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss index bb62113a1..3f06ba7d3 100644 --- a/src/client/views/search/SearchBox.scss +++ b/src/client/views/search/SearchBox.scss @@ -17,10 +17,9 @@ .searchBox-bar { height: 32px; display: flex; - justify-content: flex-end; + justify-content: center; align-items: center; - padding-left: 2px; - + background-color: black; .searchBox-barChild { &.searchBox-collection { @@ -30,24 +29,29 @@ } &.searchBox-input { + margin:5px; + border-radius:20px; + border:black; display: block; width: 130px; -webkit-transition: width 0.4s; transition: width 0.4s; align-self: stretch; - + outline:none; } .searchBox-input:focus { width: 500px; - outline: 3px solid lightblue; + outline:none; } &.searchBox-filter { align-self: stretch; + button{ + transform:none; + } button:hover{ - transform:scale(1.0); - background:"#121721"; + transform:none; } } @@ -81,8 +85,6 @@ .no-result { width: 500px; background: $light-color-secondary; - border-color: $intermediate-color; - border-bottom-style: solid; padding: 10px; height: 50px; text-transform: uppercase; @@ -96,20 +98,20 @@ background: #121721; flex-direction: column; transform-origin: top; - transition: height 0.3s ease, display 0.6s ease; + transition: height 0.3s ease, display 0.6s ease, overflow 0.6s ease; height:0px; overflow:hidden; .filter-header { - display: flex; + //display: flex; position: relative; - flex-wrap:wrap; + //flex-wrap:wrap; right: 1px; color: grey; - flex-direction: row-reverse; + //flex-direction: row-reverse; transform-origin: top; - justify-content: space-evenly; + //justify-content: space-evenly; margin-bottom: 5px; overflow:hidden; transition:height 0.3s ease-out; @@ -130,9 +132,7 @@ color: grey; transform-origin: top; border-top: 0px; - //padding-top: 5px; - margin-left: 10px; - margin-right: 10px; + overflow:hidden; transition:height 0.3s ease-out; height:0px; @@ -144,30 +144,25 @@ color: grey; transform-origin: top; border-top: 0px; - //padding-top: 5px; - margin-left: 10px; - margin-right: 10px; overflow:hidden; transition:height 0.3s ease-out; height:0px; - .filter-keybar { - display: flex; - flex-wrap: wrap; - justify-content: space-evenly; - height: auto; - width: 100%; - flex-direction: row-reverse; - margin-top:5px; + + // .filter-keybar { + // display: flex; + // flex-wrap: wrap; + // justify-content: space-evenly; + // height: auto; + // width: 100%; + // flex-direction: row-reverse; + // margin-top:5px; - .filter-item { - position: relative; - border:1px solid grey; - border-radius: 16px; - - } - } - - + // .filter-item { + // position: relative; + // border:1px solid grey; + // border-radius: 16px; + // } + // } } } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 99fa6da21..1e44a379b 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,59 +1,66 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction, IReactionDisposer, reaction } from 'mobx'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Tooltip } from '@material-ui/core'; +import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as rp from 'request-promise'; -import { Doc } from '../../../fields/Doc'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; +import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; -import { Utils } from '../../../Utils'; +import { returnFalse, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; +import { DocumentType } from "../../documents/DocumentTypes"; +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { SetupDrag } from '../../util/DragManager'; import { SearchUtil } from '../../util/SearchUtil'; -import "./SearchBox.scss"; -import { SearchItem } from './SearchItem'; -import { IconBar } from './IconBar'; -import { FieldView } from '../nodes/FieldView'; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentView } from '../nodes/DocumentView'; import { SelectionManager } from '../../util/SelectionManager'; -import { listSpec } from '../../../fields/Schema'; - -library.add(faTimes); +import { Transform } from '../../util/Transform'; +import { CollectionView, CollectionViewType } from '../collections/CollectionView'; +import { ViewBoxBaseComponent } from "../DocComponent"; +import { DocumentView } from '../nodes/DocumentView'; +import { FieldView, FieldViewProps } from '../nodes/FieldView'; +import "./SearchBox.scss"; -export interface SearchProps { - id: string; - searchQuery: string; - filterQquery?: string; - setSearchQuery: (q: string) => {}; - searchFileTypes: string[]; - setSearchFileTypes: (types: string[]) => {}; -} +export const searchSchema = createSchema({ + id: "string", + Document: Doc, + searchQuery: "string", +}); export enum Keys { TITLE = "title", AUTHOR = "author", - DATA = "data" + DATA = "data", + TEXT = "text" } +type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>; +const SearchBoxDocument = makeInterface(documentSchema, searchSchema); + +//React.Component<SearchProps> @observer -export class SearchBox extends React.Component<SearchProps> { +export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) { - private get _searchString() { return this.props.searchQuery; } - private set _searchString(value) { this.props.setSearchQuery(value); } + get _searchString() { return this.layoutDoc.searchQuery; } + @computed set _searchString(value) { this.layoutDoc.searchQuery = (value); } @observable private _resultsOpen: boolean = false; - @observable private _searchbarOpen: boolean = false; + @observable _searchbarOpen: boolean = false; @observable private _results: [Doc, string[], string[]][] = []; @observable private _openNoResults: boolean = false; @observable private _visibleElements: JSX.Element[] = []; + @observable private _visibleDocuments: Doc[] = []; private _resultsSet = new Map<Doc, number>(); private _resultsRef = React.createRef<HTMLDivElement>(); public inputRef = React.createRef<HTMLInputElement>(); private _isSearch: ("search" | "placeholder" | undefined)[] = []; + private _isSorted: ("sorted" | "placeholder" | undefined)[] = []; + private _numTotalResults = -1; private _endIndex = -1; @@ -63,55 +70,104 @@ export class SearchBox extends React.Component<SearchProps> { private _curRequest?: Promise<any> = undefined; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); } - + private new_buckets: { [characterName: string]: number } = {}; //if true, any keywords can be used. if false, all keywords are required. //this also serves as an indicator if the word status filter is applied @observable private _basicWordStatus: boolean = false; @observable private _nodeStatus: boolean = false; @observable private _keyStatus: boolean = false; + @observable private newAssign: boolean = true; constructor(props: any) { super(props); SearchBox.Instance = this; this.resultsScrolled = this.resultsScrolled.bind(this); + } + @observable setupButtons = false; + componentDidMount = () => { + if (this.setupButtons === false) { - componentDidMount = action(() => { + runInAction(() => this.setupButtons = true); + } if (this.inputRef.current) { this.inputRef.current.focus(); - this._searchbarOpen = true; + runInAction(() => { this._searchbarOpen = true; }); } - if (this.props.searchQuery) { // bcz: why was this here? } && this.props.filterQquery) { - this._searchString = this.props.searchQuery; - this.submitSearch(); + if (this.rootDoc.searchQuery && this.newAssign) { + const sq = this.rootDoc.searchQuery; + runInAction(() => { + + // this._deletedDocsStatus=this.props.filterQuery!.deletedDocsStatus; + // this._authorFieldStatus=this.props.filterQuery!.authorFieldStatus + // this._titleFieldStatus=this.props.filterQuery!.titleFieldStatus; + // this._basicWordStatus=this.props.filterQuery!.basicWordStatus; + // this._icons=this.props.filterQuery!.icons; + this.newAssign = false; + }); + runInAction(() => { + this.layoutDoc._searchString = StrCast(sq); + this.submitSearch(); + }); } - }); + } @action getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc) + + @observable newsearchstring: string = ""; @action.bound onChange(e: React.ChangeEvent<HTMLInputElement>) { - this._searchString = e.target.value; + this.layoutDoc._searchString = e.target.value; + this.newsearchstring = e.target.value; - this._openNoResults = false; - this._results = []; - this._resultsSet.clear(); - this._visibleElements = []; - this._numTotalResults = -1; - this._endIndex = -1; - this._curRequest = undefined; - this._maxSearchIndex = 0; + + if (e.target.value === "") { + this._results.forEach(result => { + Doc.UnBrushDoc(result[0]); + result[0].searchMatch = undefined; + }); + + this.props.Document._schemaHeaders = new List<SchemaHeaderField>([]); + if (this.currentSelectedCollection !== undefined) { + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]); + this.currentSelectedCollection = undefined; + this.props.Document.selectedDoc = undefined; + + } + runInAction(() => { this.open = false; }); + this._openNoResults = false; + this._results = []; + this._resultsSet.clear(); + this._visibleElements = []; + this._numTotalResults = -1; + this._endIndex = -1; + this._curRequest = undefined; + this._maxSearchIndex = 0; + } } enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { + this.layoutDoc._searchString = this.newsearchstring; + + if (StrCast(this.layoutDoc._searchString) !== "" || !this.searchFullDB) { + runInAction(() => this.open = true); + } + else { + runInAction(() => this.open = false); + + } this.submitSearch(); } } + @observable open: boolean = false; + + public static async convertDataUri(imageUri: string, returnedFilename: string) { try { const posting = Utils.prepend("/uploadURI"); @@ -134,10 +190,11 @@ export class SearchBox extends React.Component<SearchProps> { //this also serves as an indicator if the word status filter is applied @observable private _filterOpen: boolean = false; //if icons = all icons, then no icon filter is applied - get _icons() { return this.props.searchFileTypes; } - set _icons(value) { - this.props.setSearchFileTypes(value); - } + // get _icons() { return this.props.searchFileTypes; } + // set _icons(value) { + // this.props.setSearchFileTypes(value); + // } + @observable _icons: string[] = this._allIcons; //if all of these are true, no key filter is applied @observable private _titleFieldStatus: boolean = true; @observable private _authorFieldStatus: boolean = true; @@ -162,10 +219,11 @@ export class SearchBox extends React.Component<SearchProps> { query = query.replace(/\s+/g, ' ').trim(); } - //if should be searched in a specific collection + // if should be searched in a specific collection if (this._collectionStatus) { query = this.addCollectionFilter(query); query = query.replace(/\s+/g, ' ').trim(); + } return query; } @@ -176,14 +234,14 @@ export class SearchBox extends React.Component<SearchProps> { @action filterDocsByType(docs: Doc[]) { - if (this._icons.length === this._allIcons.length) { - return docs; - } const finalDocs: Doc[] = []; + const blockedTypes: string[] = ["preselement", "docholder", "collection", "search", "searchitem", "script", "fonticonbox", "button", "label"]; docs.forEach(doc => { const layoutresult = Cast(doc.type, "string"); - if (layoutresult && this._icons.includes(layoutresult)) { - finalDocs.push(doc); + if (layoutresult && !blockedTypes.includes(layoutresult)) { + if (layoutresult && this._icons.includes(layoutresult)) { + finalDocs.push(doc); + } } }); return finalDocs; @@ -216,7 +274,6 @@ export class SearchBox extends React.Component<SearchProps> { getCurCollections(): Doc[] { const selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments(); const collections: Doc[] = []; - selectedDocs.forEach(async element => { const layout: string = StrCast(element.props.Document.layout); //checks if selected view (element) is a collection. if it is, adds to list to search through @@ -236,6 +293,85 @@ export class SearchBox extends React.Component<SearchProps> { } + currentSelectedCollection: DocumentView | undefined = undefined; + docsforfilter: Doc[] = []; + + searchCollection(query: string) { + const selectedCollection: DocumentView = SelectionManager.SelectedDocuments()[0]; + query = query.toLowerCase(); + if (selectedCollection !== undefined) { + this.currentSelectedCollection = selectedCollection; + if (this.filter === true) { + this.props.Document.selectedDoc = selectedCollection.props.Document; + } + let docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]); + const found: [Doc, string[], string[]][] = []; + const docsforFilter: Doc[] = []; + let newarray: Doc[] = []; + + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + newarray.push(...DocListCast(d.data)); + } + const hlights: string[] = []; + const protos = Doc.GetAllPrototypes(d); + protos.forEach(proto => { + Object.keys(proto).forEach(key => { + if (StrCast(d[key]).toLowerCase().includes(query) && !hlights.includes(key)) { + hlights.push(key); + } + }); + }); + if (hlights.length > 0) { + found.push([d, hlights, []]); + docsforFilter.push(d); + } + }); + docs = newarray; + } + this._results = found; + this.docsforfilter = docsforFilter; + if (this.filter === true) { + selectedCollection.props.Document._searchDocs = new List<Doc>(docsforFilter); + docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(docsforFilter); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + } + this._numTotalResults = found.length; + } + else { + this.noresults = "No collection selected :("; + } + + } + + + documentKeys(doc: Doc) { + const keys: { [key: string]: boolean } = {}; + // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. + // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be + // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked. + // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu + // is displayed (unlikely) it won't show up until something else changes. + //TODO Types + Doc.GetAllPrototypes(doc).map + (proto => Object.keys(proto).forEach(key => keys[key] = false)); + return Array.from(Object.keys(keys)); + } + applyBasicFieldFilters(query: string) { let finalQuery = ""; @@ -248,26 +384,24 @@ export class SearchBox extends React.Component<SearchProps> { if (this._deletedDocsStatus) { finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA); } + if (this._deletedDocsStatus) { + finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TEXT); + } return finalQuery; } basicFieldFilters(query: string, type: string): string { - const oldWords = query.split(" "); let mod = ""; - - if (type === Keys.AUTHOR) { - mod = " author_t:"; - } if (type === Keys.DATA) { - //TODO - } if (type === Keys.TITLE) { - mod = " title_t:"; + switch (type) { + case Keys.AUTHOR: mod = " author_t:"; break; + case Keys.DATA: break; // TODO + case Keys.TITLE: mod = " _title_t:"; break; + case Keys.TEXT: mod = " text_t:"; break; } const newWords: string[] = []; - oldWords.forEach(word => { - const newWrd = mod + word; - newWords.push(newWrd); - }); + const oldWords = query.split(" "); + oldWords.forEach(word => newWords.push(mod + word)); query = newWords.join(" "); @@ -276,30 +410,64 @@ export class SearchBox extends React.Component<SearchProps> { get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); } - @action - submitSearch = async () => { - const query = this._searchString; + submitSearch = async (reset?: boolean) => { + if (reset) { + this.layoutDoc._searchString = ""; + } + this.props.Document._docFilters = undefined; + this.noresults = ""; + + this.dataDoc[this.fieldKey] = new List<Doc>([]); + this.headercount = 0; + this.children = 0; + this.buckets = []; + this.new_buckets = {}; + const query = StrCast(this.layoutDoc._searchString); + Doc.SetSearchQuery(query); this.getFinalQuery(query); + this._results.forEach(result => { + Doc.UnBrushDoc(result[0]); + result[0].searchMatch = undefined; + }); this._results = []; this._resultsSet.clear(); this._isSearch = []; + this._isSorted = []; this._visibleElements = []; + this._visibleDocuments = []; + if (StrCast(this.props.Document.searchQuery)) { + if (this._timeout) { clearTimeout(this._timeout); this._timeout = undefined; } + this._timeout = setTimeout(() => { + console.log("Resubmitting search"); + }, 60000); + } + if (query !== "") { this._endIndex = 12; this._maxSearchIndex = 0; this._numTotalResults = -1; - await this.getResults(query); - + this.searchFullDB ? await this.getResults(query) : this.searchCollection(query); runInAction(() => { this._resultsOpen = true; this._searchbarOpen = true; this._openNoResults = true; this.resultsScrolled(); + }); } } + @observable searchFullDB = true; + + @observable _timeout: any = undefined; + + @observable firststring: string = ""; + @observable secondstring: string = ""; + + @observable bucketcount: number[] = []; + @observable buckets: Doc[] | undefined; + getAllResults = async (query: string) => { return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 }); } @@ -309,7 +477,7 @@ export class SearchBox extends React.Component<SearchProps> { const baseExpr = "NOT baseProto_b:true"; const includeDeleted = this.getDataStatus() ? "" : " NOT deleted_b:true"; const includeIcons = this.getDataStatus() ? "" : " NOT type_t:fonticonbox"; - // const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`; // this line was causing issues for me, check solr logging -syip2 + // const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`; // fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection q:text_t:hello const query = [baseExpr, includeDeleted, includeIcons].join(" AND ").replace(/AND $/, ""); return query; @@ -317,21 +485,20 @@ export class SearchBox extends React.Component<SearchProps> { getDataStatus() { return this._deletedDocsStatus; } - private NumResults = 25; private lockPromise?: Promise<void>; getResults = async (query: string) => { + console.log("Get"); if (this.lockPromise) { await this.lockPromise; } this.lockPromise = new Promise(async res => { while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) { - this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*" }).then(action(async (res: SearchUtil.DocSearchResult) => { + this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*", }).then(action(async (res: SearchUtil.DocSearchResult) => { // happens at the beginning if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) { this._numTotalResults = res.numFound; } - const highlighting = res.highlighting || {}; const highlightList = res.docs.map(doc => highlighting[doc[Id]]); const lines = new Map<string, string[]>(); @@ -340,19 +507,33 @@ export class SearchBox extends React.Component<SearchProps> { const highlights: typeof res.highlighting = {}; docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]); const filteredDocs = this.filterDocsByType(docs); + runInAction(() => { - //this._results.push(...filteredDocs); - filteredDocs.forEach(doc => { + filteredDocs.forEach((doc, i) => { const index = this._resultsSet.get(doc); const highlight = highlights[doc[Id]]; const line = lines.get(doc[Id]) || []; const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []; - if (index === undefined) { - this._resultsSet.set(doc, this._results.length); - this._results.push([doc, hlights, line]); - } else { - this._results[index][1].push(...hlights); - this._results[index][2].push(...line); + doc ? console.log(Cast(doc.context, Doc)) : null; + if (this.findCommonElements(hlights)) { + } + else { + const layoutresult = Cast(doc.type, "string"); + if (layoutresult) { + if (this.new_buckets[layoutresult] === undefined) { + this.new_buckets[layoutresult] = 1; + } + else { + this.new_buckets[layoutresult] = this.new_buckets[layoutresult] + 1; + } + } + if (index === undefined) { + this._resultsSet.set(doc, this._results.length); + this._results.push([doc, hlights, line]); + } else { + this._results[index][1].push(...hlights); + this._results[index][2].push(...line); + } } }); }); @@ -363,15 +544,16 @@ export class SearchBox extends React.Component<SearchProps> { await this._curRequest; } + this.resultsScrolled(); res(); }); return this.lockPromise; } - + @observable noresults = ""; collectionRef = React.createRef<HTMLSpanElement>(); startDragCollection = async () => { - const res = await this.getAllResults(this.getFinalQuery(this._searchString)); + const res = await this.getAllResults(this.getFinalQuery(StrCast(this.layoutDoc._searchString))); const filtered = this.filterDocsByType(res.docs); const docs = filtered.map(doc => { const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); @@ -404,7 +586,7 @@ export class SearchBox extends React.Component<SearchProps> { y += 300; } } - return Docs.Create.QueryDocument({ _autoHeight: true, title: this._searchString, filterQuery: this.filterQuery, searchQuery: this._searchString }); + return Docs.Create.SearchDocument({ _autoHeight: true, _viewType: CollectionViewType.Schema, title: StrCast(this.layoutDoc._searchString), searchQuery: StrCast(this.layoutDoc._searchString) }); } @action.bound @@ -417,7 +599,7 @@ export class SearchBox extends React.Component<SearchProps> { @action.bound closeSearch = () => { - this.closeResults(); + //this.closeResults(); this._searchbarOpen = false; } @@ -427,23 +609,30 @@ export class SearchBox extends React.Component<SearchProps> { this._results = []; this._resultsSet.clear(); this._visibleElements = []; + this._visibleDocuments = []; this._numTotalResults = -1; this._endIndex = -1; this._curRequest = undefined; } + @observable children: number = 0; @action resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => { if (!this._resultsRef.current) return; + this.props.Document._schemaHeaders = new List<SchemaHeaderField>([]); + const scrollY = e ? e.currentTarget.scrollTop : this._resultsRef.current ? this._resultsRef.current.scrollTop : 0; const itemHght = 53; const startIndex = Math.floor(Math.max(0, scrollY / itemHght)); - const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this._resultsRef.current.getBoundingClientRect().height / itemHght))); - - this._endIndex = endIndex === -1 ? 12 : endIndex; - + //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", "text"]); if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { - this._visibleElements = [<div className="no-result">No Search Results</div>]; + if (this.noresults === "") { + this.noresults = "No search results :("; + } return; } @@ -456,16 +645,19 @@ export class SearchBox extends React.Component<SearchProps> { else if (this._visibleElements.length !== this._numTotalResults) { // undefined until a searchitem is put in there this._visibleElements = Array<JSX.Element>(this._numTotalResults === -1 ? 0 : this._numTotalResults); - // indicates if things are placeholders + this._visibleDocuments = Array<Doc>(this._numTotalResults === -1 ? 0 : this._numTotalResults); + // indicates if things are placeholders this._isSearch = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults); - } + this._isSorted = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults); + } for (let i = 0; i < this._numTotalResults; i++) { //if the index is out of the window then put a placeholder in //should ones that have already been found get set to placeholders? if (i < startIndex || i > endIndex) { if (this._isSearch[i] !== "placeholder") { this._isSearch[i] = "placeholder"; + this._isSorted[i] = "placeholder"; this._visibleElements[i] = <div className="searchBox-placeholder" key={`searchBox-placeholder-${i}`}>Loading...</div>; } } @@ -473,30 +665,61 @@ export class SearchBox extends React.Component<SearchProps> { if (this._isSearch[i] !== "search") { let result: [Doc, string[], string[]] | undefined = undefined; if (i >= this._results.length) { - this.getResults(this._searchString); + this.getResults(StrCast(this.layoutDoc._searchString)); if (i < this._results.length) result = this._results[i]; if (result) { const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; + const lines = new List<string>(result[2]); + result[0].lines = lines; + result[0].highlighting = highlights.join(", "); + highlights.forEach((item) => headers.add(item)); + this._visibleDocuments[i] = result[0]; this._isSearch[i] = "search"; + Doc.BrushDoc(result[0]); + result[0].searchMatch = true; + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + this.children++; } } else { result = this._results[i]; if (result) { const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; - this._isSearch[i] = "search"; + const lines = new List<string>(result[2]); + highlights.forEach((item) => headers.add(item)); + result[0].lines = lines; + result[0].highlighting = highlights.join(", "); + result[0].searchMatch = true; + if (i < this._visibleDocuments.length) { + this._visibleDocuments[i] = result[0]; + this._isSearch[i] = "search"; + Doc.BrushDoc(result[0]); + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + this.children++; + } } } } } } + const schemaheaders: SchemaHeaderField[] = []; + this.headerscale = headers.size; + headers.forEach((item) => schemaheaders.push(new SchemaHeaderField(item, "#f1efeb"))); + this.headercount = schemaheaders.length; + this.props.Document._schemaHeaders = new List<SchemaHeaderField>(schemaheaders); if (this._maxSearchIndex >= this._numTotalResults) { this._visibleElements.length = this._results.length; + this._visibleDocuments.length = this._results.length; this._isSearch.length = this._results.length; } } + @observable headercount: number = 0; + @observable headerscale: number = 0; + + findCommonElements(arr2: string[]) { + const arr1 = ["layout", "data"]; + return arr1.some(item => arr2.includes(item)); + } @computed get resFull() { return this._numTotalResults <= 8; } @@ -504,165 +727,197 @@ export class SearchBox extends React.Component<SearchProps> { @computed get resultHeight() { return this._numTotalResults * 70; } - //if true, any keywords can be used. if false, all keywords are required. - @action.bound - handleWordQueryChange = () => { - this._basicWordStatus = !this._basicWordStatus; - } - - @action.bound - handleNodeChange = () => { - this._nodeStatus = !this._nodeStatus; - if (this._nodeStatus) { - this.expandSection(`node${this.props.id}`); - } - else { - this.collapseSection(`node${this.props.id}`); - } - } - - @action.bound - handleKeyChange = () => { - this._keyStatus = !this._keyStatus; - if (this._keyStatus) { - this.expandSection(`key${this.props.id}`); - } - else { - this.collapseSection(`key${this.props.id}`); - } - } - - @action.bound - handleFilterChange = () => { - this._filterOpen = !this._filterOpen; - if (this._filterOpen) { - this.expandSection(`filterhead${this.props.id}`); - document.getElementById(`filterhead${this.props.id}`)!.style.padding = "5"; - } - else { - this.collapseSection(`filterhead${this.props.id}`); + addButtonDoc = (doc: Doc) => Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); + remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); + moveButtonDoc = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc); + @computed get searchItemTemplate() { return Cast(Doc.UserDoc().searchItemTemplate, Doc, null); } - } + getTransform = () => { + return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight } - - @computed - get menuHeight() { - return document.getElementById("hi")?.clientHeight; + panelHeight = () => { + return this.props.PanelHeight(); } - - - collapseSection(thing: string) { - const id = this.props.id; - const element = document.getElementById(thing)!; - // get the height of the element's inner content, regardless of its actual size - const sectionHeight = element.scrollHeight; - - // temporarily disable all css transitions - const elementTransition = element.style.transition; - element.style.transition = ''; - - // on the next frame (as soon as the previous style change has taken effect), - // explicitly set the element's height to its current pixel height, so we - // aren't transitioning out of 'auto' - requestAnimationFrame(function () { - element.style.height = sectionHeight + 'px'; - element.style.transition = elementTransition; - - // on the next frame (as soon as the previous style change has taken effect), - // have the element transition to height: 0 - requestAnimationFrame(function () { - element.style.height = 0 + 'px'; - thing === `filterhead${id}` ? document.getElementById(`filterhead${id}`)!.style.padding = "0" : null; - }); - }); - - // mark the section as "currently collapsed" - element.setAttribute('data-collapsed', 'true'); - } - - expandSection(thing: string) { - const element = document.getElementById(thing)!; - // get the height of the element's inner content, regardless of its actual size - const sectionHeight = element.scrollHeight; - - // have the element transition to the height of its inner content - element.style.height = sectionHeight + 'px'; - - // when the next css transition finishes (which should be the one we just triggered) - element.addEventListener('transitionend', function handler(e) { - // remove this event listener so it only gets triggered once - element.removeEventListener('transitionend', handler); - - // remove "height" from the element's inline styles, so it can return to its initial value - element.style.height = "auto"; - //element.style.height = undefined; - }); - - // mark the section as "currently not collapsed" - element.setAttribute('data-collapsed', 'false'); - + selectElement = (doc: Doc) => { + //this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex)); } - autoset(thing: string) { - const element = document.getElementById(thing)!; - element.removeEventListener('transitionend', function (e) { }); - - // remove "height" from the element's inline styles, so it can return to its initial value - element.style.height = "auto"; - //element.style.height = undefined; + addDocument = (doc: Doc) => { + return null; } - @action.bound - updateTitleStatus() { this._titleFieldStatus = !this._titleFieldStatus; } - - @action.bound - updateAuthorStatus() { this._authorFieldStatus = !this._authorFieldStatus; } - - @action.bound - updateDataStatus() { this._deletedDocsStatus = !this._deletedDocsStatus; } + @observable filter = false; + //Make id layour document render() { - + this.props.Document._chromeStatus === "disabled"; + 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; + let height = 0; + const rows = this.children; + rows > 8 ? height = 31 + 31 * 8 : height = 31 * rows + 31; return ( - <div className="searchBox-container"> + <div style={{ pointerEvents: "all" }} className="searchBox-container"> <div className="searchBox-bar"> - <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, () => this._searchString ? this.startDragCollection() : undefined)} ref={this.collectionRef} title="Drag Results as Collection"> - <FontAwesomeIcon icon="object-group" size="lg" /> - </span> - <input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this.inputRef} - className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} onFocus={this.openSearch} - style={{ width: this._searchbarOpen ? "500px" : "100px" }} /> - <button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={() => this.handleFilterChange()}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button> - </div> - - <div id={`filterhead${this.props.id}`} className="filter-form" > - <div id={`filterhead2${this.props.id}`} className="filter-header" style={this._filterOpen ? {} : {}}> - <button className="filter-item" style={this._basicWordStatus ? { background: "#aaaaa3", } : {}} onClick={this.handleWordQueryChange}>Keywords</button> - <button className="filter-item" style={this._keyStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleKeyChange}>Keys</button> - <button className="filter-item" style={this._nodeStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleNodeChange}>Nodes</button> - </div> - <div id={`node${this.props.id}`} className="filter-body" style={this._nodeStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}> - <IconBar setIcons={(icons: string[]) => { - this._icons = icons; - }} /> - </div> - <div className="filter-key" id={`key${this.props.id}`} style={this._keyStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}> - <div className="filter-keybar"> - <button className="filter-item" style={this._titleFieldStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateTitleStatus}>Title</button> - <button className="filter-item" style={this._deletedDocsStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateDataStatus}>Deleted Docs</button> - <button className="filter-item" style={this._authorFieldStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateAuthorStatus}>Author</button> + <div style={{ position: "absolute", left: 15 }}>{Doc.CurrentUserEmail}</div> + <div style={{ display: "flex", alignItems: "center" }}> + <FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg" + style={{ color: "black", padding: 1, left: 35, position: "relative" }} /> + + <div style={{ cursor: "default", left: 250, position: "relative", }}> + <Tooltip title={<div className="dash-tooltip" >only display documents matching search</div>} ><div> + <FontAwesomeIcon icon={"filter"} size="lg" + style={{ padding: 1, backgroundColor: this.filter ? "white" : "lightgray", color: this.filter ? "black" : "white" }} + onPointerDown={e => { e.stopPropagation(); SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined); }} + onClick={action(() => { + const dofilter = (currentSelectedCollection: DocumentView) => { + let docs = DocListCast(currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + const newarray: Doc[] = []; + docs.filter(d => d.data !== undefined).forEach((d) => { + d._searchDocs = new List<Doc>(this.docsforfilter); + newarray.push(...DocListCast(d.data)); + }); + docs = newarray; + } + }; + this.filter = !this.filter && !this.searchFullDB; + if (this.filter === true && this.currentSelectedCollection !== undefined) { + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>(this.docsforfilter); + + dofilter(this.currentSelectedCollection); + + this.currentSelectedCollection.props.Document._docFilters = new List<string>(Cast(this.props.Document._docFilters, listSpec("string"), [])); + this.props.Document.selectedDoc = this.currentSelectedCollection.props.Document; + } + else if (this.filter === false && this.currentSelectedCollection !== undefined) { + + dofilter(this.currentSelectedCollection); + + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]); + this.currentSelectedCollection.props.Document._docFilters = undefined; + this.props.Document.selectedDoc = undefined; + } + } + )} /> + </div></Tooltip></div> + <input value={this.newsearchstring} autoComplete="off" onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this.inputRef} + className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} onFocus={this.openSearch} + style={{ padding: 1, paddingLeft: 20, paddingRight: 20, color: "black", height: 20, width: 250 }} /> + <div style={{ + height: 25, + paddingLeft: "4px", + paddingRight: "4px", + border: "1px solid gray", + borderRadius: "0.3em", + borderBottom: this.open === false ? "1px solid" : "none", + }}> + <form className="beta" style={{ justifyContent: "space-evenly", display: "flex" }}> + <div style={{ display: "contents" }}> + <div className="radio" style={{ margin: 0 }}> + <label style={{ fontSize: 12, marginTop: 6 }} > + <input type="radio" style={{ marginLeft: -16, marginTop: -1 }} checked={!this.searchFullDB} onChange={() => { + runInAction(() => { + this.searchFullDB = !this.searchFullDB; + this.dataDoc[this.fieldKey] = new List<Doc>([]); + if (this.currentSelectedCollection !== undefined) { + let newarray: Doc[] = []; + let docs: Doc[] = []; + docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + this.currentSelectedCollection.props.Document._docFilters = undefined; + this.currentSelectedCollection.props.Document._searchDocs = undefined; + this.currentSelectedCollection = undefined; + } + this.submitSearch(); + }); + }} /> + Collection + </label> + </div> + <div className="radio" style={{ margin: 0 }}> + <label style={{ fontSize: 12, marginTop: 6 }} > + <input style={{ marginLeft: -16, marginTop: -1 }} type="radio" checked={this.searchFullDB} onChange={() => { + runInAction(() => { + this.searchFullDB = !this.searchFullDB; + this.dataDoc[this.fieldKey] = new List<Doc>([]); + this.filter = false; + if (this.currentSelectedCollection !== undefined) { + let newarray: Doc[] = []; + let docs: Doc[] = []; + docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + this.currentSelectedCollection.props.Document._docFilters = undefined; + this.currentSelectedCollection.props.Document._searchDocs = undefined; + this.currentSelectedCollection = undefined; + } + this.submitSearch(); + }); + }} /> + DB + </label> + </div> + </div> + </form> </div> </div> + + </div> + <div style={{ zIndex: 20000, color: "black" }}> + {this._searchbarOpen === true ? + <div style={{ display: "flex", justifyContent: "center", }}> + {this.noresults === "" ? <div style={{ display: this.open === true ? "flex" : "none", overflow: "auto", }}> + <CollectionView {...this.props} + Document={this.props.Document} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelHeight={this.open === true ? () => height : () => 0} + PanelWidth={this.open === true ? () => length : () => 0} + overflow={cols > 5 || rows > 8 ? true : false} + focus={this.selectElement} + ScreenToLocalTransform={Transform.Identity} + /> + </div> : + <div style={{ display: "flex", justifyContent: "center" }}><div style={{ height: 200, top: 54, minWidth: 400, position: "absolute", backgroundColor: "rgb(241, 239, 235)", display: "flex", justifyContent: "center", alignItems: "center", border: "black 1px solid", }}> + <div>{this.noresults}</div> + </div></div>} + </div> : undefined} </div> + <div className="searchBox-results" onScroll={this.resultsScrolled} style={{ display: this._resultsOpen ? "flex" : "none", height: this.resFull ? "auto" : this.resultHeight, overflow: "visibile" // this.resFull ? "auto" : "visible" }} ref={this._resultsRef}> - {this._visibleElements} </div> - </div> + </div > ); } -}
\ No newline at end of file +} diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss deleted file mode 100644 index 469f062b2..000000000 --- a/src/client/views/search/SearchItem.scss +++ /dev/null @@ -1,163 +0,0 @@ -@import "../globalCssVariables"; - -.searchItem-overview { - display: flex; - flex-direction: reverse; - justify-content: flex-end; - z-index: 0; -} - -.searchBox-placeholder, -.searchItem-overview .searchItem { - width: 100%; - background: $light-color-secondary; - border-color: $intermediate-color; - border-bottom-style: solid; - padding: 10px; - min-height: 50px; - max-height: 150px; - height: auto; - z-index: 0; - display: flex; - overflow: visible; - - .searchItem-body { - display: flex; - flex-direction: row; - width: 100%; - - .searchItem-title-container { - width: 100%; - overflow: hidden; - - .searchItem-title { - text-transform: uppercase; - text-align: left; - width: 100%; - font-weight: bold; - } - } - - .searchItem-info { - display: flex; - justify-content: flex-end; - - .icon-icons { - width: 50px - } - - .icon-live { - width: 175px; - height: 0px; - } - - .icon-icons { - height:auto; - } - .icon-icons, - .icon-live { - margin: auto; - overflow: visible; - - .searchItem-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; - } - } - - .searchItem-type:hover+.searchItem-label { - opacity: 1; - } - - .searchItem-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 { - .pdfBox-cont { - img { - width: 100% !important; - } - } - } - } - - .searchItem-info:hover { - width: 60%; - } - } -} - -.searchItem:hover~.searchBox-instances, -.searchBox-instances:hover, -.searchBox-instances:active { - opacity: 1; - background: $lighter-alt-accent; -} - -.searchItem:hover { - transition: all 0.2s; - background: $lighter-alt-accent; -} - -.searchItem-highlighting { - overflow: hidden; - text-overflow: ellipsis; - white-space: pre; -} - -.searchBox-instances { - opacity: 1; - width:40px; - height:40px; - background: gray; - transition: all 0.2s ease; - color: black; - overflow: hidden; - right:-100; - display:inline-block; -} - - -.searchItem-overview:hover { - z-index: 1; -} - -.searchBox-placeholder { - min-height: 50px; - margin-left: 150px; - width: calc(100% - 150px); - text-transform: uppercase; - text-align: left; - font-weight: bold; -} - -.collection { - display: flex; -} - -.collection-item { - width: 35px; -}
\ No newline at end of file diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx deleted file mode 100644 index 2436bf418..000000000 --- a/src/client/views/search/SearchItem.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import React = require("react"); -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlobeAsia, faImage, faLink, faMusic, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero, returnEmptyString, returnEmptyFilter } from "../../../Utils"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentManager } from "../../util/DocumentManager"; -import { DragManager, SetupDrag } from "../../util/DragManager"; -import { SearchUtil } from "../../util/SearchUtil"; -import { Transform } from "../../util/Transform"; -import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionViewType } from "../collections/CollectionView"; -import { ParentDocSelector } from "../collections/ParentDocumentSelector"; -import { ContextMenu } from "../ContextMenu"; -import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; -import { SearchBox } from "./SearchBox"; -import "./SearchItem.scss"; -import "./SelectorContextMenu.scss"; - -export interface SearchItemProps { - doc: Doc; - query: string; - highlighting: string[]; - lines: string[]; -} - -library.add(faCaretUp); -library.add(faObjectGroup); -library.add(faStickyNote); -library.add(faFile); -library.add(faFilePdf); -library.add(faFilm); -library.add(faMusic); -library.add(faLink); -library.add(faChartBar); -library.add(faGlobeAsia, faFingerprint); - -@observer -export class SelectorContextMenu extends React.Component<SearchItemProps> { - @observable private _docs: { col: Doc, target: Doc }[] = []; - @observable private _otherDocs: { col: Doc, target: Doc }[] = []; - - constructor(props: SearchItemProps) { - super(props); - this.fetchDocuments(); - } - - async fetchDocuments() { - const aliases = (await SearchUtil.GetViewsOfDocument(this.props.doc)).filter(doc => doc !== this.props.doc); - const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.doc[Id]}"` }); - const map: Map<Doc, Doc> = new Map; - const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs))); - allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); - docs.forEach(doc => map.delete(doc)); - runInAction(() => { - this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc })); - this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); - - }); - } - - getOnClick({ col, target }: { col: Doc, target: Doc }) { - return () => { - col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (col._viewType === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target._width) / 2; - const newPanY = NumCast(target.y) + NumCast(target._height) / 2; - col._panX = newPanX; - col._panY = newPanY; - } - CollectionDockingView.AddRightSplit(col); - }; - } - render() { - return ( - <div className="parents"> - <p className="contexts">Contexts:</p> - {[...this._docs, ...this._otherDocs].map(doc => { - const item = React.createRef<HTMLDivElement>(); - return <div className="collection" key={doc.col[Id] + doc.target[Id]} ref={item}> - <div className="collection-item" onPointerDown={ - SetupDrag(item, () => doc.col, undefined, undefined, () => SearchBox.Instance.closeSearch())}> - <FontAwesomeIcon icon={faStickyNote} /> - </div> - <a onClick={this.getOnClick(doc)}>{doc.col.title}</a> - </div>; - })} - </div> - ); - } -} - -export interface LinkMenuProps { - doc1: Doc; - doc2: Doc; -} - -@observer -export class LinkContextMenu extends React.Component<LinkMenuProps> { - - highlightDoc = (doc: Doc) => () => Doc.BrushDoc(doc); - - unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc); - - getOnClick = (col: Doc) => () => CollectionDockingView.AddRightSplit(col); - - 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; - - 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); - } - @observable _useIcons = true; - @observable _displayDim = 50; - - componentDidMount() { - Doc.SetSearchQuery(this.props.query); - this.props.doc.searchMatch = true; - } - componentWillUnmount() { - this.props.doc.searchMatch = undefined; - } - - //@computed - @action - public DocumentIcon() { - const layoutresult = StrCast(this.props.doc.type); - if (!this._useIcons) { - const returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); - const returnYDimension = () => this._displayDim; - 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))} > - <ContentFittingDocumentView - Document={this.props.doc} - LibraryPath={emptyPath} - rootSelected={returnFalse} - fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1} - addDocument={returnFalse} - removeDocument={returnFalse} - addDocTab={returnFalse} - pinToPres={returnFalse} - docFilters={returnEmptyFilter} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} - ScreenToLocalTransform={Transform.Identity} - renderDepth={1} - PanelWidth={returnXDimension} - PanelHeight={returnYDimension} - NativeWidth={returnZero} - NativeHeight={returnZero} - focus={emptyFunction} - moveDocument={returnFalse} - parentActive={returnFalse} - whenActiveChanged={returnFalse} - bringToFront={returnFalse} - ContentScaling={returnOne} - /> - </div>; - return docview; - } - const button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : - layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage : - layoutresult.indexOf(DocumentType.RTF) !== -1 ? faStickyNote : - layoutresult.indexOf(DocumentType.VID) !== -1 ? faFilm : - layoutresult.indexOf(DocumentType.COL) !== -1 ? faObjectGroup : - layoutresult.indexOf(DocumentType.AUDIO) !== -1 ? faMusic : - layoutresult.indexOf(DocumentType.LINK) !== -1 ? faLink : - layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia : - faCaretUp; - return <div onClick={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE); })} > - <FontAwesomeIcon icon={button} size="2x" /> - </div>; - } - - collectionRef = React.createRef<HTMLDivElement>(); - - @action - pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); } - - nextHighlight = (e: React.PointerEvent) => { - e.preventDefault(); - e.button === 0 && SearchBox.Instance.openSearch(e); - this.props.doc.searchMatch = false; - setTimeout(() => this.props.doc.searchMatch = true, 0); - } - highlightDoc = (e: React.PointerEvent) => { - if (this.props.doc.type === DocumentType.LINK) { - if (this.props.doc.anchor1 && this.props.doc.anchor2) { - - const doc1 = Cast(this.props.doc.anchor1, Doc, null); - const doc2 = Cast(this.props.doc.anchor2, Doc, null); - Doc.BrushDoc(doc1); - Doc.BrushDoc(doc2); - } - } else { - Doc.BrushDoc(this.props.doc); - } - e.stopPropagation(); - } - - unHighlightDoc = (e: React.PointerEvent) => { - if (this.props.doc.type === DocumentType.LINK) { - if (this.props.doc.anchor1 && this.props.doc.anchor2) { - - const doc1 = Cast(this.props.doc.anchor1, Doc, null); - const doc2 = Cast(this.props.doc.anchor2, Doc, null); - Doc.UnBrushDoc(doc1); - Doc.UnBrushDoc(doc2); - } - } else { - Doc.UnBrushDoc(this.props.doc); - } - } - - onContextMenu = (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ - description: "Copy ID", event: () => { - Utils.CopyText(this.props.doc[Id]); - }, - icon: "fingerprint" - }); - ContextMenu.Instance.displayMenu(e.clientX, e.clientY); - } - - _downX = 0; - _downY = 0; - _target: any; - onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => { - this._downX = e.clientX; - this._downY = e.clientY; - e.stopPropagation(); - this._target = e.currentTarget; - document.removeEventListener("pointermove", this.onPointerMoved); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointermove", this.onPointerMoved); - document.addEventListener("pointerup", this.onPointerUp); - } - onPointerMoved = (e: PointerEvent) => { - if (Math.abs(e.clientX - this._downX) > Utils.DRAG_THRESHOLD || - Math.abs(e.clientY - this._downY) > Utils.DRAG_THRESHOLD) { - document.removeEventListener("pointermove", this.onPointerMoved); - document.removeEventListener("pointerup", this.onPointerUp); - const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc; - DragManager.StartDocumentDrag([this._target], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY); - } - } - onPointerUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onPointerMoved); - document.removeEventListener("pointerup", this.onPointerUp); - } - - @computed - get contextButton() { - return <ParentDocSelector Document={this.props.doc} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />; - } - - render() { - const doc1 = Cast(this.props.doc.anchor1, Doc); - const doc2 = Cast(this.props.doc.anchor2, Doc); - return <div className="searchItem-overview" onPointerDown={this.pointerDown} onContextMenu={this.onContextMenu}> - <div className="searchItem" onPointerDown={this.nextHighlight} onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc}> - <div className="searchItem-body" onClick={this.onClick}> - <div className="searchItem-title-container"> - <div className="searchItem-title">{StrCast(this.props.doc.title)}</div> - <div className="searchItem-highlighting">{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? this.props.lines[0] : ""}</div> - {this.props.lines.filter((m, i) => i).map((l, i) => <div id={i.toString()} className="searchItem-highlighting">`${l}`</div>)} - </div> - </div> - <div className="searchItem-info" style={{ width: this._useIcons ? "30px" : "100%" }}> - <div className={`icon-${this._useIcons ? "icons" : "live"}`}> - <div className="searchItem-type" title="Click to Preview" onPointerDown={this.onPointerDown}>{this.DocumentIcon()}</div> - <div className="searchItem-label">{this.props.doc.type ? this.props.doc.type : "Other"}</div> - </div> - </div> - <div className="searchItem-context" title="Drag as document"> - {(doc1 instanceof Doc && doc2 instanceof Doc) && this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={doc1} doc2={doc2} /> : - this.contextButton} - </div> - </div> - </div>; - } -}
\ No newline at end of file |
