diff options
Diffstat (limited to 'src')
24 files changed, 348 insertions, 121 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index d653acd34..b7e6b83a0 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -117,13 +117,21 @@ export namespace Utils { return { r: r, g: g, b: b, a: a }; } + const isTransparentFunctionHack = "isTransparent(__value__)"; + const noRecursionHack = "__noRecursion"; + export function IsRecursiveFilter(val: string) { + return !val.includes(noRecursionHack); + } + export function HasTransparencyFilter(val: string) { + return val.includes(isTransparentFunctionHack); + } export function IsTransparentFilter() { // bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one - return ["backgroundColor:isTransparent(__value__):check"]; + return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:check`;// bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field } export function IsOpaqueFilter() { // bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one - return ["backgroundColor:isTransparent(__value__):x"]; + return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:x`;// bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field } @@ -450,6 +458,7 @@ export function emptyFunction() { } export function unimplementedFunction() { throw new Error("This function is not implemented, but should be."); } + export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; export type Predicate<K, V> = (entry: [K, V]) => boolean; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c6ab35d52..ee3a38a0a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,7 +1,7 @@ import { action, runInAction } from "mobx"; -import { basename, extname } from "path"; +import { basename } from "path"; import { DateField } from "../../fields/DateField"; -import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, Initializing, updateCachedAcls } from "../../fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Initializing, Opt, updateCachedAcls, WidthSym } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { HtmlField } from "../../fields/HtmlField"; import { InkField } from "../../fields/InkField"; @@ -13,20 +13,20 @@ import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; import { AudioField, ImageField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; -import { MessageStore } from "../../server/Message"; import { Upload } from "../../server/SharedMediaTypes"; import { OmitKeys, Utils } from "../../Utils"; import { YoutubeBox } from "../apis/youtube/YoutubeBox"; import { DocServer } from "../DocServer"; import { Networking } from "../Network"; +import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { DocumentManager } from "../util/DocumentManager"; import { dropActionType } from "../util/DragManager"; import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox"; import { LinkManager } from "../util/LinkManager"; import { Scripting } from "../util/Scripting"; import { undoBatch, UndoManager } from "../util/UndoManager"; -import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; +import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; import { ContextMenu } from "../views/ContextMenu"; import { ContextMenuProps } from "../views/ContextMenuItem"; @@ -36,30 +36,29 @@ import { AudioBox } from "../views/nodes/AudioBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { ComparisonBox } from "../views/nodes/ComparisonBox"; import { DocFocusOptions } from "../views/nodes/DocumentView"; +import { EquationBox } from "../views/nodes/EquationBox"; +import { FieldViewProps } from "../views/nodes/FieldView"; import { FilterBox } from "../views/nodes/FilterBox"; import { FontIconBox } from "../views/nodes/button/FontIconBox"; import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; +import { FunctionPlotBox } from "../views/nodes/FunctionPlotBox"; import { ImageBox } from "../views/nodes/ImageBox"; import { KeyValueBox } from "../views/nodes/KeyValueBox"; import { LabelBox } from "../views/nodes/LabelBox"; import { LinkBox } from "../views/nodes/LinkBox"; import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup"; import { PDFBox } from "../views/nodes/PDFBox"; -import { PresBox } from "../views/nodes/trails/PresBox"; import { ScreenshotBox } from "../views/nodes/ScreenshotBox"; import { ScriptingBox } from "../views/nodes/ScriptingBox"; import { SliderBox } from "../views/nodes/SliderBox"; import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; +import { PresBox } from "../views/nodes/trails/PresBox"; +import { PresElementBox } from "../views/nodes/trails/PresElementBox"; import { VideoBox } from "../views/nodes/VideoBox"; import { WebBox } from "../views/nodes/WebBox"; -import { PresElementBox } from "../views/nodes/trails/PresElementBox"; import { SearchBox } from "../views/search/SearchBox"; import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; -import { EquationBox } from "../views/nodes/EquationBox"; -import { FunctionPlotBox } from "../views/nodes/FunctionPlotBox"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { FieldViewProps } from "../views/nodes/FieldView"; const path = require('path'); const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); @@ -290,6 +289,8 @@ export class DocumentOptions { useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox border?: string; //for searchbox hoverBackgroundColor?: string; // background color of a label when hovered + linkRelationshipList?: List<string>; // for storing different link relationships (when set by user in the link editor) + linkColorList?: List<string>; // colors of links corresponding to specific link relationships } export namespace Docs { diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 219c83b12..f0c055049 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -3,6 +3,7 @@ import { computedFn } from "mobx-utils"; import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc"; import { List } from "../../fields/List"; import { ProxyField } from "../../fields/Proxy"; +import { listSpec } from "../../fields/Schema"; import { BoolCast, Cast, PromiseValue, StrCast } from "../../fields/Types"; import { LightboxView } from "../views/LightboxView"; import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView"; @@ -33,6 +34,7 @@ export class LinkManager { static links: Doc[] = []; constructor() { LinkManager._instance = this; + this.createLinkrelationshipLists(); setTimeout(() => { LinkManager.userLinkDBs = []; const addLinkToDoc = (link: Doc) => { @@ -97,6 +99,17 @@ export class LinkManager { }); } + + public createLinkrelationshipLists = () => { + //create new lists for link relations and their associated colors if the lists don't already exist + if (!Doc.UserDoc().linkRelationshipList && !Doc.UserDoc().linkColorList) { + const linkRelationshipList = new List<string>(); + const linkColorList = new List<string>(); + Doc.UserDoc().linkRelationshipList = linkRelationshipList; + Doc.UserDoc().linkColorList = linkColorList; + } + } + public addLink(linkDoc: Doc, checkExists = false) { if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) { Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc); diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index c08734c3e..1897572f8 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -66,6 +66,8 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight("rgba(173, 216, 230, 0.75)", true)); AnchorMenu.Instance.Highlight = this.highlight; AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight("rgba(173, 216, 230, 0.75)", true, savedAnnotations); + AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor; + /** * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation. * It also initiates a Drag/Drop interaction to place the text annotation. diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 55b1e4031..1f4c35daa 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -90,10 +90,11 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: } collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._docFilters); collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec("string"), []); - childDocFilters = () => [...this.props.docFilters(), ...this.collectionFilters()]; + childDocFilters = () => [...(this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()]; + unrecursiveDocFilters = () => [...(this.props.docFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])]; childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()]; IsFiltered = () => this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" : - this.props.docFilters().filter(f => !f.includes("__value__")).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined + this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs); @computed.struct get childDocs() { TraceMobx(); @@ -113,10 +114,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; - const docFilters = this.childDocFilters(); + const childDocFilters = this.childDocFilters(); const docRangeFilters = this.childDocRangeFilters(); const searchDocs = this.searchFilterDocs(); - if (this.props.Document.dontRegisterView || (!docFilters.length && !docRangeFilters.length && !searchDocs.length)) { + if (this.props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !docRangeFilters.length && !searchDocs.length)) { return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one } @@ -127,24 +128,27 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: const docsforFilter: Doc[] = []; childDocs.forEach((d) => { // if (DocUtils.Excluded(d, docFilters)) return; - let notFiltered = d.z || Doc.IsSystem(d) || ((!searchDocs.length || searchDocs.includes(d)) && (DocUtils.FilterDocs([d], docFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0)); - const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); - const data = d[annos ? fieldKey + "-annotations" : fieldKey]; - if (data !== undefined) { - let subDocs = DocListCast(data); - if (subDocs.length > 0) { - let newarray: Doc[] = []; - notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, docFilters, docRangeFilters, viewSpecScript, d).length); - while (subDocs.length > 0 && !notFiltered) { - newarray = []; - subDocs.forEach((t) => { - const fieldKey = Doc.LayoutFieldKey(t); - const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView"); - notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!docFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], docFilters, docRangeFilters, viewSpecScript, d).length)); - DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc)); - }); - subDocs = newarray; + let notFiltered = d.z || Doc.IsSystem(d) || (DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0); + if (notFiltered) { + notFiltered = ((!searchDocs.length || searchDocs.includes(d)) && (DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0)); + const fieldKey = Doc.LayoutFieldKey(d); + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); + const data = d[annos ? fieldKey + "-annotations" : fieldKey]; + if (data !== undefined) { + let subDocs = DocListCast(data); + if (subDocs.length > 0) { + let newarray: Doc[] = []; + notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, viewSpecScript, d).length); + while (subDocs.length > 0 && !notFiltered) { + newarray = []; + subDocs.forEach((t) => { + const fieldKey = Doc.LayoutFieldKey(t); + const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView"); + notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, viewSpecScript, d).length)); + DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc)); + }); + subDocs = newarray; + } } } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index a8f5e6dd2..59891b7a1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -9,6 +9,9 @@ import { SnappingManager } from "../../../util/SnappingManager"; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinkView.scss"; import React = require("react"); +import { LinkManager } from "../../../util/LinkManager"; +import { List } from "../../../fields/List"; + export interface CollectionFreeFormLinkViewProps { A: DocumentView; @@ -173,8 +176,14 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo render() { if (!this.renderData) return (null); const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData; + LinkManager.currentLink = this.props.LinkDocs[0]; + const linkRelationship = LinkManager.currentLink?.linkRelationship; //get string representing relationship + const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>; + const linkColorList = Doc.UserDoc().linkColorList as List<string>; + //access stroke color using index of the relationship in the color list (default black) + const strokeColor = linkRelationshipList.indexOf(linkRelationship) == -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> - <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2" }} + <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2", stroke: strokeColor }} d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} /> {textX === undefined ? (null) : <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} > {StrCast(this.props.LinkDocs[0].description)} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 5e0b31754..e812064b7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -9,6 +9,7 @@ import "./CollectionFreeFormLinksView.scss"; import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); import { DocumentType } from "../../../documents/DocumentTypes"; +import { LinkManager } from "../../../util/LinkManager"; @observer export class CollectionFreeFormLinksView extends React.Component { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3c5a63c77..985d162f9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -74,6 +74,8 @@ export type collectionFreeformViewProps = { scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; + dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are trnasparent or not. + // However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents. }; @observer @@ -1452,7 +1454,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> - <div ref={this._marqueeRef}> + <div ref={this._marqueeRef} style={{ display: this.props.dontRenderDocuments ? "none" : undefined }}> {this.layoutDoc["_backgroundGrid-show"] ? this.backgroundGrid : (null)} <CollectionFreeFormViewPannableContents isAnnotationOverlay={this.isAnnotationOverlay} diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss index e45a91d57..abd413f57 100644 --- a/src/client/views/linking/LinkEditor.scss +++ b/src/client/views/linking/LinkEditor.scss @@ -106,6 +106,24 @@ } } +.linkEditor-relationship-dropdown { + position: absolute; + width: 154px; + max-height: 90px; + overflow: auto; + background: white; + + p { + padding: 3px; + cursor: pointer; + border: 1px solid $medium-gray; + } + + p:hover { + background: $light-blue; + } +} + .linkEditor-followingDropdown { padding-left: 26px; padding-right: 6.5px; diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index f74b422d3..fba95cad2 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -2,11 +2,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../fields/Doc"; +import { Doc, StrListCast } from "../../../fields/Doc"; import { DateCast, StrCast } from "../../../fields/Types"; import { LinkManager } from "../../util/LinkManager"; import { undoBatch } from "../../util/UndoManager"; import './LinkEditor.scss'; +import { LinkRelationshipSearch } from "./LinkRelationshipSearch"; import React = require("react"); @@ -26,6 +27,8 @@ export class LinkEditor extends React.Component<LinkEditorProps> { @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; } @observable private buttonColor: string = ""; @observable private relationshipButtonColor: string = ""; + @observable private relationshipSearchVisibility: string = "none"; + @observable private searchIsActive: boolean = false; //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION"; @@ -39,12 +42,40 @@ export class LinkEditor extends React.Component<LinkEditorProps> { setRelationshipValue = action((value: string) => { if (LinkManager.currentLink) { LinkManager.currentLink.linkRelationship = value; + const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); + const linkColorList = StrListCast(Doc.UserDoc().linkColorList); + // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color + if (linkRelationshipList && !linkRelationshipList.includes(value)) { + linkRelationshipList.push(value); + const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; + linkColorList.push(randColor) + } this.relationshipButtonColor = "rgb(62, 133, 55)"; setTimeout(action(() => this.relationshipButtonColor = ""), 750); return true; } }); + /** + * returns list of strings with possible existing relationships that contain what is currently in the input field + */ + @action + getRelationshipResults = () => { + const query = this.relationship; //current content in input box + const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList) + if (linkRelationshipList) { + return linkRelationshipList.filter(rel => rel.includes(query)); + } + } + + /** + * toggles visibility of the relationship search results when the input field is focused on + */ + @action + toggleRelationshipResults = () => { + this.relationshipSearchVisibility = this.relationshipSearchVisibility == "none" ? "block" : "none"; + } + @undoBatch setDescripValue = action((value: string) => { if (LinkManager.currentLink) { @@ -55,7 +86,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> { } }); - onKey = (e: React.KeyboardEvent<HTMLInputElement>) => { + onDescriptionKey = (e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key === "Enter") { this.setDescripValue(this.description); document.getElementById('input')?.blur(); @@ -69,16 +100,38 @@ export class LinkEditor extends React.Component<LinkEditorProps> { } } - onDown = () => this.setDescripValue(this.description); - onRelationshipDown = () => this.setRelationshipValue(this.description); + onDescriptionDown = () => this.setDescripValue(this.description); + onRelationshipDown = () => this.setRelationshipValue(this.relationship); + + onBlur = () => { + //only hide the search results if the user clicks out of the input AND not on any of the search results + // i.e. if search is not active + if (!this.searchIsActive) { + this.toggleRelationshipResults(); + } + } + onFocus = () => { + this.toggleRelationshipResults(); + } + toggleSearchIsActive = () => { + this.searchIsActive = !this.searchIsActive; + } @action - handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; } + handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; } @action - handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.relationship = e.target.value; } - + handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this.relationship = e.target.value; + } + @action + handleRelationshipSearchChange = (result: string) => { + this.setRelationshipValue(result); + this.toggleRelationshipResults(); + this.relationship = result; + } @computed get editRelationship() { + //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS return <div className="linkEditor-description"> <div className="linkEditor-description-label">Link Relationship:</div> <div className="linkEditor-description-input"> @@ -87,11 +140,18 @@ export class LinkEditor extends React.Component<LinkEditorProps> { style={{ width: "100%" }} id="input" value={this.relationship} - placeholder={"enter link label"} - // color={"rgb(88, 88, 88)"} + placeholder={"Enter link relationship"} onKeyDown={this.onRelationshipKey} onChange={this.handleRelationshipChange} + onFocus={this.onFocus} + onBlur={this.onBlur} ></input> + <LinkRelationshipSearch + results={this.getRelationshipResults()} + display={this.relationshipSearchVisibility} + handleRelationshipSearchChange={this.handleRelationshipSearchChange} + toggleSearch={this.toggleSearchIsActive} + /> </div> <div className="linkEditor-description-add-button" style={{ background: this.relationshipButtonColor }} @@ -110,15 +170,14 @@ export class LinkEditor extends React.Component<LinkEditorProps> { style={{ width: "100%" }} id="input" value={this.description} - placeholder={"enter link label"} - // color={"rgb(88, 88, 88)"} - onKeyDown={this.onKey} - onChange={this.handleChange} + placeholder={"Enter link description"} + onKeyDown={this.onDescriptionKey} + onChange={this.handleDescriptionChange} ></input> </div> <div className="linkEditor-description-add-button" style={{ background: this.buttonColor }} - onPointerDown={this.onDown}>Set</div> + onPointerDown={this.onDescriptionDown}>Set</div> </div> </div>; } @@ -149,35 +208,35 @@ export class LinkEditor extends React.Component<LinkEditorProps> { <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("default")}> Default - </div> + </div> <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("add:left")}> Always open in new left pane - </div> + </div> <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("add:right")}> Always open in new right pane - </div> + </div> <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("replace:right")}> Always replace right tab - </div> + </div> <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("replace:left")}> Always replace left tab - </div> + </div> <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("fullScreen")}> Always open full screen - </div> + </div> <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("add")}> Always open in a new tab - </div> + </div> <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("replace")}> Replace Tab - </div> + </div> {this.props.linkDoc.linksToAnnotation ? <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("openExternal")}> diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 6fc860447..53fe3f682 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -41,7 +41,7 @@ export class LinkMenu extends React.Component<Props> { /** * maps each link to a JSX element to be rendered - * @param groups LinkManager containing info of all of the links + * @param groups containing info of all of the links * @returns list of link JSX elements if there at least one linked element */ renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => { diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index c7586a467..cb6571f92 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react"; -import { Doc } from "../../../fields/Doc"; +import { Doc, StrListCast } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { Cast } from "../../../fields/Types"; import { LinkManager } from "../../util/LinkManager"; @@ -20,6 +20,23 @@ interface LinkMenuGroupProps { export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { private _menuRef = React.createRef<HTMLDivElement>(); + getBackgroundColor = (): string => { + const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); + const linkColorList = StrListCast(Doc.UserDoc().linkColorList); + let color = "white"; + // if this link's relationship property is not default "link", set its color + if (linkRelationshipList) { + const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType); + const RGBcolor: string = linkColorList[relationshipIndex]; + if (RGBcolor) { + //set opacity to 0.25 by modifiying the rgb string + color = RGBcolor.slice(0, RGBcolor.length - 1) + ", 0.25)"; + console.log(color); + } + } + return color; + } + render() { const set = new Set<Doc>(this.props.group); const groupItems = Array.from(set.keys()).map(linkDoc => { @@ -39,7 +56,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { return ( <div className="linkMenu-group" ref={this._menuRef}> - <div className="linkMenu-group-name"> + <div className="linkMenu-group-name" style={{ background: this.getBackgroundColor() }}> <p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"}> {this.props.groupType}:</p> </div> <div className="linkMenu-group-wrapper"> diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss index 8ae65158d..60c9ebfcd 100644 --- a/src/client/views/linking/LinkPopup.scss +++ b/src/client/views/linking/LinkPopup.scss @@ -5,7 +5,7 @@ height: 200px; width: 200px; position: absolute; - padding: 15px; + // padding: 15px; border-radius: 3px; input { diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index df469c53b..c8be9069c 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -1,33 +1,21 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; -import { action, observable, runInAction } from 'mobx'; +import { action, observable } from 'mobx'; import { observer } from "mobx-react"; -import { Doc, DocListCast } from '../../../fields/Doc'; -import { Cast, StrCast } from '../../../fields/Types'; -import { WebField } from '../../../fields/URLField'; -import { emptyFunction, setupMoveUpEvents, returnFalse, returnTrue, returnEmptyDoclist, returnEmptyFilter } from '../../../Utils'; -import { DocumentType } from '../../documents/DocumentTypes'; -import { DocumentManager } from '../../util/DocumentManager'; -import { DragManager } from '../../util/DragManager'; -import { Hypothesis } from '../../util/HypothesisUtils'; -import { LinkManager } from '../../util/LinkManager'; -import { undoBatch } from '../../util/UndoManager'; -import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; -import { DocumentView, DocumentViewSharedProps } from '../nodes/DocumentView'; -import { LinkDocPreview } from '../nodes/LinkDocPreview'; -import './LinkPopup.scss'; -import React = require("react"); +import { EditorView } from 'prosemirror-view'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils'; +import { DocUtils } from '../../documents/Documents'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { DefaultStyleProvider } from '../StyleProvider'; import { Transform } from '../../util/Transform'; -import { DocUtils } from '../../documents/Documents'; -import { SearchBox } from '../search/SearchBox'; -import { EditorView } from 'prosemirror-view'; +import { undoBatch } from '../../util/UndoManager'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { SearchBox } from '../search/SearchBox'; +import { DefaultStyleProvider } from '../StyleProvider'; +import './LinkPopup.scss'; +import React = require("react"); +import { Doc, Opt } from '../../../fields/Doc'; interface LinkPopupProps { showPopup: boolean; + linkFrom?: () => Doc | undefined; // groupType: string; // linkDoc: Doc; // docView: DocumentView; @@ -62,9 +50,10 @@ export class LinkPopup extends React.Component<LinkPopupProps> { render() { const popupVisibility = this.props.showPopup ? "block" : "none"; + const linkDoc = this.props.linkFrom ? this.props.linkFrom : undefined; return ( <div className="linkPopup-container" style={{ display: popupVisibility }}> - <div className="linkPopup-url-container"> + {/* <div className="linkPopup-url-container"> <input autoComplete="off" type="text" value={this.linkURL} placeholder="Enter URL..." onChange={this.onLinkChange} /> <button onPointerDown={e => this.makeLinkToURL(this.linkURL, "add:right")} style={{ display: "block", margin: "10px auto", }}>Apply hyperlink</button> @@ -72,14 +61,17 @@ export class LinkPopup extends React.Component<LinkPopupProps> { <div className="divider"> <div className="line"></div> <p className="divider-text">or</p> - </div> + </div> */} <div className="linkPopup-document-search-container"> {/* <i></i> <input defaultValue={""} autoComplete="off" type="text" placeholder="Search for Document..." id="search-input" className="linkPopup-searchBox searchBox-input" /> */} - <SearchBox Document={CurrentUserUtils.MySearchPanelDoc} + <SearchBox + Document={CurrentUserUtils.MySearchPanelDoc} DataDoc={CurrentUserUtils.MySearchPanelDoc} + linkFrom={linkDoc} + linkSearch={true} fieldKey="data" dropAction="move" isSelected={returnTrue} diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx new file mode 100644 index 000000000..8f83f0e6e --- /dev/null +++ b/src/client/views/linking/LinkRelationshipSearch.tsx @@ -0,0 +1,63 @@ +import { observer } from "mobx-react"; +import './LinkEditor.scss'; +import React = require("react"); + +interface LinkRelationshipSearchProps { + results: string[] | undefined; + display: string; + //callback fn to set rel + hide dropdown upon setting + handleRelationshipSearchChange: (result: string) => void; + toggleSearch: () => void; +} +@observer +export class LinkRelationshipSearch extends React.Component<LinkRelationshipSearchProps> { + + handleResultClick = (e: React.MouseEvent) => { + const relationship = (e.target as HTMLParagraphElement).textContent; + if (relationship) { + this.props.handleRelationshipSearchChange(relationship) + } + } + + handleMouseEnter = () => { + this.props.toggleSearch(); + } + + handleMouseLeave = () => { + this.props.toggleSearch(); + } + + /** + * Render an empty div to increase the height of LinkEditor to accommodate 2+ results + */ + emptyDiv = () => { + if (this.props.results && this.props.results.length > 2 && this.props.display == "block") { + return <div style={{ height: "50px" }}></div> + } + } + + render() { + return ( + <div className="linkEditor-relationship-dropdown-container"> + <div className="linkEditor-relationship-dropdown" + style={{ display: this.props.display }} + onMouseEnter={this.handleMouseEnter} + onMouseLeave={this.handleMouseLeave} + > + { // return a dropdown of relationship results if there exist results + this.props.results + ? this.props.results.map(result => { + return (<p key={result} onClick={this.handleResultClick}> + {result} + </p>) + }) + : <p>No matching relationships</p> + } + </div> + + {/*Render an empty div to increase the height of LinkEditor to accommodate 2+ results */} + {this.emptyDiv()} + </div> + ) + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index ebbc1138a..ee81e106a 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -2,11 +2,10 @@ import React = require("react"); import { computed } from "mobx"; import { observer } from "mobx-react"; import { DateField } from "../../../fields/DateField"; -import { Doc, Field, FieldResult, Opt } from "../../../fields/Doc"; +import { Doc, Field, FieldResult } from "../../../fields/Doc"; import { List } from "../../../fields/List"; -import { VideoField, WebField } from "../../../fields/URLField"; +import { WebField } from "../../../fields/URLField"; import { DocumentViewSharedProps } from "./DocumentView"; -import { VideoBox } from "./VideoBox"; // // these properties get assigned through the render() method of the DocumentView when it creates this node. diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index 30b272a9a..b62a4dd56 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -54,7 +54,7 @@ export class LinkDescriptionPopup extends React.Component<{}> { }}> <input className="linkDescriptionPopup-input" onKeyPress={e => e.key === "Enter" && this.onDismiss(true)} - placeholder={"(optional) enter link label..."} + placeholder={"(Optional) Enter link description..."} onChange={(e) => this.descriptionChanged(e)}> </input> <div className="linkDescriptionPopup-btn"> @@ -65,4 +65,4 @@ export class LinkDescriptionPopup extends React.Component<{}> { </div> </div>; } -}
\ No newline at end of file +}
\ No newline at end of file diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index cdc79678a..ae6b90e41 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -13,7 +13,7 @@ import { ComputedField } from "../../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { WebField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils"; +import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils, returnEmptyString, returnEmptyFilter } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -508,11 +508,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; + transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; + opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; render() { const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined; const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const scale = previewScale * (this.props.scaling?.() || 1); - const renderAnnotations = (docFilters: () => string[]) => + const renderAnnotations = (docFilters?: () => string[]) => <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} renderDepth={this.props.renderDepth + 1} isAnnotationOverlay={true} @@ -524,7 +526,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ScreenToLocalTransform={this.scrollXf} scaling={returnOne} dropAction={"alias"} - docFilters={docFilters} + docFilters={docFilters || this.props.docFilters} + dontRenderDocuments={docFilters ? false : true} select={emptyFunction} isContentActive={returnFalse} ContentScaling={returnOne} @@ -553,9 +556,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps <div className={"webBox-innerContent"} style={{ height: NumCast(this.scrollHeight, 50), pointerEvents }}> {this.content} <div style={{ mixBlendMode: "multiply" }}> - {renderAnnotations(Utils.IsTransparentFilter)} + {renderAnnotations(this.transparentFilter)} </div> - {renderAnnotations(Utils.IsOpaqueFilter)} + {renderAnnotations(this.opaqueFilter)} + {renderAnnotations()} {this.annotationLayer} </div> </div> diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index b0b8ce75d..d1027dfd7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -217,6 +217,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch); return undefined; }); + AnchorMenu.Instance.onMakeAnchor = this.getAnchor; /** * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation. * It also initiates a Drag/Drop interaction to place the text annotation. diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 55816ed52..75e3f81fb 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -45,6 +45,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @observable public Highlighting: boolean = false; @observable public Status: "marquee" | "annotation" | "" = ""; + public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search + public OnClick: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined; @@ -65,7 +67,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { componentDidMount() { this._disposer = reaction(() => SelectionManager.Views(), - selected => AnchorMenu.Instance.fadeOut(true)); + selected => { + this._showLinkPopup = false; + console.log("unmount"); + AnchorMenu.Instance.fadeOut(true) + }); } pointerDown = (e: React.PointerEvent) => { @@ -145,14 +151,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { <FontAwesomeIcon icon="comment-alt" size="lg" /> </button> </Tooltip>, - - //NOTE: link popup is currently incomplete - // <Tooltip key="link" title={<div className="dash-tooltip">{"Link selected text to document or URL"}</div>}> - // <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}> - // <FontAwesomeIcon icon="link" size="lg" /> - // </button> - // </Tooltip>, - // <LinkPopup showPopup={this._showLinkPopup} /> + //NOTE: link popup is currently in progress + <Tooltip key="link" title={<div className="dash-tooltip">{"Link selected text to document"}</div>}> + <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}> + <FontAwesomeIcon icon="link" size="lg" /> + </button> + </Tooltip>, + <LinkPopup showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor} /> ] : [ <Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}> <button className="antimodeMenu-button" onPointerDown={this.Delete}> diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0261d24d9..734d9127c 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -9,7 +9,7 @@ import { createSchema } from "../../../fields/Schema"; import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; import { PdfField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils, returnFalse } from "../../../Utils"; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils, returnFalse, returnEmptyString, returnEmptyFilter } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { Networking } from "../../Network"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -506,8 +506,10 @@ export class PDFViewer extends React.Component<IViewerProps> { overlayTransform = () => this.scrollXf().scale(1 / this._zoomed); panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); + transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; + opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; @computed get overlayLayer() { - const renderAnnotations = (docFilters: () => string[]) => + const renderAnnotations = (docFilters?: () => string[]) => <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} isAnnotationOverlay={true} isContentActive={returnFalse} @@ -519,7 +521,8 @@ export class PDFViewer extends React.Component<IViewerProps> { select={emptyFunction} ContentScaling={this.contentZoom} bringToFront={emptyFunction} - docFilters={docFilters} + docFilters={docFilters || this.props.docFilters} + dontRenderDocuments={docFilters ? false : true} CollectionView={undefined} ScreenToLocalTransform={this.overlayTransform} renderDepth={this.props.renderDepth + 1} @@ -531,7 +534,7 @@ export class PDFViewer extends React.Component<IViewerProps> { mixBlendMode: "multiply", transform: `scale(${this._zoomed})` }}> - {renderAnnotations(Utils.IsTransparentFilter)} + {renderAnnotations(this.transparentFilter)} </div> <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`} style={{ @@ -539,7 +542,8 @@ export class PDFViewer extends React.Component<IViewerProps> { mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined, transform: `scale(${this._zoomed})` }}> - {renderAnnotations(Utils.IsOpaqueFilter)} + {renderAnnotations(this.opaqueFilter)} + {renderAnnotations()} </div> </div>; } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index b07879674..07186fe8d 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -7,23 +7,31 @@ import { Id } from '../../../fields/FieldSymbols'; import { createSchema, makeInterface } from '../../../fields/Schema'; import { StrCast } from '../../../fields/Types'; import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentManager } from "../../util/DocumentManager"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { ViewBoxBaseComponent } from "../DocComponent"; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import "./SearchBox.scss"; +import { DocumentManager } from '../../util/DocumentManager'; +import { DocUtils } from '../../documents/Documents'; -export const searchSchema = createSchema({ Document: Doc }); +export const searchSchema = createSchema({ + Document: Doc +}); type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>; const SearchBoxDocument = makeInterface(documentSchema, searchSchema); +export interface SearchBoxProps extends FieldViewProps { + linkSearch: boolean; + linkFrom?: (() => Doc | undefined) | undefined; +} + /** * This is the SearchBox component. It represents the search box input and results in * the search panel on the left side of the screen. */ @observer -export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) { +export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDocument>(SearchBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); } public static Instance: SearchBox; @@ -100,6 +108,18 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this._selectedResult = doc; }); + makeLink = action((linkTo: Doc) => { + console.log("[makeLink-1] got here!") + console.log(linkTo.title); + if (this.props.linkFrom){ + const linkFrom = this.props.linkFrom(); + if (linkFrom){ + console.log(linkFrom.title); + DocUtils.MakeLink({doc: linkFrom}, {doc:linkTo}); + } + } + }); + /** * @param {Doc[]} docs - docs to be searched through recursively * @param {number, Doc => void} func - function to be called on each doc @@ -275,6 +295,9 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc render() { var validResults = 0; + const isLinkSearch:boolean = this.props.linkSearch; + + const results = this._results.map(result => { var className = "searchBox-results-scroll-view-result"; @@ -285,7 +308,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc if (this._docTypeString == "all" || this._docTypeString == result[0].type) { validResults++; return ( - <div key={result[0][Id]} onClick={() => this.onResultClick(result[0])} className={className}> + <div key={result[0][Id]} onClick={isLinkSearch ? () => this.makeLink(result[0]) : () => this.onResultClick(result[0])} className={className}> <div className="searchBox-result-title"> {result[0].title} </div> @@ -306,11 +329,11 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc return ( <div style={{ pointerEvents: "all" }} className="searchBox-container"> - <div className="searchBox-bar"> - <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}> + <div className="searchBox-bar" > + {isLinkSearch ? (null) : <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}> {this.selectOptions} - </select> - <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} type="text" placeholder="Search..." id="search-input" className="searchBox-input" ref={this._inputRef} /> + </select>} + <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} type="text" placeholder="Search..." id="search-input" className="searchBox-input" style={{width: isLinkSearch ? "100%" : undefined, borderRadius: isLinkSearch ? "5px" : undefined}} ref={this._inputRef} /> </div > <div className="searchBox-results-container"> <div className="searchBox-results-count"> diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss index d02a77fe4..05aeb177c 100644 --- a/src/client/views/topbar/TopBar.scss +++ b/src/client/views/topbar/TopBar.scss @@ -55,6 +55,7 @@ .topbar-dashboards { display: flex; flex-direction: row; + gap: 5px; } .topbar-lozenge-dashboard { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index f6efefdf9..17f41fac8 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1088,7 +1088,7 @@ export namespace Doc { } export function matchFieldValue(doc: Doc, key: string, value: any): boolean { - if (value === "isTransparent(__value__)") { + if (Utils.HasTransparencyFilter(value)) { const isTransparent = (color: string) => color !== "" && (Color(color).alpha() !== 1); return isTransparent(StrCast(doc[key])); } |