From c6d1059e24f362a167b9ac24e6f13d1e45361da9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 29 Nov 2022 12:05:29 -0500 Subject: changes to streamline link editing UI (got rid of LinkEditor). cleaned up link (un)highlighting. --- src/client/views/global/globalCssVariables.scss | 1 + src/client/views/global/globalCssVariables.scss.d.ts | 1 + 2 files changed, 2 insertions(+) (limited to 'src/client/views/global') diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index e68f9abe3..430a36dce 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -84,4 +84,5 @@ $TREE_BULLET_WIDTH: 20px; LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH; TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH; INK_MASK_SIZE: $INK_MASK_SIZE; + MEDIUM_GRAY: $medium-gray; } diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts index 76259113c..3375579d6 100644 --- a/src/client/views/global/globalCssVariables.scss.d.ts +++ b/src/client/views/global/globalCssVariables.scss.d.ts @@ -11,6 +11,7 @@ interface IGlobalScss { LEFT_MENU_WIDTH: string; TREE_BULLET_WIDTH: string; INK_MASK_SIZE: number; + MEDIUM_GRAY: string; } declare const globalCssVariables: IGlobalScss; -- cgit v1.2.3-70-g09d2 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/Utils.ts | 17 +- src/client/documents/Documents.ts | 3 +- src/client/util/CurrentUserUtils.ts | 12 +- src/client/views/DocComponent.tsx | 13 -- src/client/views/DocumentDecorations.tsx | 8 +- src/client/views/InkingStroke.tsx | 2 +- src/client/views/MainView.tsx | 8 +- src/client/views/StyleProvider.tsx | 4 +- .../views/collections/CollectionStackingView.tsx | 2 + src/client/views/collections/TabDocView.tsx | 4 +- src/client/views/collections/TreeView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 100 +-------- .../collectionLinear/CollectionLinearView.scss | 10 +- .../collectionLinear/CollectionLinearView.tsx | 18 +- .../collectionSchema/CollectionSchemaView.scss | 3 +- .../collectionSchema/CollectionSchemaView.tsx | 46 ++-- .../collections/collectionSchema/SchemaRowBox.tsx | 8 +- src/client/views/global/globalCssVariables.scss | 3 +- .../views/global/globalCssVariables.scss.d.ts | 2 +- src/client/views/nodes/ColorBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 238 ++++----------------- src/client/views/nodes/FunctionPlotBox.tsx | 2 +- src/client/views/nodes/KeyValueBox.tsx | 15 +- src/client/views/nodes/KeyValuePair.tsx | 9 +- src/client/views/nodes/LinkBox.tsx | 9 +- src/client/views/nodes/ScriptingBox.tsx | 4 +- src/client/views/nodes/WebBox.tsx | 2 +- src/client/views/nodes/button/FontIconBox.scss | 10 +- src/client/views/nodes/button/FontIconBox.tsx | 6 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 9 +- src/client/views/nodes/trails/PresBox.tsx | 4 +- src/client/views/pdf/PDFViewer.tsx | 9 - src/fields/Doc.ts | 2 +- src/fields/ScriptField.ts | 2 +- 34 files changed, 171 insertions(+), 421 deletions(-) (limited to 'src/client/views/global') diff --git a/src/Utils.ts b/src/Utils.ts index ae1478943..0c7deaf5d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -8,8 +8,12 @@ import { Message } from './server/Message'; import Color = require('color'); export namespace Utils { + export let CLICK_TIME = 300; export let DRAG_THRESHOLD = 4; export let SNAP_THRESHOLD = 10; + export function isClick(x: number, y: number, downX: number, downY: number, downTime: number) { + return Date.now() - downTime < Utils.CLICK_TIME && Math.abs(x - downX) < Utils.DRAG_THRESHOLD && Math.abs(y - downY) < Utils.DRAG_THRESHOLD; + } export function readUploadedFileAsText(inputFile: File) { const temporaryFileReader = new FileReader(); @@ -509,11 +513,22 @@ export function returnTrue() { return true; } +export function returnAlways(): 'always' { + return 'always'; +} +export function returnNever(): 'never' { + return 'never'; +} + +export function returnDefault(): 'default' { + return 'default'; +} + export function returnFalse() { return false; } -export function returnAll() { +export function returnAll(): 'all' { return 'all'; } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2bc1b5b1d..c484db0db 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -230,6 +230,7 @@ export class DocumentOptions { contextMenuIcons?: List; defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire. 'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait + enableDragWhenActive?: boolean; dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel) description?: string; // added for links layout?: string | Doc; // default layout string for a document @@ -587,7 +588,7 @@ export namespace Docs { DocumentType.FONTICON, { layout: { view: FontIconBox, dataField: 'icon' }, - options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', hideLinkButton: true, _width: 40, _height: 40 }, + options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', enableDragWhenActive: true, hideLinkButton: true, _width: 40, _height: 40 }, }, ], [ diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 814b7b072..b21f53221 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -62,7 +62,7 @@ export class CurrentUserUtils { const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [ { btnOpts: { title: "slide", icon: "address-card" }, - templateOpts: { _width: 400, _height: 300, title: "slideView", childDocumentsActive: true, _xMargin: 3, _yMargin: 3, system: true }, + templateOpts: { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true }, template: (opts:DocumentOptions) => Docs.Create.MultirowDocument( [ Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), @@ -95,7 +95,7 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, - _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, childDocumentsActive: true, + _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, }; const reqdScripts = { dropConverter : "convertToButtons(dragData)" }; @@ -184,10 +184,10 @@ export class CurrentUserUtils { case DocumentType.IMG : creator = imageBox; break; case DocumentType.FONTICON: creator = fontBox; break; } - const allopts = {system: true, ...opts}; + const allopts = {system: true, onClickScriptDisable: "never", ...opts}; return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))), - {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)"}); + {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", }); }; const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({ textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts @@ -314,7 +314,7 @@ export class CurrentUserUtils { const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => { const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined; const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, - _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, + _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, _removeDropProperties: new List(["_stayInCollection"]), }; @@ -324,7 +324,7 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true, - childDocumentsActive: true, childDropAction: 'alias' + childDropAction: 'alias' }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 0b92fd864..9fc1487a0 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -82,12 +82,6 @@ export function ViewBoxBaseComponent

() { return this.props.fieldKey; } - isContentActive = (outsideReaction?: boolean) => - this.props.isContentActive?.() === false - ? false - : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) - ? true - : undefined; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; } return Component; @@ -135,13 +129,6 @@ export function ViewBoxAnnotatableComponent

() isAnyChildContentActive = () => this._isAnyChildContentActive; - isContentActive = (outsideReaction?: boolean) => - this.props.isContentActive?.() === false - ? false - : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.isAnyChildContentActive() - ? true - : undefined; - lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2d2d3c2f6..985e6f88f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -916,9 +916,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P pointerEvents: 'none', }}> {this._isRotating ? null : ( -

e.preventDefault()}> - } isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} /> -
+ tap to set rotate center, drag to rotate}> +
e.preventDefault()}> + } isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} /> +
+
)} {!this._showRotCenter ? null : ( diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index a085b69a5..e3642fdaf 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -473,7 +473,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { dontRegisterView={true} noSidebar={true} dontScale={true} - isContentActive={this.isContentActive} + isContentActive={this.props.isContentActive} /> )} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index c84d204d5..f5adc17d0 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -40,7 +40,7 @@ import { DashboardView } from './DashboardView'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import { GestureOverlay } from './GestureOverlay'; -import { DASHBOARD_SELECTOR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss'; +import { TOPBAR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss'; import { Colors } from './global/globalEnums'; import { KeyManager } from './GlobalKeyHandler'; import { InkTranscription } from './InkTranscription'; @@ -86,7 +86,7 @@ export class MainView extends React.Component { return this._hideUI ? 0 : 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js @computed private get topOfDashUI() { - return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', '')); + return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(TOPBAR_HEIGHT.replace('px', '')); } @computed private get topOfHeaderBarDoc() { return this.topOfDashUI; @@ -872,7 +872,7 @@ export class MainView extends React.Component { @computed get docButtons() { return !Doc.MyDockedBtns ? null : ( -
+
dv.rootDoc.showSnapLines) ? null : (
{SnappingManager.horizSnapLines().map(l => ( diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 1b5eb3342..faaa4e1f9 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -89,6 +89,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt doc && BoolCast(doc._lockedPosition); const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor); @@ -200,7 +201,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt { ContainingCollectionDoc={undefined} onBrowseClick={MainView.Instance.exploreMode} isContentActive={returnTrue} + isDocumentActive={returnFalse} PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight} styleProvider={DefaultStyleProvider} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 257428d56..99b7549c0 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -573,6 +573,7 @@ export class TreeView extends React.Component {
)}
    { ); } else if (this.treeViewExpandedView === 'fields') { return ( -
      +
        {this.expandedField}
      ); @@ -903,7 +904,8 @@ export class TreeView extends React.Component { hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} styleProvider={this.titleStyleProvider} - onClickScriptDisable="never" + enableDragWhenActive={true} + onClickScriptDisable="never" // tree docViews have a script to show fields, etc. docViewPath={returnEmptyDoclist} treeViewDoc={this.props.treeView.props.Document} addDocument={undefined} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8104ab1a7..aed3683d4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -821,9 +821,7 @@ export class CollectionFreeFormView extends CollectionSubView) => { - if (!e.cancelBubble) { - const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); - if (myTouches[0]) { - if (Doc.ActiveTool === InkTool.None) { - if (this.tryDragCluster(e, this._hitCluster)) { - e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers - e.preventDefault(); - document.removeEventListener('pointermove', this.onPointerMove); - return; - } - // TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected' - this.pan(myTouches[0]); - } - } - // e.stopPropagation(); - e.preventDefault(); - } - }; - - handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - // pinch zooming - if (!e.cancelBubble) { - const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); - const pt1 = myTouches[0]; - const pt2 = myTouches[1]; - - if (this.prevPoints.size === 2) { - const oldPoint1 = this.prevPoints.get(pt1.identifier); - const oldPoint2 = this.prevPoints.get(pt2.identifier); - if (oldPoint1 && oldPoint2) { - const dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2); - - // if zooming, zoom - if (dir !== 0) { - const d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2)); - const d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2)); - const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; - const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - - // calculate the raw delta value - const rawDelta = dir * (d1 + d2); - - // this floors and ceils the delta value to prevent jitteriness - const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8); - this.zoom(centerX, centerY, delta * window.devicePixelRatio); - this.prevPoints.set(pt1.identifier, pt1); - this.prevPoints.set(pt2.identifier, pt2); - } - // this is not zooming. derive some form of panning from it. - else { - // use the centerx and centery as the "new mouse position" - const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; - const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY); - - this._lastX = centerX; - this._lastY = centerY; - } - } - } - // e.stopPropagation(); - e.preventDefault(); - } - }; - - @action - handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - if (this.props.isContentActive(true)) { - // const pt1: React.Touch | null = e.targetTouches.item(0); - // const pt2: React.Touch | null = e.targetTouches.item(1); - // // if (!pt1 || !pt2) return; - const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); - const pt1 = myTouches[0]; - const pt2 = myTouches[1]; - if (pt1 && pt2) { - const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; - const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - this._lastX = centerX; - this._lastY = centerY; - - this.removeMoveListeners(); - this.addMoveListeners(); - this.removeEndListeners(); - this.addEndListeners(); - e.stopPropagation(); - } - } - }; - cleanUpInteractions = () => { this.removeMoveListeners(); this.removeEndListeners(); @@ -1637,7 +1545,11 @@ export class CollectionFreeFormView extends CollectionSubView 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * this.rootDoc[HeightSym]() : 10), + () => 5 + this.dimension() + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[WidthSym]() || this.dimension()) + tot + 4, 0) : 0), width => this.childDocs.length && (this.layoutDoc._width = width), { fireImmediately: true } ); @@ -180,10 +178,10 @@ export class CollectionLinearView extends CollectionSubView() { ref={r => (dref = r || undefined)} style={{ pointerEvents: 'all', - width: nested ? undefined : NumCast(doc._width), - height: nested ? undefined : NumCast(doc._height), - marginLeft: !nested ? 2.5 : 0, - marginRight: !nested ? 2.5 : 0, + width: NumCast(doc._width), + height: NumCast(doc._height), + marginLeft: 2, + marginRight: 2, // width: NumCast(pair.layout._width), // height: NumCast(pair.layout._height), }}> @@ -199,7 +197,7 @@ export class CollectionLinearView extends CollectionSubView() { rootSelected={this.props.isSelected} removeDocument={this.props.removeDocument} ScreenToLocalTransform={docXf} - PanelWidth={nested ? doc[WidthSym] : this.dimension} + PanelWidth={doc[WidthSym]} PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension} renderDepth={this.props.renderDepth + 1} dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)} @@ -236,7 +234,7 @@ export class CollectionLinearView extends CollectionSubView() { return (
      -
      +
      {!this.props.Document.linearViewExpandable ? null : ( {isExpanded ? 'Close' : 'Open'}
      } placement="top"> {menuOpener} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 34e591195..1ef2fb4ef 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -8,7 +8,8 @@ .schema-table { background-color: $white; - cursor: default; + cursor: grab; + overflow: scroll; .schema-column-menu, .schema-filter-menu { diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 60202a19e..d47c9762c 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -9,7 +9,7 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; +import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; @@ -46,7 +46,7 @@ export class CollectionSchemaView extends CollectionSubView() { public static _rowHeight: number = 40; public static _minColWidth: number = 25; - public static _rowMenuWidth: number = 100; + public static _rowMenuWidth: number = 60; public static _previewDividerWidth: number = 4; @computed get _selectedDocs() { @@ -364,7 +364,11 @@ export class CollectionSchemaView extends CollectionSubView() { this.dataDoc[this.fieldKey ?? 'data'] = new List([...removed, ...draggedDocs, ...pushedDocs]); this.setSort(undefined); SelectionManager.DeselectAll(); - setTimeout(() => draggedDocs.forEach(doc => DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true))), 100); + draggedDocs.forEach(doc => { + const draggedView = DocumentManager.Instance.getFirstDocumentView(doc); + if (draggedView) DocumentManager.Instance.RemoveView(draggedView); + DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true)); + }); e.stopPropagation(); return true; } @@ -812,26 +816,17 @@ export class CollectionSchemaView extends CollectionSubView() { previewWidthFunc = () => this.previewWidth; render() { return ( -
      { - this.createDashEventsTarget(ele); - }} - onPointerDown={e => { - // this is analogous to the panning code for a freeform view. - // however, schema views don't pan so it does nothing. but it does eat the pointerDown event - // if the content is active to prevent the schema from being dragged - this.isContentActive() && setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false); - }} - onDrop={this.onExternalDrop.bind(this)}> -
      +
      this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}> +
      this.props.isContentActive() && e.stopPropagation()} + ref={r => { + // prevent wheel events from passively propagating up through containers + r?.addEventListener('wheel', (e: WheelEvent) => {}, { passive: false }); + }}>
      -
      { - this._columnMenuIndex && this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true); - }}> +
      (this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true))}>
      @@ -867,8 +862,9 @@ export class CollectionSchemaView extends CollectionSubView() { DataDoc={undefined} fitContentsToBox={returnTrue} dontCenter={'y'} + onClickScriptDisable="always" focus={DocUtils.DefaultFocus} - renderDepth={this.props.renderDepth} + renderDepth={this.props.renderDepth + 1} rootSelected={this.rootSelected} PanelWidth={this.previewWidthFunc} PanelHeight={this.props.PanelHeight} @@ -915,16 +911,22 @@ class CollectionSchemaViewDocs extends React.Component () { return (
      { @@ -103,7 +99,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth, - pointerEvents: !this.isContentActive() ? 'none' : undefined, + pointerEvents: !this.props.isContentActive() ? 'none' : undefined, }}>
      () { const scaling = Math.min(this.layoutDoc.fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[HeightSym](), this.props.PanelWidth() / this.rootDoc[WidthSym]()); return (
      e.button === 0 && !e.ctrlKey && e.stopPropagation()} onClick={e => e.stopPropagation()} style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }}> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 541dddd83..9a3e77e6e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -5,7 +5,6 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from 'mobx-react'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; import { AclAdmin, AclEdit, AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; -import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; @@ -15,7 +14,7 @@ import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from import { AudioField } from '../../../fields/URLField'; import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; -import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnNone, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils'; +import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from '../../DocServer'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -50,11 +49,9 @@ import { FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { RadialMenu } from './RadialMenu'; -import { ScriptingBox } from './ScriptingBox'; import { PresEffect, PresEffectDirection } from './trails'; import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); -import { KeyValueBox } from './KeyValueBox'; const { Howl } = require('howler'); interface Window { @@ -135,6 +132,7 @@ export interface DocComponentView { componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; incrementalRendering?: () => void; fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox) + overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document fieldKey?: string; annotationKey?: string; getTitle?: () => string; @@ -195,6 +193,9 @@ export interface DocumentViewSharedProps { forceAutoHeight?: boolean; disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected + enableDragWhenActive?: boolean; + waitForDoubleClickToClick?: () => 'never' | 'always' | undefined; + defaultDoubleClick?: () => 'default' | 'ignore' | undefined; pointerEvents?: () => Opt; scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document createNewFilterDoc?: () => void; @@ -255,8 +256,6 @@ export class DocumentViewInternal extends DocComponent(); @@ -333,11 +332,12 @@ export class DocumentViewInternal extends DocComponent disposer?.()); } - handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent): any => { - this.removeMoveListeners(); - this.removeEndListeners(); - document.removeEventListener('pointermove', this.onPointerMove); - document.removeEventListener('pointerup', this.onPointerUp); - if (RadialMenu.Instance._display === false) { - this.addHoldMoveListeners(); - this.addHoldEndListeners(); - this.onRadialMenu(e, me); - const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; - this._firstX = pt.pageX; - this._firstY = pt.pageY; - } - }; - - handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { - const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; - - if (this._firstX === -1 || this._firstY === -1) { - return; - } - if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) { - this.handle1PointerHoldEnd(e, me); - } - }; - - handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { - this.removeHoldMoveListeners(); - this.removeHoldEndListeners(); - RadialMenu.Instance.closeMenu(); - this._firstX = -1; - this._firstY = -1; - SelectionManager.DeselectAll(); - me.touchEvent.stopPropagation(); - me.touchEvent.preventDefault(); - e.stopPropagation(); - if (RadialMenu.Instance.used) { - this.onContextMenu(undefined, me.touches[0].pageX, me.touches[0].pageY); - } - }; - - handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - if (!this.props.isSelected()) { - e.stopPropagation(); - e.preventDefault(); - - this.removeMoveListeners(); - this.addMoveListeners(); - this.removeEndListeners(); - this.addEndListeners(); - } - }; - - handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - SelectionManager.DeselectAll(); - if (this.Document.onPointerDown) return; - const touch = me.touchEvent.changedTouches.item(0); - if (touch) { - this._downX = touch.clientX; - this._downY = touch.clientY; - if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { - e.stopPropagation(); - } - this.removeMoveListeners(); - this.addMoveListeners(); - this.removeEndListeners(); - this.addEndListeners(); - e.stopPropagation(); - } - }; - - handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - if (e.cancelBubble && this.props.isDocumentActive?.()) { - this.removeMoveListeners(); - } else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { - const touch = me.touchEvent.changedTouches.item(0); - if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) { - if (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) { - this.cleanUpInteractions(); - this.startDragging(this._downX, this._downY, this.Document.dropAction ? (this.Document.dropAction as any) : e.ctrlKey || e.altKey ? 'alias' : undefined); - } - } - e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers - e.preventDefault(); - } - }; - - @action - handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); - const pt1 = myTouches[0]; - const pt2 = myTouches[1]; - const oldPoint1 = this.prevPoints.get(pt1.identifier); - const oldPoint2 = this.prevPoints.get(pt2.identifier); - const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!); - if (pinching !== 0 && oldPoint1 && oldPoint2) { - const dW = Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX); - const dH = Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY); - const dX = -1 * Math.sign(dW); - const dY = -1 * Math.sign(dH); - - if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { - const doc = Document(this.props.Document); - const layoutDoc = Document(Doc.Layout(this.props.Document)); - let nwidth = Doc.NativeWidth(layoutDoc); - let nheight = Doc.NativeHeight(layoutDoc); - const width = layoutDoc._width || 0; - const height = layoutDoc._height || (nheight / nwidth) * width; - const scale = this.props.ScreenToLocalTransform().Scale * this.NativeDimScaling; - const actualdW = Math.max(width + dW * scale, 20); - const actualdH = Math.max(height + dH * scale, 20); - doc.x = (doc.x || 0) + dX * (actualdW - width); - doc.y = (doc.y || 0) + dY * (actualdH - height); - const fixedAspect = e.ctrlKey || (nwidth && nheight); - if (fixedAspect && (!nwidth || !nheight)) { - Doc.SetNativeWidth(layoutDoc, (nwidth = layoutDoc._width || 0)); - Doc.SetNativeHeight(layoutDoc, (nheight = layoutDoc._height || 0)); - } - if (nwidth > 0 && nheight > 0) { - if (Math.abs(dW) > Math.abs(dH)) { - if (!fixedAspect) { - Doc.SetNativeWidth(layoutDoc, (actualdW / (layoutDoc._width || 1)) * Doc.NativeWidth(layoutDoc)); - } - layoutDoc._width = actualdW; - if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = (nheight / nwidth) * layoutDoc._width; - else layoutDoc._height = actualdH; - } else { - if (!fixedAspect) { - Doc.SetNativeHeight(layoutDoc, (actualdH / (layoutDoc._height || 1)) * Doc.NativeHeight(doc)); - } - layoutDoc._height = actualdH; - if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = (nwidth / nheight) * layoutDoc._height; - else layoutDoc._width = actualdW; - } - } else { - dW && (layoutDoc._width = actualdW); - dH && (layoutDoc._height = actualdH); - dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false); - } - } - e.stopPropagation(); - e.preventDefault(); - } - }; - @action onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; @@ -591,11 +446,12 @@ export class DocumentViewInternal extends DocComponent { - if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { + if (!this.Document.ignoreClick && this.pointerEvents !== 'none' && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { let stopPropagate = true; let preventDefault = true; (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); if (this._doubleTap) { + const defaultDblclick = this.props.defaultDoubleClick?.() || this.Document.defaultDoubleClick; if (this.onDoubleClickHandler?.script) { const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables // prettier-ignore @@ -609,7 +465,7 @@ export class DocumentViewInternal extends DocComponent (func().result?.select === true ? this.props.select(false) : ''), 'on double click'); - } else if (!Doc.IsSystem(this.rootDoc) && (this.Document.defaultDoubleClick === undefined || this.Document.defaultDoubleClick === 'default')) { + } else if (!Doc.IsSystem(this.rootDoc) && (defaultDblclick === undefined || defaultDblclick === 'default')) { UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap'); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); @@ -649,15 +505,16 @@ export class DocumentViewInternal extends DocComponent 0)) { - // 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 stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template } preventDefault = false; } this._singleClickFunc = clickFunc ?? (() => (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey)); - if ((clickFunc && this.Document.waitForDoubleClickToClick !== 'never') || this.Document.waitForDoubleClickToClick === 'always') { + const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick; + if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') { this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout); this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300); } else { @@ -673,55 +530,46 @@ export class DocumentViewInternal extends DocComponent { this._longPressSelector = setTimeout(() => DocumentView.LongPress && this.props.select(false), 1000); - if (!(e.nativeEvent as any).DownDocView) (e.nativeEvent as any).DownDocView = GestureOverlay.DownDocView = this.props.DocumentView(); - if (this.rootDoc.type === DocumentType.INK && Doc.ActiveTool === InkTool.Eraser) return; - // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document) - if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool))) { - if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { - e.stopPropagation(); - if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it - // TODO: check here for panning/inking - } - return; - } + if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.props.DocumentView(); + this._downX = e.clientX; this._downY = e.clientY; this._downTime = Date.now(); if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) { - // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking + // click events stop here if the document is active and no modes are overriding it + // if this is part of a template, let the event go up to the template root unless right/ctrl clicking if ( - (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && + // prettier-ignore + this.props.isDocumentActive?.() && !this.props.onBrowseClick?.() && !this.Document.ignoreClick && !e.ctrlKey && - (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && + e.button === 0 && + this.pointerEvents !== 'none' && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc) ) { e.stopPropagation(); // don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault(); + + // listen to move events if document content isn't active or document is draggable + if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || this.props.enableDragWhenActive || this.rootDoc.enableDragWhenActive)) { + document.addEventListener('pointermove', this.onPointerMove); + } } - if (this.props.isDocumentActive?.()) { - document.removeEventListener('pointermove', this.onPointerMove); - document.addEventListener('pointermove', this.onPointerMove); - } - document.removeEventListener('pointerup', this.onPointerUp); document.addEventListener('pointerup', this.onPointerUp); } }; @action onPointerMove = (e: PointerEvent): void => { - if (this.layoutDoc._lockedPosition || DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) return; if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return; - if (((!this.topMost && this.props.isDocumentActive?.()) || this.layoutDoc.onDragStart) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { + 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)); } - e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers - e.preventDefault(); } }; @@ -738,13 +586,11 @@ export class DocumentViewInternal extends DocComponent ); } - @observable _: string = ''; + renderDoc = (style: object) => { TraceMobx(); const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png'); - const isButton = this.props.Document.type === DocumentType.FONTICON; + 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 ?? ( @@ -1399,7 +1245,7 @@ export class DocumentViewInternal extends DocComponent { TraceMobx(); const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined; const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined; - const isButton = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear; return this.hidden ? null : (
      { ref={this.ContentRef} style={{ transition: this.props.dataTransition, - transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, - width: isButton ? '100%' : xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`, - height: - isButton || this.props.LayoutTemplateString?.includes(KeyValueBox.name) || this.props.forceAutoHeight - ? undefined - : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`), + transform: `translate(${this.centeringX}px, ${this.centeringY}px)`, + width: xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`, + height: this.props.forceAutoHeight + ? undefined + : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`), }}>
      diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 9c31ed3e4..57018fb93 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -4,8 +4,9 @@ import { Doc, Field, FieldResult } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, DocCast, NumCast } from '../../../fields/Types'; +import { DocCast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; +import { returnAll, returnAlways, returnTrue } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting'; @@ -20,7 +21,6 @@ import './KeyValueBox.scss'; import { KeyValuePair } from './KeyValuePair'; import React = require('react'); import e = require('express'); -import { returnTrue } from '../../../Utils'; export type KVPScript = { script: CompiledScript; @@ -42,8 +42,11 @@ export class KeyValueBox extends React.Component { componentDidMount() { this.props.setContentView?.(this); } - onClickScriptDisable: () => 'always' = () => 'always'; + reverseNativeScaling = returnTrue; + able = returnAlways; fitWidth = returnTrue; + overridePointerEvents = returnAll; + onClickScriptDisable = returnAlways; @observable private rows: KeyValuePair[] = []; @@ -69,9 +72,9 @@ export class KeyValueBox extends React.Component { }; public static CompileKVPScript(value: string): KVPScript | undefined { const eq = value.startsWith('='); - value = eq ? value.substr(1) : value; - const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith(';=') ? 'script' : false; - value = dubEq ? value.substr(2) : value; + value = eq ? value.substring(1) : value; + const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false; + value = dubEq ? value.substring(2) : value; const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: false }; if (dubEq) options.typecheck = false; const script = CompileScript(value, options); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index c4adc7f1a..94434dce7 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,19 +1,18 @@ import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, Field, Opt } from '../../../fields/Doc'; -import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist, emptyPath } from '../../../Utils'; -import { Docs } from '../../documents/Documents'; +import { Doc, Field } from '../../../fields/Doc'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { EditableView } from '../EditableView'; +import { DefaultStyleProvider } from '../StyleProvider'; +import { OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { KeyValueBox } from './KeyValueBox'; import './KeyValueBox.scss'; import './KeyValuePair.scss'; import React = require('react'); -import { DefaultStyleProvider } from '../StyleProvider'; -import { OpenWhere } from './DocumentView'; // Represents one row in a key value plane diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 470f7e803..46ccdecae 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,6 +1,6 @@ import React = require('react'); import { observer } from 'mobx-react'; -import { emptyFunction, returnFalse, returnTrue } from '../../../Utils'; +import { emptyFunction, returnAlways, returnFalse, returnTrue } from '../../../Utils'; import { ViewBoxBaseComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import { ComparisonBox } from './ComparisonBox'; @@ -12,23 +12,22 @@ export class LinkBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); } - isContentActiveFunc = () => this.isContentActive(); - onClickScriptDisable: () => 'always' = () => 'always'; + onClickScriptDisable = returnAlways; componentDidMount() { this.props.setContentView?.(this); } render() { if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => (this.dataDoc.treeViewOpen = true)); return ( -
      +
      'always' = () => 'always'; + onClickScriptDisable = returnAlways; @action componentDidMount() { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 73283263f..d57518a8d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -876,7 +876,7 @@ export class WebBox extends ViewBoxAnnotatableComponent e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']) || `100%` : '100%' }}> {this.urlContent} diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index 7fe1436c7..f3b43501b 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -158,7 +158,7 @@ width: 100%; border-radius: 100%; flex-direction: column; - margin-top: -4px; + // margin-top: -4px; svg { width: 60% !important; @@ -427,11 +427,11 @@ } .dropbox-background { - width: 100vw; - height: 100vh; - top: 0; + width: 200vw; + height: 200vh; + top: -100vh; z-index: 20; - left: 0; + left: -100vw; background: transparent; position: fixed; } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 468bcc4d8..28e6eaf1c 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -618,11 +618,10 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh const editorView = RichTextMenu.Instance?.TextView?.EditorView; const selected = SelectionManager.Docs().lastElement(); // prettier-ignore - const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void; setMode?: () => void }> = new Map([ + const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void;}> = new Map([ ['font', { checkResult: () => RichTextMenu.Instance?.fontFamily, setDoc: () => value && RichTextMenu.Instance.setFontFamily(value), - setMode: () => Doc.UserDoc().textAlign = value, }], ['highlight', { checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight, @@ -645,8 +644,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh if (checkResult) { return map.get(attr)?.checkResult(); } - if (editorView?.state) map.get(attr)?.setDoc(); - else map.get(attr)?.setMode?.(); + map.get(attr)?.setDoc?.(); }); type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal'; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 3ce2366f8..ee4249b02 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1470,20 +1470,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - this.props.DocumentView?.().docView?.cancelMoveEvents(); - e.stopPropagation(); - }; onSelectEnd = (e: PointerEvent) => { document.removeEventListener('pointerup', this.onSelectEnd); - document.removeEventListener('pointermove', this.onSelectMove); }; onPointerUp = (e: React.PointerEvent): void => { const editor = this._editorView!; @@ -1534,6 +1528,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + if (!this.props.isContentActive()) return; if ((e.nativeEvent as any).handledByInnerReactInstance) { e.stopPropagation(); return; @@ -1876,7 +1871,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this.isContentActive()) { + if (this.props.isContentActive()) { if (!NumCast(this.layoutDoc._scrollTop) && e.deltaY <= 0) e.preventDefault(); e.stopPropagation(); } 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. diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index eb3087399..c5060a2c2 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -381,7 +381,6 @@ export class PDFViewer extends React.Component { const target = e.target as any; if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) { this._textSelecting = false; - document.addEventListener('pointermove', this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called } else { // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee. setTimeout( @@ -391,7 +390,6 @@ export class PDFViewer extends React.Component { this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' }); document.addEventListener('pointerup', this.onSelectEnd); - document.addEventListener('pointermove', this.onSelectMove); } } }; @@ -401,12 +399,6 @@ export class PDFViewer extends React.Component { this.isAnnotating = false; this._marqueeing = undefined; this._textSelecting = true; - document.removeEventListener('pointermove', this.onSelectMove); - }; - - onSelectMove = (e: PointerEvent) => { - this.props.DocumentView?.().docView?.cancelMoveEvents(); - e.stopPropagation(); }; @action @@ -414,7 +406,6 @@ export class PDFViewer extends React.Component { this.isAnnotating = false; clearStyleSheetRules(PDFViewer._annotationStyle); this.props.select(false); - document.removeEventListener('pointermove', this.onSelectMove); document.removeEventListener('pointerup', this.onSelectEnd); const sel = window.getSelection(); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 4d82551db..553c4525c 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -31,7 +31,7 @@ export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { const onDelegate = Object.keys(doc).includes(key); const field = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); + return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field)); } export function toScriptString(field: Field): string { switch (typeof field) { diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index feb419597..2b8750714 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -114,7 +114,7 @@ export class ScriptField extends ObjectField { } [ToScriptString]() { - return 'script field'; + return this.script.originalScript; } [ToString]() { return this.script.originalScript; -- cgit v1.2.3-70-g09d2 From ad32fce170ffeef8e03c108569113dca9de15d5d Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 21 May 2023 22:25:28 -0400 Subject: fixed comparisonBox to create embeddings of documents by fixing targetDropAction to work for non-collections. fixed carouse3D view to have correct screentolocal XFs, --- src/Utils.ts | 2 +- src/client/documents/Documents.ts | 4 +- src/client/util/DragManager.ts | 9 ++++- src/client/views/DashboardView.tsx | 1 - src/client/views/DocumentDecorations.tsx | 12 +++--- src/client/views/GlobalKeyHandler.ts | 46 +++++----------------- src/client/views/PreviewCursor.tsx | 21 +++------- src/client/views/PropertiesButtons.tsx | 2 +- .../collections/CollectionCarousel3DView.scss | 14 +++++-- .../views/collections/CollectionCarousel3DView.tsx | 32 +++++++++------ src/client/views/global/globalCssVariables.scss | 7 ++++ .../views/global/globalCssVariables.scss.d.ts | 3 ++ src/client/views/nodes/ComparisonBox.tsx | 12 ++---- src/client/views/nodes/DocumentView.tsx | 2 +- src/fields/Doc.ts | 28 ++++++++++--- 15 files changed, 102 insertions(+), 93 deletions(-) (limited to 'src/client/views/global') diff --git a/src/Utils.ts b/src/Utils.ts index 73de6d754..f2ad814d4 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -540,7 +540,7 @@ export function returnNone() { } export function returnVal(val1?: number, val2?: number) { - return val1 !== undefined ? val1 : val2 !== undefined ? val2 : 0; + return val1 ? val1 : val2 !== undefined ? val2 : 0; } export function returnOne() { diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 966cd6a68..3be476e68 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1195,12 +1195,14 @@ export namespace Docs { }, ], }; - return DockDocument( + const doc = DockDocument( configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id ); + configs.map(c => (c.doc.embedContainer = doc)); + return doc; } export function DelegateDocument(proto: Doc, options: DocumentOptions = {}) { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c8bc26b93..fb4a8985c 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -174,6 +174,13 @@ export namespace DragManager { userDropAction: dropActionType; } + let defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { + if (de.complete.docDragData) { + targetAction && (de.complete.docDragData.dropAction = targetAction); + e.stopPropagation(); + } + }; + export function MakeDropTarget(element: HTMLElement, dropFunc: (e: Event, de: DropEvent) => void, doc?: Doc, preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void): DragDropDisposer { if ('canDrop' in element.dataset) { throw new Error("Element is already droppable, can't make it droppable again"); @@ -182,7 +189,7 @@ export namespace DragManager { const handler = (e: Event) => dropFunc(e, (e as CustomEvent).detail); const preDropHandler = (e: Event) => { const de = (e as CustomEvent).detail; - preDropFunc?.(e, de, StrCast(doc?.targetDropAction) as dropActionType); + (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc?.targetDropAction) as dropActionType); }; element.addEventListener('dashOnDrop', handler); doc && element.addEventListener('dashPreDrop', preDropHandler); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index b51c77cb2..fb41ca8af 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -366,7 +366,6 @@ export class DashboardView extends React.Component { const title = name ? name : `Dashboard ${dashboardCount}`; const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row'); - freeformDoc.embedContainer = dashboardDoc; // switching the tabs from the datadoc to the regular doc const dashboardTabs = DocListCast(dashboardDoc[DataSym].data); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index beddc0e8a..72c6d449a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -189,7 +189,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P @action onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => { const dragDocView = SelectionManager.Views()[0]; - const containers = new Set(); + const containers = new Set(); SelectionManager.Views().forEach(v => containers.add(DocCast(v.rootDoc.embedContainer))); if (containers.size > 1) return false; const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 }; @@ -274,19 +274,19 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P if (selectedDocs.length) { if (e.ctrlKey) { // open an embedding in a new tab with Ctrl Key - CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0].props.Document), OpenWhereMod.right); + CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0].rootDoc), OpenWhereMod.right); } else if (e.shiftKey) { // open centered in a new workspace with Shift Key - const embedding = Doc.MakeEmbedding(selectedDocs[0].props.Document); + const embedding = Doc.MakeEmbedding(selectedDocs[0].rootDoc); embedding.embedContainer = undefined; embedding.x = -embedding[WidthSym]() / 2; embedding.y = -embedding[HeightSym]() / 2; CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right); } else if (e.altKey) { // open same document in new tab - CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, OpenWhereMod.right); + CollectionDockingView.ToggleSplit(selectedDocs[0].rootDoc, OpenWhereMod.right); } else { - var openDoc = selectedDocs[0].props.Document; + var openDoc = selectedDocs[0].rootDoc; if (openDoc.layout_fieldKey === 'layout_icon') { openDoc = DocListCast(openDoc.proto_embeddings).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc); Doc.deiconifyView(openDoc); @@ -294,7 +294,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P LightboxView.SetLightboxDoc( openDoc, undefined, - selectedDocs.slice(1).map(view => view.props.Document) + selectedDocs.slice(1).map(view => view.rootDoc) ); } } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 625bc760d..9a78eb9fc 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -1,22 +1,19 @@ import { random } from 'lodash'; import { action, runInAction } from 'mobx'; -import { DateField } from '../../fields/DateField'; import { Doc, DocListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { InkTool } from '../../fields/InkField'; -import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; -import { Cast, PromiseValue } from '../../fields/Types'; +import { Cast, DocCast, NumCast, PromiseValue } from '../../fields/Types'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; -import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from '../util/DragManager'; import { GroupManager } from '../util/GroupManager'; import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { SnappingManager } from '../util/SnappingManager'; -import { undoBatch, UndoManager } from '../util/UndoManager'; +import { undoable, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { CollectionFreeFormViewChrome } from './collections/CollectionMenu'; @@ -350,38 +347,13 @@ export class KeyManager { }); public paste(e: ClipboardEvent) { - const plain = e.clipboardData?.getData('text/plain'); - const clone = plain?.startsWith('__DashCloneId('); - if (plain && (plain.startsWith('__DashDocId(') || clone)) { - const first = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; - if (first?.props.Document.type === DocumentType.COL) { - const docids = plain.split(':'); - let count = 1; - const list: Doc[] = []; - const targetDataDoc = Doc.GetProto(first.props.Document); - const fieldKey = first.LayoutFieldKey; - const docList = DocListCast(targetDataDoc[fieldKey]); - docids.map( - (did, i) => - i && - DocServer.GetRefField(did).then(async doc => { - count++; - if (doc instanceof Doc) { - list.push(doc); - } - if (count === docids.length) { - const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => (clone ? (await Doc.MakeClone(d, true)).clone : d))); - if (added.length) { - added.map(doc => (doc.embedContainer = targetDataDoc)); - undoBatch(() => { - targetDataDoc[fieldKey] = new List([...docList, ...added]); - targetDataDoc[fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())); - })(); - } - } - }) - ); - } + const plain = e.clipboardData?.getData('text/plain'); // list of document ids, separated by ':'s + if (!plain) return; + const clone = plain.startsWith('__DashCloneId('); + const docids = plain.split(':'); // hack! docids[0] is the top left of the selection rectangle + const addDocument = SelectionManager.Views().lastElement()?.ComponentView?.addDocument; + if (addDocument && (plain.startsWith('__DashDocId(') || clone)) { + Doc.Paste(docids.slice(1), clone, addDocument); } } diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index f7fb443c0..8dd13ff08 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -1,14 +1,15 @@ import { action, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; +import { listenerCount } from 'process'; import * as React from 'react'; import { Doc } from '../../fields/Doc'; -import { Cast, NumCast, StrCast } from '../../fields/Types'; +import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocServer } from '../DocServer'; import { Docs, DocumentOptions, DocUtils } from '../documents/Documents'; import { ImageUtils } from '../util/Import & Export/ImageUtils'; import { Transform } from '../util/Transform'; -import { undoBatch, UndoManager } from '../util/UndoManager'; +import { undoable, undoBatch, UndoManager } from '../util/UndoManager'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import './PreviewCursor.scss'; @@ -66,23 +67,11 @@ export class PreviewCursor extends React.Component<{}> { } else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) { const clone = plain.startsWith('__DashCloneId('); const docids = plain.split(':'); - const strs = docids[0].split(','); + const strs = docids[0].split(','); // hack! docids[0] is the top left of the selection rectangle const ptx = Number(strs[0].substring((clone ? '__DashCloneId(' : '__DashDocId(').length)); const pty = Number(strs[1].substring(0, strs[1].length - 1)); + Doc.Paste(docids.slice(1), clone, PreviewCursor._addDocument, ptx, pty, newPoint); - const batch = UndoManager.StartBatch('cloning'); - { - const toCopy = await Promise.all(docids.slice(1).map(async did => Cast(await DocServer.GetRefField(did), Doc, null))); - const docs = clone ? (await Promise.all(Doc.MakeClones(toCopy, false))).map(res => res.clone) : toCopy; - const firstx = docs.length ? NumCast(docs[0].x) + ptx - newPoint[0] : 0; - const firsty = docs.length ? NumCast(docs[0].y) + pty - newPoint[1] : 0; - docs.map(doc => { - doc.x = NumCast(doc.x) - firstx; - doc.y = NumCast(doc.y) - firsty; - }); - PreviewCursor._addDocument(docs); - } - batch.end(); e.stopPropagation(); } else { FormattedTextBox.PasteOnLoad = e; diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index a5c58c9d2..49e8ed369 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -414,7 +414,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { {toggle(this.onClickButton)} {toggle(this.layout_fitWidthButton)} {toggle(this.freezeThumb)} - {toggle(this.forceActiveButton, { display: !isFreeForm && !isMap ? 'none' : '' })} + {toggle(this.forceActiveButton)} {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {toggle(this.layout_autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })} diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss index 5c8b491eb..6bd1d9f5f 100644 --- a/src/client/views/collections/CollectionCarousel3DView.scss +++ b/src/client/views/collections/CollectionCarousel3DView.scss @@ -1,3 +1,4 @@ +@import '../global/globalCssVariables'; .collectionCarousel3DView-outer { height: 100%; position: relative; @@ -7,8 +8,8 @@ .carousel-wrapper { display: flex; position: absolute; - top: 15%; - height: 60%; + top: $CAROUSEL3D_TOP * 1%; + height: ($CAROUSEL3D_SIDE_SCALE * 100) * 1%; align-items: center; transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); @@ -17,10 +18,17 @@ flex: 1; transition: opacity 0.3s linear, transform 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955); pointer-events: none; + opacity: 0.5; + z-index: 1; + transform: scale($CAROUSEL3D_SIDE_SCALE); + user-select: none; } .collectionCarousel3DView-item-active { pointer-events: unset; + opacity: 1; + z-index: 2; + transform: scale($CAROUSEL3D_CENTER_SCALE); } } @@ -105,4 +113,4 @@ .carousel3DView-back-scroll:hover, .carousel3DView-fwd-scroll:hover { background: lightgray; -} \ No newline at end of file +} diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index b10b453bf..d94e552b4 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -7,6 +7,8 @@ import { Id } from '../../../fields/FieldSymbols'; import { NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { returnFalse, returnZero, Utils } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; +import { SelectionManager } from '../../util/SelectionManager'; +import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss'; import { DocumentView } from '../nodes/DocumentView'; import { StyleProp } from '../StyleProvider'; import './CollectionCarousel3DView.scss'; @@ -32,11 +34,17 @@ export class CollectionCarousel3DView extends CollectionSubView() { } }; + centerScale = Number(CAROUSEL3D_CENTER_SCALE); panelWidth = () => this.props.PanelWidth() / 3; - panelHeight = () => this.props.PanelHeight() * 0.6; + panelHeight = () => this.props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE); onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); isChildContentActive = () => (this.isContentActive() ? true : false); + childScreenToLocal = () => + this.props // document's left is the panel shifted by the doc's index * panelWidth/#docs. But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1) + .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor. + .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this.props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2) + .scale(1 / this.centerScale); @computed get content() { const currentIndex = NumCast(this.layoutDoc._carousel_index); @@ -46,13 +54,14 @@ export class CollectionCarousel3DView extends CollectionSubView() { {...this.props} NativeWidth={returnZero} NativeHeight={returnZero} - suppressSetHeight={true} + //suppressSetHeight={true} onDoubleClick={this.onChildDoubleClick} renderDepth={this.props.renderDepth + 1} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} Document={childPair.layout} DataDoc={childPair.data} + ScreenToLocalTransform={this.childScreenToLocal} isContentActive={this.isChildContentActive} isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive} PanelWidth={this.panelWidth} @@ -64,10 +73,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { return this.childLayoutPairs.map((childPair, index) => { return ( -
      +
      {displayDoc(childPair)}
      ); @@ -75,6 +81,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { } changeSlide = (direction: number) => { + SelectionManager.DeselectAll(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length; }; @@ -114,10 +121,10 @@ export class CollectionCarousel3DView extends CollectionSubView() { return (
      this.onArrowClick(e, -1)}> - +
      this.onArrowClick(e, 1)}> - +
      {this.autoScrollButton}
      @@ -141,11 +148,12 @@ export class CollectionCarousel3DView extends CollectionSubView() { @computed get dots() { return this.childLayoutPairs.map((_child, index) =>
      (this.layoutDoc._carousel_index = index)} />); } - - render() { + @computed get translateX() { const index = NumCast(this.layoutDoc._carousel_index); - const translateX = this.panelWidth() * (1 - index); + return this.panelWidth() * (1 - index); + } + render() { return (
      -
      +
      {this.content}
      {this.props.Document._chromeHidden ? null : this.buttons} diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index 3496bb835..422dae15b 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -70,6 +70,10 @@ $DFLT_IMAGE_NATIVE_DIM: 900px; $LEFT_MENU_WIDTH: 60px; $TREE_BULLET_WIDTH: 20px; +$CAROUSEL3D_CENTER_SCALE: 1.3; +$CAROUSEL3D_SIDE_SCALE: 0.3; +$CAROUSEL3D_TOP: 15; + :export { contextMenuZindex: $contextMenu-zindex; SCHEMA_DIVIDER_WIDTH: $SCHEMA_DIVIDER_WIDTH; @@ -84,4 +88,7 @@ $TREE_BULLET_WIDTH: 20px; TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH; INK_MASK_SIZE: $INK_MASK_SIZE; MEDIUM_GRAY: $medium-gray; + CAROUSEL3D_CENTER_SCALE: $CAROUSEL3D_CENTER_SCALE; + CAROUSEL3D_SIDE_SCALE: $CAROUSEL3D_SIDE_SCALE; + CAROUSEL3D_TOP: $CAROUSEL3D_TOP; } diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts index 537ea1e5d..efb702564 100644 --- a/src/client/views/global/globalCssVariables.scss.d.ts +++ b/src/client/views/global/globalCssVariables.scss.d.ts @@ -12,6 +12,9 @@ interface IGlobalScss { TREE_BULLET_WIDTH: string; INK_MASK_SIZE: number; MEDIUM_GRAY: string; + CAROUSEL3D_CENTER_SCALE: string; + CAROUSEL3D_SIDE_SCALE: string; + CAROUSEL3D_TOP: string; } declare const globalCssVariables: IGlobalScss; diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 3abe7a331..1cc09a63c 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -41,18 +41,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent this.dropHandler(e, dropEvent, fieldKey), this.layoutDoc); + this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.internalDrop(e, dropEvent, fieldKey), this.layoutDoc); } }; @undoBatch - private dropHandler = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => { + private internalDrop = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => { if (dropEvent.complete.docDragData) { event.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments; - if (droppedDocs?.length) { - this.dataDoc[fieldKey] = droppedDocs[0]; - } + droppedDocs.lastElement().embedContainer = this.dataDoc; + this.dataDoc[fieldKey] = droppedDocs.lastElement(); } }; @@ -130,9 +129,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { - //whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true }); - }} {...this.props} NativeWidth={returnZero} NativeHeight={returnZero} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ab285b230..cc105326f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -38,7 +38,6 @@ import { EditableView } from '../EditableView'; import { GestureOverlay } from '../GestureOverlay'; import { InkingStroke } from '../InkingStroke'; import { LightboxView } from '../LightboxView'; -import { OverlayView } from '../OverlayView'; import { StyleProp } from '../StyleProvider'; import { UndoStack } from '../UndoStack'; import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView'; @@ -117,6 +116,7 @@ export interface DocComponentView { 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 addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox + addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling. shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views select?: (ctrlKey: boolean, shiftKey: boolean) => void; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 128cfffb2..8dd322e05 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -9,7 +9,7 @@ import { LinkManager } from '../client/util/LinkManager'; import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { SelectionManager } from '../client/util/SelectionManager'; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper'; -import { UndoManager } from '../client/util/UndoManager'; +import { undoable, UndoManager } from '../client/util/UndoManager'; import { decycle } from '../decycler/decycler'; import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils'; import { DateField } from './DateField'; @@ -211,14 +211,14 @@ export class Doc extends RefField { public static get MyTrails() { return DocCast(Doc.ActiveDashboard?.myTrails); } - public static IsInMyOverlay(doc:Doc) { + public static IsInMyOverlay(doc: Doc) { return DocListCast(Doc.MyOverlayDocs?.data).includes(doc); } - public static AddToMyOverlay(doc:Doc) { + public static AddToMyOverlay(doc: Doc) { Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); } - public static RemFromMyOverlay(doc:Doc) { - Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc) + public static RemFromMyOverlay(doc: Doc) { + Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc); } public static get MyOverlayDocs() { return DocCast(Doc.UserDoc().myOverlayDocs); @@ -1520,6 +1520,24 @@ export namespace Doc { return style; } + export function Paste(docids: string[], clone: boolean, addDocument: (doc: Doc | Doc[]) => boolean, ptx?: number, pty?: number, newPoint?: number[]) { + DocServer.GetRefFields(docids).then(async fieldlist => { + const list = Array.from(Object.values(fieldlist)) + .map(d => DocCast(d)) + .filter(d => d); + const docs = clone ? (await Promise.all(Doc.MakeClones(list, false))).map(res => res.clone) : list; + if (ptx !== undefined && pty !== undefined && newPoint !== undefined) { + const firstx = list.length ? NumCast(list[0].x) + ptx - newPoint[0] : 0; + const firsty = list.length ? NumCast(list[0].y) + pty - newPoint[1] : 0; + docs.map(doc => { + doc.x = NumCast(doc.x) - firstx; + doc.y = NumCast(doc.y) - firsty; + }); + } + undoable(addDocument, 'Paste Doc')(docs); // embedContainer gets set in addDocument + }); + } + // prettier-ignore export function toIcon(doc?: Doc, isOpen?: boolean) { switch (StrCast(doc?.type)) { -- cgit v1.2.3-70-g09d2