aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/linking/LinkEditor.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/linking/LinkEditor.tsx')
-rw-r--r--src/client/views/linking/LinkEditor.tsx454
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>
- );
- }
-}