aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2022-11-29 12:05:29 -0500
committerbobzel <zzzman@gmail.com>2022-11-29 12:05:29 -0500
commitc6d1059e24f362a167b9ac24e6f13d1e45361da9 (patch)
treea0136cee04608c73f54ac3ecd63bed0307a37fd4
parent1f5db9cfc594dbf337d752ec94dab5fca7d8b6f7 (diff)
changes to streamline link editing UI (got rid of LinkEditor). cleaned up link (un)highlighting.
-rw-r--r--src/client/documents/Documents.ts1
-rw-r--r--src/client/util/DocumentManager.ts19
-rw-r--r--src/client/util/LinkFollower.ts2
-rw-r--r--src/client/util/LinkManager.ts2
-rw-r--r--src/client/util/SelectionManager.ts1
-rw-r--r--src/client/views/DocumentDecorations.tsx14
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/PropertiesView.scss15
-rw-r--r--src/client/views/PropertiesView.tsx94
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx2
-rw-r--r--src/client/views/global/globalCssVariables.scss1
-rw-r--r--src/client/views/global/globalCssVariables.scss.d.ts1
-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
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx16
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx110
-rw-r--r--src/fields/Doc.ts64
21 files changed, 194 insertions, 1031 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c5e08eeea..eed839520 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1375,7 +1375,6 @@ export namespace DocUtils {
'acl-Public': SharingPermissions.Augment,
'_acl-Public': SharingPermissions.Augment,
linkDisplay: true,
- _hidden: true,
_linkAutoMove: true,
linkRelationship,
_showCaption: 'description',
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7120eeb88..4f02a8202 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
import { action, observable, runInAction } from 'mobx';
import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
-import { Cast } from '../../fields/Types';
+import { Cast, DocCast } from '../../fields/Types';
import { returnFalse } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
import { LightboxView } from '../views/LightboxView';
@@ -36,15 +36,14 @@ export class DocumentManager {
//console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString);
if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
const viewAnchorIndex = view.props.LayoutTemplateString.includes('anchor2') ? 'anchor2' : 'anchor1';
- DocListCast(view.rootDoc.links).forEach(link => {
- this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView =>
- this.LinkedDocumentViews.push({
- a: viewAnchorIndex === 'anchor2' ? otherView : view,
- b: viewAnchorIndex === 'anchor2' ? view : otherView,
- l: link,
- })
- );
- });
+ const link = view.rootDoc;
+ this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView =>
+ this.LinkedDocumentViews.push({
+ a: viewAnchorIndex === 'anchor2' ? otherView : view,
+ b: viewAnchorIndex === 'anchor2' ? view : otherView,
+ l: link,
+ })
+ );
this.LinkAnchorBoxViews.push(view);
// this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " +
// view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString));
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 5374bf44b..282116f1b 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -67,7 +67,7 @@ export class LinkFollower {
docViewProps.ContainingCollectionDoc,
action(() => {
batch.end();
- DocumentDecorations.Instance.overrideBounds = false;
+ Doc.AddUnlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false)));
}),
altKey ? true : undefined
);
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 49cc3218d..01f4df723 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -44,7 +44,7 @@ export class LinkManager {
if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
Doc.GetProto(a1)[DirectLinksSym].add(link);
Doc.GetProto(a2)[DirectLinksSym].add(link);
- Doc.GetProto(link)[DirectLinksSym].add(link);
+ //Doc.GetProto(link)[DirectLinksSym].add(link); // bcz: links are not linked to themself, so this was a hack
}
})
);
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 02d672a65..a3d6f5227 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -4,7 +4,6 @@ import { Doc, Opt } from '../../fields/Doc';
import { DocCast } from '../../fields/Types';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocumentView } from '../views/nodes/DocumentView';
-import { LinkManager } from './LinkManager';
import { ScriptingGlobals } from './ScriptingGlobals';
export namespace SelectionManager {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3efb5fb37..d1f0bf2ac 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -27,7 +27,7 @@ import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
import { LightboxView } from './LightboxView';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { ImageBox } from './nodes/ImageBox';
import React = require('react');
@@ -253,17 +253,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if (selectedDocs.length) {
if (e.ctrlKey) {
// open an alias in a new tab with Ctrl Key
- CollectionDockingView.AddSplit(Doc.BestAlias(selectedDocs[0].props.Document), 'right');
+ CollectionDockingView.AddSplit(Doc.BestAlias(selectedDocs[0].props.Document), OpenWhereMod.right);
} else if (e.shiftKey) {
// open centered in a new workspace with Shift Key
const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
alias.context = undefined;
alias.x = -alias[WidthSym]() / 2;
alias.y = -alias[HeightSym]() / 2;
- CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), 'right');
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), OpenWhereMod.right);
} else if (e.altKey) {
// open same document in new tab
- CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, 'right');
+ CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, OpenWhereMod.right);
} else {
var openDoc = selectedDocs[0].props.Document;
if (openDoc.layoutKey === 'layout_icon') {
@@ -720,13 +720,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
// if multiple documents have been opened at the same time, then don't show open button
- const hideOpenButton =hideDecorations ||
+ const hideOpenButton =
+ hideDecorations ||
seldocview.props.hideOpenButton ||
seldocview.rootDoc.hideOpenButton ||
SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) ||
this._isRounding ||
this._isRotating;
- const hideDeleteButton =hideDecorations ||
+ const hideDeleteButton =
+ hideDecorations ||
this._isRounding ||
this._isRotating ||
seldocview.props.hideDeleteButton ||
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 98d0378be..09063901d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -484,7 +484,7 @@ export class MainView extends React.Component {
}
globalPointerDown = action((e: PointerEvent) => {
- runInAction(() => (Doc.HighlightBrush.linkFollowEffect = undefined));
+ Doc.linkFollowUnhighlight();
AudioBox.Enabled = true;
const targets = document.elementsFromPoint(e.x, e.y);
if (targets.length) {
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 30806f718..897be9a32 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -4,8 +4,13 @@
height: 100%;
width: 250;
font-family: 'Roboto';
+ font-size: 12px;
cursor: auto;
+ .slider-text {
+ font-size: 8px;
+ }
+
overflow-x: hidden;
overflow-y: auto;
@@ -865,7 +870,15 @@
}
.propertiesButton {
- width: 4rem;
+ width: 2rem;
+ height: 2rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ > svg {
+ width: 15px;
+ height: 15px;
+ }
}
}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index ad3f62990..e8fd540a8 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -55,7 +55,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@computed get selectedDoc() {
- return LinkManager.currentLink || SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard;
+ return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard;
}
@computed get selectedDocumentView() {
if (SelectionManager.Views().length) return SelectionManager.Views()[0];
@@ -306,7 +306,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
@computed get links() {
- return !this.selectedDoc ? null : <PropertiesDocBacklinksSelector Document={this.selectedDoc} hideTitle={true} addDocTab={this.props.addDocTab} />;
+ const selAnchor = this.selectedDocumentView?.anchorViewDoc;
+ return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this.props.addDocTab} />;
}
@computed get layoutPreview() {
@@ -1426,11 +1427,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@undoBatch
animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
const lanch = this.sourceAnchor;
- const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.LIGHT_BLUE : 'black';
+ const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.MEDIUM_BLUE : '';
return (
<Tooltip title={<div className="dash-tooltip">{direction}</div>}>
<div
- style={{ ...opts, border: direction === PresEffectDirection.Center ? `solid 2px ${color}` : undefined, borderRadius: '100%', cursor: 'pointer', gridColumn, gridRow, justifySelf: 'center', color }}
+ style={{ ...opts, border: direction === PresEffectDirection.Center ? `solid 2px ${color}` : undefined, borderRadius: '20%', cursor: 'pointer', gridColumn, gridRow, justifySelf: 'center', background: color, color: 'black' }}
onClick={() => this.updateEffectDirection(direction)}>
{icon ? <FontAwesomeIcon icon={icon as any} /> : null}
</div>
@@ -1470,19 +1471,21 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
};
- toggleProp = (e: React.PointerEvent, prop: string) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc[prop] = !this.selectedDoc[prop]))));
+ toggleLinkProp = (e: React.PointerEvent, prop: string) => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.currentLink && (LinkManager.currentLink[prop] = !LinkManager.currentLink[prop]))));
};
@computed get destinationAnchor() {
const ldoc = LinkManager.currentLink;
- const lanch = LinkManager.currentLinkAnchor;
+ const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor;
if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch;
return ldoc ? DocCast(ldoc.anchor2) : ldoc;
}
@computed get sourceAnchor() {
- return LinkManager.currentLinkAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink);
+ const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor;
+
+ return selAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink);
}
toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc) => {
@@ -1545,9 +1548,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const isNovice = Doc.noviceMode;
const zoom = Number((NumCast(this.sourceAnchor?.presZoom, 1) * 100).toPrecision(3));
const targZoom = this.sourceAnchor?.followLinkZoom;
- const selectedDoc = this.selectedDoc;
const indent = 30;
- if (!selectedDoc && !this.isPres) {
+ if (!this.selectedDoc && !this.isPres) {
return (
<div className="propertiesView" style={{ width: this.props.width }}>
<div className="propertiesView-title" style={{ width: this.props.width }}>
@@ -1556,7 +1558,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>
);
} else {
- if (selectedDoc && !this.isPres) {
+ if (this.selectedDoc && !this.isPres) {
return (
<div
className="propertiesView"
@@ -1573,9 +1575,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
{this.contextsSubMenu}
{this.linksSubMenu}
- {!selectedDoc || !LinkManager.currentLink || !SelectionManager.Views().some(dv => DocListCast(dv.rootDoc.links).includes(LinkManager.currentLink!)) ? null : (
+ {!this.selectedDoc || !LinkManager.currentLink || !SelectionManager.Views().some(dv => DocListCast(this.sourceAnchor?.links).includes(LinkManager.currentLink!)) ? null : (
<>
- <div className="propertiesView-section">
+ <div className="propertiesView-section" style={{ background: 'darkgray' }}>
<div className="propertiesView-input first" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}>
<p>Relationship</p>
{this.editRelationship}
@@ -1584,11 +1586,41 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<p>Description</p>
{this.editDescription}
</div>
+ <div className="propertiesView-input inline">
+ <p>Show link</p>
+ <button
+ style={{ background: !LinkManager.currentLink?.linkDisplay ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleLinkProp(e, 'linkDisplay')}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
+ <p>Auto-move anchors</p>
+ <button
+ style={{ background: !LinkManager.currentLink?.linkAutoMove ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleLinkProp(e, 'linkAutoMove')}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
+ <p>Display arrow</p>
+ <button
+ style={{ background: !LinkManager.currentLink?.linkDisplayArrow ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleLinkProp(e, 'linkDisplayArrow')}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
</div>
<div className="propertiesView-section">
<div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 84px)' }}>
<p>Follow by</p>
- <select onChange={e => this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(selectedDoc.followLinkLocation, 'default')}>
+ <select onChange={e => this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkLocation, 'default')}>
<option value="default">Default</option>
<option value="add:left">Opening in new left pane</option>
<option value="add:right">Opening in new right pane</option>
@@ -1598,7 +1630,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<option value="add">Opening in new tab</option>
<option value="replace">Replacing current tab</option>
<option value="inPlace">Opening in place</option>
- {selectedDoc.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
+ {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
</select>
</div>
<div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}>
@@ -1609,7 +1641,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<option value={effect.toString()}>{effect.toString()}</option>
))}
</select>
- <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, gridColumn: 3 }}>
+ <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, height: 36, gridColumn: 3, gridTemplateRows: '12px 12px 12px' }}>
{this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})}
{this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})}
{this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})}
@@ -1650,34 +1682,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
</button>
</div>
- <div className="propertiesView-input inline">
- <p>Show link</p>
- <button style={{ background: !selectedDoc.linkDisplay ? '' : '#4476f7', borderRadius: 3 }} onPointerDown={e => this.toggleProp(e, 'linkDisplay')} onClick={e => e.stopPropagation()} className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
- <p>Auto-move anchor</p>
- <button
- style={{ background: !selectedDoc.linkAutoMove ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleProp(e, 'linkAutoMove')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
- <p>Display arrow</p>
- <button
- style={{ background: !selectedDoc.linkDisplayArrow ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleProp(e, 'linkDisplayArrow')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 128px) 50px' }}>
- <p>Zoom % screen</p>
+ <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}>
+ <p>Zoom %</p>
<div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}>
<input className="presBox-input" style={{ width: '100%' }} type="number" value={zoom} />
<div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index b8344dc0c..9e360f557 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -2,10 +2,10 @@ import { computed } from 'mobx';
import { observer } from 'mobx-react';
import { Id } from '../../../../fields/FieldSymbols';
import { DocumentManager } from '../../../util/DocumentManager';
+import { LightboxView } from '../../LightboxView';
import './CollectionFreeFormLinksView.scss';
import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView';
import React = require('react');
-import { LightboxView } from '../../LightboxView';
@observer
export class CollectionFreeFormLinksView extends React.Component<React.PropsWithChildren<{}>> {
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index e68f9abe3..430a36dce 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -84,4 +84,5 @@ $TREE_BULLET_WIDTH: 20px;
LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
INK_MASK_SIZE: $INK_MASK_SIZE;
+ MEDIUM_GRAY: $medium-gray;
}
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
index 76259113c..3375579d6 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.scss.d.ts
@@ -11,6 +11,7 @@ interface IGlobalScss {
LEFT_MENU_WIDTH: string;
TREE_BULLET_WIDTH: string;
INK_MASK_SIZE: number;
+ MEDIUM_GRAY: string;
}
declare const globalCssVariables: IGlobalScss;
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);
})
);
};
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 627487a9e..99fa62fa7 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -305,7 +305,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
top: 0,
pointerEvents: 'none',
}}>
- <Tooltip title={!DocumentLinksButton.LinkEditorDocView ? <div className="dash-tooltip">{title}</div> : <></>}>{this.linkButtonInner}</Tooltip>
+ {!DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>}
</div>
);
}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2729a5047..a8bea61c9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
import { observer, renderReporter } from 'mobx-react';
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
+import { AclAdmin, AclEdit, AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
import { Document } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
@@ -11,7 +11,7 @@ import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { AudioField } from '../../../fields/URLField';
import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
import { MobileInterface } from '../../../mobile/MobileInterface';
@@ -1156,7 +1156,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
anchorPanelHeight = () => this.props.PanelHeight() || 1;
anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
// prettier-ignore
- switch (property) {
+ switch (property.split(':')[0]) {
case StyleProp.ShowTitle: return '';
case StyleProp.PointerEvents: return 'none';
case StyleProp.LinkSource: return this.props.Document; // pass the LinkSource to the LinkAnchorBox
@@ -1188,7 +1188,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
TraceMobx();
if (this.layoutDoc.unrendered || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
if (this.rootDoc.type === DocumentType.PRES || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return null;
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
+ const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => d.linkDisplay);
return filtered.map((link, i) => (
<div className="documentView-anchorCont" key={link[Id]}>
<DocumentView
@@ -1415,7 +1415,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined,
});
- const animRenderDoc = Doc.IsHighlighted(this.rootDoc) || PresBox.Instance?.isActiveItemTarget(this.layoutDoc) ? PresBox.AnimationEffect(renderDoc, PresBox.Instance?.activeItem ?? Doc.HighlightBrush.linkFollowEffect, this.rootDoc) : renderDoc;
+ const animRenderDoc = Doc.IsHighlighted(this.rootDoc) || PresBox.Instance?.isActiveItemTarget(this.layoutDoc) ? PresBox.AnimationEffect(renderDoc, PresBox.Instance?.activeItem ?? this.rootDoc[AnimationSym], this.rootDoc) : renderDoc;
return (
<div
className={`${DocumentView.ROOT_DIV} docView-hack`}
@@ -1535,8 +1535,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.screenToLocalTransform().Scale;
@computed get linkCountView() {
- return (this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton) &&
- DocumentLinksButton.LinkEditorDocView?.rootDoc !== this.rootDoc ? null : (
+ return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton ? null : (
<DocumentLinksButton View={this} scaling={this.linkButtonInverseScaling} OnHover={true} Bottom={this.topMost} ShowCount={true} />
);
}
@@ -1654,6 +1653,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
+ @computed get anchorViewDoc() {
+ return this.props.LayoutTemplateString?.includes('anchor2') ? DocCast(this.rootDoc['anchor2']) : this.props.LayoutTemplateString?.includes('anchor1') ? DocCast(this.rootDoc['anchor1']) : this.rootDoc;
+ }
docViewPathFunc = () => this.docViewPath;
isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection);
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index be9565452..e89076c1f 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,4 +1,3 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../../fields/Doc';
@@ -7,20 +6,17 @@ import { TraceMobx } from '../../../fields/util';
import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { LinkFollower } from '../../util/LinkFollower';
-import { SelectionManager } from '../../util/SelectionManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxBaseComponent } from '../DocComponent';
-import { LinkEditor } from '../linking/LinkEditor';
import { StyleProp } from '../StyleProvider';
import { FieldView, FieldViewProps } from './FieldView';
import './LinkAnchorBox.scss';
import { LinkDocPreview } from './LinkDocPreview';
import React = require('react');
-import { OpenWhere } from './DocumentView';
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
+import { LinkManager } from '../../util/LinkManager';
+import globalCssVariables = require('../global/globalCssVariables.scss');
+import { SelectionManager } from '../../util/SelectionManager';
@observer
export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -34,15 +30,23 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
_timeout: NodeJS.Timeout | undefined;
@observable _x = 0;
@observable _y = 0;
- @observable _selected = false;
- @observable _editing = false;
- @observable _forceOpen = false;
onPointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false);
+ const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
+ setupMoveUpEvents(
+ this,
+ e,
+ this.onPointerMove,
+ emptyFunction,
+ (e, doubleTap) => {
+ if (doubleTap) LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false);
+ else this.props.select(false);
+ },
+ false
+ );
};
onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
- const cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
+ const cdiv = this._ref?.current?.parentElement;
if (!this._isOpen && cdiv) {
const bounds = cdiv.getBoundingClientRect();
const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
@@ -60,58 +64,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
return false;
});
- @action
- onClick = (e: React.MouseEvent) => {
- if (e.button === 2 || e.ctrlKey || !this.layoutDoc.isLinkButton) {
- this.props.select(false);
- }
- if (!this._doubleTap && !e.ctrlKey && e.button < 2) {
- const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
- this._editing = true;
- anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
- if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
- this._timeout = setTimeout(
- action(() => {
- LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false);
- this._editing = false;
- }),
- 300 - (Date.now() - this._lastTap)
- );
- e.stopPropagation();
- }
- } else {
- this._timeout && clearTimeout(this._timeout);
- this._timeout = undefined;
- this._doubleTap = false;
- this.openLinkEditor(e);
- e.stopPropagation();
- }
- };
- openLinkDocOnRight = (e: React.MouseEvent) => {
- this.props.addDocTab(this.rootDoc, OpenWhere.addRight);
- };
- openLinkTargetOnRight = (e: React.MouseEvent) => {
- const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
- alias._isLinkButton = undefined;
- alias.layoutKey = 'layout';
- this.props.addDocTab(alias, OpenWhere.addRight);
- };
- @action
- openLinkEditor = action((e: React.MouseEvent) => {
- SelectionManager.DeselectAll();
- this._editing = this._forceOpen = true;
- });
-
- specificContextMenu = (e: React.MouseEvent): void => {
- const funcs: ContextMenuProps[] = [];
- funcs.push({ description: 'Open Link Target on Right', event: () => this.openLinkTargetOnRight(e), icon: 'eye' });
- funcs.push({ description: 'Open Link on Right', event: () => this.openLinkDocOnRight(e), icon: 'eye' });
- funcs.push({ description: 'Open Link Editor', event: () => this.openLinkEditor(e), icon: 'eye' });
- funcs.push({ description: 'Toggle Always Show Link', event: () => (this.props.Document.linkDisplay = !this.props.Document.linkDisplay), icon: 'eye' });
-
- ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
- };
+ specificContextMenu = (e: React.MouseEvent): void => {};
render() {
TraceMobx();
@@ -122,22 +76,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ':anchor');
const anchor = this.fieldKey === 'anchor1' ? 'anchor2' : 'anchor1';
const anchorScale = !this.dataDoc[this.fieldKey + '-useLinkSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25;
-
const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title);
- const flyout = (
- <div className="linkAnchorBoxBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.rootDoc)}>
- <LinkEditor sourceDoc={Cast(this.dataDoc[this.fieldKey], Doc, null)} hideback={true} linkDoc={this.rootDoc} showLinks={action(() => {})} />
- {!this._forceOpen ? null : (
- <div className="linkAnchorBox-linkCloser" onPointerDown={action(() => (this._isOpen = this._editing = this._forceOpen = false))}>
- <FontAwesomeIcon color="dimgray" icon={'times'} size={'sm'} />
- </div>
- )}
- </div>
- );
+ const selView = SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('anchor1') ? 'anchor1' : SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('anchor2') ? 'anchor2' : '';
return (
<div
+ ref={this._ref}
+ title={targetTitle}
className={`linkAnchorBox-cont${small ? '-small' : ''}`}
- //onPointerLeave={} //LinkDocPreview.Clear}
onPointerEnter={e =>
LinkDocPreview.SetLinkInfo({
docProps: this.props,
@@ -149,24 +94,15 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
})
}
onPointerDown={this.onPointerDown}
- onClick={this.onClick}
- title={targetTitle}
onContextMenu={this.specificContextMenu}
- ref={this._ref}
style={{
+ border: selView && this.rootDoc[selView] === this.rootDoc[this.fieldKey] ? `solid ${globalCssVariables.MEDIUM_GRAY} 2px` : undefined,
background,
left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`,
top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`,
transform: `scale(${anchorScale})`,
- }}>
- {!this._editing && !this._forceOpen ? null : (
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} open={this._forceOpen ? true : undefined} onOpen={() => (this._isOpen = true)} onClose={action(() => (this._isOpen = this._forceOpen = this._editing = false))}>
- <span className="linkAnchorBox-button">
- <FontAwesomeIcon icon={'eye'} size={'lg'} />
- </span>
- </Flyout>
- )}
- </div>
+ }}
+ />
);
}
}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index c10751698..c6cabe269 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -94,6 +94,8 @@ export function DocListCastOrNull(field: FieldResult) {
export const WidthSym = Symbol('Width');
export const HeightSym = Symbol('Height');
+export const AnimationSym = Symbol('Animation');
+export const HighlightSym = Symbol('Highlight');
export const DataSym = Symbol('Data');
export const LayoutSym = Symbol('Layout');
export const FieldsSym = Symbol('Fields');
@@ -329,6 +331,8 @@ export class Doc extends RefField {
@observable private ___fieldKeys: any = {};
@observable public [AclSym]: { [key: string]: symbol } = {};
@observable public [DirectLinksSym]: Set<Doc> = new Set();
+ @observable public [AnimationSym]: Opt<Doc>;
+ @observable public [HighlightSym]: boolean = false;
private [UpdatingFromServer]: boolean = false;
private [ForceServerWrite]: boolean = false;
@@ -1261,53 +1265,55 @@ export namespace Doc {
}
export function linkFollowUnhighlight() {
- Doc.UnhighlightAll();
+ UnhighlightWatchers.length = 0;
+ highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
document.removeEventListener('pointerdown', linkFollowUnhighlight);
- runInAction(() => (HighlightBrush.linkFollowEffect = undefined));
}
- let _lastDate = 0;
+ let UnhighlightWatchers: (() => void)[] = [];
+ let UnhighlightTimer: any;
+ export function AddUnlightWatcher(watcher: () => void) {
+ if (UnhighlightTimer) {
+ UnhighlightWatchers.push(watcher);
+ } else watcher();
+ }
export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presEffect?: Doc) {
- //linkFollowUnhighlight();
- // runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = undefined));
- // setTimeout(() => runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = presEffect)));
- runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = presEffect));
- (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs));
+ linkFollowUnhighlight();
+ (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presEffect));
document.removeEventListener('pointerdown', linkFollowUnhighlight);
document.addEventListener('pointerdown', linkFollowUnhighlight);
- const lastDate = (_lastDate = Date.now());
- window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000);
+ if (UnhighlightTimer) clearTimeout(UnhighlightTimer);
+ UnhighlightTimer = window.setTimeout(() => {
+ UnhighlightWatchers.forEach(watcher => watcher());
+ linkFollowUnhighlight();
+ UnhighlightTimer = 0;
+ }, 5000);
}
- export class HighlightBrush {
- @observable HighlightedDoc: Map<Doc, boolean> = new Map();
- @observable static linkFollowEffect: Doc | undefined;
- }
- const highlightManager = new HighlightBrush();
+ var highlightedDocs = new Set<Doc>();
export function IsHighlighted(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false;
- return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
+ return doc[HighlightSym] || Doc.GetProto(doc)[HighlightSym];
}
- export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
+ export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) {
runInAction(() => {
- highlightManager.HighlightedDoc.set(doc, true);
- dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true);
+ doc[AnimationSym] = presEffect;
+ highlightedDocs.add(doc);
+ doc[HighlightSym] = true;
+ if (dataAndDisplayDocs) {
+ highlightedDocs.add(Doc.GetProto(doc));
+ Doc.GetProto(doc)[HighlightSym] = true;
+ }
});
}
export function UnHighlightDoc(doc: Doc) {
runInAction(() => {
- highlightManager.HighlightedDoc.set(doc, false);
- highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false);
+ highlightedDocs.delete(doc);
+ highlightedDocs.delete(Doc.GetProto(doc));
+ doc[HighlightSym] = Doc.GetProto(doc)[HighlightSym] = false;
+ doc[AnimationSym] = undefined;
});
}
- export function UnhighlightAll() {
- const mapEntries = highlightManager.HighlightedDoc.keys();
- let docEntry: IteratorResult<Doc>;
- while (!(docEntry = mapEntries.next()).done) {
- const targetDoc = docEntry.value;
- targetDoc && Doc.UnHighlightDoc(targetDoc);
- }
- }
export function UnBrushAllDocs() {
brushManager.BrushedDoc.clear();
}