diff options
Diffstat (limited to 'src/client/views/linking/LinkEditor.tsx')
-rw-r--r-- | src/client/views/linking/LinkEditor.tsx | 454 |
1 files changed, 0 insertions, 454 deletions
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx deleted file mode 100644 index 01e33708a..000000000 --- a/src/client/views/linking/LinkEditor.tsx +++ /dev/null @@ -1,454 +0,0 @@ -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, BoolCast, DocCast, NumCast } 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 { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils'; -import { PresBox, PresEffect } from '../nodes/trails'; - -interface LinkEditorProps { - sourceDoc: Doc; - linkDoc: Doc; - showLinks?: () => void; - hideback?: boolean; -} -@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 = BoolCast(this.props.sourceDoc.followLinkZoom); - @observable audioFollow = BoolCast(this.props.sourceDoc.followLinkAudio); - @observable openDropdown: boolean = false; - @observable openEffectDropdown: boolean = false; - @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"; - - @undoBatch - setRelationshipValue = action((value: string) => { - if (LinkManager.currentLink) { - const prevRelationship = LinkManager.currentLink.linkRelationship as string; - LinkManager.currentLink.linkRelationship = value; - Doc.GetProto(LinkManager.currentLink).linkRelationship = value; - const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); - 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 (!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) + ')'; - 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) { - const index = linkRelationshipList.indexOf(value); - //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; - } - //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); - } - } - } - 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) { - Doc.GetProto(LinkManager.currentLink).description = value; - this.buttonColor = 'rgb(62, 133, 55)'; - setTimeout( - action(() => (this.buttonColor = '')), - 750 - ); - return true; - } - }); - - onDescriptionKey = (e: React.KeyboardEvent<HTMLInputElement>) => { - if (e.key === 'Enter') { - this.setDescripValue(this.description); - document.getElementById('input')?.blur(); - } - e.stopPropagation(); - }; - - onRelationshipKey = (e: React.KeyboardEvent<HTMLInputElement>) => { - 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 - // 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; - }; - @action - handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { - this.relationship = e.target.value; - }; - @action - handleZoomFollowChange = () => { - this.props.sourceDoc.followLinkZoom = !this.props.sourceDoc.followLinkZoom; - }; - @action - handleAudioFollowChange = () => { - this.props.sourceDoc.followLinkAudio = !this.props.sourceDoc.followLinkAudio; - }; - @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">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> - ); - } - @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 type="checkbox" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.handleZoomFollowChange)} defaultChecked={this.zoomFollow} /> - </div> - </div> - </div> - ); - } - - @computed - get editAudioFollow() { - //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">Play Target Audio:</div> - <div className="linkEditor-zoomFollow-input"> - <div className="linkEditor-zoomFollow-editing"> - <input type="checkbox" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.handleAudioFollowChange)} defaultChecked={this.audioFollow} /> - </div> - </div> - </div> - ); - } - - @computed - get editDescription() { - return ( - <div className="linkEditor-description"> - <div className="linkEditor-description-label">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> - ); - } - - @action - changeDropdown = () => { - this.openDropdown = !this.openDropdown; - }; - - @undoBatch - changeFollowBehavior = action((follow: string) => { - this.openDropdown = false; - Doc.GetProto(this.props.linkDoc).followLinkLocation = follow; - }); - - @computed - get followingDropdown() { - return ( - <div className="linkEditor-followingDropdown"> - <div className="linkEditor-followingDropdown-label">Follow by:</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 - </div> - <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add:left')}> - Always opening in new left pane - </div> - <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add:right')}> - Always opening in new right pane - </div> - <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace:right')}> - Always replacing right tab - </div> - <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace:left')}> - Always replacing left tab - </div> - <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('fullScreen')}> - Always opening full screen - </div> - <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add')}> - Always opening in a new tab - </div> - <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace')}> - Replacing Tab - </div> - <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('inPlace')}> - Opening in Place - </div> - {this.props.linkDoc.linksToAnnotation ? ( - <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('openExternal')}> - Always open in external page - </div> - ) : null} - </div> - </div> - </div> - ); - } - - @computed get destinationAnchor() { - const ldoc = this.props.linkDoc; - if (this.props.sourceDoc !== ldoc.anchor1 && this.props.sourceDoc !== ldoc.anchor2) { - if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor1).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor2); - if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor2).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor1); - } - return LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc) ?? this.props.sourceDoc; - } - @action - changeEffectDropdown = () => { - this.openEffectDropdown = !this.openEffectDropdown; - }; - - @undoBatch - changeEffect = action((follow: string) => { - this.openEffectDropdown = false; - this.destinationAnchor.presEffect = follow; - }); - - @computed - get effectDropdown() { - return ( - <div className="linkEditor-followingDropdown"> - <div className="linkEditor-followingDropdown-label">Animation:</div> - <div className="linkEditor-followingDropdown-dropdown"> - <div className="linkEditor-followingDropdown-header" onPointerDown={this.changeEffectDropdown}> - {StrCast(this.destinationAnchor.presEffect, 'default')} - <FontAwesomeIcon className="linkEditor-followingDropdown-icon" icon={this.openEffectDropdown ? 'chevron-up' : 'chevron-down'} size={'lg'} /> - </div> - <div className="linkEditor-followingDropdown-optionsList" style={{ display: this.openEffectDropdown ? '' : 'none' }}> - {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( - <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeEffect(effect.toString())}> - {effect.toString()} - </div> - ))} - </div> - </div> - </div> - ); - } - - autoMove = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove)))); - }; - - showAnchor = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.hidden = !this.props.linkDoc.hidden)))); - }; - - showLink = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay)))); - }; - - deleteLink = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this.props.linkDoc)))); - }; - - render() { - const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - - return !destination ? null : ( - <div className="linkEditor" tabIndex={0} onKeyDown={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> - <div className="linkEditor-info"> - {!this.props.showLinks ? null : ( - <Tooltip title={<div className="dash-tooltip">Return to link menu</div>} placement="top"> - <button className="linkEditor-button-back" style={{ display: this.props.hideback ? 'none' : '' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.props.showLinks?.())}> - <FontAwesomeIcon icon="arrow-left" size="sm" />{' '} - </button> - </Tooltip> - )} - <p className="linkEditor-linkedTo"> - Editing Link to: <b>{StrCast(destination.proto?.title, StrCast(destination.title, 'untitled'))}</b> - </p> - <Tooltip title={<div className="dash-tooltip">Delete Link</div>}> - <div className="linkEditor-deleteBtn" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}> - <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /> - </div> - </Tooltip> - </div> - <div className="linkEditor-moreInfo"> - {this.props.linkDoc.author ? ( - <> - {' '} - <b>Author:</b> {StrCast(this.props.linkDoc.author)} - </> - ) : null} - {this.props.linkDoc.creationDate ? ( - <> - {' '} - <b>Creation Date:</b> - {DateCast(this.props.linkDoc.creationDate).toString()} - </> - ) : null} - </div> - {this.editDescription} - {this.editRelationship} - {this.editZoomFollow} - {this.editAudioFollow} - <div className="linkEditor-description"> - Show Anchor: - <Tooltip title={<div className="dash-tooltip">{this.props.linkDoc.hidden ? 'Show Link Anchor' : 'Hide Link Anchor'}</div>}> - <div - className="linkEditor-button" - style={{ background: this.props.linkDoc.hidden ? 'gray' : '#4476f7' /* $medium-blue */ }} - onPointerDown={this.showAnchor} - onClick={e => e.stopPropagation()}> - <FontAwesomeIcon className="fa-icon" icon={'eye'} size="sm" /> - </div> - </Tooltip> - </div> - <div className="linkEditor-description"> - Show Link Line: - <Tooltip title={<div className="dash-tooltip">{this.props.linkDoc.linkDisplay ? 'Hide Link Line' : 'Show Link Line'}</div>}> - <div - className="linkEditor-button" - style={{ background: this.props.linkDoc.hidden ? 'gray' : this.props.linkDoc.linkDisplay ? '#4476f7' /* $medium-blue */ : '' }} - onPointerDown={this.showLink} - onClick={e => e.stopPropagation()}> - <FontAwesomeIcon className="fa-icon" icon={'project-diagram'} size="sm" /> - </div> - </Tooltip> - </div> - <div className="linkEditor-description"> - Freeze Anchor: - <Tooltip title={<div className="dash-tooltip">{this.props.linkDoc.linkAutoMove ? 'Click to freeze link anchor position' : 'Click to auto move link anchor'}</div>}> - <div - className="linkEditor-button" - style={{ background: this.props.linkDoc.hidden ? 'gray' : this.props.linkDoc.linkAutoMove ? '' : '#4476f7' /* $medium-blue */ }} - onPointerDown={this.autoMove} - onClick={e => e.stopPropagation()}> - <FontAwesomeIcon className="fa-icon" icon={'play'} size="sm" /> - </div> - </Tooltip> - </div> - {this.followingDropdown} - {this.effectDropdown} - {PresBox.inputter('0.1', '0.1', '10', NumCast(this.destinationAnchor.presTransition) / 1000, true, (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => (this.destinationAnchor.presTransition = timeInMS)))} - <div - className={'slider-headers'} - style={{ - marginLeft: 30, - marginRight: 30, - display: 'grid', - justifyContent: 'space-between', - width: '100%', - gridTemplateColumns: 'auto auto auto', - }}> - <div className="slider-text">Fast</div> - <div className="slider-text">Medium</div> - <div className="slider-text">Slow</div> - </div>{' '} - </div> - ); - } -} |