diff options
author | bobzel <zzzman@gmail.com> | 2022-08-03 09:01:29 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2022-08-03 09:01:29 -0400 |
commit | 9c26b676be062f9bd0f1ab66f8ba40fc9ec85d42 (patch) | |
tree | ba6f48f871ddc356fdf7fd51ce2465c6d9f80c69 /src/client/views/linking/LinkEditor.tsx | |
parent | c1cd00c7664df694b867f4989a1f61d959390742 (diff) | |
parent | 85dade366a9517796d1d80cee2be022d5cacdc93 (diff) |
Merge branch 'master' into parker
Diffstat (limited to 'src/client/views/linking/LinkEditor.tsx')
-rw-r--r-- | src/client/views/linking/LinkEditor.tsx | 346 |
1 files changed, 185 insertions, 161 deletions
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 1414bfdf7..ba301962b 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -1,15 +1,14 @@ -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, NumListCast, StrListCast, Field } from "../../../fields/Doc"; -import { DateCast, StrCast, Cast } from "../../../fields/Types"; -import { LinkManager } from "../../util/LinkManager"; -import { undoBatch } from "../../util/UndoManager"; +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, NumListCast, StrListCast, Field } from '../../../fields/Doc'; +import { DateCast, StrCast, Cast } from '../../../fields/Types'; +import { LinkManager } from '../../util/LinkManager'; +import { undoBatch } from '../../util/UndoManager'; import './LinkEditor.scss'; -import { LinkRelationshipSearch } from "./LinkRelationshipSearch"; -import React = require("react"); - +import { LinkRelationshipSearch } from './LinkRelationshipSearch'; +import React = require('react'); interface LinkEditorProps { sourceDoc: Doc; @@ -19,16 +18,20 @@ interface LinkEditorProps { } @observer export class LinkEditor extends React.Component<LinkEditorProps> { - @observable description = Field.toString(LinkManager.currentLink?.description as any as Field); @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship); @observable zoomFollow = StrCast(this.props.sourceDoc.followLinkZoom); @observable openDropdown: boolean = false; @observable showInfo: boolean = false; - @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"; + @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"; @@ -37,7 +40,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> { deleteLink = (): void => { LinkManager.Instance.deleteLink(this.props.linkDoc); this.props.showLinks(); - } + }; @undoBatch setRelationshipValue = action((value: string) => { @@ -49,11 +52,11 @@ export class LinkEditor extends React.Component<LinkEditorProps> { const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes); 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 the relationship does not exist in the list, add it and a corresponding unique randomly generated color if (!linkRelationshipList?.includes(value)) { linkRelationshipList.push(value); linkRelationshipSizes.push(1); - const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; + const randColor = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')'; linkColorList.push(randColor); // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes } else if (linkRelationshipList && value !== prevRelationship) { @@ -61,20 +64,22 @@ export class LinkEditor extends React.Component<LinkEditorProps> { //increment size of new relationship size if (index !== -1 && index < linkRelationshipSizes.length) { const pvalue = linkRelationshipSizes[index]; - linkRelationshipSizes[index] = (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1); + linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1; } //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) if (linkRelationshipList.includes(prevRelationship)) { const pindex = linkRelationshipList.indexOf(prevRelationship); if (pindex !== -1 && pindex < linkRelationshipSizes.length) { const pvalue = linkRelationshipSizes[pindex]; - linkRelationshipSizes[pindex] = Math.max(0, (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1)); + linkRelationshipSizes[pindex] = Math.max(0, pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1); } } - } - this.relationshipButtonColor = "rgb(62, 133, 55)"; - setTimeout(action(() => this.relationshipButtonColor = ""), 750); + this.relationshipButtonColor = 'rgb(62, 133, 55)'; + setTimeout( + action(() => (this.relationshipButtonColor = '')), + 750 + ); return true; } }); @@ -89,144 +94,143 @@ export class LinkEditor extends React.Component<LinkEditorProps> { 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"; - } + this.relationshipSearchVisibility = this.relationshipSearchVisibility === 'none' ? 'block' : 'none'; + }; @undoBatch setDescripValue = action((value: string) => { if (LinkManager.currentLink) { Doc.GetProto(LinkManager.currentLink).description = value; - this.buttonColor = "rgb(62, 133, 55)"; - setTimeout(action(() => this.buttonColor = ""), 750); + this.buttonColor = 'rgb(62, 133, 55)'; + setTimeout( + action(() => (this.buttonColor = '')), + 750 + ); return true; } }); onDescriptionKey = (e: React.KeyboardEvent<HTMLInputElement>) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { this.setDescripValue(this.description); document.getElementById('input')?.blur(); } e.stopPropagation(); - } + }; onRelationshipKey = (e: React.KeyboardEvent<HTMLInputElement>) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { this.setRelationshipValue(this.relationship); document.getElementById('input')?.blur(); } e.stopPropagation(); - } + }; 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 + //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 - handleDescriptionChange = (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 - handleZoomFollowChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.props.sourceDoc.followLinkZoom = e.target.checked; } + handleZoomFollowChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this.props.sourceDoc.followLinkZoom = e.target.checked; + }; @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"> - <div className="linkEditor-description-editing"> - <input - style={{ width: "100%" }} - id="input" - value={this.relationship} - autoComplete={"off"} - 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} - /> + return ( + <div className="linkEditor-description"> + <div className="linkEditor-description-label">Link Relationship:</div> + <div className="linkEditor-description-input"> + <div className="linkEditor-description-editing"> + <input + style={{ width: '100%' }} + id="input" + value={this.relationship} + autoComplete={'off'} + 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 }} onPointerDown={this.onRelationshipDown}> + Set + </div> </div> - <div className="linkEditor-description-add-button" - style={{ background: this.relationshipButtonColor }} - onPointerDown={this.onRelationshipDown}>Set</div> </div> - </div>; + ); } @computed get editZoomFollow() { //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-zoomFollow"> - <div className="linkEditor-zoomFollow-label">Zoom To Link Target:</div> - <div className="linkEditor-zoomFollow-input"> - <div className="linkEditor-zoomFollow-editing"> - <input - style={{ width: "100%" }} - type="checkbox" - value={this.zoomFollow} - onChange={this.handleZoomFollowChange} /> + return ( + <div className="linkEditor-zoomFollow"> + <div className="linkEditor-zoomFollow-label">Zoom To Link Target:</div> + <div className="linkEditor-zoomFollow-input"> + <div className="linkEditor-zoomFollow-editing"> + <input style={{ width: '100%' }} type="checkbox" value={this.zoomFollow} onChange={this.handleZoomFollowChange} /> + </div> </div> </div> - </div >; + ); } @computed get editDescription() { - return <div className="linkEditor-description"> - <div className="linkEditor-description-label">Link Description:</div> - <div className="linkEditor-description-input"> - <div className="linkEditor-description-editing"> - <input - style={{ width: "100%" }} - autoComplete={"off"} - id="input" - value={this.description} - placeholder={"Enter link description"} - onKeyDown={this.onDescriptionKey} - onChange={this.handleDescriptionChange} - ></input> + return ( + <div className="linkEditor-description"> + <div className="linkEditor-description-label">Link Description:</div> + <div className="linkEditor-description-input"> + <div className="linkEditor-description-editing"> + <input style={{ width: '100%' }} autoComplete={'off'} id="input" value={this.description} placeholder={'Enter link description'} onKeyDown={this.onDescriptionKey} onChange={this.handleDescriptionChange}></input> + </div> + <div className="linkEditor-description-add-button" style={{ background: this.buttonColor }} onPointerDown={this.onDescriptionDown}> + Set + </div> </div> - <div className="linkEditor-description-add-button" - style={{ background: this.buttonColor }} - onPointerDown={this.onDescriptionDown}>Set</div> </div> - </div>; + ); } @action - changeDropdown = () => { this.openDropdown = !this.openDropdown; } + changeDropdown = () => { + this.openDropdown = !this.openDropdown; + }; @undoBatch changeFollowBehavior = action((follow: string) => { @@ -236,94 +240,114 @@ export class LinkEditor extends React.Component<LinkEditorProps> { @computed get followingDropdown() { - return <div className="linkEditor-followingDropdown"> - <div className="linkEditor-followingDropdown-label">Follow Behavior:</div> - <div className="linkEditor-followingDropdown-dropdown"> - <div className="linkEditor-followingDropdown-header" - onPointerDown={this.changeDropdown}> - {StrCast(this.props.linkDoc.followLinkLocation, "default")} - <FontAwesomeIcon className="linkEditor-followingDropdown-icon" - icon={this.openDropdown ? "chevron-up" : "chevron-down"} - size={"lg"} /> - </div> - <div className="linkEditor-followingDropdown-optionsList" - style={{ display: this.openDropdown ? "" : "none" }}> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("default")}> - Default + return ( + <div className="linkEditor-followingDropdown"> + <div className="linkEditor-followingDropdown-label">Follow Behavior:</div> + <div className="linkEditor-followingDropdown-dropdown"> + <div className="linkEditor-followingDropdown-header" onPointerDown={this.changeDropdown}> + {StrCast(this.props.linkDoc.followLinkLocation, 'default')} + <FontAwesomeIcon className="linkEditor-followingDropdown-icon" icon={this.openDropdown ? 'chevron-up' : 'chevron-down'} size={'lg'} /> </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("add:left")}> - Always open in new left pane - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("add:right")}> - Always open in new right pane - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("replace:right")}> - Always replace right tab - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("replace:left")}> - Always replace left tab - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("fullScreen")}> - Always open full screen - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("add")}> - Always open in a new tab - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("replace")}> - Replace Tab - </div> - {this.props.linkDoc.linksToAnnotation ? - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("openExternal")}> - Always open in external page + <div className="linkEditor-followingDropdown-optionsList" style={{ display: this.openDropdown ? '' : 'none' }}> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('default')}> + Default + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add:left')}> + Always open in new left pane + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add:right')}> + Always open in new right pane + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace:right')}> + Always replace right tab + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace:left')}> + Always replace left tab </div> - : null} + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('fullScreen')}> + Always open full screen + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add')}> + Always open in a new tab + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace')}> + Replace Tab + </div> + {this.props.linkDoc.linksToAnnotation ? ( + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('openExternal')}> + Always open in external page + </div> + ) : null} + </div> </div> </div> - </div>; + ); } @action - changeInfo = () => { this.showInfo = !this.showInfo; } + changeInfo = () => { + this.showInfo = !this.showInfo; + }; render() { const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - return !destination ? (null) : ( + return !destination ? null : ( <div className="linkEditor" tabIndex={0} onKeyDown={e => e.stopPropagation()}> <div className="linkEditor-info"> - <Tooltip title={<><div className="dash-tooltip">Return to link menu</div></>} placement="top"> - <button className="linkEditor-button-back" - style={{ display: this.props.hideback ? "none" : "" }} - onClick={this.props.showLinks}> - <FontAwesomeIcon icon="arrow-left" size="sm" /> </button> + <Tooltip + title={ + <> + <div className="dash-tooltip">Return to link menu</div> + </> + } + placement="top"> + <button className="linkEditor-button-back" style={{ display: this.props.hideback ? 'none' : '' }} onClick={this.props.showLinks}> + <FontAwesomeIcon icon="arrow-left" size="sm" />{' '} + </button> </Tooltip> - <p className="linkEditor-linkedTo">Editing Link to: <b>{ - destination.proto?.title ?? destination.title ?? "untitled"}</b></p> - <Tooltip title={<><div className="dash-tooltip">Show more link information</div></>} placement="top"> - <div className="linkEditor-downArrow"><FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} /></div> + <p className="linkEditor-linkedTo"> + Editing Link to: <b>{StrCast(destination.proto?.title, StrCast(destination.title, 'untitled'))}</b> + </p> + <Tooltip + title={ + <> + <div className="dash-tooltip">Show more link information</div> + </> + } + placement="top"> + <div className="linkEditor-downArrow"> + <FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} /> + </div> </Tooltip> </div> - {this.showInfo ? <div className="linkEditor-moreInfo"> - <div>{this.props.linkDoc.author ? <div> <b>Author:</b> {this.props.linkDoc.author}</div> : null}</div> - <div>{this.props.linkDoc.creationDate ? <div> <b>Creation Date:</b> - {DateCast(this.props.linkDoc.creationDate).toString()}</div> : null}</div> - </div> : null} + {this.showInfo ? ( + <div className="linkEditor-moreInfo"> + <div> + {this.props.linkDoc.author ? ( + <div> + {' '} + <b>Author:</b> {StrCast(this.props.linkDoc.author)} + </div> + ) : null} + </div> + <div> + {this.props.linkDoc.creationDate ? ( + <div> + {' '} + <b>Creation Date:</b> + {DateCast(this.props.linkDoc.creationDate).toString()} + </div> + ) : null} + </div> + </div> + ) : null} {this.editDescription} {this.editRelationship} {this.editZoomFollow} {this.followingDropdown} </div> - ); } -}
\ No newline at end of file +} |