aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-02-07 16:18:16 -0500
committerbobzel <zzzman@gmail.com>2024-02-07 16:18:16 -0500
commite3fde25014d523c5f43a138093718899fe17d108 (patch)
tree8ae6e905408302857dcdadf39f0a7ba68479c2a9 /src
parent97bc8fb32741051554509eeaf9d223b327ebd611 (diff)
made various render methods in DocumentView computed getters for efficiency and to avoid artifacts (LInkanchorBox dragging) when something else invalidates causing components to regenerate. fixed linklines to animate when doing a zoom transition and to be able to target texts hyperlinks. fixed link lines to share properties with ink and updated the properties panel / menus to allow editing of either. addding toggling link lines on and off from linkitemmenu
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts2
-rw-r--r--src/client/views/DocComponent.tsx1
-rw-r--r--src/client/views/PropertiesButtons.tsx2
-rw-r--r--src/client/views/PropertiesView.tsx104
-rw-r--r--src/client/views/StyleProvider.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx1
-rw-r--r--src/client/views/global/globalScripts.ts26
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx24
-rw-r--r--src/client/views/nodes/DocumentView.tsx35
-rw-r--r--src/client/views/nodes/LinkBox.tsx197
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts5
-rw-r--r--src/fields/Doc.ts3
-rw-r--r--src/fields/DocSymbols.ts1
13 files changed, 201 insertions, 202 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1978c144b..355a4c937 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -599,7 +599,7 @@ export namespace Docs {
_width: 1,
link: '',
link_description: '',
- backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area
+ color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area
_dropPropertiesToRemove: new List(['onClick']),
},
},
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index dacd359c5..99b9c3045 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -35,6 +35,7 @@ export interface ViewBoxInterface {
addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
select?: (ctrlKey: boolean, shiftKey: boolean) => void;
focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>;
+ viewTransition?: () => Opt<string>; // duration of a view transition animation
isAnyChildContentActive?: () => boolean; // is any child content of the document active
onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index bba6285c2..cb38ab602 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -411,7 +411,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
docView.noOnClick();
switch (onClick) {
case 'enterPortal':
- docView.makeIntoPortal();
+ DocUtils.makeIntoPortal(docView.Document, docView.layoutDoc, docView.allLinks);
break;
case 'toggleDetail':
docView.setToggleDetail();
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 07f285eaf..3ae2362a1 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -741,7 +741,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
switch (field) {
case 'Xps': selDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10); break;
case 'Yps': selDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10); break;
- case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
+ case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.[DocData].stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
case 'wid':
const oldWidth = NumCast(selDoc._width);
const oldHeight = NumCast(selDoc._height);
@@ -786,7 +786,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
};
getField(key: string) {
- return Field.toString(this.selectedDoc?.[key] as Field);
+ return Field.toString(this.selectedDoc?.[DocData][key] as Field);
}
@computed get shapeXps() {
@@ -801,6 +801,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get shapeWid() {
return NumCast(this.selectedDoc?._width);
}
+ @computed get strokeThk() {
+ return NumCast(this.selectedDoc?.[DocData].stroke_width);
+ }
set shapeXps(value) {
this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100);
}
@@ -813,6 +816,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
set shapeHgt(value) {
this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100);
}
+ set strokeThk(value) {
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100);
+ }
@computed get hgtInput() {
return this.inputBoxDuo(
@@ -845,16 +851,16 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
private _lastDash: any = '2';
@computed get colorFil() {
- return StrCast(this.selectedDoc?.fillColor);
+ return StrCast(this.selectedDoc?.[DocData].fillColor);
}
@computed get colorStk() {
- return StrCast(this.selectedDoc?.color);
+ return StrCast(this.selectedDoc?.[DocData].color);
}
set colorFil(value) {
- this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined);
+ this.selectedDoc && (this.selectedDoc[DocData].fillColor = value ? value : undefined);
}
set colorStk(value) {
- this.selectedDoc && (this.selectedDoc.color = value ? value : undefined);
+ this.selectedDoc && (this.selectedDoc[DocData].color = value ? value : undefined);
}
colorButton(value: string, type: string, setter: () => void) {
@@ -943,19 +949,19 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
set dashdStk(value) {
value && (this._lastDash = value);
- this.selectedDoc && (this.selectedDoc.stroke_dash = value ? this._lastDash : undefined);
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_dash = value ? this._lastDash : undefined);
}
set markScal(value) {
- this.selectedDoc && (this.selectedDoc.stroke_markerScale = Number(value));
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value));
}
set widthStk(value) {
- this.selectedDoc && (this.selectedDoc.stroke_width = Number(value));
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Number(value));
}
set markHead(value) {
- this.selectedDoc && (this.selectedDoc.stroke_startMarker = value);
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value);
}
set markTail(value) {
- this.selectedDoc && (this.selectedDoc.stroke_endMarker = value);
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value);
}
@computed get stkInput() {
@@ -996,53 +1002,11 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get widthAndDash() {
return (
<div className="widthAndDash">
- <div className="width">
- <div className="width-top">
- <div className="width-title">Width:</div>
- <div className="width-input">{this.stkInput}</div>
- </div>
- <input
- className="width-range"
- type="range"
- style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
- defaultValue={Number(this.widthStk)}
- min={1}
- max={100}
- onChange={action(e => (this.widthStk = e.target.value))}
- onMouseDown={e => {
- this._widthUndo = UndoManager.StartBatch('width undo');
- }}
- onMouseUp={e => {
- this._widthUndo?.end();
- this._widthUndo = undefined;
- }}
- />
- </div>
+ <div className="width">{this.getNumber('Thickness', '', 0, Math.max(50, this.strokeThk), this.strokeThk, (val: number) => !isNaN(val) && (this.strokeThk = val), 50, 1)}</div>
+ <div className="width">{this.getNumber('Arrow Scale', '', 0, Math.max(10, this.markScal), this.markScal, (val: number) => !isNaN(val) && (this.markScal = val), 10, 1)}</div>
<div className="arrows">
<div className="arrows-head">
- <div className="width-top">
- <div className="width-title">Arrow Scale:</div>
- {/* <div className="width-input">{this.markScalInput}</div> */}
- </div>
- <input
- className="width-range"
- type="range"
- defaultValue={this.markScal}
- style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
- min={0}
- max={10}
- onChange={action(e => (this.markScal = +e.target.value))}
- onMouseDown={e => {
- this._widthUndo = UndoManager.StartBatch('scale undo');
- }}
- onMouseUp={e => {
- this._widthUndo?.end();
- this._widthUndo = undefined;
- }}
- />
- </div>
- <div className="arrows-head">
<div className="arrows-head-title">Arrow Head: </div>
<input key="markHead" className="arrows-head-input" type="checkbox" checked={this.markHead !== ''} onChange={undoBatch(action(() => (this.markHead = this.markHead ? '' : 'arrow')))} />
</div>
@@ -1442,36 +1406,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
<p>Description</p>
{this.editDescription}
</div>
- <div className="propertiesView-input inline">
- <p>Show link</p>
- <button
- style={{ background: !LinkManager.Instance.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')}
- 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.Instance.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')}
- 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.Instance.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
- </button>
- </div>
</div>
{!hasSelectedAnchor ? null : (
<div className="propertiesView-section">
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index b7e64d9a8..0794efe4c 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -134,7 +134,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize));
case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
- case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
+ case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30);
case StyleProp.ShowTitle:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b08221f99..61fd5fd05 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -238,6 +238,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
+ viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined);
fitContentOnce = () => {
const vals = this.fitToContentVals;
this.layoutDoc._freeform_panX = vals.bounds.cx;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 813cb9338..0541a9ca7 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -43,14 +43,14 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
} else if (selectedViews.length) {
if (checkResult) {
const selView = selectedViews.lastElement();
- const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+ const fieldKey = selView.Document._layout_isSvg ? 'fillColor' : 'backgroundColor';
const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent';
}
selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
selectedViews.forEach(dv => {
- const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+ const fieldKey = dv.Document._layout_isSvg ? 'fillColor' : 'backgroundColor';
const layoutFrameNumber = Cast(dv.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
if (contentFrameNumber !== undefined) {
@@ -344,24 +344,24 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
// prettier-ignore
const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
['inkMask', {
- checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.stroke_isInkMask) : ActiveIsInkMask())),
- setInk: (doc: Doc) => (doc.stroke_isInkMask = !doc.stroke_isInkMask),
+ checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
+ setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask),
setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
}],
['fillColor', {
- checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"),
- setInk: (doc: Doc) => (doc.fillColor = StrCast(value)),
+ checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"),
+ setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)),
setMode: () => SetActiveFillColor(StrCast(value)),
}],
[ 'strokeWidth', {
- checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.stroke_width) : ActiveInkWidth()),
- setInk: (doc: Doc) => (doc.stroke_width = NumCast(value)),
- setMode: () => { SetActiveInkWidth(value.toString()); setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
+ checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()),
+ setInk: (doc: Doc) => (doc[DocData].stroke_width = NumCast(value)),
+ setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
}],
['strokeColor', {
- checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()),
- setInk: (doc: Doc) => (doc.color = String(value)),
- setMode: () => { SetActiveInkColor(StrCast(value)); setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
+ checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()),
+ setInk: (doc: Doc) => (doc[DocData].color = String(value)),
+ setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK && setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
}],
]);
@@ -369,7 +369,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
return map.get(option)?.checkResult();
}
map.get(option)?.setMode();
- SelectionManager.Docs.filter(doc => doc.type === DocumentType.INK).map(doc => map.get(option)?.setInk(doc));
+ SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc));
});
/** WEB
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 7427f4310..92c63cd56 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -74,6 +74,14 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
return this._props.sourceDoc;
}
+ onIconDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, () => {
+ if (!this._props.docView._props.removeDocument?.(this._props.linkDoc)) {
+ this._props.docView._props.addDocument?.(this._props.linkDoc);
+ }
+ });
+ };
+
onEdit = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
@@ -196,12 +204,16 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
</p>
) : null}
<div className="linkMenu-title-wrapper">
- <div className="destination-icon-wrapper">
- <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" />
- </div>
- <p className="linkMenu-destination-title">
- {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
- </p>
+ <Tooltip title={<div className="dash-tooltip">Show/Hide Link</div>}>
+ <div title="click to show link" className="destination-icon-wrapper" onPointerDown={this.onIconDown}>
+ <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" />
+ </div>
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">Follow Link</div>}>
+ <p className="linkMenu-destination-title">
+ {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
+ </p>
+ </Tooltip>
</div>
{!this._props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this._props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
</div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5efa028d1..042ae6e55 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -48,6 +48,7 @@ import { KeyValueBox } from './KeyValueBox';
import { LinkAnchorBox } from './LinkAnchorBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { PresEffect, PresEffectDirection } from './trails';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
interface Window {
MediaRecorder: MediaRecorder;
}
@@ -726,7 +727,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
};
removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false);
- allLinkEndpoints = () => {
+ @computed get allLinkEndpoints() {
// the small blue dots that mark the endpoints of links
if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
return this.filteredLinks.map(link => (
@@ -750,9 +751,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
/>
</div>
));
- };
+ }
- viewBoxContents = () => {
+ @computed get viewBoxContents() {
TraceMobx();
const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
@@ -778,10 +779,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
setTitleFocus={this.setTitleFocus}
hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
/>
- {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints()}
+ {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
</div>
);
- };
+ }
captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
@@ -814,7 +815,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
* setting layout_showTitle using the format: field1[;field2[...][:hover]]
* from the UI, this is done by clicking the title field and prefixin the format with '#'. eg., #field1[;field2;...][:hover]
**/
- titleView = () => {
+ @computed get titleView() {
const showTitle = this.layout_showTitle?.split(':')[0];
const showTitleHover = this.layout_showTitle?.includes(':hover');
@@ -888,9 +889,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
</div>
</div>
);
- };
+ }
- captionView = () => {
+ @computed get captionView() {
return !this.layout_showCaption ? null : (
<div
className="documentView-captionWrapper"
@@ -913,7 +914,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
/>
</div>
);
- };
+ }
renderDoc = (style: object) => {
TraceMobx();
@@ -933,15 +934,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
fontFamily: StrCast(this.Document._text_fontFamily, 'inherit'),
fontSize: Cast(this.Document._text_fontSize, 'string', null),
transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
- transition: !this._animateScalingTo ? this._props.DataTransition?.() || StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
+ transition: !this._animateScalingTo ? this._props.DataTransition?.() : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
}}>
{this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
- this.viewBoxContents()
+ this.viewBoxContents
) : (
<div className="documentView-styleWrapper">
- {this.titleView()}
- {this.viewBoxContents()}
- {this.captionView()}
+ {this.titleView}
+ {this.viewBoxContents}
+ {this.captionView}
</div>
)}
{this.widgetDecorations ?? null}
@@ -1191,8 +1192,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), transition: undefined };
}
// transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox
- const transition = this.docViewPath().find((parent: DocumentView) => parent._props.DataTransition?.() || StrCast(parent.Document.dataTransition));
- return { left, top, right, bottom, transition: transition?._props.DataTransition?.() || StrCast(transition?.Document.dataTransition) };
+ const transition = this.docViewPath().find((parent: DocumentView) => parent.DataTransition?.() || parent.ComponentView?.viewTransition?.());
+ return { left, top, right, bottom, transition: transition?.DataTransition?.() || transition?.ComponentView?.viewTransition?.() };
}
@computed get nativeWidth() {
@@ -1337,6 +1338,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
}
};
+ DataTransition = () => this._props.DataTransition?.() || StrCast(this.Document.dataTransition);
ShouldNotScale = () => this.shouldNotScale;
NativeWidth = () => this.effectiveNativeWidth;
NativeHeight = () => this.effectiveNativeHeight;
@@ -1402,6 +1404,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
<DocumentViewInternal
{...this._props}
fieldKey={this.LayoutFieldKey}
+ DataTransition={this.DataTransition}
DocumentView={this.selfView}
docViewPath={this.docViewPath}
PanelWidth={this.PanelWidth}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 85d22abfd..0788e5adc 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,18 +1,21 @@
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import Xarrow from 'react-xarrows';
+import { DocData } from '../../../fields/DocSymbols';
+import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, lightOrDark, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
import { LinkManager } from '../../util/LinkManager';
import { SnappingManager } from '../../util/SnappingManager';
import { ViewBoxBaseComponent } from '../DocComponent';
+import { EditableView } from '../EditableView';
+import { LightboxView } from '../LightboxView';
import { StyleProp } from '../StyleProvider';
import { ComparisonBox } from './ComparisonBox';
import { FieldView, FieldViewProps } from './FieldView';
import './LinkBox.scss';
-import { LinkDescriptionPopup } from './LinkDescriptionPopup';
@observer
export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -43,19 +46,20 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.disposer = reaction(
() => ({ drag: SnappingManager.IsDragging, a: this.anchor1, b: this.anchor2 }),
({ drag, a, b }) => {
- setTimeout(
- // need to wait for drag manager to set 'hidden' flag on dragged elements
- action(() => {
- let a1 = a && document.getElementById(a.Guid);
- let a2 = b && document.getElementById(b.Guid);
- if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true;
- else {
- for (; a1 && !a1.hidden; a1 = a1.parentElement);
- for (; a2 && !a2.hidden; a2 = a2.parentElement);
- this._hide = a1 || a2 ? true : false;
- }
- })
- );
+ !LightboxView.Contains(this.DocumentView?.()) &&
+ setTimeout(
+ // need to wait for drag manager to set 'hidden' flag on dragged elements
+ action(() => {
+ let a1 = a && document.getElementById(a.Guid);
+ let a2 = b && document.getElementById(b.Guid);
+ if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true;
+ else {
+ for (; a1 && !a1.hidden; a1 = a1.parentElement);
+ for (; a2 && !a2.hidden; a2 = a2.parentElement);
+ this._hide = a1 || a2 ? true : false;
+ }
+ })
+ );
},
{ fireImmediately: true }
);
@@ -63,89 +67,130 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
select = (ctrlKey: boolean, shiftKey: boolean) => (LinkManager.Instance.currentLink = this.Document);
- descriptionDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(
- this,
- e,
- returnFalse,
- returnFalse,
- action(() => {
- LinkManager.Instance.currentLink = this.Document;
- LinkDescriptionPopup.Instance.popupX = e.clientX;
- LinkDescriptionPopup.Instance.popupY = e.clientY;
- LinkDescriptionPopup.Instance.display = true;
-
- const rect = document.body.getBoundingClientRect();
- if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
- LinkDescriptionPopup.Instance.popupX -= 190;
- }
- if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
- LinkDescriptionPopup.Instance.popupY -= 40;
- }
- }),
- false
- );
- };
render() {
- trace();
+ if (this._hide) return null;
const a = this.anchor1;
const b = this.anchor2;
this._forceAnimate;
- if (a && b && !this._hide) {
+ if (a && b && !LightboxView.Contains(this.DocumentView?.())) {
+ // text selection bounds are not directly observable, so we have to
+ // force an update when anything that could affect them changes (text edits causing reflow, scrolling)
+ a.Document[a.LayoutFieldKey];
+ b.Document[b.LayoutFieldKey];
+ a.Document.layout_scrollTop;
+ b.Document.layout_scrollTop;
+
const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove)
const bxf = b.screenToViewTransform();
const scale = a.CollectionFreeFormView === this.DocumentView?.().CollectionFreeFormView ? axf.Scale : bxf.Scale;
const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
- const bt = b.getBounds?.transition;
+ const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
+
+ // if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
+ // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
+ // otherwise, we just use the computed nearest point on the document boundary to the target Document
+ const targetAhyperlink = Array.from(window.document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement();
+ const targetBhyperlink = Array.from(window.document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement();
+
+ const aid = targetAhyperlink?.id || a.Document[Id];
+ const bid = targetBhyperlink?.id || b.Document[Id];
+ if (!document.getElementById(aid) || !document.getElementById(bid)) {
+ setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
+ return null;
+ }
+
if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
- const color = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
- const { strokeWidth, stroke_startMarker, stroke_endMarker } = this.Document;
- const dash = StrCast(this.Document.stroke_dash);
- const stroke = highlightColor ?? 'lightblue';
+ const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
+ const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document;
+ const strokeWidth = NumCast(stroke_width, 4);
const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '');
return (
- <Xarrow
- divContainerStyle={{ transform: `scale(${scale})` }}
- start={a.Guid}
- end={b.Guid} //
- strokeWidth={NumCast(strokeWidth, 4)}
- dashness={dash ? true : false}
- showHead={stroke_startMarker ? true : false}
- showTail={stroke_endMarker ? true : false}
- tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
- headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
- color={stroke}
- labels={
- <div
- onPointerDown={this.descriptionDown} //
- style={{
- borderRadius: '20%', //
- padding: '3',
- pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
- background: stroke,
- color: color || lightOrDark(stroke),
- fontSize,
- fontFamily /*, fontStyle: 'italic'*/,
- }}>
- {labelText}
- </div>
- }
- passProps={{ onPointerDown: this.descriptionDown }}
- />
+ <>
+ {!highlightColor ? null : (
+ <Xarrow
+ divContainerStyle={{ transform: `scale(${scale})` }}
+ start={aid}
+ end={bid} //
+ strokeWidth={strokeWidth + Math.max(2, strokeWidth * 0.1)}
+ showHead={stroke_startMarker ? true : false}
+ showTail={stroke_endMarker ? true : false}
+ headSize={NumCast(stroke_markerScale, 3)}
+ tailSize={NumCast(stroke_markerScale, 3)}
+ tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ color={highlightColor}
+ />
+ )}
+ <Xarrow
+ divContainerStyle={{ transform: `scale(${scale})` }}
+ start={aid}
+ end={bid} //
+ strokeWidth={strokeWidth}
+ dashness={Number(stroke_dash) ? true : false}
+ showHead={stroke_startMarker ? true : false}
+ showTail={stroke_endMarker ? true : false}
+ headSize={NumCast(stroke_markerScale, 3)}
+ tailSize={NumCast(stroke_markerScale, 3)}
+ tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ color={color}
+ labels={
+ <div
+ style={{
+ borderRadius: '8px',
+ pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
+ fontSize,
+ fontFamily /*, fontStyle: 'italic'*/,
+ color: fontColor || lightOrDark(DashColor(color).fade(0.5).toString()),
+ paddingLeft: 4,
+ paddingRight: 4,
+ paddingTop: 3,
+ paddingBottom: 3,
+ background: DashColor(highlightColor || color)
+ .fade(0.5)
+ .toString(),
+ }}>
+ <EditableView
+ key="editableView"
+ oneLine
+ contents={labelText}
+ height={fontSize + 4}
+ fontSize={fontSize}
+ GetValue={() => linkDesc}
+ SetValue={action(val => {
+ this.Document[DocData].link_description = val;
+ return true;
+ })}
+ />
+
+ {/* <EditableText
+ placeholder={labelText}
+ background={color}
+ color={fontColor || lightOrDark(DashColor(color).fade(0.5).toString())}
+ type={Type.PRIM}
+ val={StrCast(this.Document[DocData].link_description)}
+ setVal={action(val => (this.Document[DocData].link_description = val))}
+ fillWidth
+ /> */}
+ </div>
+ }
+ passProps={{}}
+ />
+ </>
);
}
- return null;
return (
<div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
<ComparisonBox
- {...this._props} //
+ {...this.props} //
fieldKey="link_anchor"
setHeight={emptyFunction}
dontRegisterView={true}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index a342285b0..a141ef041 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,6 +1,7 @@
import * as React from 'react';
import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
import { Doc } from '../../../../fields/Doc';
+import { Utils } from '../../../../Utils';
const emDOM: DOMOutputSpec = ['em', 0];
const strongDOM: DOMOutputSpec = ['strong', 0];
@@ -44,7 +45,7 @@ export const marks: { [index: string]: MarkSpec } = {
toDOM(node: any) {
const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
- return ['a', { class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
+ return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
},
},
noAutoLinkAnchor: {
@@ -104,7 +105,7 @@ export const marks: { [index: string]: MarkSpec } = {
node.attrs.title,
],
]
- : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0];
+ : ['a', { id: '' + Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0];
},
},
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 3cd4efcf7..56d50846a 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -16,7 +16,7 @@ import { DateField } from './DateField';
import {
AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
- Initializing, Self, SelfProxy, UpdatingFromServer, Width
+ Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
} from './DocSymbols'; // prettier-ignore
import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { InkField, InkTool } from './InkField';
@@ -309,6 +309,7 @@ export class Doc extends RefField {
public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
public [Width] = () => NumCast(this[SelfProxy]._width);
public [Height] = () => NumCast(this[SelfProxy]._height);
+ public [TransitionTimer]: any = undefined;
public [ToJavascriptString] = () => `idToDoc("${this[Self][Id]}")`; // what should go here?
public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 9c563abbf..f8a57acd5 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -15,6 +15,7 @@ export const DocLayout = Symbol('DocLayout');
export const DocFields = Symbol('DocFields');
export const DocCss = Symbol('DocCss');
export const DocAcl = Symbol('DocAcl');
+export const TransitionTimer = Symbol('DocTransitionTimer');
export const DirectLinks = Symbol('DocDirectLinks');
export const AclPrivate = Symbol('DocAclOwnerOnly');
export const AclReadonly = Symbol('DocAclReadOnly');