From 661c1367d27fa23c3aeb62369e92cd36eb5edabd Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 21 Oct 2023 00:41:23 -0400 Subject: change to doc decorations to be more "lightweight". made linkBox render links in a freeform view as a DocView. added an auto-reset view option for freeforms. fixed highlighting ink strokes. Made groups behave better for selecting things 'inside' the group bounding box that aren't in the group. Added vertically centered text option. --- src/Utils.ts | 2 +- src/client/documents/Documents.ts | 35 ++++--- src/client/util/CurrentUserUtils.ts | 4 +- src/client/util/DropConverter.ts | 6 +- src/client/util/InteractionUtils.tsx | 2 +- src/client/util/LinkManager.ts | 4 +- src/client/views/DocumentDecorations.scss | 42 ++++---- src/client/views/DocumentDecorations.tsx | 50 +++++----- src/client/views/InkControlPtHandles.tsx | 12 +-- src/client/views/InkingStroke.tsx | 8 +- src/client/views/PropertiesButtons.tsx | 15 ++- src/client/views/StyleProvider.tsx | 21 ++-- .../views/collections/CollectionNoteTakingView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 5 +- src/client/views/collections/TreeView.tsx | 6 +- .../CollectionFreeFormLayoutEngines.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 65 +++++++------ .../collections/collectionFreeForm/MarqueeView.tsx | 1 - src/client/views/global/globalCssVariables.scss | 1 + .../views/nodes/CollectionFreeFormDocumentView.tsx | 11 ++- src/client/views/nodes/DocumentView.tsx | 23 +++-- src/client/views/nodes/LinkBox.tsx | 108 ++++++++++++++++++++- .../views/nodes/RecordingBox/RecordingBox.tsx | 2 +- .../nodes/formattedText/FormattedTextBox.scss | 8 ++ .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/fields/Doc.ts | 16 ++- 27 files changed, 300 insertions(+), 154 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index b5ca53a33..34419f665 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -555,7 +555,7 @@ export function returnAll(): 'all' { return 'all'; } -export function returnNone() { +export function returnNone(): 'none' { return 'none'; } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 11b5f9f08..d61e4b3e9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -215,6 +215,7 @@ export class DocumentOptions { _lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed'); layout?: string | Doc; // default layout string or template document + layout_isSvg?: BOOLt = new BoolInfo('whether document decorations and other selections should handle pointerEvents for svg content or use doc bounding box'); layout_keyValue?: STRt = new StrInfo('layout definition for showing keyValue view of document', false); layout_explainer?: STRt = new StrInfo('explanation displayed at top of a collection to describe its purpose', false); layout_headerButton?: DOCt = new DocInfo('the (button) Doc to display at the top of a collection.', false); @@ -569,10 +570,10 @@ export namespace Docs { childDontRegisterViews: true, onClick: FollowLinkScript(), layout_hideLinkAnchors: true, - _height: 150, + _height: 1, + _width: 1, link: '', link_description: '', - layout_showCaption: 'link_description', backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area _dropPropertiesToRemove: new List(['onClick']), }, @@ -667,7 +668,7 @@ export namespace Docs { { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method layout: { view: InkingStroke, dataField: 'stroke' }, - options: { systemIcon: 'BsFillPencilFill' }, + options: { systemIcon: 'BsFillPencilFill', nativeDimModifiable: true, nativeHeightUnfrozen: true, layout_isSvg: true, layout_forceReflow: true }, }, ], [ @@ -770,7 +771,7 @@ export namespace Docs { const existing = actualProtos[id] as Doc; const type = id.replace(suffix, '') as DocumentType; // get or create prototype of the specified type... - const target = existing || buildPrototype(type, id); + const target = buildPrototype(type, id, existing); // ...and set it if not undefined (can be undefined only if TemplateMap does not contain // an entry dedicated to the given DocumentType) target && PrototypeMap.set(type, target); @@ -818,7 +819,7 @@ export namespace Docs { * @param options any value specified in the DocumentOptions object likewise * becomes the default value for that key for all delegates */ - function buildPrototype(type: DocumentType, prototypeId: string): Opt { + function buildPrototype(type: DocumentType, prototypeId: string, existing?: Doc): Opt { // load template from type const template = TemplateMap.get(type); if (!template) { @@ -844,12 +845,14 @@ export namespace Docs { layout: layout.view?.LayoutString(layout.dataField), data: template.data, }; - Object.entries(options).map(pair => { - if (typeof pair[1] === 'string' && pair[1].startsWith('@')) { - (options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1)); - } - }); - return Doc.assign(new Doc(prototypeId, true), options as any, undefined, true); + Object.entries(options) + .filter(pair => typeof pair[1] === 'string' && pair[1].startsWith('@')) + .map(pair => { + if (!existing || ScriptCast(existing[pair[0]])?.script.originalScript !== pair[1].substring(1)) { + (options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1)); + } + }); + return Doc.assign(existing ?? new Doc(prototypeId, true), OmitKeys(options, Object.keys(existing ?? {})).omit, undefined, true); } } @@ -1034,6 +1037,10 @@ export namespace Docs { I[Initializing] = true; I.type = DocumentType.INK; I.layout = InkingStroke.LayoutString('stroke'); + I.nativeDimModifiable = true; + I.nativeHeightUnfrozen = true; + I.layout_isSvg = true; + I.layout_forceReflow = true; I.layout_fitWidth = false; I.layout_hideDecorationTitle = true; // don't show title when selected // I.layout_hideOpenButton = true; // don't show open full screen button when selected @@ -1402,8 +1409,10 @@ export namespace DocUtils { link_relationship: linkSettings.link_relationship, link_description: linkSettings.link_description, link_autoMoveAnchors: true, - _layout_showCaption: 'link_description', - _layout_showTitle: 'link_relationship', + _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box) + _layout_showTitle: '', + // _layout_showCaption: 'link_description', + // _layout_showTitle: 'link_relationship', }, id ), diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index cc8f72ddf..268ee2790 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -659,7 +659,7 @@ export class CurrentUserUtils { subMenu: [ { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, ] }, { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}}, @@ -832,7 +832,7 @@ export class CurrentUserUtils { // childContextMenuLabels: new List(["Add to Dashboards",]), // childContextMenuIcons: new List(["user-plus",]), "acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment, - childDragAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true, + childDragAction: "embed", isSystem: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true, // NOTE: treeView_HideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar _layout_showTitle: "title", treeView_HideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, layout_explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index dbdf580cd..2c371f28e 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -7,7 +7,7 @@ import { Cast, StrCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; -import { ButtonType } from '../views/nodes/FontIconBox/FontIconBox'; +import { ButtonType, FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { DragManager } from './DragManager'; import { ScriptingGlobals } from './ScriptingGlobals'; @@ -56,7 +56,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { data?.draggedDocuments.map((doc, i) => { let dbox = doc; // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant - if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes('FontIconBox')) { + if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) { if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) { //dbox = Doc.MakeEmbedding(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon dbox = Doc.MakeEmbedding(dbox); @@ -78,7 +78,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { backgroundColor: StrCast(doc.backgroundColor), title: StrCast(layoutDoc.title), btnType: ButtonType.ClickButton, - icon: layoutDoc.isTemplateDoc ? 'font' : 'bolt', + icon: 'bolt', }); dbox.dragFactory = layoutDoc; dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined; diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 4e32ed67f..6406624bb 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -172,7 +172,7 @@ export namespace InteractionUtils { (); private _inkDragDocs: { doc: Doc; x: number; y: number; width: number; height: number }[] = []; @@ -70,8 +71,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P DocumentDecorations.Instance = this; reaction( () => SelectionManager.Views().slice(), - action(docs => { - this._showNothing = !DocumentView.LongPress && docs.length === 1; // show decorations if multiple docs are selected or we're long pressing + action(views => { + this._showNothing = !DocumentView.LongPress && views.length === 1; // show decorations if multiple docs are selected or we're long pressing this._editingTitle = false; }) ); @@ -80,8 +81,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P 'pointermove', action(e => { if (this.Bounds.x || this.Bounds.y || this.Bounds.r || this.Bounds.b) { - if (this.Bounds.x !== Number.MAX_VALUE && (this.Bounds.x > e.clientX || this.Bounds.r < e.clientX || this.Bounds.y > e.clientY || this.Bounds.b < e.clientY)) { + if (this.Bounds.x !== Number.MAX_VALUE && (this.Bounds.x > e.clientX + 10 || this.Bounds.r < e.clientX - 10 || this.Bounds.y > e.clientY + 10 || this.Bounds.b < e.clientY - 10)) { this._showNothing = false; + } else { + this._showNothing = true; } } }) @@ -225,10 +228,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P e.x, e.y, { - dragComplete: action(e => { - dragData.canEmbed && SelectionManager.DeselectAll(); - this._hidden = false; - }), + dragComplete: action(e => (this._hidden = false)), hideSource: true, } ); @@ -255,7 +255,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P iconView.props.removeDocument?.(iconView.props.Document); } }); - SelectionManager.DeselectAll(); + views.forEach(v => SelectionManager.DeselectView()); } this._iconifyBatch?.end(); this._iconifyBatch = undefined; @@ -403,10 +403,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P }), // moveEvent action(action(() => (this._isRotating = false))), // upEvent action((e, doubleTap) => { - if (doubleTap) { - seldocview.rootDoc.rotation_centerX = 0.5; - seldocview.rootDoc.rotation_centerY = 0.5; - } + seldocview.rootDoc.rotation_centerX = 0; + seldocview.rootDoc.rotation_centerY = 0; }) ); }; @@ -777,10 +775,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations; const hideResizers = ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating; - const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating; + const hideTitle = this._showNothing || hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating; const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.layout_hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button const hideOpenButton = + this._showNothing || hideDecorations || seldocview.props.hideOpenButton || seldocview.rootDoc.layout_hideOpenButton || @@ -788,6 +787,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P this._isRounding || this._isRotating; const hideDeleteButton = + this._showNothing || hideDecorations || this._isRounding || this._isRotating || @@ -821,7 +821,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P // Radius constants const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView; - const borderRadius = numberValue(StrCast(seldocview.rootDoc.layout_borderRounding)); + const borderRadius = numberValue(Cast(seldocview.rootDoc.layout_borderRounding, 'string', null)); const docMax = Math.min(NumCast(seldocview.rootDoc.width) / 2, NumCast(seldocview.rootDoc.height) / 2); const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2); const radiusHandle = (borderRadius / docMax) * maxDist; @@ -884,9 +884,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P )} ); - + const freeformDoc = SelectionManager.Views().some(v => v.props.CollectionFreeFormDocumentView?.()); return ( -
+
e.preventDefault()} /> )} - {hideDocumentButtonBar ? null : ( + {hideDocumentButtonBar || this._showNothing ? null : (
{this._isRotating ? null : ( tap to set rotate center, drag to rotate
}> -
e.preventDefault()}> - } color={Colors.LIGHT_GRAY} /> +
e.preventDefault()}> + } color={SettingsManager.userColor} />
)} diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 07e3270b1..0d7f7ebd8 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -2,18 +2,16 @@ import React = require('react'); import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; -import { ControlPoint, InkData, PointData, InkField } from '../../fields/InkField'; +import { ControlPoint, InkData, PointData } from '../../fields/InkField'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; -import { Cast, NumCast } from '../../fields/Types'; -import { setupMoveUpEvents, returnFalse } from '../../Utils'; -import { Transform } from '../util/Transform'; +import { Cast } from '../../fields/Types'; +import { returnFalse, setupMoveUpEvents } from '../../Utils'; +import { SelectionManager } from '../util/SelectionManager'; import { UndoManager } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; -import { DocumentView } from './nodes/DocumentView'; -import { SelectionManager } from '../util/SelectionManager'; export interface InkControlProps { inkDoc: Doc; @@ -155,7 +153,7 @@ export class InkControlPtHandles extends React.Component { cx={control.X} cy={control.Y} r={this.props.screenSpaceLineWidth * 2 * scale} - opacity={this.props.inkView.controlUndo ? 0.15 : 1} + opacity={this.props.inkView.controlUndo ? 0.35 : 1} height={this.props.screenSpaceLineWidth * 4 * scale} width={this.props.screenSpaceLineWidth * 4 * scale} strokeWidth={this.props.screenSpaceLineWidth / 2} diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 9b52f5870..d619f5123 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -372,6 +372,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { } render() { TraceMobx(); + if (!this.rootDoc._layout_isSvg) setTimeout(action(() => (this.rootDoc._layout_isSvg = true))); const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData(); const startMarker = StrCast(this.layoutDoc.stroke_startMarker); @@ -390,7 +391,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { } const highlight = !this.controlUndo && this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting); const highlightIndex = highlight?.highlightIndex; - const highlightColor = (!this.props.isSelected() || !isInkMask) && highlight?.highlightIndex ? highlight?.highlightColor : undefined; + const highlightColor = !this.props.isSelected() && !isInkMask && highlight?.highlightIndex ? highlight?.highlightColor : undefined; const color = StrCast(this.layoutDoc.stroke_outlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent'); // Visually renders the polygonal line made by the user. @@ -419,15 +420,16 @@ export class InkingStroke extends ViewBoxBaseComponent() { undefined, color === 'transparent' ? highlightColor : undefined ); + const higlightMargin = Math.min(12, Math.max(2, 0.3 * inkStrokeWidth)); // Invisible polygonal line that enables the ink to be selected by the user. const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, mask: boolean = false) => InteractionUtils.CreatePolyline( inkData, inkLeft, inkTop, - mask && color === 'transparent' ? this.strokeColor : color, + mask && color === 'transparent' ? this.strokeColor : highlightColor ?? color, inkStrokeWidth, - inkStrokeWidth + NumCast(this.layoutDoc.stroke_borderWidth) + (fillColor ? (closed ? 2 : (highlightIndex ?? 0) + 2) : 2), + inkStrokeWidth + NumCast(this.layoutDoc.stroke_borderWidth) + (fillColor ? (closed ? higlightMargin : (highlightIndex ?? 0) + higlightMargin) : higlightMargin), StrCast(this.layoutDoc.stroke_lineJoin), StrCast(this.layoutDoc.stroke_lineCap), StrCast(this.layoutDoc.stroke_bezier), diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index d1561fd67..61aa616ec 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -61,8 +61,8 @@ export class PropertiesButtons extends React.Component<{}, {}> { text={label(targetDoc?.[property])} color={SettingsManager.userColor} icon={icon(targetDoc?.[property] as any)} - iconPlacement={'left'} - align={'flex-start'} + iconPlacement="left" + align="flex-start" fillWidth={true} toggleType={ToggleType.BUTTON} onClick={undoable(() => { @@ -173,6 +173,16 @@ export class PropertiesButtons extends React.Component<{}, {}> { ); } + @computed get verticalAlignButton() { + //select text + return this.propertyToggleBtn( + on => (on ? 'ALIGN TOP' : 'ALIGN CENTER'), + '_layout_centered', + on => `${on ? 'Text is aligned with top of document' : 'Text is aligned with center of document'} `, + on => // 'eye' + ); + } + @computed get fitContentButton() { return this.propertyToggleBtn( on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), //'View All', @@ -512,6 +522,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { {toggle(this.layout_fitWidthButton)} {/* {toggle(this.freezeThumb)} */} {toggle(this.forceActiveButton)} + {toggle(this.verticalAlignButton, { display: !isText ? 'none' : '' })} {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {/* {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })} */} {toggle(this.layout_autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 6ee96de5b..48c96d064 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -1,10 +1,11 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { Dropdown, DropdownType, IconButton, IListItemProps, ListBox, ListItem, Popup, Shadows, Size, Type } from 'browndash-components'; +import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from 'browndash-components'; import { action, runInAction, untracked } from 'mobx'; import { extname } from 'path'; import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs'; +import { FaFilter } from 'react-icons/fa'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types'; import { DashColor, lightOrDark, Utils } from '../../Utils'; @@ -13,18 +14,16 @@ import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; import { IsFollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; -import { ColorScheme, SettingsManager } from '../util/SettingsManager'; +import { SettingsManager } from '../util/SettingsManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { TreeSort } from './collections/TreeSort'; import { Colors } from './global/globalEnums'; -import { InkingStroke } from './InkingStroke'; import { DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; +import { PropertiesView } from './PropertiesView'; import './StyleProvider.scss'; import React = require('react'); -import { PropertiesView } from './PropertiesView'; -import { FaFilter } from 'react-icons/fa'; export enum StyleProp { TreeViewIcon = 'treeView_Icon', @@ -85,7 +84,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString; + const isInk = () => doc && doc._layout_isSvg && !props?.LayoutTemplateString; const isOpen = property.includes(':open'); const isEmpty = property.includes(':empty'); const boxBackground = property.includes(':box'); @@ -124,7 +123,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt, props: Opt, props: Opt { diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index c7ad80f11..afeef5a8f 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -265,7 +265,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} - contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} + contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents) as any} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} addDocTab={this.props.addDocTab} bringToFront={returnFalse} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index c43a9d2b8..0b29e7286 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -357,7 +357,7 @@ export class CollectionStackingView extends CollectionSubView(moreProps?: X) { (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.rootDoc)); const moved = docDragData.moveDocument(movedDocs, this.rootDoc, canAdd ? this.addDocument : returnFalse); added = canAdd || moved ? moved : undefined; - } else { + } else if (addedDocs.length) { + added = this.addDocument(addedDocs); + } + if (!added && ScriptCast(this.rootDoc.dropConverter)) { ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData }); added = addedDocs.length ? this.addDocument(addedDocs) : true; } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index f89aa065b..b57402531 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -201,12 +201,10 @@ export class TreeView extends React.Component { if (!docView) { this._editTitle = false; } else if (docView.isSelected()) { - const doc = docView.Document; - SelectionManager.SelectSchemaViewDoc(doc); this._editTitle = true; this._disposers.selection = reaction( - () => SelectionManager.SelectedSchemaDoc(), - seldoc => seldoc !== doc && this.setEditTitle(undefined) + () => docView.isSelected(), + isSel => !isSel && this.setEditTitle(undefined) ); } else { docView.select(false); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index c1c01eacb..d93e44ab7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -42,6 +42,7 @@ export interface PoolData { transition?: string; highlight?: boolean; replica: string; + pointerEvents?: string; // without this, toggling lockPosition of a group/collection in a freeform view won't update until something else invalidates the freeform view's documents forcing -- this is a problem with doLayoutComputation which makes a performance test to insure somethingChanged pair: { layout: Doc; data?: Doc }; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index bfc61f601..5eff6b8e0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -16,17 +16,18 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, emptyFunction, intersectRect, lightOrDark, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, emptyFunction, intersectRect, lightOrDark, returnFalse, returnNone, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { InteractionUtils } from '../../../util/InteractionUtils'; +import { FollowLinkScript } from '../../../util/LinkFollower'; import { ReplayMovements } from '../../../util/ReplayMovements'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SelectionManager } from '../../../util/SelectionManager'; -import { ColorScheme, freeformScrollMode } from '../../../util/SettingsManager'; +import { freeformScrollMode } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; @@ -51,7 +52,6 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; import React = require('react'); -import { FollowLinkScript } from '../../../util/LinkFollower'; export type collectionFreeformViewProps = { NativeWidth?: () => number; @@ -156,12 +156,11 @@ export class CollectionFreeFormView extends CollectionSubView e.bounds?.width && !e.bounds.z).map(e => e.bounds!), - NumCast(this.layoutDoc._xPadding, 10), - NumCast(this.layoutDoc._yPadding, 10) - ); + : aggregateBounds( + this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!), + NumCast(this.layoutDoc._xPadding, 10), + NumCast(this.layoutDoc._yPadding, 10) + ); } @computed get nativeWidth() { return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null))); @@ -1183,23 +1182,24 @@ export class CollectionFreeFormView extends CollectionSubView { - if (sendToBack) { - const docs = this.childLayoutPairs.map(pair => pair.layout).slice(); - docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - let zfirst = docs.length ? NumCast(docs[0].zIndex) : 0; - doc.zIndex = zfirst - 1; - } else if (doc.stroke_isInkMask) { + if (doc.stroke_isInkMask) { doc.zIndex = 5000; } else { - const docs = this.childLayoutPairs.map(pair => pair.layout).slice(); - docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1; - if (docs.lastElement() !== doc) { - if (zlast - docs.length > 100) { - for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1; - zlast = docs.length + 1; + // prettier-ignore + const docs = this.childLayoutPairs.map(pair => pair.layout) + .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); + if (sendToBack) { + const zfirst = docs.length ? NumCast(docs[0].zIndex) : 0; + doc.zIndex = zfirst - 1; + } else { + let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1; + if (docs.lastElement() !== doc) { + if (zlast - docs.length > 100) { + for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1; + zlast = docs.length + 1; + } + doc.zIndex = zlast + 1; } - doc.zIndex = zlast + 1; } } }; @@ -1291,25 +1291,28 @@ export class CollectionFreeFormView extends CollectionSubView this._pointerEvents; + childPointerEventsFunc = () => this.childPointerEvents; childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)(); + groupChildPointerEvents = () => (this.props.isDocumentActive?.() ? 'all' : 'none'); getChildDocView(entry: PoolData) { const childLayout = entry.pair.layout; const childData = entry.pair.data; return ( ); @@ -1412,6 +1415,7 @@ export class CollectionFreeFormView extends CollectionSubView this.isContentActive(), - active => this.rootDoc[this.autoResetFieldKey] && !active && this.resetView() + () => this.isContentActive(), // if autoreset is on, then whenever the view is selected, it will be restored to it default pan/zoom positions + active => this.rootDoc[this.autoResetFieldKey] && active && this.resetView() ); this._disposers.fitContent = reaction( diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 5c053fefc..a30ec5302 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -377,7 +377,6 @@ export class MarqueeView extends React.Component 'none' | 'all' | undefined; // pointer events for this freeform doc view wrapper that are not passed to the docView. This allows items in a group to trigger the group to be selected, without selecting the items themselves } @observer @@ -191,17 +191,18 @@ export class CollectionFreeFormDocumentView extends DocComponent this.sizeProvider?.height || this.props.PanelHeight?.(); screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y); returnThis = () => this; + render() { TraceMobx(); const divProps: DocumentViewProps = { - ...this.props, + ...OmitKeys(this.props, ['GroupPointerEvents']).omit, CollectionFreeFormDocumentView: this.returnThis, styleProvider: this.styleProvider, ScreenToLocalTransform: this.screenToLocalTransform, PanelWidth: this.panelWidth, PanelHeight: this.panelHeight, }; - const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString && !this.layoutDoc._stroke_isInkMask; + const isInk = this.layoutDoc._layout_isSvg && !this.props.LayoutTemplateString && !this.layoutDoc._stroke_isInkMask; return (
{this.props.renderCutoffProvider(this.props.Document) ? (
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index da665a502..c2355e4f7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,5 +1,5 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; @@ -53,6 +53,7 @@ import { PresEffect, PresEffectDirection } from './trails'; import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); import { KeyValueBox } from './KeyValueBox'; +import { LinkBox } from './LinkBox'; const { Howl } = require('howler'); interface Window { @@ -142,6 +143,7 @@ export interface DocComponentView { annotationKey?: string; getTitle?: () => string; getCenter?: (xf: Transform) => { X: number; Y: number }; + screenBounds?: () => { left: number; top: number; right: number; bottom: number; center?:{X:number, Y:number} }; ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number }; @@ -154,7 +156,6 @@ export interface DocumentViewSharedProps { renderDepth: number; Document: Doc; DataDoc?: Doc; - contentBounds?: () => undefined | { x: number; y: number; r: number; b: number }; fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document suppressSetHeight?: boolean; setContentView?: (view: DocComponentView) => any; @@ -219,7 +220,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps { hideLinkAnchors?: boolean; isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events isContentActive: () => boolean | undefined; // whether document contents should handle pointer events - contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents + contentPointerEvents?: 'none' | 'all' | undefined; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents radialMenu?: String[]; LayoutTemplateString?: string; dontCenter?: 'x' | 'y' | 'xy'; @@ -425,7 +426,7 @@ export class DocumentViewInternal extends DocComponent boolean = returnFalse; onClick = action((e: React.MouseEvent | React.PointerEvent) => { - if (!this.Document.ignoreClick && this.pointerEvents !== 'none' && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { + if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { let stopPropagate = true; let preventDefault = true; !this.rootDoc._keepZWhenDragged && this.props.bringToFront(this.rootDoc); @@ -536,7 +537,6 @@ export class DocumentViewInternal extends DocComponent this._contentPointerEvents; @computed get contents() { TraceMobx(); - const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString; + const isInk = this.layoutDoc._layout_isSvg && !this.props.LayoutTemplateString; return (
{ return this.props.dontCenter?.includes('y') ? 0 : this.Yshift; } - public toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight()); + public toggleNativeDimensions = () => this.docView && this.rootDoc.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight()); public getBounds = () => { if (!this.docView?.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { return undefined; } + if (this.docView._componentView?.screenBounds) { + return this.docView._componentView.screenBounds(); + } const xf = this.docView.props .ScreenToLocalTransform() .scale(this.trueNativeWidth() ? this.nativeScaling : 1) .inverse(); const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)]; + if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { const docuBox = this.docView.ContentDiv.getElementsByClassName('linkAnchorBox-cont'); if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined }; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index efb949a47..d871c88ba 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,11 +1,19 @@ import React = require('react'); +import { Bezier } from 'bezier-js'; +import { computed, action } from 'mobx'; import { observer } from 'mobx-react'; -import { emptyFunction, returnAlways, returnFalse, returnTrue } from '../../../Utils'; +import { Height, Width } from '../../../fields/DocSymbols'; +import { Id } from '../../../fields/FieldSymbols'; +import { DocCast, StrCast } from '../../../fields/Types'; +import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils'; +import { DocumentManager } from '../../util/DocumentManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import { ComparisonBox } from './ComparisonBox'; import { FieldView, FieldViewProps } from './FieldView'; import './LinkBox.scss'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm'; +import { Transform } from '../../util/Transform'; @observer export class LinkBox extends ViewBoxBaseComponent() { @@ -17,8 +25,104 @@ export class LinkBox extends ViewBoxBaseComponent() { componentDidMount() { this.props.setContentView?.(this); } + @computed get anchor1() { + const anchor1 = DocCast(this.rootDoc.link_anchor_1); + const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1; + return DocumentManager.Instance.getDocumentView(anchor_1); + } + @computed get anchor2() { + const anchor2 = DocCast(this.rootDoc.link_anchor_2); + const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2; + return DocumentManager.Instance.getDocumentView(anchor_2); + } + screenBounds = () => { + if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) { + const a_invXf = this.anchor1.props.ScreenToLocalTransform().inverse(); + const b_invXf = this.anchor2.props.ScreenToLocalTransform().inverse(); + const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(this.anchor1.rootDoc[Width](), this.anchor1.rootDoc[Height]()) }; + const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(this.anchor2.rootDoc[Width](), this.anchor2.rootDoc[Height]()) }; + + const pts = [] as number[][]; + pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]); + pts.push(Utils.getNearestPointInPerimeter(a_scrBds.tl[0], a_scrBds.tl[1], a_scrBds.br[0] - a_scrBds.tl[0], a_scrBds.br[1] - a_scrBds.tl[1], (b_scrBds.tl[0] + b_scrBds.br[0]) / 2, (b_scrBds.tl[1] + b_scrBds.br[1]) / 2)); + pts.push(Utils.getNearestPointInPerimeter(b_scrBds.tl[0], b_scrBds.tl[1], b_scrBds.br[0] - b_scrBds.tl[0], b_scrBds.br[1] - b_scrBds.tl[1], (a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2)); + pts.push([(b_scrBds.tl[0] + b_scrBds.br[0]) / 2, (b_scrBds.tl[1] + b_scrBds.br[1]) / 2]); + const agg = aggregateBounds( + pts.map(pt => ({ x: pt[0], y: pt[1] })), + 0, + 0 + ); + return { left: agg.x, top: agg.y, right: agg.r, bottom: agg.b, center: undefined }; + } + return { left: 0, top: 0, right: 0, bottom: 0, center: undefined }; + }; render() { - if (this.dataDoc.treeView_Open === undefined) setTimeout(() => (this.dataDoc.treeView_Open = true)); + if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) { + const a = (this.anchor1 ?? this.anchor2)!; + const b = (this.anchor2 ?? this.anchor1)!; + + const parxf = this.props.docViewPath()[this.props.docViewPath().length - 2].ComponentView as CollectionFreeFormView; + const this_xf = parxf?.getTransform() ?? Transform.Identity; //this.props.ScreenToLocalTransform(); + const a_invXf = a.props.ScreenToLocalTransform().inverse(); + const b_invXf = b.props.ScreenToLocalTransform().inverse(); + const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(a.rootDoc[Width](), a.rootDoc[Height]()) }; + const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(b.rootDoc[Width](), b.rootDoc[Height]()) }; + const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) }; + const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) }; + + const ppt1 = [(a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2]; + const pt1 = Utils.getNearestPointInPerimeter(a_bds.tl[0], a_bds.tl[1], a_bds.br[0] - a_bds.tl[0], a_bds.br[1] - a_bds.tl[1], (b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2); + const pt2 = Utils.getNearestPointInPerimeter(b_bds.tl[0], b_bds.tl[1], b_bds.br[0] - b_bds.tl[0], b_bds.br[1] - b_bds.tl[1], (a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2); + const ppt2 = [(b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2]; + + const pts = [ppt1, pt1, pt2, ppt2].map(pt => [pt[0], pt[1]]); + const [lx, rx, ty, by] = [Math.min(pt1[0], pt2[0]), Math.max(pt1[0], pt2[0]), Math.min(pt1[1], pt2[1]), Math.max(pt1[1], pt2[1])]; + setTimeout( + action(() => { + this.layoutDoc.x = lx; + this.layoutDoc.y = ty; + this.layoutDoc._width = rx - lx; + this.layoutDoc._height = by - ty; + }) + ); + + const highlight = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting); + const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined; + + const bez = new Bezier(pts.map(p => ({ x: p[0], y: p[1] }))); + const text = bez.get(0.5); + const linkDesc = StrCast(this.rootDoc.link_description) || 'description'; + return ( +
+ + + + + + + + + + + + +   + {linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')} +   + + +
+ ); + } return (
() { Doc.AddToMyOverlay(value); DocumentManager.Instance.AddViewRenderedCb(value, docView => { Doc.UserDoc().currentRecording = docView.rootDoc; - SelectionManager.SelectSchemaViewDoc(value); + docView.select(false); RecordingBox.resumeWorkspaceReplaying(value); }); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 348bdd79e..6765e1dea 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -5,6 +5,14 @@ height: 100%; min-height: 100%; } +.formattedTextBox-inner.centered, +.formattedTextBox-inner-rounded.centered { + align-items: center; + display: flex; + .ProseMirror { + min-height: unset; + } +} .ProseMirror:focus { outline: none !important; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index fcdfbdf2a..41b1c59b0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -2149,7 +2149,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
{ - const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - const [bptX, bptY] = [sptX + doc[Width](), sptY + doc[Height]()]; - return { - x: Math.min(sptX, bounds.x), - y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), - b: Math.max(bptY, bounds.b), - }; - }, + (bounds, doc) => ({ + x: Math.min(NumCast(doc.x), bounds.x), + y: Math.min(NumCast(doc.y), bounds.y), + r: Math.max(NumCast(doc.x) + doc[Width](), bounds.r), + b: Math.max(NumCast(doc.y) + doc[Height](), bounds.b), + }), { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE } ); return bounds; -- cgit v1.2.3-70-g09d2