diff options
22 files changed, 313 insertions, 308 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 8d8975763..e59e847c2 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -198,7 +198,7 @@ export namespace DragManager { }; const finishDrag = async (e: DragCompleteEvent) => { const docDragData = e.docDragData; - setTimeout(() => dragData.draggedViews.forEach(view => view.props.CollectionFreeFormDocumentView?.().dragEnding())); + setTimeout(() => dragData.draggedViews.forEach(view => view.props.dragEnding?.())); onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails if (docDragData && !docDragData.droppedDocuments.length) { docDragData.dropAction = dragData.userDropAction || dragData.dropAction; @@ -234,7 +234,7 @@ export namespace DragManager { }; dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded StartDrag(eles, dragData, downX, downY, options, finishDrag); - dragData.draggedViews.forEach(view => view.props.CollectionFreeFormDocumentView?.().dragStarting()); + dragData.draggedViews.forEach(view => view.props.dragStarting?.()); return true; } @@ -475,7 +475,7 @@ export namespace DragManager { }; const cleanupDrag = action((undo: boolean) => { - (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.CollectionFreeFormDocumentView?.().dragEnding()); + (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.()); hideDragShowOriginalElements(false); document.removeEventListener('pointermove', moveHandler, true); document.removeEventListener('pointerup', upHandler, true); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index ff7daa2eb..33886e791 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -486,8 +486,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P this._snapY = e.pageY; const ffviewSet = new Set<CollectionFreeFormView>(); SelectionManager.Views().forEach(docView => { - const ffview = docView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - ffview && ffviewSet.add(ffview); + docView.CollectionFreeFormView && ffviewSet.add(docView.CollectionFreeFormView); this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) }); }); Array.from(ffviewSet).map(ffview => ffview.dragStarting(false, false)); @@ -814,8 +813,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth)); bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); - const useLock = bounds.r - bounds.x > 135 && seldocview.props.CollectionFreeFormDocumentView; - const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate? + const useLock = bounds.r - bounds.x > 135 && seldocview.CollectionFreeFormDocumentView; + const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate? const rotation = SelectionManager.Views().length == 1 ? NumCast(seldocview.rootDoc._rotation) : 0; const resizerScheme = ''; @@ -878,7 +877,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P )} </div> ); - const freeformDoc = SelectionManager.Views().some(v => v.props.CollectionFreeFormDocumentView?.()); + const freeformDoc = SelectionManager.Views().some(v => v.CollectionFreeFormDocumentView); return ( <div className="documentDecorations" style={{ display: this._showNothing && !freeformDoc ? 'none' : undefined }}> <div diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index aa0683964..13791b763 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -84,41 +84,39 @@ export class KeyManager { } }); + nudge = (x: number, y: number, label: string) => { + const nudgeable = SelectionManager.Views().some(dv => dv.CollectionFreeFormDocumentView?.nudge); + nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.CollectionFreeFormDocumentView?.nudge(x, y)), label); + return { stopPropagation: nudgeable, preventDefault: nudgeable }; + }; + private unmodified = action((keyname: string, e: KeyboardEvent) => { - const hasFffView = SelectionManager.Views().some(dv => dv.props.CollectionFreeFormDocumentView?.()); switch (keyname) { case 'u': - if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') { - return { stopPropagation: false, preventDefault: false }; + if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { + const ungroupings = SelectionManager.Views().slice(); + UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup'); + SelectionManager.DeselectAll(); } - - const ungroupings = SelectionManager.Views().slice(); - UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup'); - SelectionManager.DeselectAll(); break; case 'g': - if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') { - return { stopPropagation: false, preventDefault: false }; - } - - const groupings = SelectionManager.Views().slice(); - const randomGroup = random(0, 1000); - const collectionView = groupings.reduce( - (col, g) => (col === null || g.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView === col ? g.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView : undefined), - null as null | undefined | CollectionFreeFormView - ); - if (collectionView) { - UndoManager.RunInBatch(() => { - collectionView._marqueeViewRef.current?.collection( - e, - true, - groupings.map(g => g.rootDoc) - ); - }, 'grouping'); - break; + if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { + const groupings = SelectionManager.Views().slice(); + const randomGroup = random(0, 1000); + const collectionView = groupings.reduce((col, g) => (col === null || g.CollectionFreeFormView === col ? g.CollectionFreeFormView : undefined), null as null | undefined | CollectionFreeFormView); + if (collectionView) { + UndoManager.RunInBatch(() => { + collectionView._marqueeViewRef.current?.collection( + e, + true, + groupings.map(g => g.rootDoc) + ); + }, 'grouping'); + break; + } + UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group'); + SelectionManager.DeselectAll(); } - UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group'); - SelectionManager.DeselectAll(); break; case ' ': // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI @@ -167,19 +165,11 @@ export class KeyManager { return { stopPropagation: true, preventDefault: true }; } break; - case 'arrowleft': - UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge(-1, 0)), 'nudge left'); - return { stopPropagation: hasFffView, preventDefault: hasFffView }; - case 'arrowright': - UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(1, 0)), 'nudge right'); - return { stopPropagation: hasFffView, preventDefault: hasFffView }; - case 'arrowup': - UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -1)), 'nudge up'); - return { stopPropagation: hasFffView, preventDefault: hasFffView }; - case 'arrowdown': - UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 1)), 'nudge down'); - return { stopPropagation: hasFffView, preventDefault: hasFffView }; - } + case 'arrowleft': return this.nudge(-1,0, 'nudge left') + case 'arrowright': return this.nudge(1,0, 'nudge right'); + case 'arrowup': return this.nudge(0, -1, 'nudge up'); + case 'arrowdown': return this.nudge(0, 1, 'nudge down'); + } // prettier-ignore return { stopPropagation: false, @@ -188,37 +178,24 @@ export class KeyManager { }); private shift = action((keyname: string) => { - const stopPropagation = false; - const preventDefault = false; - switch (keyname) { - case 'arrowleft': - UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(-10, 0)), 'nudge left'); - break; - case 'arrowright': - UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(10, 0)), 'nudge right'); - break; - case 'arrowup': - UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -10)), 'nudge up'); - break; - case 'arrowdown': - UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 10)), 'nudge down'); - break; + case 'arrowleft': return this.nudge(-10,0, 'nudge left'); + case 'arrowright': return this.nudge(10, 0, 'nudge right'); + case 'arrowup': return this.nudge(0, -10, 'nudge up'); + case 'arrowdown': return this.nudge(0, 10, 'nudge down'); case 'g': - if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') { - return { stopPropagation: false, preventDefault: false }; + if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { + const groupings = SelectionManager.Views().slice(); + const randomGroup = random(0, 1000); + UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group'); + SelectionManager.DeselectAll(); } - - const groupings = SelectionManager.Views().slice(); - const randomGroup = random(0, 1000); - UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group'); - SelectionManager.DeselectAll(); break; - } + } // prettier-ignore return { - stopPropagation: stopPropagation, - preventDefault: preventDefault, + stopPropagation: false, + preventDefault: false, }; }); @@ -230,7 +207,7 @@ export class KeyManager { case 'ƒ': case 'f': const dv = SelectionManager.Views()?.[0]; - UndoManager.RunInBatch(() => dv.props.CollectionFreeFormDocumentView?.().float(), 'float'); + UndoManager.RunInBatch(() => dv.CollectionFreeFormDocumentView?.float(), 'float'); } return { diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 736ca8d90..62165bc48 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -372,8 +372,8 @@ export class InkStrokeProperties { }; snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => { - const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - const containingDocView = inkView.props.CollectionFreeFormDocumentView?.().props.DocumentView?.(); + const containingCollection = inkView.CollectionFreeFormView; + const containingDocView = containingCollection?.props.DocumentView?.(); containingCollection?.childDocs .filter(doc => doc.type === DocumentType.INK) .forEach(doc => { diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 513061e79..cf565abc8 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -466,7 +466,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() { <svg className="inkStroke" style={{ - transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.().Rot)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined, + transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.().props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined, // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset', cursor: this.props.isSelected() ? 'default' : undefined, }} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f67bd3288..a6f0b9e84 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -26,7 +26,7 @@ import { RTFMarkup } from '../util/RTFMarkup'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { SelectionManager } from '../util/SelectionManager'; import { ServerStats } from '../util/ServerStats'; -import { ColorScheme, SettingsManager } from '../util/SettingsManager'; +import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { SnappingManager } from '../util/SnappingManager'; import { Transform } from '../util/Transform'; @@ -35,6 +35,7 @@ import { CollectionDockingView } from './collections/CollectionDockingView'; import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { CollectionMenu } from './collections/CollectionMenu'; +import { TabDocView } from './collections/TabDocView'; import './collections/TreeView.scss'; import { ComponentDecorations } from './ComponentDecorations'; import { ContextMenu } from './ContextMenu'; @@ -62,6 +63,7 @@ import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu'; import { MapBox } from './nodes/MapBox/MapBox'; import { RadialMenu } from './nodes/RadialMenu'; import { TaskCompletionBox } from './nodes/TaskCompletedBox'; +import { PresBox } from './nodes/trails'; import { OverlayView } from './OverlayView'; import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; @@ -563,7 +565,7 @@ export class MainView extends React.Component { @action openPresentation = (pres: Doc) => { if (pres.type === DocumentType.PRES) { - CollectionDockingView.AddSplit(pres, OpenWhereMod.right); + CollectionDockingView.AddSplit(pres, OpenWhereMod.right, undefined, PresBox.PanelName); Doc.MyTrails && (Doc.ActivePresentation = pres); Doc.AddDocToList(Doc.MyTrails, 'data', pres); this.closeFlyout(); @@ -696,16 +698,17 @@ export class MainView extends React.Component { mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0); static addDocTabFunc_impl = (doc: Doc, location: OpenWhere): boolean => { const whereFields = location.split(':'); - const keyValue = whereFields[1]?.includes('KeyValue'); - const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none; + const keyValue = whereFields.includes(OpenWhereMod.keyvalue); + const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; + const panelName = whereFields.length > 1 ? whereFields.lastElement() : ''; if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc); - // prettier-ignore switch (whereFields[0]) { case OpenWhere.lightbox: return LightboxView.Instance.AddDocTab(doc, location); case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); - case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, undefined, "dontSelectOnActivate"); // bcz: hack! mark the toggle so that it won't be selected on activation- this is needed so that the backlinks menu can toggle views of targets on and off without selecting them + case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, undefined, TabDocView.DontSelectOnActivate); // bcz: hack! mark the toggle so that it won't be selected on activation- this is needed so that the backlinks menu can toggle views of targets on and off without selecting them + case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, undefined, panelName); case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, undefined, undefined, keyValue); - } + } // prettier-ignore }; @computed get flyout() { @@ -923,7 +926,7 @@ export class MainView extends React.Component { SnappingManager.GetIsDragging(); SnappingManager.GetIsResizing(); const dragged = DragManager.docsBeingDragged.lastElement() ?? SelectionManager.Docs().lastElement(); - const dragPar = dragged ? DocumentManager.Instance.getDocumentView(dragged)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView : undefined; + const dragPar = dragged ? DocumentManager.Instance.getDocumentView(dragged)?.CollectionFreeFormView : undefined; return !dragPar?.rootDoc.freeform_snapLines ? null : ( <div className="mainView-snapLines"> <svg style={{ width: '100%', height: '100%' }}> diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1b616d6a8..9c644e2cc 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -130,7 +130,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch @action - public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean, keyValue?: boolean): boolean { + public static ReplaceTab(document: Doc, mods: OpenWhereMod, stack: any, panelName: string, addToSplit?: boolean, keyValue?: boolean): boolean { const instance = CollectionDockingView.Instance; if (!instance) return false; const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue); @@ -151,7 +151,7 @@ export class CollectionDockingView extends CollectionSubView() { } return false; } - return CollectionDockingView.AddSplit(document, panelName, stack, panelName); + return CollectionDockingView.AddSplit(document, mods, stack, panelName); } @undoBatch @@ -619,6 +619,3 @@ ScriptingGlobals.add( 'opens up document in screen overlay layer', '(doc: any)' ); -ScriptingGlobals.add(function useRightSplit(doc: any, addToRightSplit?: boolean) { - CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, addToRightSplit); -}); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 22f0f8a1f..cf154be8d 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -652,7 +652,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu pointerEvents: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._type_collection !== CollectionViewType.Freeform ? 'none' : undefined, color: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._type_collection !== CollectionViewType.Freeform ? 'dimgrey' : undefined, }} - onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}> + onClick={undoBatch(() => this.props.docView.CollectionFreeFormDocumentView?.float())}> <FontAwesomeIcon icon={['fab', 'buffer']} size={'lg'} /> </button> </Tooltip> diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index ac72a45e0..ac916fef3 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -443,7 +443,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { } return true; } - } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { + } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) { const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' }); if (!this.props.addDocument?.(source)) e.preventDefault(); de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index e5695f63b..da00093dd 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -471,7 +471,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } return true; } - } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { + } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) { const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' }); if (!this.props.addDocument?.(source)) e.preventDefault(); de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss index 13bb3a577..d447991a1 100644 --- a/src/client/views/collections/TabDocView.scss +++ b/src/client/views/collections/TabDocView.scss @@ -15,7 +15,6 @@ input.lm_title { } input.lm_title { - transition-delay: 0.35s; width: max-content; cursor: pointer; } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index de23d7570..6e1e6cf8d 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -11,7 +11,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { FieldId } from '../../../fields/RefField'; import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils'; +import { DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; @@ -64,24 +64,6 @@ export class TabDocView extends React.Component<TabDocViewProps> { @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } - @computed get tabBorderColor() { - const highlight = DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting); - if (highlight?.highlightIndex === Doc.DocBrushStatus.highlighted) return highlight.highlightColor; - return 'transparent'; - } - @computed get tabColor() { - return this._isUserActivated ? Colors.WHITE : this._hovering ? Colors.LIGHT_GRAY : Colors.MEDIUM_GRAY; - } - @computed get tabTextColor() { - return this._document?.type === DocumentType.PRES ? 'black' : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color))); - } - // @computed get renderBounds() { - // const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0]; - // const xbounds = bounds[2] - bounds[0]; - // const ybounds = bounds[3] - bounds[1]; - // const dim = Math.max(xbounds, ybounds); - // return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim }; - // } get stack() { return (this.props as any).glContainer.parent.parent; @@ -163,14 +145,31 @@ export class TabDocView extends React.Component<TabDocViewProps> { tab.reactComponents = [iconWrap, closeWrap]; tab.element[0].prepend(iconWrap); tab._disposers.color = reaction( - () => ({ color: this.tabColor, borderColor: this.tabBorderColor }), - coloring => { - const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color + () => ({ variant: SettingsManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }), + ({ variant, degree, highlight }) => { + const color = highlight?.highlightIndex === Doc.DocBrushStatus.highlighted ? highlight.highlightColor : degree ? ['transparent', variant, variant, 'orange'][degree] : variant; + + const textColor = color === variant ? SettingsManager.userColor : lightOrDark(color); titleEle.style.color = textColor; - titleEle.style.backgroundColor = coloring.borderColor; iconWrap.style.color = textColor; closeWrap.style.color = textColor; - tab.element[0].style.background = coloring.color; + tab.element[0].style.background = + color === variant + ? DashColor(color) + .fade( + this._isUserActivated + ? 0 + : this._hovering + ? 0.25 + : degree === Doc.DocBrushStatus.selfBrushed + ? 0.5 + : degree === Doc.DocBrushStatus.protoBrushed // + ? 0.7 + : 0.9 + ) + .rgb() + .toString() + : color; }, { fireImmediately: true } ); @@ -181,6 +180,10 @@ export class TabDocView extends React.Component<TabDocViewProps> { tab.header.parent.setActiveContentItem(tab.contentItem); tab.setActive(true); } + this._document && Doc.BrushDoc(this._document); + }; + tab.element[0].onmouseleave = (e: MouseEvent) => { + this._document && Doc.UnBrushDoc(this._document); }; tab.element[0].oncontextmenu = (e: MouseEvent) => { @@ -210,22 +213,18 @@ export class TabDocView extends React.Component<TabDocViewProps> { if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) { undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')(); } - toggle.style.fontWeight = selected ? 'bold' : ''; + //toggle.style.fontWeight = selected ? 'bold' : ''; // toggle.style.textTransform = selected ? "uppercase" : ""; }), { fireImmediately: true } ); // highlight the tab when the tab document is brushed in any part of the UI - // tab._disposers.reactionDisposer = reaction( - // () => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), - // ({ title, degree }) => { - // titleEle.value = title; - // titleEle.style.padding = degree ? 0 : 2; - // titleEle.style.border = `${['gray', 'gray', 'gray'][degree]} ${['none', 'dashed', 'solid'][degree]} 2px`; - // }, - // { fireImmediately: true } - // ); + tab._disposers.reactionDisposer = reaction( + () => doc?.title, + title => (titleEle.value = title), + { fireImmediately: true } + ); // clean up the tab when it is closed tab.closeElement @@ -346,11 +345,15 @@ export class TabDocView extends React.Component<TabDocViewProps> { this.props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged); } + // Flag indicating that when a tab is activated, it should not select it's document. + // this is used by the link properties menu when it wants to display the link target without selecting the target (which would make the link property window go away since it would no longer be selected) + public static DontSelectOnActivate = 'dontSelectOnActivate'; + @action.bound private onActiveContentItemChanged(contentItem: any) { if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; - if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== 'dontSelectOnActivate') setTimeout(() => SelectionManager.SelectView(this._view, false)); + if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => SelectionManager.SelectView(this._view, false)); !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } } @@ -365,8 +368,9 @@ export class TabDocView extends React.Component<TabDocViewProps> { addDocTab = (doc: Doc, location: OpenWhere) => { SelectionManager.DeselectAll(); const whereFields = location.split(':'); - const keyValue = whereFields[1]?.includes('KeyValue'); - const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none; + const keyValue = whereFields.includes(OpenWhereMod.keyvalue); + const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; + const panelName = whereFields.length > 1 ? whereFields.lastElement() : ''; if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc); // prettier-ignore switch (whereFields[0]) { @@ -381,8 +385,8 @@ export class TabDocView extends React.Component<TabDocViewProps> { } return LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox); case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); - case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, undefined, keyValue); - case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, "dontSelectOnActivate", keyValue); + case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, panelName, undefined, keyValue); + case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue); case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue); } }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 57c943479..cafa8b38f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../ import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -36,7 +36,7 @@ import { GestureOverlay } from '../../GestureOverlay'; import { CtrlKey } from '../../GlobalKeyHandler'; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; import { LightboxView } from '../../LightboxView'; -import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; +import { CollectionFreeFormDocumentViewWrapper, CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -45,7 +45,6 @@ import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; -import { TabDocView } from '../TabDocView'; import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents'; @@ -86,9 +85,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection private _hitCluster: number = -1; private _disposers: { [name: string]: IReactionDisposer } = {}; private _renderCutoffData = observable.map<string, boolean>(); - private _layoutPoolData = observable.map<string, PoolData>(); - private _layoutSizeData = observable.map<string, { width?: number; height?: number }>(); - private _cachedPool: Map<string, PoolData> = new Map(); private _batch: UndoManager.Batch | undefined = undefined; private _brushtimer: any; private _brushtimer1: any; @@ -109,7 +105,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return (this.props.viewField ?? '') + '_freeform_autoReset'; } - @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables + @computed get _layoutElements(): ViewDefResult[] { + const computation = this.doInternalLayoutComputation; + return this.doLayoutComputation(computation.newPool, computation.computedElementData); + } @observable _panZoomTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0 @observable _firstRender = false; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown. could be used for performance improvement @observable _showAnimTimeline = false; @@ -120,7 +119,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. - @computed get views() { + @computed get contentViews() { const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele); const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && (ele.inkMask === -1 || ele.inkMask === undefined)).map(ele => ele.ele); if (viewsMask.length) renderableEles.push(<div className={`collectionfreeformview-mask${this._layoutElements.some(ele => (ele.inkMask ?? 0) > 0) ? '' : '-empty'}`}>{viewsMask}</div>); @@ -637,7 +636,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._downX = this._lastX = e.pageX; this._downY = this._lastY = e.pageY; this._downTime = Date.now(); - if (e.button === 0 && !e.altKey && (!(e.ctrlKey && !e.metaKey) || Doc.UserDoc().freeformScrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) { + const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode; + if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) { if ( !this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && @@ -1052,7 +1052,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc); const measuredDocs = docs - .map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ''), size: this.childSizeProviderUnmemoized(doc, '') })) + .map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc.width), height: NumCast(doc.height) } })) .filter(({ pos, size }) => pos && size) .map(({ pos, size }) => ({ pos: pos!, size: size! })); if (measuredDocs.length) { @@ -1247,7 +1247,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.isContentActive() === false ? 'none' : this.props.pointerEvents?.()); - console.log(`${this.rootDoc.title} pe = ` + pointerevents); return pointerevents; } @@ -1258,16 +1257,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const childLayout = entry.pair.layout; const childData = entry.pair.data; return ( - <CollectionFreeFormDocumentView + <CollectionFreeFormDocumentViewWrapper + {...OmitKeys(entry, ['replica', 'pair']).omit} key={childLayout[Id] + (entry.replica || '')} DataDoc={childData} Document={childLayout} + dragStarting={this.dragStarting} + dragEnding={this.dragEnding} isGroupActive={this.props.isGroupActive} renderDepth={this.props.renderDepth + 1} - replica={entry.replica} hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)} suppressSetHeight={this.layoutEngine ? true : false} - renderCutoffProvider={this.renderCutoffProvider} + RenderCutoffProvider={this.renderCutoffProvider} CollectionFreeFormView={this} LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate} LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString} @@ -1295,8 +1296,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection docViewPath={this.props.docViewPath} styleProvider={this.clusterStyleProvider} dragAction={(this.rootDoc.childDragAction ?? this.props.childDragAction) as dropActionType} - dataProvider={this.childDataProvider} - sizeProvider={this.childSizeProvider} bringToFront={this.bringToFront} layout_showTitle={this.props.childlayout_showTitle} dontRegisterView={this.props.dontRegisterView} @@ -1425,20 +1424,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }.bind(this) ); - childPositionProviderUnmemoized = (doc: Doc, replica: string) => this._layoutPoolData.get(doc[Id] + (replica || '')); - childDataProvider = computedFn( - function childDataProvider(this: any, doc: Doc, replica: string) { - return this.childPositionProviderUnmemoized(doc, replica); - }.bind(this) - ); - - childSizeProviderUnmemoized = (doc: Doc, replica: string) => this._layoutSizeData.get(doc[Id] + (replica || '')); - childSizeProvider = computedFn( - function childSizeProvider(this: any, doc: Doc, replica: string) { - return this.childSizeProviderUnmemoized(doc, replica); - }.bind(this) - ); - doEngineLayout( poolData: Map<string, PoolData>, engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[] @@ -1467,46 +1452,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } - @observable _numLoaded = 1; @action doLayoutComputation = (newPool: Map<string, PoolData>, computedElementData: ViewDefResult[]) => { - const array = Array.from(newPool.entries()); - for (const entry of array) { - const lastPos = this._cachedPool.get(entry[0]); // last computed pos - const newPos = entry[1]; - if ( - !lastPos || - newPos.color !== lastPos.color || - newPos.backgroundColor !== lastPos.backgroundColor || - newPos.opacity !== lastPos.opacity || - newPos.x !== lastPos.x || - newPos.y !== lastPos.y || - newPos.z !== lastPos.z || - newPos.rotation !== lastPos.rotation || - newPos.zIndex !== lastPos.zIndex || - newPos.transition !== lastPos.transition || - newPos.pointerEvents !== lastPos.pointerEvents - ) { - this._layoutPoolData.set(entry[0], newPos); - } - if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) { - this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height }); - } - } - this._cachedPool.clear(); - Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1])); const elements = computedElementData.slice(); Array.from(newPool.entries()) .filter(entry => this.isCurrent(entry[1].pair.layout)) - .forEach((entry, i) => { - const childData: ViewDefBounds = this.childDataProvider(entry[1].pair.layout, entry[1].replica); - const childSize = this.childSizeProvider(entry[1].pair.layout, entry[1].replica); + .forEach(entry => elements.push({ ele: this.getChildDocView(entry[1]), - bounds: childData.opacity === 0 ? { ...childData, width: 0, height: 0 } : { ...childData, width: childSize.width, height: childSize.height }, + bounds: (entry[1].opacity === 0 ? { ...entry[1], width: 0, height: 0 } : { ...entry[1] }) as any, inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1, - }); - }); + }) + ); this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true); return elements; @@ -1580,12 +1537,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection { fireImmediately: true } ); - this._disposers.layoutComputation = reaction( - () => this.doInternalLayoutComputation, - ({ newPool, computedElementData }) => (this._layoutElements = this.doLayoutComputation(newPool, computedElementData)), - { fireImmediately: true, name: 'layoutComputationReaction' } - ); - this._disposers.active = reaction( () => this.isContentActive(), // if autoreset is on, then whenever the view is selected, it will be restored to it default pan/zoom positions active => !SnappingManager.GetIsDragging() && this.rootDoc[this.autoResetFieldKey] && active && this.resetView() @@ -1752,7 +1703,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return; } !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' }); - appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' }); + appearanceItems.push({ description: `Pin View`, event: () => this.props.pinToPres(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' }); this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); @@ -1799,13 +1750,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection SnappingManager.clearSnapLines(); }; @action - dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean, visited = new Set<Doc>()) => { + dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean = true, visited = new Set<Doc>()) => { if (visited.has(this.rootDoc)) return; visited.add(this.rootDoc); showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document._isGroup)); - if (this.rootDoc._isGroup && this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) { - this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.dragStarting(snapToDraggedDoc, false, visited); - } const activeDocs = this.getActiveDocuments(); const size = this.screenToLocalXf.transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; @@ -1813,13 +1761,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect); const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to - activeDocs.forEach( - doc => - doc._isGroup && - SnappingManager.GetIsResizing() !== doc && - !DragManager.docsBeingDragged.includes(doc) && - (DocumentManager.Instance.getDocumentView(doc)?.ComponentView as CollectionFreeFormView)?.dragStarting(snapToDraggedDoc, false, visited) - ); + activeDocs + .filter(doc => doc._isGroup && SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc)) + .forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited)); const horizLines: number[] = []; const vertLines: number[] = []; @@ -1850,10 +1794,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); }); - get children() { - this.incrementalRender(); - const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : this.props.children ? [this.props.children] : []; - return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />]; + // if a freeform view has any children, then the children will likely consist of a single child + // which will be a DocumentView. In this sitation, this freeform views acts as an annotation overlay for + // the underlying DocumentView and will pan and scoll with the underlying Documen tView. + @computed get underlayViews() { + return this.props.children ? [this.props.children] : []; } @computed get placeholder() { @@ -1889,6 +1834,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); } @computed get pannableContents() { + this.incrementalRender(); return ( <CollectionFreeFormPannableContents rootDoc={this.rootDoc} @@ -1897,7 +1843,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection transform={this.PanZoomCenterXf} transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))} viewDefDivClick={this.props.viewDefDivClick}> - {this.children} + {this.underlayViews} + {this.contentViews} + <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> </CollectionFreeFormPannableContents> ); } @@ -2048,11 +1996,11 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY if (!focused) { const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; let containers = dv.props.docViewPath(); - let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + let parFfview = dv.CollectionFreeFormView; for (var cont of containers) { - parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + parFfview = parFfview ?? cont.CollectionFreeFormView; } - while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView; const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview ffview?.zoomSmoothlyAboutPt(ffview.screenToLocalXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime); Doc.linkFollowHighlight(dv?.props.Document, false); @@ -2084,8 +2032,8 @@ ScriptingGlobals.add(function pinWithView(pinContent: boolean) { ); }); ScriptingGlobals.add(function bringToFront() { - SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc)); + SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc)); }); ScriptingGlobals.add(function sendToBack(doc: Doc) { - SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true)); + SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc, true)); }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index f831478a7..23f9bc69e 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -22,7 +22,6 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { PreviewCursor } from '../../PreviewCursor'; import { SubCollectionViewProps } from '../CollectionSubView'; -import { TabDocView } from '../TabDocView'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; import './MarqueeView.scss'; import React = require('react'); @@ -212,14 +211,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque // if (this.props.pointerEvents?.() === 'none') return; this._downX = this._lastX = e.clientX; this._downY = this._lastY = e.clientY; + const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode; + if (!(e.nativeEvent as any).marqueeHit) { (e.nativeEvent as any).marqueeHit = true; // allow marquee if ( e.button === 2 || //if right drag - e.altKey || // or alt drag - e.metaKey || // or alt drag - Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan // or pan mode + e.metaKey || // or meta drag + scrollMode === freeformScrollMode.Pan // or pan mode ) { // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) { this.setPreviewCursor(e.clientX, e.clientY, true, false, this.props.Document); @@ -403,7 +403,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @undoBatch @action pinWithView = () => { - TabDocView.PinDoc(this.props.Document, { pinViewport: this.Bounds }); + this.props.pinToPres(this.props.Document, { pinViewport: this.Bounds }); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); }; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index d2df58102..c690b8623 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -93,7 +93,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { if (NumCast(selected?.Document.z) >= 1) return true; return false; } - selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); + selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); }); ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce', checkResult?: boolean) { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss index f99011b8f..7f0a39550 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss @@ -1,9 +1,9 @@ .collectionFreeFormDocumentView-container { - transform-origin: left top; + transform-origin: 50% 50%; position: absolute; background-color: transparent; touch-action: manipulation; top: 0; left: 0; - //pointer-events: none; + pointer-events: none; } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 09dfa4f99..a05d6c904 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, observable, trace } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; import { List } from '../../../fields/List'; @@ -6,10 +6,9 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { numberRange } from '../../../Utils'; +import { numberRange, OmitKeys } from '../../../Utils'; import { DocumentManager } from '../../util/DocumentManager'; import { SelectionManager } from '../../util/SelectionManager'; -import { Transform } from '../../util/Transform'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { DocComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; @@ -17,18 +16,102 @@ import './CollectionFreeFormDocumentView.scss'; import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import React = require('react'); -export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { - dataProvider: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; rotation?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string }; - sizeProvider: (doc: Doc, replica: string) => { width: number; height: number }; - renderCutoffProvider: (doc: Doc) => boolean; +export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps { + x: number; + y: number; + z: number; + width: number; + height: number; zIndex?: number; + rotation?: number; + color?: string; + backgroundColor?: string; + opacity?: number; + highlight?: boolean; + transition?: string; dataTransition?: string; - replica: string; + RenderCutoffProvider: (doc: Doc) => boolean; + CollectionFreeFormView: CollectionFreeFormView; +} +@observer +export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps>() implements CollectionFreeFormDocumentViewProps { + @observable X = this.props.x; + @observable Y = this.props.y; + @observable Z = this.props.z; + @observable ZIndex = this.props.zIndex; + @observable Rotation = this.props.rotation; + @observable Opacity = this.props.opacity; + @observable BackgroundColor = this.props.backgroundColor; + @observable Color = this.props.color; + @observable Highlight = this.props.highlight; + @observable Width = this.props.width; + @observable Height = this.props.height; + @observable Transition = this.props.transition; + @observable DataTransition = this.props.dataTransition; + CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking + RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking + + @computed get WrapperKeys() { + return Object.keys(this).filter(key => key.startsWith('w_')).map(key => key.replace('w_', '')) + .map(key => ({upper:key, lower:key[0].toLowerCase() + key.substring(1)})); // prettier-ignore + } + + // wrapper functions around prop fields that have been converted to observables to keep 'props' from ever changing. + // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes + w_X = () => this.X; // prettier-ignore + w_Y = () => this.Y; // prettier-ignore + w_Z = () => this.Z; // prettier-ignore + w_ZIndex = () => this.ZIndex ?? NumCast(this.props.Document.zIndex); // prettier-ignore + w_Rot = () => this.Rotation ?? NumCast(this.props.Document._rotation); // prettier-ignore + w_Opacity = () => this.Opacity; // prettier-ignore + w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.props.Document._backgroundColor, 'string', null); // prettier-ignore + w_Color = () => this.Color ?? Cast(this.props.Document._color, 'string', null); // prettier-ignore + w_Highlight = () => this.Highlight; // prettier-ignore + w_Width = () => this.Width; // prettier-ignore + w_Height = () => this.Height; // prettier-ignore + w_Transition = () => this.Transition; // prettier-ignore + w_DataTransition = () => this.DataTransition; // prettier-ignore + + PanelWidth = () => this.Width || this.props.PanelWidth?.(); // prettier-ignore + PanelHeight = () => this.Height || this.props.PanelHeight?.(); // prettier-ignore + @action + componentDidUpdate() { + this.WrapperKeys.forEach(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])); + } + render() { + const layoutProps = this.WrapperKeys.reduce((val, keys) => [(val['w_' + keys.upper] = (this as any)['w_' + keys.upper]), val][1], {} as { [key: string]: Function }); + return ( + <CollectionFreeFormDocumentView + {...OmitKeys(this.props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore + {...layoutProps} + PanelWidth={this.PanelWidth} + PanelHeight={this.PanelHeight} + /> + ); + } +} +export interface CollectionFreeFormDocumentViewProps { + w_X: () => number; + w_Y: () => number; + w_Z: () => number; + w_ZIndex?: () => number; + w_Rotation?: () => number; + w_Color: () => string; + w_BackgroundColor: () => string; + w_Opacity: () => number | undefined; + w_Highlight: () => boolean | undefined; + w_Transition: () => string | undefined; + w_Width: () => number; + w_Height: () => number; + w_DataTransition: () => string | undefined; + PanelWidth: () => number; + PanelHeight: () => number; + RenderCutoffProvider: (doc: Doc) => boolean; CollectionFreeFormView: CollectionFreeFormView; } @observer -export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() { +export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() { get displayName() { // this makes mobx trace() statements more descriptive return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')'; } // prettier-ignore @@ -48,26 +131,19 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames - get X() { return this.dataProvider.x; } // prettier-ignore - get Y() { return this.dataProvider.y; } // prettier-ignore - get ZInd() { return this.dataProvider.zIndex ?? NumCast(this.Document.zIndex); } // prettier-ignore - get Rot() { return this.dataProvider.rotation ?? NumCast(this.Document._rotation); } // prettier-ignore - get Opacity() { return this.dataProvider.opacity; } // prettier-ignore - get BackgroundColor() { return this.dataProvider.backgroundColor ?? Cast(this.Document._backgroundColor, 'string', null); } // prettier-ignore - get Color() { return this.dataProvider.color ?? Cast(this.Document._color, 'string', null); } // prettier-ignore - @observable _animPos: number[] | undefined = undefined; @observable _contentView: DocumentView | undefined | null; - @computed get dataProvider() { return this.props.dataProvider(this.props.Document, this.props.replica); } // prettier-ignore - @computed get sizeProvider() { return this.props.sizeProvider(this.props.Document, this.props.replica); } // prettier-ignore + get CollectionFreeFormView() { + return this.props.CollectionFreeFormView; + } styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => { if (doc === this.layoutDoc) { switch (property) { - case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children - case StyleProp.BackgroundColor: return this.BackgroundColor; - case StyleProp.Color: return this.Color; + case StyleProp.Opacity: return this.props.w_Opacity(); // only change the opacity for this specific document, not its children + case StyleProp.BackgroundColor: return this.props.w_BackgroundColor(); + case StyleProp.Color: return this.props.w_Color(); } // prettier-ignore } return this.props.styleProvider?.(doc, props, property); @@ -146,16 +222,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF } }; - dragEnding = () => this.props.CollectionFreeFormView?.dragEnding(); - dragStarting = () => this.props.CollectionFreeFormView?.dragStarting(false, true); - nudge = (x: number, y: number) => { - this.props.Document.x = NumCast(this.props.Document.x) + x; - this.props.Document.y = NumCast(this.props.Document.y) + y; + this.props.Document.x = this.props.w_X() + x; + this.props.Document.y = this.props.w_Y() + y; }; - panelWidth = () => this.sizeProvider?.width || this.props.PanelWidth?.(); - panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.(); - screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y); + screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(-this.props.w_X(), -this.props.w_Y()); returnThis = () => this; /// this indicates whether the doc view is activated because of its relationshop to a group @@ -164,38 +235,35 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF // 'inactive' - this is a group child but it is not active // undefined - this is not activated by a group isGroupActive = () => { - if (this.props.CollectionFreeFormView.isAnyChildContentActive()) return undefined; + if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined; const isGroup = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent'); return isGroup ? (this.props.isDocumentActive?.() ? 'group' : this.props.isGroupActive?.() ? 'child' : 'inactive') : this.props.isGroupActive?.() ? 'child' : undefined; }; render() { TraceMobx(); - const divProps: DocumentViewProps = { - ...this.props, - CollectionFreeFormDocumentView: this.returnThis, - styleProvider: this.styleProvider, - ScreenToLocalTransform: this.screenToLocalTransform, - PanelWidth: this.panelWidth, - PanelHeight: this.panelHeight, - isGroupActive: this.isGroupActive, - }; + const passOnProps = OmitKeys(this.props, Object.keys(this.props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore return ( <div className="collectionFreeFormDocumentView-container" style={{ - width: this.panelWidth(), - height: this.panelHeight(), - transform: `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rot, this.Rot)}deg)`, - transformOrigin: '50% 50%', - transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)), - zIndex: this.ZInd, - display: this.sizeProvider?.width ? undefined : 'none', - pointerEvents: 'none', + width: this.props.PanelWidth(), + height: this.props.PanelHeight(), + transform: `translate(${this.props.w_X()}px, ${this.props.w_Y()}px) rotate(${NumCast(this.props.w_Rotation?.())}deg)`, + transition: this.props.w_Transition?.() ?? (this.props.w_DataTransition?.() || this.props.w_Transition?.()), + zIndex: this.props.w_ZIndex?.(), + display: this.props.w_Width?.() ? undefined : 'none', }}> - {this.props.renderCutoffProvider(this.props.Document) ? ( - <div style={{ position: 'absolute', width: this.panelWidth(), height: this.panelHeight(), background: 'lightGreen' }} /> + {this.props.RenderCutoffProvider(this.props.Document) ? ( + <div style={{ position: 'absolute', width: this.props.PanelWidth(), height: this.props.PanelHeight(), background: 'lightGreen' }} /> ) : ( - <DocumentView {...divProps} ref={action((r: DocumentView | null) => (this._contentView = r))} /> + <DocumentView + ref={action((r: DocumentView | null) => (this._contentView = r))} + {...passOnProps} + CollectionFreeFormDocumentView={this.returnThis} + styleProvider={this.styleProvider} + ScreenToLocalTransform={this.screenToLocalTransform} + isGroupActive={this.isGroupActive} + /> )} </div> ); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index e1de2fa76..c4703a47c 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -98,7 +98,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> { }; render() { const style: { [key: string]: any } = {}; - const divKeys = OmitKeys(this.props, ['children', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit; + const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit; const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string) || ''; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4dba5cbd3..005ec6019 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -64,6 +64,14 @@ declare class MediaRecorder { constructor(e: any); } +export enum OpenWhereMod { + none = '', + left = 'left', + right = 'right', + top = 'top', + bottom = 'bottom', + keyvalue = 'keyValue', +} export enum OpenWhere { lightbox = 'lightbox', add = 'add', @@ -79,14 +87,7 @@ export enum OpenWhere { inParent = 'inParent', inParentFromScreen = 'inParentFromScreen', overlay = 'overlay', -} -export enum OpenWhereMod { - none = '', - left = 'left', - right = 'right', - top = 'top', - bottom = 'bottom', - rightKeyValue = 'rightKeyValue', + addRightKeyvalue = 'add:right:keyValue', } export interface DocFocusOptions { @@ -134,6 +135,7 @@ export interface DocComponentView { setFocus?: () => void; // sets input focus to the componentView setData?: (data: Field | Promise<RefField | undefined>) => boolean; componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; + dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void; incrementalRendering?: () => void; layout_fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox) overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document (e.g, KeyValueBox always allows pointer events) @@ -166,7 +168,6 @@ export interface DocumentViewSharedProps { childHideDecorationTitle?: () => boolean; childHideResizeHandles?: () => boolean; childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar. - dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt styleProvider: Opt<StyleProviderFunc>; setTitleFocus?: () => void; focus: DocFocusFunc; @@ -235,6 +236,8 @@ export interface DocumentViewProps extends DocumentViewSharedProps { onPointerUp?: () => ScriptField; onBrowseClick?: () => ScriptField | undefined; onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined; + dragStarting?: () => void; + dragEnding?: () => void; } // these props are only available in DocumentViewIntenral @@ -321,7 +324,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined; @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined { - TraceMobx(); return this._pointerEvents; } @computed get finalLayoutKey() { @@ -334,7 +336,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps return this.props.NativeHeight(); } @computed get disableClickScriptFunc() { - TraceMobx(); const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable; // prettier-ignore return ( @@ -359,7 +360,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps componentWillUnmount() { this.cleanupHandlers(true); } - @observable _mounted = false; + @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI ) @action componentDidMount() { this._mounted = true; @@ -669,7 +670,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]); if (de.complete.linkDocument) { de.complete.linkDocument.layout_isSvg = true; - this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.addDocument(de.complete.linkDocument); + this.props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument); } } e.stopPropagation(); @@ -766,6 +767,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (this.props.renderDepth === 0) { appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => LightboxView.Instance.SetLightboxDoc(this.rootDoc), icon: 'external-link-alt' }); } + this.rootDoc.type === DocumentType.PRES && appearanceItems.push({ description: 'Pin', event: () => this.props.pinToPres(this.rootDoc, {}), icon: 'eye' }); !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' }); @@ -841,7 +843,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' }); } } - constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'table-columns' }); + constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' }); cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' }); const help = cm.findByDescription('Help...'); @@ -1519,6 +1521,13 @@ export class DocumentView extends React.Component<DocumentViewProps> { return this.props.dontCenter?.includes('y') ? 0 : this.Yshift; } + @computed get CollectionFreeFormView() { + return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; + } + @computed get CollectionFreeFormDocumentView() { + return this.props.CollectionFreeFormDocumentView?.(); + } + public toggleNativeDimensions = () => this.docView && this.rootDoc.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight()); public getBounds = () => { if (!this.docView?.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { @@ -1667,7 +1676,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { className="contentFittingDocumentView-previewDoc" ref={this.ContentRef} style={{ - transition: this.props.dataTransition, + transition: 'inherit', // this.props.dataTransition, transform: `translate(${this.centeringX}px, ${this.centeringY}px)`, width: xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`, height: this.props.forceAutoHeight diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index f22cb195f..6f99b7c28 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -7,7 +7,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { EditableView } from '../EditableView'; import { DefaultStyleProvider } from '../StyleProvider'; -import { OpenWhere } from './DocumentView'; +import { OpenWhere, OpenWhereMod } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { KeyValueBox } from './KeyValueBox'; import './KeyValueBox.scss'; @@ -50,7 +50,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { if (value instanceof Doc) { e.stopPropagation(); e.preventDefault(); - ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, ((OpenWhere.addRight as string) + 'KeyValue') as OpenWhere), icon: 'layer-group' }); + ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, OpenWhere.addRightKeyvalue), icon: 'layer-group' }); ContextMenu.Instance.displayMenu(e.clientX, e.clientY); } }; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 034eb4011..416cb11cc 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -32,7 +32,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { return DocumentManager.Instance.getDocumentView(anchor_2, this.props.docViewPath()[this.props.docViewPath().length - 2]); // this.props.docViewPath().lastElement()); } screenBounds = () => { - if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) { + if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) { const a_invXf = this.anchor1.props.ScreenToLocalTransform().inverse(); const b_invXf = this.anchor2.props.ScreenToLocalTransform().inverse(); const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.rootDoc._width), NumCast(this.anchor1.rootDoc._height)) }; @@ -57,7 +57,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { this.props.setContentView?.(this); this.disposer = reaction( () => { - if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) { + if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) { const a = (this.anchor1 ?? this.anchor2)!; const b = (this.anchor2 ?? this.anchor1)!; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index c4ef07123..180fa9f5d 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -27,7 +27,6 @@ import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm'; import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline'; import { CollectionView } from '../../collections/CollectionView'; -import { TabDocView } from '../../collections/TabDocView'; import { TreeView } from '../../collections/TreeView'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; @@ -439,7 +438,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc); const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext; if (context) { - const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.CollectionFreeFormView; if (ffview?.childDocs) { PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime); ffview.rootDoc._currentFrame = NumCast(activeFrame); @@ -768,6 +767,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection); }; + public static PanelName = 'PRESBOX'; + static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) { if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); @@ -782,7 +783,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { zoomTime: activeItem.presentation_movement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime), effect: activeItem, noSelect: true, - openLocation: OpenWhere.addLeft, + openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft, anchorDoc: activeItem, easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any, zoomTextSelections: BoolCast(activeItem.presentation_zoomText), @@ -1068,7 +1069,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { audio.config_clipStart = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */)); audio.config_clipEnd = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */)); audio.presentation_duration = audio.config_clipStart - audio.config_clipEnd; - TabDocView.PinDoc(audio, { audioRange: true }); + this.props.pinToPres(audio, { audioRange: true }); setTimeout(() => this.removeDocument(doc), 0); return false; } @@ -2223,7 +2224,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const config_data = Cast(this.rootDoc.data, listSpec(Doc)); if (data && config_data) { data.push(doc); - TabDocView.PinDoc(doc, {}); + this.props.pinToPres(doc, {}); this.gotoDocument(this.childDocs.length, this.activeItem); } else { this.props.addDocTab(doc, OpenWhere.addRight); |