From dabac69d26c8e8e69bd55466ce221d9e33e36638 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Mar 2023 14:23:17 -0400 Subject: numerous changes to try to simplify event handling in DocumentView - got rid of isContentActive in DocComponent since it's in DocumentView. Including adding 'enableDragWhenActive' , 'onClickScriptDisable', --- src/client/views/nodes/trails/PresBox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes/trails') diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index e79e7472a..3376c29a9 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1027,8 +1027,8 @@ export class PresBox extends ViewBoxBaseComponent() { removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; - isContentActive = (outsideReaction?: boolean) => - Doc.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false; + isContentActive = (outsideReaction?: boolean) => this.props.isContentActive(outsideReaction); + //.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false; /** * For sorting the array so that the order is maintained when it is dropped. -- cgit v1.2.3-70-g09d2 From 0d899471be676f6619244350982630b151bb4b41 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Mar 2023 19:53:49 -0400 Subject: mostly code cleaning. small bugs --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/OverlayView.tsx | 6 +- src/client/views/StyleProvider.tsx | 12 +-- .../views/collections/CollectionTimeView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 1 + .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentView.scss | 6 +- src/client/views/nodes/DocumentView.tsx | 93 ++++++++++------------ src/client/views/nodes/LinkAnchorBox.tsx | 32 +++----- src/client/views/nodes/trails/PresBox.tsx | 5 +- 11 files changed, 76 insertions(+), 89 deletions(-) (limited to 'src/client/views/nodes/trails') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c484db0db..a110782b2 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -263,6 +263,7 @@ export class DocumentOptions { defaultBackgroundColor?: string; _isLinkButton?: boolean; // marks a document as a button that will follow its primary link when clicked _linkAutoMove?: boolean; // whether link endpoint should move around the edges of a document to make shortest path to other link endpoint + hideLinkAnchors?: boolean; // suppresses link anchor dots from being displayed isFolder?: boolean; lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide) activeFrame?: number; // the active frame of a document in a frame base collection @@ -505,6 +506,7 @@ export namespace Docs { options: { childDontRegisterViews: true, _isLinkButton: true, + hideLinkAnchors: true, _height: 150, description: '', showCaption: 'description', @@ -581,7 +583,7 @@ export namespace Docs { DocumentType.PRES, { layout: { view: PresBox, dataField: defaultDataKey }, - options: { defaultDoubleClick: 'ignore' }, + options: { defaultDoubleClick: 'ignore', hideLinkAnchors: true }, }, ], [ diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b21f53221..753cd1cb7 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -301,7 +301,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true }, - { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", clickFactory: "repl" as any, openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", clickFactory: 'repl' as any, openFactoryLocation: OpenWhere.overlay}, ].map(tuple => ( { openFactoryLocation: OpenWhere.addRight, scripts: { onClick: 'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)', diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 34e8cd6dd..ec22128d4 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -219,6 +219,7 @@ export class OverlayView extends React.Component { PanelHeight={d[HeightSym]} ScreenToLocalTransform={this.docScreenToLocalXf(d)} renderDepth={1} + hideDecorations={true} isDocumentActive={returnTrue} isContentActive={returnTrue} whenChildContentsActiveChanged={emptyFunction} @@ -252,8 +253,3 @@ export class OverlayView extends React.Component { ); } } -// bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error -ScriptingGlobals.add(function addOverlayWindow(type: string, options: OverlayElementOptions) { - OverlayView.Instance.addWindow(, options); - addOverlayWindow('ScriptingRepl', { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' }); -}); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index faaa4e1f9..bd0539069 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -36,10 +36,10 @@ export enum StyleProp { FillColor = 'fillColor', // fill color of an ink stroke or shape WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note HideLinkButton = 'hideLinkButton', // hides the blue-dot link button. used when a document acts like a button - LinkSource = 'linkSource', // source document of a link -- used by LinkAnchorBox PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that + ShowCaption = 'showCaption', TitleHeight = 'titleHeight', // Height of Title area ShowTitle = 'showTitle', // whether to display a title on a Document (optional :hover suffix) JitterRotation = 'jitterRotation', // whether documents should be randomly rotated @@ -182,6 +182,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt (props?.PanelHeight() || 0) ? 5 : 10) : 0; + case StyleProp.ShowCaption: + return doc?._viewType === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._showCaption); case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || (doc?.type === DocumentType.RTF && !showTitle()?.includes('noMargin')) || @@ -299,12 +301,12 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 ? ( + if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform) { + return doc?.pointerEvents !== 'none' ? null : (
toggleLockedPosition(doc)}> - +
- ) : null; + ); } } } diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 89b2fbfe3..4d5978548 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -150,7 +150,7 @@ export class CollectionTimeView extends CollectionSubView() { engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }} fitContentsToBox={returnTrue} childClickScript={this._childClickedScript} - viewDefDivClick={this._viewDefDivClick} + viewDefDivClick={this.layoutEngine() === computeTimelineLayout.name ? undefined : this._viewDefDivClick} //dontScaleFilter={this.dontScaleFilter} layoutEngine={this.layoutEngine} /> diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 93b816581..4ad09628f 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -422,6 +422,7 @@ export class TabDocView extends React.Component { })} renderDepth={0} LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined} + hideTitle={this.props.keyValue} Document={this._document} DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined} ContainingCollectionView={undefined} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 0714bffbc..f16371592 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -609,7 +609,7 @@ export class MarqueeView extends React.Component { const layoutDoc = Doc.Layout(doc); diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index e5913d997..1265651ad 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -17,8 +17,7 @@ top: 0; } -.documentView-node, -.documentView-node-topmost { +.documentView-node { position: inherit; top: 0; left: 0; @@ -209,8 +208,7 @@ } } -.documentView-node:hover, -.documentView-node-topmost:hover { +.documentView-node:hover { > .documentView-styleWrapper { > .documentView-titleWrapper-hover { display: inline-block; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9a3e77e6e..ab93ce87b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -268,9 +268,6 @@ export class DocumentViewInternal extends DocComponent; // milliseconds for animating between views. defaults to 300 if not uset @observable _animateScalingTo = 0; - private get topMost() { - return this.props.renderDepth === 0 && !LightboxView.LightboxDoc; - } public get animateScaleTime() { return this._animateScaleTime ?? 300; } @@ -307,8 +304,9 @@ export class DocumentViewInternal extends DocComponent { - this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); + this.props.ContainingCollectionView?.removeDocument(this.props.Document); + RadialMenu.Instance.closeMenu(); }, icon: 'external-link-square-alt', selected: -1, @@ -563,13 +565,11 @@ export class DocumentViewInternal extends DocComponent { - if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return; + if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return; - if (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { - this.cleanupPointerEvents(); - this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType)); - } + if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) { + this.cleanupPointerEvents(); + this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType)); } }; @@ -888,7 +888,7 @@ export class DocumentViewInternal extends DocComponent d.linkDisplay); return filtered.map((link, i) => (
@@ -1059,11 +1056,13 @@ export class DocumentViewInternal extends DocComponent this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1; const showTitle = this.ShowTitle?.split(':')[0]; const showTitleHover = this.ShowTitle?.includes(':hover'); - const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined; - const captionView = !showCaption ? null : ( + const captionView = !this.showCaption ? null : (
@@ -1161,12 +1159,13 @@ export class DocumentViewInternal extends DocComponent @@ -1220,7 +1219,7 @@ export class DocumentViewInternal extends DocComponent
); - return this.props.hideTitle || (!showTitle && !showCaption) ? ( + return this.props.hideTitle || (!showTitle && !this.showCaption) ? ( this.contents ) : (
@@ -1235,31 +1234,27 @@ export class DocumentViewInternal extends DocComponent { TraceMobx(); - const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png'); - const background = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box'); - if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null; - return ( - this.docContents ?? ( -
- {this.innards} - {!this.disableClickScriptFunc && this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ?
: null} - {this.widgetDecorations ?? null} -
- ) - ); + return !DocCast(this.Document) || GetEffectiveAcl(this.Document[DataSym]) === AclPrivate || this.hidden + ? null + : this.docContents ?? ( +
+ {this.innards} + {this.widgetDecorations ?? null} +
+ ); }; /** @@ -1506,7 +1501,7 @@ export class DocumentView extends React.Component { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); } @computed get panelHeight() { - if (this.effectiveNativeHeight && !this.layoutDoc.nativeHeightUnfrozen) { + if (this.effectiveNativeHeight && (!this.fitWidth || !this.layoutDoc.nativeHeightUnfrozen)) { return Math.min(this.props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling); } return this.props.PanelHeight(); @@ -1528,7 +1523,7 @@ export class DocumentView extends React.Component { toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight()); getBounds = () => { - if (!this.docView || !this.docView.ContentDiv || this.props.Document.type === DocumentType.PRES || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { + if (!this.docView || !this.docView.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { return undefined; } const xf = this.docView?.props diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index e12548f18..3feb95ce9 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -1,22 +1,19 @@ -import { action, observable } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../fields/Doc'; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { LinkFollower } from '../../util/LinkFollower'; -import { ContextMenu } from '../ContextMenu'; -import { ContextMenuProps } from '../ContextMenuItem'; +import { SelectionManager } from '../../util/SelectionManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import './LinkAnchorBox.scss'; import { LinkDocPreview } from './LinkDocPreview'; import React = require('react'); -import { LinkManager } from '../../util/LinkManager'; import globalCssVariables = require('../global/globalCssVariables.scss'); -import { SelectionManager } from '../../util/SelectionManager'; @observer export class LinkAnchorBox extends ViewBoxBaseComponent() { @@ -31,19 +28,15 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { @observable _x = 0; @observable _y = 0; + @computed get linkSource() { + return this.props.docViewPath()[this.props.docViewPath().length - 2].rootDoc; // this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource); + } + onPointerDown = (e: React.PointerEvent) => { - const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource); - setupMoveUpEvents( - this, - e, - this.onPointerMove, - emptyFunction, - (e, doubleTap) => { - if (doubleTap) LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, false); - else this.props.select(false); - }, - false - ); + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => { + if (doubleTap) LinkFollower.FollowLink(this.rootDoc, this.linkSource, false); + else this.props.select(false); + }); }; onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => { const cdiv = this._ref?.current?.parentElement; @@ -73,7 +66,6 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { const small = this.props.PanelWidth() <= 1; // this happens when rendered in a treeView const x = NumCast(this.rootDoc[this.fieldKey + '_x'], 100); const y = NumCast(this.rootDoc[this.fieldKey + '_y'], 100); - const linkSource = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource); const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ':anchor'); const anchor = this.fieldKey === 'anchor1' ? 'anchor2' : 'anchor1'; const anchorScale = !this.dataDoc[this.fieldKey + '-useLinkSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25; @@ -87,7 +79,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { onPointerEnter={e => LinkDocPreview.SetLinkInfo({ docProps: this.props, - linkSrc: linkSource, + linkSrc: this.linkSource, linkDoc: this.rootDoc, showHeader: true, location: [e.clientX, e.clientY + 20], diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 3376c29a9..0afb36214 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -919,6 +919,7 @@ export class PresBox extends ViewBoxBaseComponent() { */ @action enterMinimize = () => { + this.updateCurrentPresentation(this.rootDoc); clearTimeout(this._presTimer); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); this.props.removeDocument?.(this.layoutDoc); @@ -939,7 +940,7 @@ export class PresBox extends ViewBoxBaseComponent() { doc._height = 30; doc._width = PresBox.minimizedWidth; Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); - PresBox.Instance.initializePresState(PresBox.Instance.itemIndex); + PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex); return (doc.presStatus = PresStatus.Manual); } @@ -2435,7 +2436,7 @@ export class PresBox extends ViewBoxBaseComponent() {
Slide {this.itemIndex + 1} - {this.activeItem.presIndexed !== undefined ? `(${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length} + {this.activeItem?.presIndexed !== undefined ? `(${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}> -- cgit v1.2.3-70-g09d2 From 38eb3a1b3f005b79a222f76b730e883b7e2b5253 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 5 Apr 2023 14:36:37 -0400 Subject: made pinning from topbar available for all docs. --- src/client/util/CurrentUserUtils.ts | 8 +++---- src/client/views/DocumentButtonBar.tsx | 25 +++++++++++----------- .../collectionFreeForm/CollectionFreeFormView.tsx | 16 +++++++++----- src/client/views/nodes/trails/PresBox.tsx | 1 - src/client/views/nodes/trails/PresElementBox.tsx | 2 +- 5 files changed, 28 insertions(+), 24 deletions(-) (limited to 'src/client/views/nodes/trails') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 753cd1cb7..9571209b0 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -682,9 +682,9 @@ export class CurrentUserUtils { CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid, CollectionViewType.NoteTaking]), title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, - { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, toolType:"tab", funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}}, - { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, toolType:"tab", ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected - { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, toolType:"tab", ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, + { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 20, scripts: { onClick: 'pinWithView(altKey)'}}, + { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected + { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag",btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(_readOnly_)'}}, // Only when floating document is selected in freeform { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, @@ -693,7 +693,7 @@ export class CurrentUserUtils { { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available - { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(),expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected { title: "Schema", icon: "Schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected ]; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 3e1a0f265..e83ea00cf 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -289,7 +289,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV @observable subEndLink = ''; @computed get endLinkButton() { - const linkBtn = (pinDocLayout: boolean, pinDocContent: boolean, icon: IconProp) => { + const linkBtn = (pinLayout: boolean, pinContent: boolean, icon: IconProp) => { const tooltip = `Finish Link and Save ${this.subEndLink} data`; return !this.view0 ? null : ( {tooltip}
}> @@ -300,7 +300,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV key={icon.toString()} size="sm" icon={icon} - onPointerEnter={action(e => (this.subEndLink = (pinDocLayout ? 'Layout' : '') + (pinDocLayout && pinDocContent ? ' &' : '') + (pinDocContent ? ' Content' : '')))} + onPointerEnter={action(e => (this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '')))} onPointerLeave={action(e => (this.subEndLink = ''))} onClick={e => { const docs = this.props @@ -309,9 +309,8 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .map(dv => dv!.rootDoc); this.view0 && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.props.Document, true, this.view0, { - pinDocLayout, - pinDocContent, - pinData: !pinDocContent ? {} : { poslayoutview: true, dataannos: true, dataview: true }, + pinDocLayout: pinLayout, + pinData: !pinContent ? {} : { poslayoutview: true, dataannos: true, dataview: pinContent }, } as PinProps); e.stopPropagation(); @@ -337,7 +336,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV @computed get pinButton() { const targetDoc = this.view0?.props.Document; - const pinBtn = (pinDocLayout: boolean, pinDocContent: boolean, icon: IconProp) => { + const pinBtn = (pinLayoutView: boolean, pinContentView: boolean, icon: IconProp) => { const tooltip = `Pin Document and Save ${this.subPin} to trail`; return !tooltip ? null : ( {tooltip}
}> @@ -351,10 +350,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV onPointerEnter={action( e => (this.subPin = - (pinDocLayout ? 'Layout' : '') + - (pinDocLayout && pinDocContent ? ' &' : '') + - (pinDocContent ? ' Content View' : '') + - (pinDocLayout && pinDocContent ? '(shift+alt)' : pinDocLayout ? '(shift)' : pinDocContent ? '(alt)' : '')) + (pinLayoutView ? 'Layout' : '') + + (pinLayoutView && pinContentView ? ' &' : '') + + (pinContentView ? ' Content View' : '') + + (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '')) )} onPointerLeave={action(e => (this.subPin = ''))} onClick={e => { @@ -364,8 +363,8 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .map(dv => dv!.rootDoc); TabDocView.PinDoc(docs, { pinAudioPlay: true, - pinDocLayout, - pinData: { dataview: true }, + pinDocLayout: pinLayoutView, + pinData: { dataview: pinContentView }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null), currentFrame: Cast(docs.lastElement()?.currentFrame, 'number', null), }); @@ -385,7 +384,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: {dataview: e.altKey}, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index fb0bd2a19..22a2a429f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -2196,11 +2196,17 @@ ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) { if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent'; runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.())); }); -ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) { - !readOnly && - SelectionManager.Views().forEach(view => - TabDocView.PinDoc(view.rootDoc, { currentFrame: Cast(view.rootDoc.currentFrame, 'number', null), pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) }) - ); +ScriptingGlobals.add(function pinWithView(pinContent: boolean) { + SelectionManager.Views().forEach(view => + view.props.pinToPres(view.rootDoc, { + currentFrame: Cast(view.rootDoc.currentFrame, 'number', null), + pinData: { + poslayoutview: pinContent, + dataview: pinContent, + }, + pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()), + }) + ); }); ScriptingGlobals.add(function bringToFront() { SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc)); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 0afb36214..bfaae8069 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -59,7 +59,6 @@ export interface PinProps { hidePresBox?: boolean; pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected) pinDocLayout?: boolean; // pin layout info (width/height/x/y) - pinDocContent?: boolean; // pin data info (scroll/pan/zoom/text) pinAudioPlay?: boolean; // pin audio annotation pinData?: pinDataTypes; } diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 698a2817e..9a74b5dba 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -312,7 +312,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { @action updateCapturedViewContents = (presTargetDoc: Doc, activeItem: Doc) => { const target = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc; - PresBox.pinDocView(activeItem, { pinDocContent: true, pinData: PresBox.pinDataTypes(target) }, target); + PresBox.pinDocView(activeItem, { pinData: PresBox.pinDataTypes(target) }, target); }; @computed get recordingIsInOverlay() { -- cgit v1.2.3-70-g09d2 From 99fba6d6e99da0154d40d2e3e87e120d8e6f2810 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 5 Apr 2023 22:44:38 -0400 Subject: fixed up dataviz to work again. fixed point selection, tooltips, making and following links --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/nodes/DataVizBox/ChartBox.tsx | 62 ++++----------------- .../views/nodes/DataVizBox/ChartInterface.ts | 6 +-- src/client/views/nodes/DataVizBox/DataVizBox.scss | 4 ++ src/client/views/nodes/DataVizBox/DataVizBox.tsx | 63 ++++++++++------------ .../views/nodes/DataVizBox/components/Chart.scss | 9 +++- .../nodes/DataVizBox/components/LineChart.tsx | 41 ++++++++------ src/client/views/nodes/DocumentView.tsx | 1 + src/client/views/nodes/trails/PresBox.tsx | 9 +++- src/server/DataVizUtils.ts | 22 ++++---- 11 files changed, 98 insertions(+), 125 deletions(-) (limited to 'src/client/views/nodes/trails') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c4014a752..1f09cdd3d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1148,8 +1148,8 @@ export namespace Docs { return Prototypes.get(DocumentType.PRESELEMENT); } - export function DataVizDocument(url: string, options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }); + export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) { + return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }, undefined, undefined, undefined, overwriteDoc); } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9aceed366..623aaf357 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -273,7 +273,7 @@ export class CurrentUserUtils { {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, - // {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, + {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}}, {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true, _fitWidth:true, _chromeHidden: true, boxShadow: "0 0" }}, {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _fitWidth: true, _backgroundGridShow: true, }}, diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx index c229e5471..ee577b9fd 100644 --- a/src/client/views/nodes/DataVizBox/ChartBox.tsx +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -5,10 +5,12 @@ import { action, computed, observable } from 'mobx'; import { NumCast, StrCast } from '../../../../fields/Types'; import { LineChart } from './components/LineChart'; import { Chart, ChartData } from './ChartInterface'; +import { PinProps } from '../trails'; export interface ChartBoxProps { rootDoc: Doc; dataDoc: Doc; + height: number; pairs: { x: number; y: number }[]; setChartBox: (chartBox: ChartBox) => void; getAnchor: () => Doc; @@ -71,11 +73,7 @@ export class ChartBox extends React.Component { componentDidMount = () => { this.generateChartData(); - // setting timeout to allow rerender cycle of the actual chart tof inish - // setTimeout(() => { this.props.setChartBox(this); - // }); - // this.props.setChartBox(this); }; @action @@ -86,59 +84,17 @@ export class ChartBox extends React.Component { this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase(); }; - scrollFocus(doc: Doc, smooth: boolean): number | undefined { - // if x and y exist on this - // then highlight/focus the x and y position (datapoint) - // highlight for a few seconds and then remove (set a timer to unhighlight after a period of time, - // to be consistent, use 5 seconds and highlight color is orange) - // if x and y don't exist => link to whole doc => dash will take care of focusing on the entire doc - // return value is how long this action takes - // undefined => immediately, 0 => introduces a timeout - // number in ms => won't do any of the focus or call the automated highlighting thing until this timeout expires - // in this case probably number in ms doesn't matter + getAnchor = (pinProps?: PinProps) => this.currChart?._getAnchor(pinProps); - // for now could just update the currSelected in linechart to be what doc.x and doc.y - - if (this.currChart && doc.x && doc.y) { - // update curr selected - this.currChart.setCurrSelected(Number(doc.x), Number(doc.y)); - } - return 0; - } - - _getAnchor() { - return this.currChart?._getAnchor(); - } + restoreView = (data: Doc) => this.currChart?.restoreView(data); render() { if (this.props.pairs && this._chartData) { - let width = NumCast(this.props.rootDoc._width); - width = width * 0.7; - let height = NumCast(this.props.rootDoc._height); - height = height * 0.7; - console.log(width, height); - const margin = { top: 50, right: 50, bottom: 50, left: 50 }; - return ( -
-
- {/*{this.props.rootDoc._currChartView == 'line' ? ( - this.onClick(e)} /> - ) : ( - this.onClick(e)} /> - )} */} - {/* {this.reLineChart} */} - -
- - -
- ); - } else { - return
; + const width = NumCast(this.props.rootDoc._width) * 0.9; + const height = this.props.height * 0.9; + const margin = { top: 10, right: 50, bottom: 50, left: 50 }; + return ; } + return
; } } diff --git a/src/client/views/nodes/DataVizBox/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts index 494242ac5..8ebcc2a5f 100644 --- a/src/client/views/nodes/DataVizBox/ChartInterface.ts +++ b/src/client/views/nodes/DataVizBox/ChartInterface.ts @@ -1,15 +1,15 @@ import { Doc } from '../../../../fields/Doc'; +import { PinProps } from '../trails'; import { DataPoint } from './ChartBox'; -import { LineChart } from './components/LineChart'; export interface Chart { tooltipContent: (data: DataPoint) => string; drawChart: () => void; + restoreView: (data: any) => boolean; height: number; width: number; // TODO: nda - probably want to get rid of this to keep charts more generic - _getAnchor: () => Doc; - setCurrSelected: (x: number, y: number) => void; + _getAnchor: (pinProps?: PinProps) => Doc; } export interface ChartProps { diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index e69de29bb..cd500e9ae 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss @@ -0,0 +1,4 @@ +.dataviz { + overflow: auto; + height: 100%; +} diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index db677ff61..68766e8dc 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -2,7 +2,7 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; -import { Cast, StrCast } from '../../../../fields/Types'; +import { BoolCast, Cast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { Docs } from '../../../documents/Documents'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; @@ -10,6 +10,7 @@ import { FieldViewProps, FieldView } from '../FieldView'; import { ChartBox } from './ChartBox'; import './DataVizBox.scss'; import { TableBox } from './components/TableBox'; +import { PinProps } from '../trails'; enum DataVizView { TABLE = 'table', @@ -54,40 +55,23 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { }; @computed get currView() { - if (this.rootDoc._dataVizView) { - return StrCast(this.rootDoc._dataVizView); - } else { - return 'table'; - } + return StrCast(this.rootDoc._dataVizView, 'table'); } - scrollFocus = (doc: Doc, smooth: boolean) => { - // reconstruct the view based on the anchor -> - // keep track of the datavizbox view and the type of chart we're rendering - - // use dataview to restore part of it and then pass on the rest to the chartbox - - // pseudocode - return this._chartBox?.scrollFocus(doc, smooth); + @action + restoreView = (data: Doc) => { + const changed = this.currView !== data.dataView && (this.rootDoc._dataVizView = data.dataView); + const func = () => this._chartBox?.restoreView(data); + if (changed) { + setTimeout(func); + return changed ? true : false; + } + return func() ?? false; }; - getAnchor = () => { - // TODO: nda - look into DocumentButtonBar and DocumentLinksButton - - /* - if nothing selected: - return this.rootDoc - this part does not specify view type (doesn't change type when following the link) - - // this slightly diff than the stuff below: - below part returns an anchor that specifies the viewtype for the whole doc and nothing else - - */ - - // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element) + getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => { const anchor = - // this._COMPONENTNAME._getAnchor() ?? - this._chartBox?._getAnchor() ?? + this._chartBox?.getAnchor(pinProps) ?? Docs.Create.TextanchorDocument({ // when we clear selection -> we should have it so chartBox getAnchor returns undefined // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker) @@ -98,7 +82,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { this.addDocument(anchor); return anchor; - // have some other function in code that }; constructor(props: any) { super(props); @@ -117,7 +100,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { case 'table': return ; case 'histogram': - return ; + return ; // case "histogram": // return () } @@ -152,7 +135,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { @action changeViewHandler(e: React.MouseEvent) { e.preventDefault(); e.stopPropagation(); - this.rootDoc._dataVizView = this.currView == 'table' ? 'histogram' : 'table'; + this.rootDoc._dataVizView = this.currView === 'table' ? 'histogram' : 'table'; } render() { @@ -161,7 +144,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { } return ( -
+
e.stopPropagation()} + ref={r => + r?.addEventListener( + 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) + (e: WheelEvent) => { + if (!r.scrollTop && e.deltaY <= 0) e.preventDefault(); + e.stopPropagation(); + }, + { passive: false } + ) + }> {this.selectView}
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 7792a2758..2d6c5f0f2 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -1,10 +1,15 @@ .tooltip { // make the height width bigger - width: 50px; - height: 50px; + width: fit-content; + height: fit-content; } .highlight { // change the color of the circle element to be red fill: red; } +.chart-container { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index d893b3e12..e5f7dc4f4 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -9,6 +9,10 @@ import { Docs } from '../../../../documents/Documents'; import { Doc, DocListCast } from '../../../../../fields/Doc'; import { Chart, ChartProps } from '../ChartInterface'; import './Chart.scss'; +import { List } from '../../../../../fields/List'; +import { PinProps, PresBox } from '../../trails'; +import { listSpec } from '../../../../../fields/Schema'; +import { Cast } from '../../../../../fields/Types'; type minMaxRange = { xMin: number | undefined; @@ -103,21 +107,25 @@ export class LineChart extends React.Component implements Chart { // loop through and remove any annotations that no longer exist } - _getAnchor() { + restoreView = (data: Doc) => { + const coords = Cast(data.presDataViz, listSpec('number'), null); + if ((coords && this._currSelected?.x !== coords[0]) || this._currSelected?.y !== coords[1]) { + this.setCurrSelected(coords[0], coords[1]); + return true; + } + return false; + }; + _getAnchor = (pinProps?: PinProps) => { // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element) - // TODO: nda - look at pdfviewer get anchor for args - const doc = Docs.Create.TextanchorDocument({ + const anchor = Docs.Create.TextanchorDocument({ /*put in some options*/ title: 'line doc selection' + this._currSelected?.x, }); - // access curr selected from the charts - doc.x = this._currSelected?.x; - doc.y = this._currSelected?.y; - doc.chartType = 'line'; - return doc; + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), dataviz: this._currSelected ? [this._currSelected.x, this._currSelected.y] : undefined } }, this.props.dataDoc); + return anchor; // have some other function in code that - } + }; componentWillUnmount() { if (this._dataReactionDisposer) { @@ -132,7 +140,7 @@ export class LineChart extends React.Component implements Chart { } tooltipContent(data: DataPoint) { - return `x: ${data.x} y: ${data.y}`; + return `(${data.x},${data.y})`; } @computed get height(): number { @@ -235,7 +243,7 @@ export class LineChart extends React.Component implements Chart { const mousemove = action((e: any) => { const bisect = d3.bisector((d: DataPoint) => d.x).left; const xPos = d3.pointer(e)[0]; - const x0 = bisect(data, xScale.invert(xPos)); + const x0 = bisect(data, xScale.invert(xPos - 25)); const d0 = data[x0]; this._x = d0.x; this._y = d0.y; @@ -243,23 +251,22 @@ export class LineChart extends React.Component implements Chart { // TODO: nda - implement tooltips tooltip.transition().duration(300).style('opacity', 0.9); // TODO: nda - updating the inner html could be deadly cause injection attacks! - tooltip.html(() => this.tooltipContent(d0)).style('transform', `translate(${xScale(d0.x) - (this.width + margin.left + margin.right) + 30}px,${yScale(d0.y) + 30}px)`); + tooltip + .html(() => this.tooltipContent(d0)) + .style('pointer-events', 'none') + .style('transform', `translate(${xScale(d0.x) - this.width / 2 + 12}px,${yScale(d0.y) + 30}px)`); }); const onPointClick = action((e: any) => { const bisect = d3.bisector((d: DataPoint) => d.x).left; const xPos = d3.pointer(e)[0]; - const x0 = bisect(data, xScale.invert(xPos)); + const x0 = bisect(data, xScale.invert(xPos - 25)); const d0 = data[x0]; this._x = d0.x; this._y = d0.y; // find .circle-d1 with data-x = d0.x and data-y = d0.y const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y); this._currSelected = { x: d0.x, y: d0.y, elem: selected }; - console.log('Getting here'); - // this.drawAnnotations(this._x, this._y); - // this.props.getAnchor(); - console.log(this._currSelected); }); this._chartSvg diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0e0e22b84..e24bf35f5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -108,6 +108,7 @@ export type StyleProviderFunc = (doc: Opt, props: Opt, p export interface DocComponentView { updateIcon?: () => void; // updates the icon representation of the document getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) + restoreView?: (viewSpec: Doc) => boolean; scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt; // returns the duration of the focus brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void; getView?: (doc: Doc) => Promise>; // returns a nested DocumentView for the specified doc or undefined diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index bfaae8069..0e22ea3b1 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; +import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; import { Copy, Id } from '../../../../fields/FieldSymbols'; import { InkField, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -39,6 +39,7 @@ const { Howl } = require('howler'); export interface pinDataTypes { scrollable?: boolean; + dataviz?: number[]; pannable?: boolean; viewType?: boolean; inkable?: boolean; @@ -491,6 +492,11 @@ export class PresBox extends ViewBoxBaseComponent() { changed = true; } } + if (!pinDataTypes && activeItem.presDataViz !== undefined) { + if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) { + changed = true; + } + } if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presViewScroll !== undefined)) { if (bestTarget._scrollTop !== activeItem.presViewScroll) { @@ -648,6 +654,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (pinProps.pinData.viewType) pinDoc.presViewType = targetDoc._viewType; if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField; + if (pinProps.pinData.dataviz) pinDoc.presDataViz = new List(pinProps.pinData.dataviz); if (pinProps.pinData.pannable) { pinDoc.presPanX = NumCast(targetDoc._panX); pinDoc.presPanY = NumCast(targetDoc._panY); diff --git a/src/server/DataVizUtils.ts b/src/server/DataVizUtils.ts index 2528fb1ab..15f03b319 100644 --- a/src/server/DataVizUtils.ts +++ b/src/server/DataVizUtils.ts @@ -1,19 +1,17 @@ -import { readFileSync } from "fs"; +import { readFileSync } from 'fs'; export function csvParser(csv: string) { - const lines = csv.split("\n"); - const headers = lines[0].split(","); - const data = lines.slice(1).map(line => { - const values = line.split(","); - const obj: any = {}; - for (let i = 0; i < headers.length; i++) { - obj[headers[i]] = values[i]; - } - return obj; - }); + const lines = csv.split('\n'); + const headers = lines[0].split(',').map(header => header.trim()); + const data = lines.slice(1).map(line => + line.split(',').reduce((last, value, i) => { + last[headers[i]] = value.trim(); + return last; + }, {} as any) + ); return data; } export function csvToString(path: string) { return readFileSync(path, 'utf8'); -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From 7975e3f4aaa3601a5512a7dce4c261e7f9411374 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 6 Apr 2023 10:01:39 -0400 Subject: added selection ui for choosing linechart axes. --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 31 +++++++++------- .../nodes/DataVizBox/components/LineChart.tsx | 33 ++++++++++++----- .../views/nodes/DataVizBox/components/TableBox.tsx | 43 +++++++++++++++++++--- src/client/views/nodes/trails/PresBox.tsx | 7 +--- 4 files changed, 79 insertions(+), 35 deletions(-) (limited to 'src/client/views/nodes/trails') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index e279a1262..0ce589a13 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,7 +1,8 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc } from '../../../../fields/Doc'; +import { Doc, StrListCast } from '../../../../fields/Doc'; +import { List } from '../../../../fields/List'; import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { Docs } from '../../../documents/Documents'; @@ -26,7 +27,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { // 2 ways of doing it // @observable private pairs: { [key: string]: number | string | undefined }[] = []; // @observable private pairs: { [key: string]: FieldResult }[] = []; - @observable private pairs: { x: number; y: number }[] = []; + @observable private pairs: { [key: string]: string }[] = []; private _chartRenderer: LineChart | undefined; // // another way would be store a schema that defines the type of data we are expecting from an imported doc @@ -51,16 +52,17 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops @computed get dataVizView(): DataVizView { - return StrCast(this.rootDoc._dataVizView, 'table') as DataVizView; + return StrCast(this.layoutDoc._dataVizView, 'table') as DataVizView; } @action restoreView = (data: Doc) => { - const changed = this.dataVizView !== data.presDataVizView && (this.rootDoc._dataVizView = data.presDataVizView); + const changedView = this.dataVizView !== data.presDataVizView && (this.layoutDoc._dataVizView = data.presDataVizView); + const changedAxes = this.axes.join('') !== StrListCast(data.presDataVizAxes).join('') && (this.layoutDoc._dataVizAxes = new List(StrListCast(data.presDataVizAxes))); const func = () => this._chartRenderer?.restoreView(data); - if (changed) { + if (changedView || changedAxes) { setTimeout(func, 100); - return changed ? true : false; + return true; } return func() ?? false; }; @@ -75,26 +77,27 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { }); anchor.presDataVizView = this.dataVizView; + anchor.presDataVizAxes = this.axes.length ? new List(this.axes) : undefined; this.addDocument(anchor); return anchor; }; + @computed.struct get axes() { + return StrListCast(this.layoutDoc.dataVizAxes); + } + selectAxes = (axes: string[]) => (this.layoutDoc.dataVizAxes = new List(axes)); + @computed get selectView() { const width = NumCast(this.rootDoc._width) * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; const margin = { top: 10, right: 50, bottom: 50, left: 50 }; // prettier-ignore switch (this.dataVizView) { - case DataVizView.TABLE: return ; - case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />; + case DataVizView.TABLE: return ; + case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; } } - - @computed get pairVals() { - return fetch('/csvData?uri=' + this.dataUrl?.url.href).then(res => res.json()); - } - @computed get dataUrl() { return Cast(this.dataDoc[this.fieldKey], CsvField); } @@ -113,7 +116,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { @action changeViewHandler(e: React.MouseEvent) { e.preventDefault(); e.stopPropagation(); - this.rootDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE; + this.layoutDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE; } render() { diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 313164691..2357b7c69 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; // import d3 import * as d3 from 'd3'; import { Doc, DocListCast } from '../../../../../fields/Doc'; +import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; import { Cast } from '../../../../../fields/Types'; import { Docs } from '../../../../documents/Documents'; @@ -32,7 +33,8 @@ export interface LineChartData { } export interface LineChartProps { rootDoc: Doc; - pairs: { x: number; y: number }[]; + axes: string[]; + pairs: { [key: string]: any }[]; width: number; height: number; dataDoc: Doc; @@ -67,8 +69,13 @@ export class LineChart extends React.Component { Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]()); } componentDidMount = () => { + this._disposers.chartdata = reaction( + () => this.props.axes.slice(), + axes => axes.length > 1 && this.generateChartData(), + { fireImmediately: true } + ); this._disposers.chartData = reaction( - () => ({ dataSet: this._lineChartData?.dataSet, w: this.props.width, h: this.props.height }), + () => ({ dataSet: this._lineChartData?.dataSet, axes: this.props.axes.slice(), w: this.props.width, h: this.props.height }), vals => { if (vals.dataSet) { this._rangeVals = minMaxRange(vals.dataSet); @@ -89,16 +96,15 @@ export class LineChart extends React.Component { }, { fireImmediately: true } ); - this.generateChartData(); }; @action generateChartData() { this._lineChartData = { - xLabel: 'x', - yLabel: 'y', + xLabel: this.props.axes[0], + yLabel: this.props.axes[1], // TODO: nda - add actual support for multiple sets of data - dataSet: [this.props.pairs?.map(pair => ({ x: pair.x, y: pair.y }))], + dataSet: [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) }))], }; } @@ -129,12 +135,17 @@ export class LineChart extends React.Component { // loop through and remove any annotations that no longer exist } + @action restoreView = (data: Doc) => { - const coords = Cast(data.presDataViz, listSpec('number'), null); - if ((coords && this._currSelected?.x !== coords[0]) || this._currSelected?.y !== coords[1]) { + const coords = Cast(data.presDataVizSelection, listSpec('number'), null); + if (coords?.length > 1 && (this._currSelected?.x !== coords[0] || this._currSelected?.y !== coords[1])) { this.setCurrSelected(coords[0], coords[1]); return true; } + if (this._currSelected) { + this._currSelected = undefined; + return true; + } return false; }; @@ -143,7 +154,8 @@ export class LineChart extends React.Component { const anchor = Docs.Create.TextanchorDocument({ title: 'line doc selection' + this._currSelected?.x, }); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), dataviz: this._currSelected ? [this._currSelected.x, this._currSelected.y] : undefined } }, this.props.dataDoc); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); + anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected.x, this._currSelected.y]) : undefined; return anchor; }; @@ -277,9 +289,10 @@ export class LineChart extends React.Component { }; render() { + const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'; return (
- Curr Selected: {this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'} + {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Curr Selected: ${selectedPt}`}
); } diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 28114036f..adefe90cd 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,28 +1,59 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../../Utils'; interface TableBoxProps { - pairs: { x: number; y: number }[]; + pairs: { [key: string]: any }[]; + selectAxes: (axes: string[]) => void; + axes: string[]; } +@observer export class TableBox extends React.Component { + @computed get columns() { + return this.props.pairs.length ? Array.from(Object.keys(this.props.pairs[0])) : []; + } render() { return (
- - + {this.columns.map(col => ( + + ))} {this.props.pairs?.map(p => { return ( - - + {this.columns.map(col => ( + + ))} ); })} diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 0e22ea3b1..3589a9065 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -492,10 +492,8 @@ export class PresBox extends ViewBoxBaseComponent() { changed = true; } } - if (!pinDataTypes && activeItem.presDataViz !== undefined) { - if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) { - changed = true; - } + if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) { + changed = true; } if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presViewScroll !== undefined)) { @@ -654,7 +652,6 @@ export class PresBox extends ViewBoxBaseComponent() { if (pinProps.pinData.viewType) pinDoc.presViewType = targetDoc._viewType; if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField; - if (pinProps.pinData.dataviz) pinDoc.presDataViz = new List(pinProps.pinData.dataviz); if (pinProps.pinData.pannable) { pinDoc.presPanX = NumCast(targetDoc._panX); pinDoc.presPanY = NumCast(targetDoc._panY); -- cgit v1.2.3-70-g09d2 From 8a1267faf796b2e2a30a6ba9f86879854e9ee983 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Apr 2023 13:51:44 -0400 Subject: removed arrangeItems context button for collections to make it a funciton on drop instead of a reaction. Converted isLinkButton to be an onClick script. got rid of unused PARAMS field on templates. fixed PresElementBox rendering of embedded docs. --- src/client/documents/Documents.ts | 7 +-- src/client/util/CurrentUserUtils.ts | 4 +- src/client/util/DropConverter.ts | 10 +--- src/client/util/LinkFollower.ts | 6 ++ src/client/views/DocumentButtonBar.tsx | 9 +-- src/client/views/MarqueeAnnotator.tsx | 8 ++- src/client/views/PropertiesButtons.tsx | 7 ++- src/client/views/StyleProvider.tsx | 5 +- src/client/views/collections/CollectionMenu.tsx | 19 +----- .../views/collections/CollectionNoteTakingView.tsx | 4 +- .../collections/CollectionStackedTimeline.tsx | 6 +- .../views/collections/CollectionStackingView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 56 ++++++++++-------- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 67 +++++----------------- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/LinkAnchorBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 7 ++- .../views/nodes/formattedText/DashFieldView.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- src/fields/Doc.ts | 10 ++-- src/fields/documentSchemas.ts | 1 - 24 files changed, 104 insertions(+), 147 deletions(-) (limited to 'src/client/views/nodes/trails') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 979b294e0..7600fcac9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -263,7 +263,6 @@ export class DocumentOptions { caption?: RichTextField; opacity?: number; defaultBackgroundColor?: string; - _isLinkButton?: boolean; // marks a document as a button that will follow its primary link when clicked _linkAutoMove?: boolean; // whether link endpoint should move around the edges of a document to make shortest path to other link endpoint hideLinkAnchors?: boolean; // suppresses link anchor dots from being displayed isFolder?: boolean; @@ -508,13 +507,13 @@ export namespace Docs { layout: { view: LinkBox, dataField: defaultDataKey }, options: { childDontRegisterViews: true, - _isLinkButton: true, + onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }), hideLinkAnchors: true, _height: 150, description: '', showCaption: 'description', backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area - _removeDropProperties: new List(['isLinkButton']), + _removeDropProperties: new List(['onClick']), }, }, ], @@ -1695,7 +1694,7 @@ export namespace DocUtils { _width: 15, _height: 15, _xPadding: 0, - _isLinkButton: true, + onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }), _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), }); Doc.AddDocToList(context, annotationField, pushpin); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 623aaf357..3d0db04b4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -271,7 +271,7 @@ export class CurrentUserUtils { {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _fitWidth: true, _showSidebar: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, - {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }}, + {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, onClick: ScriptField.MakeScript("followLink(this,altKey)", {altKey:"boolean"}) }}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}}, @@ -622,7 +622,7 @@ export class CurrentUserUtils { { title: "Snap\xA0Lines",icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform { title: "View\xA0All", icon: "object-group",toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Arrange", icon: "window", toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Arrange", icon: "window", toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform { title: "Reset", icon: "check", toolTip: "Reset View", btnType: ButtonType.ClickButton, expertMode: false, backgroundColor:"transparent", scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform ] } diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 7c209d1e0..2e1d6ba23 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -33,15 +33,7 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt = und let any = false; docs.forEach(d => { if (!StrCast(d.title).startsWith('-')) { - const params = StrCast(d.title) - .match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1] - .replace('()', ''); - if (params) { - any = makeTemplate(d, false) || any; - d.PARAMS = params; - } else { - any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any; - } + any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any; } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) { any = makeTemplate(d, false) || any; } diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index df61ecece..f5aae6632 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -7,6 +7,7 @@ import { DocFocusOptions, DocumentViewSharedProps, OpenWhere } from '../views/no import { PresBox } from '../views/nodes/trails'; import { DocumentManager } from './DocumentManager'; import { LinkManager } from './LinkManager'; +import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; import { UndoManager } from './UndoManager'; /* @@ -118,3 +119,8 @@ export class LinkFollower { }); } } + +ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) { + SelectionManager.DeselectAll(); + LinkFollower.FollowLink(undefined, doc, altKey); +}); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index e83ea00cf..7e1b32398 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -5,7 +5,7 @@ import { action, computed, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, DocCast, NumCast } from '../../fields/Types'; +import { Cast, DocCast, NumCast, ScriptCast } from '../../fields/Types'; import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; @@ -238,11 +238,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); }; + const followLink = ScriptCast(targetDoc?.onClick)?.script.originalScript.includes('followLink('); return !targetDoc ? null : ( Set onClick to follow primary link}>
this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
{followBtn( @@ -384,7 +385,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: {dataview: e.altKey}, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }}>
@@ -593,7 +594,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV (link.linkDisplay = !this.props.views().lastElement()?.rootDoc.isLinkButton)} + linkCreated={link => (link.linkDisplay = !ScriptCast(this.props.views().lastElement()?.rootDoc.onClick)?.script.originalScript.includes('followLink('))} linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)} linkFrom={() => this.props.views().lastElement()?.rootDoc} /> diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 30867a774..6b4f8ad85 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -14,6 +14,7 @@ import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { AnchorMenu } from './pdf/AnchorMenu'; import React = require('react'); +import { ScriptField } from '../../fields/ScriptField'; const _global = (window /* browser */ || global) /* node */ as any; export interface MarqueeAnnotatorProps { @@ -146,7 +147,12 @@ export class MarqueeAnnotator extends React.Component { const scale = this.props.scaling?.() || 1; const anno = savedAnnos[0]; const containerOffset = this.props.containerOffset?.() || [0, 0]; - const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: 'Annotation on ' + this.props.rootDoc.title }); + const marqueeAnno = Docs.Create.FreeformDocument([], { + onClick: isLinkButton ? ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }) : undefined, + backgroundColor: color, + annotationOn: this.props.rootDoc, + title: 'Annotation on ' + this.props.rootDoc.title, + }); marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1); marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1) + NumCast(this.props.scrollTop); marqueeAnno._height = parseInt(anno.style.height || '0') / scale / NumCast(this.props.docView.props.Document._viewScale, 1); diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 11e9dd9c9..0723966e1 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -289,6 +289,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { .filter(dv => dv.docView) .map(dv => dv.docView!) .forEach(docView => { + const linkButton = ScriptCast(docView.props.Document.onClick)?.script.originalScript.includes('followLink('); docView.noOnClick(); switch (onClick) { case 'enterPortal': @@ -299,11 +300,11 @@ export class PropertiesButtons extends React.Component<{}, {}> { break; case 'linkInPlace': docView.toggleFollowLink(false, false); - docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.lightbox : undefined; + docView.props.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined; break; case 'linkOnRight': docView.toggleFollowLink(false, false); - docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.addRight : undefined; + docView.props.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined; break; } }); @@ -326,7 +327,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { ]; const list = buttonList.map(value => { const click = () => this.handleOptionChange(value[0]); - const linkButton = BoolCast(this.selectedDoc._isLinkButton); + const linkButton = BoolCast(ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('followLink(')); const followLoc = this.selectedDoc._followLinkLocation; const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index f7b9420a7..8d67f0528 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -4,7 +4,7 @@ import { Shadows } from 'browndash-components'; import { action, runInAction } from 'mobx'; import { extname } from 'path'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; -import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; import { DashColor, lightOrDark, Utils } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; @@ -279,7 +279,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt {}), initialize: emptyFunction, }; - _openLinkInCommand = { - params: ['target', 'container'], - title: 'link follow target', - script: `{ if (self.container?.length) { - getProto(self.target).linkContainer = self.container[0]; - getProto(self.target).isLinkButton = true; - getProto(self.target).onClick = makeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])"); - }}`, - immediate: undoBatch((container: Doc[]) => { - if (container.length) { - Doc.GetProto(this.target).linkContainer = container[0]; - Doc.GetProto(this.target).isLinkButton = true; - Doc.GetProto(this.target).onClick = ScriptField.MakeScript('getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])'); - } - }), - initialize: emptyFunction, - }; _viewCommand = { params: ['target'], title: 'bookmark view', @@ -328,7 +311,7 @@ export class CollectionViewBaseChrome extends React.Component (this.docsDraggedRowCol.length ? 'none' : undefined); // getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView) getDisplayDoc(doc: Doc, width: () => number) { - const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; + const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); let dref: Opt; const noteTakingDocTransform = () => this.getDocTransform(doc, dref); @@ -293,7 +293,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { getDocHeight(d?: Doc) { if (!d || d.hidden) return 0; const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); - const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc; + const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc; const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 642d29d2a..5891cfe78 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -7,7 +7,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { Cast, NumCast } from '../../../fields/Types'; +import { Cast, NumCast, ScriptCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; import { Docs } from '../../documents/Documents'; @@ -406,7 +406,7 @@ export class CollectionStackedTimeline extends CollectionSubView { - if (anchorDoc.isLinkButton) { + if (ScriptCast(anchorDoc.onClick)?.script.originalScript.includes('followLink(')) { LinkFollower.FollowLink(undefined, anchorDoc, false); } const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 1e02fc9d4..a85ee0e02 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -305,7 +305,7 @@ export class CollectionStackingView extends CollectionSubView number, count: number) { - const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; + const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); let dref: Opt; @@ -323,7 +323,7 @@ export class CollectionStackingView extends CollectionSubView (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1ecc69174..7ae7be3c8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -349,6 +349,7 @@ export class CollectionFreeFormView extends CollectionSubView (doc.zIndex = doc.isInkMask ? 5000 : index + 1)); const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000)); const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)]; + for (let i = 0; i < docDragData.droppedDocuments.length; i++) { const d = docDragData.droppedDocuments[i]; const layoutDoc = Doc.Layout(d); @@ -370,6 +371,30 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(a.layout.y) - NumCast(b.layout.y)); + sorted.splice( + sorted.findIndex(pair => pair.layout === refDoc), + 1 + ); + if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) { + const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height); + const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0; + const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0; + + let lastx = NumCast(refDoc.x); + let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height)); + setTimeout( + action(() => + sorted.slice(1).forEach((pair, i) => { + lastx = pair.layout.x = lastx + deltax; + lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height)); + }) + ) + ); + } + } + (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments); return true; } @@ -1409,24 +1434,6 @@ export class CollectionFreeFormView extends CollectionSubView) { - if (this.layoutDoc._autoArrange) { - const sorted = this.childLayoutPairs.slice().sort((a, b) => (NumCast(a.layout.y) < NumCast(b.layout.y) ? -1 : 1)); - if (sorted.length > 1) { - const deltay = sorted.length > 1 ? NumCast(sorted[1].layout.y) - (NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height)) : 0; - const deltax = sorted.length > 1 ? NumCast(sorted[1].layout.x) - NumCast(sorted[0].layout.x) : 0; - - let lastx = NumCast(sorted[0].layout.x); - let lasty = NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height); - setTimeout( - action(() => - sorted.slice(1).forEach((pair, i) => { - lastx = pair.layout.x = lastx + deltax; - lasty = (pair.layout.y = lasty + deltay) + NumCast(pair.layout._height); - }) - ) - ); - } - } this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document }))); return [] as ViewDefResult[]; } @@ -1712,11 +1719,12 @@ export class CollectionFreeFormView extends CollectionSubView (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange), - icon: 'compress-arrows-alt', - }); + !Doc.noviceMode && + appearanceItems.push({ + description: 'Toggle auto arrange', + event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange), + icon: 'compress-arrows-alt', + }); if (this.props.setContentView === emptyFunction) { !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); return; @@ -1729,7 +1737,7 @@ export class CollectionFreeFormView extends CollectionSubView TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' }); !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' }); - appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); + this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' }); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 9e56de8c2..2d12005fb 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -139,7 +139,6 @@ export class DocumentContentsView extends React.Component< return proto instanceof Promise ? undefined : proto; } get layoutDoc() { - const params = StrCast(this.props.Document.PARAMS); // bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined); const template: Doc = @@ -147,7 +146,7 @@ export class DocumentContentsView extends React.Component< (this.props.LayoutTemplateString && this.props.Document) || (this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined); - return Doc.expandTemplateLayout(template, this.props.Document, params ? '(' + params + ')' : this.props.layoutKey); + return Doc.expandTemplateLayout(template, this.props.Document, this.props.layoutKey); } CreateBindings(onClick: Opt, onInput: Opt): JsxBindings { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e24bf35f5..a39c3cf8f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -469,11 +469,6 @@ export class DocumentViewInternal extends DocComponent (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click')); - } else if (!this.disableClickScriptFunc && this.allLinks.length && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { - clickFunc = () => { - SelectionManager.DeselectAll(); - LinkFollower.FollowLink(undefined, this.Document, e.altKey); - }; } else { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { @@ -563,48 +558,24 @@ export class DocumentViewInternal extends DocComponent { - this.Document.ignoreClick = false; - if (setTargetToggle) { - this.Document.followLinkToggle = !this.Document.followLinkToggle; - this.Document._isLinkButton = this.Document.followLinkToggle || this.Document._isLinkButton; - } else { - this.Document._isLinkButton = !this.Document._isLinkButton; - } - if (this.Document._isLinkButton && !this.onClickHandler) { - zoom !== undefined && (this.Document.followLinkZoom = zoom); - } else if (this.Document._isLinkButton && this.onClickHandler) { - this.Document._isLinkButton = false; - this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined; - } - }; - @undoBatch - @action - toggleTargetOnClick = (): void => { - this.Document.ignoreClick = false; - this.Document._isLinkButton = true; - this.Document.followLinkToggle = true; + const hadOnClick = this.rootDoc.onClick; + this.noOnClick(); + this.Document.onClick = hadOnClick ? undefined : ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }); + this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never'; }; @undoBatch @action - followLinkOnClick = (location: Opt, zoom: boolean): void => { + followLinkOnClick = (): void => { this.Document.ignoreClick = false; - this.Document._isLinkButton = true; + this.Document.onClick = ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }); this.Document.followLinkToggle = false; - this.Document.followLinkZoom = zoom; - this.Document.followLinkLocation = location; - }; - @undoBatch - @action - selectOnClick = (): void => { - this.Document.ignoreClick = false; - this.Document._isLinkButton = false; - this.Document.followLinkToggle = false; - this.Document.onClick = this.layoutDoc.onClick = undefined; + this.Document.followLinkZoom = false; + this.Document.followLinkLocation = undefined; }; @undoBatch noOnClick = (): void => { this.Document.ignoreClick = false; - this.Document._isLinkButton = false; + this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined; }; @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document); @@ -652,8 +623,7 @@ export class DocumentViewInternal extends DocComponent { @@ -744,29 +714,20 @@ export class DocumentViewInternal extends DocComponent (this.Document.followLinkZoom = !this.Document.followLinkZoom), - icon: this.Document.ignoreClick ? 'unlock' : 'lock', - }); if (!this.Document.annotationOn) { const options = cm.findByDescription('Options...'); const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : []; !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' }); - onClicks.push({ description: this.Document.ignoreClick ? 'Select' : 'Do Nothing', event: () => (this.Document.ignoreClick = !this.Document.ignoreClick), icon: this.Document.ignoreClick ? 'unlock' : 'lock' }); - onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' }); - onClicks.push({ description: (this.Document.followLinkToggle ? 'Remove' : 'Make') + ' Target Visibility Toggle', event: () => this.toggleFollowLink(false, true), icon: 'map-pin' }); + onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' }); onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } else if (LinkManager.Links(this.Document).length) { - onClicks.push({ description: 'Select on Click', event: () => this.selectOnClick(), icon: 'link' }); - onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(undefined, false), icon: 'link' }); - onClicks.push({ description: 'Toggle Link Target on Click', event: () => this.toggleTargetOnClick(), icon: 'map-pin' }); + onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' }); + onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' }); !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, subitems: onClicks, icon: 'mouse-pointer' }); } } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index f38ebba27..22ecaa299 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -202,7 +202,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { if (separation > 100) { const dragData = new DragManager.DocumentDragData([this.rootDoc]); dragData.dropAction = 'alias'; - dragData.removeDropProperties = ['anchor1_x', 'anchor1_y', 'anchor2_x', 'anchor2_y', 'isLinkButton']; + dragData.removeDropProperties = ['anchor1_x', 'anchor1_y', 'anchor2_x', 'anchor2_y', 'onClick']; DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]); return true; } else { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index e38d7e2a8..502811e35 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -119,7 +119,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent 1 ? new List(splits) : newText; if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal; Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 361e000f9..809315963 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -17,7 +17,7 @@ import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from '../../../../fields/RichTextField'; import { RichTextUtils } from '../../../../fields/RichTextUtils'; -import { ComputedField } from '../../../../fields/ScriptField'; +import { ComputedField, ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; @@ -1933,7 +1933,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 9a74b5dba..92696240b 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -97,7 +97,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? null : (
1 ? args.split('=')[0] : 'PARAMS'; + const params = args.split('=').length > 1 ? args.split('=')[0] : ''; const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc); const expandedLayoutFieldKey = (templateField || layoutFielddKey) + '-layout[' + templateLayoutDoc[Id] + (args ? `(${args})` : '') + ']'; let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey]; @@ -945,7 +945,7 @@ export namespace Doc { expandedTemplateLayout = undefined; _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true); } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) { - if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) { + if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc))) { expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params } else { templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype @@ -981,8 +981,8 @@ export namespace Doc { console.log('No, no, no!'); return { layout: childDoc, data: childDoc }; } - const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc; - return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '(' + StrCast(containerDoc.PARAMS) + ')'), data: resolvedDataDoc }; + const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc; + return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '()'), data: resolvedDataDoc }; } export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc { diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 5b489a96c..b7fd06973 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -89,7 +89,6 @@ export const documentSchema = createSchema({ hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden linkDisplay: 'boolean', // whether a link connection should be shown between link anchor endpoints. isLightbox: 'boolean', // whether the marked object will display addDocTab() calls that target "lightbox" destinations - isLinkButton: 'boolean', // whether document functions as a link follow button to follow the first link on the document when clicked layers: listSpec('string'), // which layers the document is part of _lockedPosition: 'boolean', // whether the document can be moved (dragged) _lockedTransform: 'boolean', // whether a freeformview can pan/zoom -- cgit v1.2.3-70-g09d2
xy + setupMoveUpEvents( + {}, + e, + returnFalse, + emptyFunction, + action(e => { + const newAxes = this.props.axes; + if (newAxes.includes(col)) { + newAxes.splice(newAxes.indexOf(col), 1); + } else if (newAxes.length >= 1) { + newAxes[1] = col; + } else { + newAxes[0] = col; + } + this.props.selectAxes(newAxes); + }) + ) + }> + {col} +
{p.x}{p.y}{p[col]}