diff options
Diffstat (limited to 'src/client/views/linking')
-rw-r--r-- | src/client/views/linking/LinkEditor.scss | 40 | ||||
-rw-r--r-- | src/client/views/linking/LinkEditor.tsx | 103 | ||||
-rw-r--r-- | src/client/views/linking/LinkMenu.scss | 28 | ||||
-rw-r--r-- | src/client/views/linking/LinkMenu.tsx | 8 | ||||
-rw-r--r-- | src/client/views/linking/LinkMenuGroup.tsx | 23 | ||||
-rw-r--r-- | src/client/views/linking/LinkMenuItem.scss | 20 | ||||
-rw-r--r-- | src/client/views/linking/LinkPopup.scss | 45 | ||||
-rw-r--r-- | src/client/views/linking/LinkPopup.tsx | 105 | ||||
-rw-r--r-- | src/client/views/linking/LinkRelationshipSearch.tsx | 63 |
9 files changed, 374 insertions, 61 deletions
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss index 7e6999cdc..abd413f57 100644 --- a/src/client/views/linking/LinkEditor.scss +++ b/src/client/views/linking/LinkEditor.scss @@ -1,4 +1,4 @@ -@import "../globalCssVariables"; +@import "../global/globalCssVariables"; .linkEditor { width: 100%; @@ -20,9 +20,9 @@ } .linkEditor-info { - //border-bottom: 0.5px solid $light-color-secondary; + //border-bottom: 0.5px solid $light-gray; //padding-bottom: 1px; - padding-top: 5px; + padding: 12px; padding-left: 5px; //margin-bottom: 6px; display: flex; @@ -61,7 +61,7 @@ } .linkEditor-description { - padding-left: 6.5px; + padding-left: 26px; padding-right: 6.5px; padding-bottom: 3.5px; @@ -106,10 +106,28 @@ } } +.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: 6.5px; + padding-left: 26px; padding-right: 6.5px; - padding-bottom: 6px; + padding-bottom: 15px; &:hover { cursor: pointer; @@ -195,7 +213,7 @@ } .linkEditor-group { - background-color: $light-color-secondary; + background-color: $light-gray; padding: 6px; margin: 3px 0; border-radius: 3px; @@ -254,8 +272,8 @@ } .linkEditor-option { - background-color: $light-color-secondary; - border: 1px solid $intermediate-color; + background-color: $light-gray; + border: 1px solid $medium-gray; border-top: 0; padding: 3px; cursor: pointer; @@ -272,7 +290,7 @@ .linkEditor-typeButton { background-color: transparent; - color: $dark-color; + color: $dark-gray; height: 20px; padding: 0 3px; padding-bottom: 2px; @@ -285,7 +303,7 @@ width: calc(100% - 40px); &:hover { - background-color: $light-color; + background-color: $white; } } diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index f74b422d3..240a71c3e 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.scss b/src/client/views/linking/LinkMenu.scss index a90bf8b0a..19c6463d3 100644 --- a/src/client/views/linking/LinkMenu.scss +++ b/src/client/views/linking/LinkMenu.scss @@ -1,4 +1,4 @@ -@import "../globalCssVariables"; +@import "../global/globalCssVariables"; .linkMenu { width: auto; @@ -7,20 +7,19 @@ z-index: 2001; .linkMenu-list, - .linkMenu-listEditor - { + .linkMenu-listEditor { display: inline-block; position: relative; - border: 1px solid black; - box-shadow: 3px 3px 1.5px grey; + border: 1px solid #e4e4e4; + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23); background: white; - min-width: 170px; - max-height: 170px; + max-height: 230px; overflow-y: scroll; z-index: 10; - } - .linkMenu-list { + } + + .linkMenu-list { white-space: nowrap; overflow-x: hidden; width: 240px; @@ -46,13 +45,13 @@ } .linkMenu-group-name { + padding: 10px; &:hover { - p { - background-color: lightgray; - - } + // p { + // background-color: lightgray; + // } p.expand-one { width: calc(100% + 20px); @@ -65,10 +64,9 @@ p { width: 100%; - //padding: 4px 6px; line-height: 12px; border-radius: 5px; - font-weight: bold; + text-transform: capitalize; } .linkEditor-tableButton { diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index c7888c5ee..53fe3f682 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -15,6 +15,9 @@ interface Props { changeFlyout: () => void; } +/** + * the outermost component for the link menu of a node that contains a list of its linked nodes + */ @observer export class LinkMenu extends React.Component<Props> { private _editorRef = React.createRef<HTMLDivElement>(); @@ -36,6 +39,11 @@ export class LinkMenu extends React.Component<Props> { } } + /** + * maps each link to a JSX element to be rendered + * @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> => { const linkItems = Array.from(groups.entries()).map(group => <LinkMenuGroup diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index 74af78234..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,8 +56,8 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { return ( <div className="linkMenu-group" ref={this._menuRef}> - <div className="linkMenu-group-name"> - <p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p> + <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"> {groupItems} diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss index 4e13ef8c8..90722daf9 100644 --- a/src/client/views/linking/LinkMenuItem.scss +++ b/src/client/views/linking/LinkMenuItem.scss @@ -1,10 +1,10 @@ -@import "../globalCssVariables"; +@import "../global/globalCssVariables"; .linkMenu-item { - // border-top: 0.5px solid $main-accent; + // border-top: 0.5px solid $medium-gray; position: relative; display: flex; - border-bottom: 0.5px solid black; + border-top: 0.5px solid #cdcdcd; padding-left: 6.5px; padding-right: 2px; @@ -55,8 +55,8 @@ .linkMenu-destination-title { text-decoration: none; - color: rgb(85, 120, 196); - font-size: 14px; + color: #4476F7; + font-size: 16px; padding-bottom: 2px; padding-right: 4px; margin-right: 4px; @@ -76,7 +76,7 @@ text-decoration: none; font-style: italic; color: rgb(95, 97, 102); - font-size: 10px; + font-size: 9px; margin-left: 20px; max-width: 125px; height: auto; @@ -102,7 +102,7 @@ .link-metadata { padding: 0 10px 0 16px; margin-bottom: 4px; - color: $main-accent; + color: $medium-gray; font-style: italic; font-size: 10.5px; } @@ -143,8 +143,8 @@ padding-right: 6px; border-radius: 50%; pointer-events: auto; - background-color: $dark-color; - color: $light-color; + background-color: $dark-gray; + color: $white; font-size: 65%; transition: transform 0.2s; text-align: center; @@ -162,7 +162,7 @@ } &:hover { - background: $main-accent; + background: $medium-gray; cursor: pointer; } } diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss new file mode 100644 index 000000000..60c9ebfcd --- /dev/null +++ b/src/client/views/linking/LinkPopup.scss @@ -0,0 +1,45 @@ +.linkPopup-container { + background: white; + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23); + top: 35px; + height: 200px; + width: 200px; + position: absolute; + // padding: 15px; + border-radius: 3px; + + input { + border: 1px solid #b9b9b9; + border-radius: 20px; + height: 25px; + width: 100%; + padding-left: 10px; + } + + .divider { + margin: 10px 0; + height: 20px; + width: 100%; + + .line { + height: 1px; + background-color: #b9b9b9; + width: 100%; + position: relative; + top: 12px; + } + + .divider-text { + width: 20px; + background-color: white; + text-align: center; + position: relative; + margin: auto; + } + } + + + .searchBox-container { + background: pink; + } +}
\ No newline at end of file diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx new file mode 100644 index 000000000..c8be9069c --- /dev/null +++ b/src/client/views/linking/LinkPopup.tsx @@ -0,0 +1,105 @@ +import { action, observable } from 'mobx'; +import { observer } from "mobx-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 { Transform } from '../../util/Transform'; +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; + // sourceDoc: Doc; +} + +/** + * Popup component for creating links from text to Dash documents + */ + +@observer +export class LinkPopup extends React.Component<LinkPopupProps> { + @observable private linkURL: string = ""; + @observable public view?: EditorView; + + + + // TODO: should check for valid URL + @undoBatch + makeLinkToURL = (target: string, lcoation: string) => { + ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target); + } + + @action + onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this.linkURL = e.target.value; + } + + + getPWidth = () => 500; + getPHeight = () => 500; + + 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"> + <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> + </div> + <div className="divider"> + <div className="line"></div> + <p className="divider-text">or</p> + </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} + DataDoc={CurrentUserUtils.MySearchPanelDoc} + linkFrom={linkDoc} + linkSearch={true} + fieldKey="data" + dropAction="move" + isSelected={returnTrue} + isContentActive={returnTrue} + select={returnTrue} + setHeight={returnFalse} + addDocument={undefined} + addDocTab={returnTrue} + pinToPres={emptyFunction} + rootSelected={returnTrue} + styleProvider={DefaultStyleProvider} + layerProvider={undefined} + removeDocument={undefined} + ScreenToLocalTransform={Transform.Identity} + PanelWidth={this.getPWidth} + PanelHeight={this.getPHeight} + renderDepth={0} + focus={DocUtils.DefaultFocus} + docViewPath={returnEmptyDoclist} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} /> + </div> + </div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx new file mode 100644 index 000000000..53da880e4 --- /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" }} />; + } + } + + 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 |