aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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');