aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/linking
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/linking')
-rw-r--r--src/client/views/linking/LinkEditor.scss334
-rw-r--r--src/client/views/linking/LinkEditor.tsx454
-rw-r--r--src/client/views/linking/LinkMenu.tsx37
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx50
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx4
5 files changed, 39 insertions, 840 deletions
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
deleted file mode 100644
index b0ee4e46d..000000000
--- a/src/client/views/linking/LinkEditor.scss
+++ /dev/null
@@ -1,334 +0,0 @@
-@import '../global/globalCssVariables';
-
-.linkEditor {
- width: 100%;
- height: auto;
- font-size: 13px; // TODO
- user-select: none;
- max-width: 280px;
-}
-
-.linkEditor-button-back {
- //margin-bottom: 6px;
- border-radius: 10px;
- width: 18px;
- height: 18px;
- padding: 0;
-
- &:hover {
- cursor: pointer;
- }
-}
-
-.linkEditor-info {
- padding-top: 12px;
- padding-left: 5px;
- padding-bottom: 3px;
- //margin-bottom: 6px;
- display: flex;
- justify-content: space-between;
- color: black;
-
- .linkEditor-linkedTo {
- width: calc(100% - 46px);
- overflow: hidden;
- position: relative;
- text-overflow: ellipsis;
- white-space: pre;
-
- .linkEditor-downArrow {
- &:hover {
- cursor: pointer;
- }
- }
- }
-}
-
-.linkEditor-moreInfo {
- margin-left: 12px;
- padding-left: 13px;
- padding-right: 6.5px;
- padding-bottom: 4px;
- font-size: 9px;
- //font-style: italic;
- text-decoration-color: grey;
-
- .button {
- color: black;
-
- &:hover {
- cursor: pointer;
- }
- }
-}
-
-.linkEditor-zoomFollow {
- padding-left: 26px;
- padding-right: 6.5px;
- padding-bottom: 3.5px;
- display: flex;
-
- .linkEditor-zoomFollow-label {
- text-decoration-color: black;
- color: black;
- line-height: 1.7;
- }
-
- .linkEditor-zoomFollow-input {
- display: block;
- width: 20px;
- }
-}
-.linkEditor-deleteBtn {
- padding-left: 3px;
-}
-
-.linkEditor-description {
- padding-left: 26px;
- padding-bottom: 3.5px;
- display: flex;
-
- .linkEditor-description-label {
- text-decoration-color: black;
- color: black;
- }
-
- .linkEditor-description-input {
- display: flex;
-
- .linkEditor-description-editing {
- min-width: 85%;
- //border: 1px solid grey;
- //border-radius: 4px;
- padding-left: 2px;
- //margin-right: 4px;
- color: black;
- text-decoration-color: grey;
- }
-
- .linkEditor-description-add-button {
- display: inline;
- border-radius: 7px;
- font-size: 9px;
- background: black;
- height: 80%;
- color: white;
- padding: 3px;
- margin-left: 3px;
-
- &:hover {
- cursor: pointer;
- background: grey;
- }
- }
- }
-}
-
-.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;
- padding-bottom: 15px;
- display: flex;
-
- &:hover {
- cursor: pointer;
- }
-
- .linkEditor-followingDropdown-label {
- color: black;
- padding-right: 3px;
- }
-
- .linkEditor-followingDropdown-dropdown {
- .linkEditor-followingDropdown-header {
- border: 1px solid grey;
- border-radius: 4px;
- //background-color: rgb(236, 236, 236);
- padding-left: 2px;
- padding-right: 2px;
- text-decoration-color: black;
- color: rgb(94, 94, 94);
-
- .linkEditor-followingDropdown-icon {
- float: right;
- color: black;
- }
- }
-
- .linkEditor-followingDropdown-optionsList {
- padding-left: 3px;
- padding-right: 3px;
-
- &:last-child {
- border-bottom: none;
- }
-
- .linkEditor-followingDropdown-option {
- border: 0.25px solid grey;
- //background-color: rgb(236, 236, 236);
- padding-left: 2px;
- padding-right: 2px;
- color: grey;
- text-decoration-color: grey;
- font-size: 9px;
- border-top: none;
-
- &:hover {
- background-color: rgb(187, 220, 231);
- }
- }
- }
- }
-}
-
-.linkEditor-button,
-.linkEditor-addbutton {
- width: 15%;
- border-radius: 7px;
- font-size: 9px;
- background: black;
- padding: 3px;
- height: 80%;
- color: white;
- text-align: center;
- margin: auto;
- margin-left: 3px;
- > svg {
- margin: auto;
- }
- &:disabled {
- background-color: gray;
- }
-}
-
-.linkEditor-addbutton {
- margin-left: 0px;
-}
-
-.linkEditor-groupsLabel {
- display: flex;
- justify-content: space-between;
-}
-
-.linkEditor-group {
- background-color: $light-gray;
- padding: 6px;
- margin: 3px 0;
- border-radius: 3px;
-
- .linkEditor-group-row {
- display: flex;
- margin-bottom: 3px;
- }
-
- .linkEditor-group-row-label {
- margin-right: 6px;
- display: inline-block;
- }
-
- .linkEditor-metadata-row {
- display: flex;
- justify-content: space-between;
- margin-bottom: 6px;
-
- .linkEditor-error {
- border-color: red;
- }
-
- input {
- width: calc(50% - 16px);
- height: 20px;
- }
-
- button {
- width: 20px;
- height: 20px;
- margin-left: 3px;
- padding: 0;
- font-size: 10px;
- }
- }
-}
-
-.linkEditor-dropdown {
- width: 100%;
- position: relative;
- z-index: 999;
-
- input {
- width: 100%;
- }
-
- .linkEditor-options-wrapper {
- width: 100%;
- position: absolute;
- top: 19px;
- left: 0;
- display: flex;
- flex-direction: column;
- }
-
- .linkEditor-option {
- background-color: $light-gray;
- border: 1px solid $medium-gray;
- border-top: 0;
- padding: 3px;
- cursor: pointer;
-
- &:hover {
- background-color: lightgray;
- }
-
- &.onDown {
- background-color: gray;
- }
- }
-}
-
-.linkEditor-typeButton {
- background-color: transparent;
- color: $dark-gray;
- height: 20px;
- padding: 0 3px;
- padding-bottom: 2px;
- text-align: left;
- text-transform: none;
- letter-spacing: normal;
- font-size: 12px;
- font-weight: bold;
- display: inline-block;
- width: calc(100% - 40px);
-
- &:hover {
- background-color: $white;
- }
-}
-
-.linkEditor-group-buttons {
- height: 20px;
- display: flex;
- justify-content: flex-end;
- margin-top: 5px;
-
- .linkEditor-button {
- margin-left: 3px;
- }
-}
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>
- );
- }
-}
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 0c46a6d96..c9112eec3 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,14 +1,13 @@
-import { action, computed, observable } from 'mobx';
+import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../../fields/Doc';
+import { DocCast } from '../../../fields/Types';
import { LinkManager } from '../../util/LinkManager';
import { DocumentView } from '../nodes/DocumentView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
-import { LinkEditor } from './LinkEditor';
import './LinkMenu.scss';
import { LinkMenuGroup } from './LinkMenuGroup';
import React = require('react');
-import { emptyFunction } from '../../../Utils';
interface Props {
docView: DocumentView;
@@ -23,15 +22,9 @@ interface Props {
@observer
export class LinkMenu extends React.Component<Props> {
_editorRef = React.createRef<HTMLDivElement>();
- @observable _editingLink?: Doc;
@observable _linkMenuRef = React.createRef<HTMLDivElement>();
- clear = !this.props.clearLinkEditor
- ? undefined
- : action(() => {
- this.props.clearLinkEditor?.();
- this._editingLink = undefined;
- });
+ clear = () => this.props.clearLinkEditor?.();
componentDidMount() {
this.props.clearLinkEditor && document.addEventListener('pointerdown', this.onPointerDown, true);
@@ -43,7 +36,7 @@ export class LinkMenu extends React.Component<Props> {
onPointerDown = action((e: PointerEvent) => {
LinkDocPreview.Clear();
if (!this._linkMenuRef.current?.contains(e.target as any) && !this._editorRef.current?.contains(e.target as any)) {
- this.clear?.();
+ this.clear();
}
});
@@ -54,34 +47,20 @@ export class LinkMenu extends React.Component<Props> {
*/
renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
const linkItems = Array.from(groups.entries()).map(group => (
- <LinkMenuGroup
- key={group[0]}
- itemHandler={this.props.itemHandler}
- docView={this.props.docView}
- sourceDoc={this.props.docView.props.Document}
- group={group[1]}
- groupType={group[0]}
- clearLinkEditor={this.clear}
- showEditor={action(linkDoc => (this._editingLink = linkDoc))}
- />
+ <LinkMenuGroup key={group[0]} itemHandler={this.props.itemHandler} docView={this.props.docView} sourceDoc={this.props.docView.props.Document} group={group[1]} groupType={group[0]} clearLinkEditor={this.clear} />
));
return linkItems.length ? linkItems : this.props.style ? [<></>] : [<p key="">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
};
render() {
- const sourceDoc = this.props.docView.props.Document;
+ const sourceDoc = this.props.docView.rootDoc;
+ const sourceAnchor = this.props.docView.anchorViewDoc ?? sourceDoc;
const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds());
return (
<div className="linkMenu" ref={this._linkMenuRef} style={{ ...style }}>
- {this._editingLink ? (
- <div className="linkMenu-listEditor">
- <LinkEditor sourceDoc={sourceDoc} linkDoc={this._editingLink} showLinks={action(() => (this._editingLink = undefined))} />
- </div>
- ) : (
- <div className="linkMenu-list">{this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceDoc))}</div>
- )}
+ <div className="linkMenu-list">{this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceAnchor))}</div>
</div>
);
}
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 9d2082e21..d02a1c4eb 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -2,19 +2,19 @@ import { observer } from 'mobx-react';
import { observable, action } from 'mobx';
import { Doc, StrListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
-import { Cast } from '../../../fields/Types';
+import { Cast, DocCast } from '../../../fields/Types';
import { LinkManager } from '../../util/LinkManager';
import { DocumentView } from '../nodes/DocumentView';
import './LinkMenu.scss';
import { LinkMenuItem } from './LinkMenuItem';
import React = require('react');
+import { DocumentType } from '../../documents/DocumentTypes';
interface LinkMenuGroupProps {
sourceDoc: Doc;
group: Doc[];
groupType: string;
clearLinkEditor?: () => void;
- showEditor: (linkDoc: Doc) => void;
docView: DocumentView;
itemHandler?: (doc: Doc) => void;
}
@@ -44,25 +44,33 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
render() {
const set = new Set<Doc>(this.props.group);
const groupItems = Array.from(set.keys()).map(linkDoc => {
- const destination =
- LinkManager.getOppositeAnchor(linkDoc, this.props.sourceDoc) ||
- LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null));
- if (destination && this.props.sourceDoc) {
- return (
- <LinkMenuItem
- key={linkDoc[Id]}
- itemHandler={this.props.itemHandler}
- groupType={this.props.groupType}
- docView={this.props.docView}
- linkDoc={linkDoc}
- sourceDoc={this.props.sourceDoc}
- destinationDoc={destination}
- clearLinkEditor={this.props.clearLinkEditor}
- showEditor={this.props.showEditor}
- menuRef={this._menuRef}
- />
- );
- }
+ const sourceDoc =
+ this.props.docView.anchorViewDoc ??
+ (this.props.docView.rootDoc.type === DocumentType.LINK //
+ ? this.props.docView.props.LayoutTemplateString?.includes('anchor1')
+ ? DocCast(linkDoc.anchor1)
+ : DocCast(linkDoc.anchor2)
+ : this.props.sourceDoc);
+ const destDoc = !sourceDoc
+ ? undefined
+ : this.props.docView.rootDoc.type === DocumentType.LINK
+ ? this.props.docView.props.LayoutTemplateString?.includes('anchor1')
+ ? DocCast(linkDoc.anchor2)
+ : DocCast(linkDoc.anchor1)
+ : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) || LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null));
+ return !destDoc || !sourceDoc ? null : (
+ <LinkMenuItem
+ key={linkDoc[Id]}
+ itemHandler={this.props.itemHandler}
+ groupType={this.props.groupType}
+ docView={this.props.docView}
+ linkDoc={linkDoc}
+ sourceDoc={sourceDoc}
+ destinationDoc={destDoc}
+ clearLinkEditor={this.props.clearLinkEditor}
+ menuRef={this._menuRef}
+ />
+ );
});
return (
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index c3705b0e1..fb4c6873e 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -18,6 +18,7 @@ import { DocumentView } from '../nodes/DocumentView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
import './LinkMenuItem.scss';
import React = require('react');
+import { SelectionManager } from '../../util/SelectionManager';
interface LinkMenuItemProps {
groupType: string;
@@ -26,7 +27,6 @@ interface LinkMenuItemProps {
sourceDoc: Doc;
destinationDoc: Doc;
clearLinkEditor?: () => void;
- showEditor: (linkDoc: Doc) => void;
menuRef: React.Ref<HTMLDivElement>;
itemHandler?: (doc: Doc) => void;
}
@@ -100,10 +100,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
},
emptyFunction,
action(() => {
+ SelectionManager.SelectView(this.props.docView, false);
if ((SettingsManager.propertiesWidth ?? 0) < 100) {
SettingsManager.propertiesWidth = 250;
}
- //this.props.showEditor(this.props.linkDoc);
})
);
};