diff options
Diffstat (limited to 'src/client/views/collections')
27 files changed, 632 insertions, 610 deletions
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 92319d080..ffc004df6 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -25,6 +25,7 @@ import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; import React = require('react'); +import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -142,7 +143,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch @action - public static ReplaceTab(document: Doc, panelName: string, stack: any, addToSplit?: boolean): boolean { + public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean): boolean { const instance = CollectionDockingView.Instance; if (!instance) return false; const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName); @@ -164,7 +165,7 @@ export class CollectionDockingView extends CollectionSubView() { } @undoBatch - public static ToggleSplit(doc: Doc, location: string, stack?: any, panelName?: string) { + public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string) { return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1 ? CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName); @@ -175,7 +176,7 @@ export class CollectionDockingView extends CollectionSubView() { // @undoBatch @action - public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) { + public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string) { if (document?._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document); if (!CollectionDockingView.Instance) return false; const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document); @@ -190,7 +191,7 @@ export class CollectionDockingView extends CollectionSubView() { if (!pullSide && stack) { stack.addChild(docContentConfig, undefined); - stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]); + setTimeout(() => stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1])); } else { const newContentItem = () => { const newItem = glayRoot.layoutManager.createContentItem({ type: 'stack', content: [docContentConfig] }, instance._goldenLayout); @@ -208,14 +209,15 @@ export class CollectionDockingView extends CollectionSubView() { // if row switch (pullSide) { default: - case 'right': + case OpenWhereMod.none: + case OpenWhereMod.right: glayRoot.contentItems[0].addChild(newContentItem()); break; - case 'left': + case OpenWhereMod.left: glayRoot.contentItems[0].addChild(newContentItem(), 0); break; - case 'top': - case 'bottom': + case OpenWhereMod.top: + case OpenWhereMod.bottom: // if not going in a row layout, must add already existing content into column const rowlayout = glayRoot.contentItems[0]; const newColumn = rowlayout.layoutManager.createContentItem({ type: 'column' }, instance._goldenLayout); @@ -387,6 +389,7 @@ export class CollectionDockingView extends CollectionSubView() { const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : ''; if (!className.includes('lm_close') && !className.includes('lm_maximise')) { this._flush = UndoManager.StartBatch('golden layout edit'); + DocServer.UPDATE_SERVER_CACHE(); } } } @@ -452,7 +455,7 @@ export class CollectionDockingView extends CollectionSubView() { .map(id => DocServer.GetCachedRefField(id)) .filter(f => f) .map(f => f as Doc); - const changesMade = this.props.Document.dockcingConfig !== json; + const changesMade = this.props.Document.dockingConfig !== json; if (changesMade && !this._flush) { UndoManager.RunInBatch(() => { this.props.Document.dockingConfig = json; @@ -496,7 +499,7 @@ export class CollectionDockingView extends CollectionSubView() { title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); - CollectionDockingView.AddSplit(docToAdd, '', stack); + CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }); @@ -507,7 +510,12 @@ export class CollectionDockingView extends CollectionSubView() { action(() => { //if (confirm('really close this?')) { if ((!stack.parent.isRoot && !stack.parent.parent.isRoot) || stack.parent.contentItems.length > 1) { + const batch = UndoManager.StartBatch('close stack'); stack.remove(); + setTimeout(() => { + this.stateChanged(); + batch.end(); + }); } else { alert('cant delete the last stack'); } @@ -534,7 +542,7 @@ export class CollectionDockingView extends CollectionSubView() { title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); - CollectionDockingView.AddSplit(docToAdd, '', stack); + CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }) ); @@ -563,14 +571,14 @@ export class CollectionDockingView extends CollectionSubView() { ScriptingGlobals.add( function openInLightbox(doc: any) { - LightboxView.AddDocTab(doc, 'lightbox'); + LightboxView.AddDocTab(doc, OpenWhere.lightbox); }, 'opens up document in a lightbox', '(doc: any)' ); ScriptingGlobals.add( function openOnRight(doc: any) { - return CollectionDockingView.AddSplit(doc, 'right'); + return CollectionDockingView.AddSplit(doc, OpenWhereMod.right); }, 'opens up document in tab on right side of the screen', '(doc: any)' @@ -583,5 +591,5 @@ ScriptingGlobals.add( '(doc: any)' ); ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) { - CollectionDockingView.ReplaceTab(doc, 'right', undefined, shiftKey); + CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, shiftKey); }); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index db81f28f6..e2594b6ae 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -31,7 +31,7 @@ import { Colors } from '../global/globalEnums'; import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke'; import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; -import { DocumentView } from '../nodes/DocumentView'; +import { DocumentView, OpenWhereMod } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { DefaultStyleProvider } from '../StyleProvider'; @@ -569,7 +569,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu startRecording = () => { const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _fitWidth: true, _width: 400, _height: 200, mediaState: 'pendingRecording' }); //Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); - CollectionDockingView.AddSplit(doc, 'right'); + CollectionDockingView.AddSplit(doc, OpenWhereMod.right); }; @computed @@ -733,11 +733,12 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView doc._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } - CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); + CollectionFreeFormDocumentView.updateKeyframe(undefined, childDocs, currentFrame || 0); doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame); } } + _keyTimer: NodeJS.Timeout | undefined; @undoBatch @action nextKeyframe = (): void => { @@ -746,7 +747,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView this.document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } - CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0); + this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0); this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1); this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame)); }; @@ -758,7 +759,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView this.document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } - CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); + this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice()); this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1); }; diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index b0f64ed60..26c73438c 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -19,7 +19,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { LightboxView } from '../LightboxView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; @@ -180,16 +180,8 @@ export class CollectionNoteTakingView extends CollectionSubView() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } - addDocTab = (doc: Doc, where: string) => { - if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]); - return true; - } - return this.props.addDocTab(doc, where); - }; - scrollToBottom = () => { - smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); + smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight, 'ease'); }; // let's dive in and get the actual document we want to drag/move around @@ -201,13 +193,12 @@ export class CollectionNoteTakingView extends CollectionSubView() { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); } } const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing); this.props.focus(this.rootDoc, { - willZoom: options?.willZoom, - scale: options?.scale, + ...options, afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), }); }; @@ -274,7 +265,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { removeDocument={this.props.removeDocument} contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.addDocTab} + addDocTab={this.props.addDocTab} bringToFront={returnFalse} scriptContext={this.props.scriptContext} pinToPres={this.props.pinToPres} diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 4489601db..ba90ed8cd 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -1,33 +1,37 @@ -import { action, computed, IReactionDisposer, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, HeightSym, WidthSym } from "../../../fields/Doc"; -import { NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils"; -import { DocUtils } from "../../documents/Documents"; -import { SelectionManager } from "../../util/SelectionManager"; -import { SnappingManager } from "../../util/SnappingManager"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; -import "./CollectionPileView.scss"; -import { CollectionSubView } from "./CollectionSubView"; -import React = require("react"); -import { ScriptField } from "../../../fields/ScriptField"; +import { action, computed, IReactionDisposer, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, HeightSym, WidthSym } from '../../../fields/Doc'; +import { NumCast, StrCast } from '../../../fields/Types'; +import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; +import { DocUtils } from '../../documents/Documents'; +import { SelectionManager } from '../../util/SelectionManager'; +import { SnappingManager } from '../../util/SnappingManager'; +import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; +import './CollectionPileView.scss'; +import { CollectionSubView } from './CollectionSubView'; +import React = require('react'); +import { ScriptField } from '../../../fields/ScriptField'; +import { OpenWhere } from '../nodes/DocumentView'; +import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'; @observer export class CollectionPileView extends CollectionSubView() { - _originalChrome: any = ""; + _originalChrome: any = ''; _disposers: { [name: string]: IReactionDisposer } = {}; componentDidMount() { - if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") { - this.Document._pileLayoutEngine = "pass"; + if (this.layoutEngine() !== computePassLayout.name && this.layoutEngine() !== computeStarburstLayout.name) { + this.Document._pileLayoutEngine = computePassLayout.name; } this._originalChrome = this.layoutDoc._chromeHidden; this.layoutDoc._chromeHidden = true; - // pileups are designed to go away when they are empty. - this._disposers.selected = reaction(() => this.childDocs.length, - (num) => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document)); + // pileups are designed to go away when they are empty. + this._disposers.selected = reaction( + () => this.childDocs.length, + num => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document) + ); } componentWillUnmount() { this.layoutDoc._chromeHidden = this._originalChrome; @@ -38,37 +42,41 @@ export class CollectionPileView extends CollectionSubView() { @undoBatch addPileDoc = (doc: Doc | Doc[]) => { - (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d)); + (doc instanceof Doc ? [doc] : doc).map(d => DocUtils.iconify(d)); return this.props.addDocument?.(doc) || false; - } + }; @undoBatch removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d))); + (doc instanceof Doc ? [doc] : doc).map(undoBatch(d => Doc.deiconifyView(d))); return this.props.moveDocument?.(doc, targetCollection, addDoc) || false; - } + }; toggleIcon = () => { - return ScriptField.MakeScript("documentView.iconify()", { documentView: "any" }); - } + return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' }); + }; // returns the contents of the pileup in a CollectionFreeFormView @computed get contents() { - const isStarburst = this.layoutEngine() === "starburst"; - return <div className="collectionPileView-innards" - style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : "none" }} > - <CollectionFreeFormView {...this.props} - layoutEngine={this.layoutEngine} - childDocumentsActive={isStarburst ? returnTrue : undefined} - addDocument={this.addPileDoc} - childClickScript={this.toggleIcon()} - moveDocument={this.removePileDoc} /> - </div>; + const isStarburst = this.layoutEngine() === computeStarburstLayout.name; + return ( + <div className="collectionPileView-innards" style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : 'none' }}> + <CollectionFreeFormView + {...this.props} + layoutEngine={this.layoutEngine} + childDocumentsActive={isStarburst ? returnTrue : undefined} + addDocument={this.addPileDoc} + childCanEmbedOnDrag={true} + childClickScript={this.toggleIcon()} + moveDocument={this.removePileDoc} + /> + </div> + ); } // toggles the pileup between starburst to compact toggleStarburst = action(() => { - if (this.layoutEngine() === 'starburst') { + if (this.layoutEngine() === computeStarburstLayout.name) { const defaultSize = 110; this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2; this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2; @@ -77,12 +85,12 @@ export class CollectionPileView extends CollectionSubView() { DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false); this.layoutDoc._panX = 0; this.layoutDoc._panY = -10; - this.props.Document._pileLayoutEngine = 'pass'; + this.props.Document._pileLayoutEngine = computePassLayout.name; } else { const defaultSize = 25; !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250); !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5); - if (this.layoutEngine() === 'pass') { + if (this.layoutEngine() === computePassLayout.name) { this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2; this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2; this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym](); @@ -90,7 +98,7 @@ export class CollectionPileView extends CollectionSubView() { } this.layoutDoc._panX = this.layoutDoc._panY = 0; this.layoutDoc._width = this.layoutDoc._height = defaultSize; - this.props.Document._pileLayoutEngine = 'starburst'; + this.props.Document._pileLayoutEngine = computeStarburstLayout.name; } }); @@ -99,27 +107,35 @@ export class CollectionPileView extends CollectionSubView() { pointerDown = (e: React.PointerEvent) => { let dist = 0; SnappingManager.SetIsDragging(true); - setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { - if (this.layoutEngine() === "pass" && this.childDocs.length && e.shiftKey) { - dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]); - if (dist > 100) { - if (!this._undoBatch) { - this._undoBatch = UndoManager.StartBatch("layout pile"); + setupMoveUpEvents( + this, + e, + (e: PointerEvent, down: number[], delta: number[]) => { + if (this.layoutEngine() === 'pass' && this.childDocs.length && e.shiftKey) { + dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]); + if (dist > 100) { + if (!this._undoBatch) { + this._undoBatch = UndoManager.StartBatch('layout pile'); + } + const doc = this.childDocs[0]; + doc.x = e.clientX; + doc.y = e.clientY; + this.props.addDocTab(doc, OpenWhere.inParentFromScreen) && (this.props.removeDocument?.(doc) || false); + dist = 0; } - const doc = this.childDocs[0]; - doc.x = e.clientX; - doc.y = e.clientY; - this.props.addDocTab(doc, "inParent") && (this.props.removeDocument?.(doc) || false); - dist = 0; } - } - return false; - }, () => { - this._undoBatch?.end(); - this._undoBatch = undefined; - SnappingManager.SetIsDragging(false); - }, emptyFunction, e.shiftKey && this.layoutEngine() === "pass", this.layoutEngine() === "pass" && e.shiftKey); // this sets _doubleTap - } + return false; + }, + () => { + this._undoBatch?.end(); + this._undoBatch = undefined; + SnappingManager.SetIsDragging(false); + }, + emptyFunction, + e.shiftKey && this.layoutEngine() === computePassLayout.name, + this.layoutEngine() === computePassLayout.name && e.shiftKey + ); // this sets _doubleTap + }; // onClick for toggling the pileup view @undoBatch @@ -130,12 +146,13 @@ export class CollectionPileView extends CollectionSubView() { this.toggleStarburst(); e.stopPropagation(); } - } + }; render() { - return <div className={`collectionPileView`} onClick={this.onClick} onPointerDown={this.pointerDown} - style={{ width: this.props.PanelWidth(), height: "100%" }}> - {this.contents} - </div>; + return ( + <div className={`collectionPileView`} onClick={this.onClick} onPointerDown={this.pointerDown} style={{ width: this.props.PanelWidth(), height: '100%' }}> + {this.contents} + </div> + ); } } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 7bf798656..e9bf03208 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -8,6 +8,7 @@ import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { Cast, NumCast } from '../../../fields/Types'; +import { ImageField } from '../../../fields/URLField'; import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -23,13 +24,10 @@ import { AudioWaveform } from '../AudioWaveform'; import { CollectionSubView } from '../collections/CollectionSubView'; import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; -import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, DocumentViewSharedProps } from '../nodes/DocumentView'; +import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { LabelBox } from '../nodes/LabelBox'; -import './CollectionStackedTimeline.scss'; import { VideoBox } from '../nodes/VideoBox'; -import { ImageField } from '../../../fields/URLField'; -import { StyleProp } from '../StyleProvider'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import './CollectionStackedTimeline.scss'; export type CollectionStackedTimelineProps = { Play: () => void; @@ -610,7 +608,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack height={height} toTimeline={this.toTimeline} layoutDoc={this.layoutDoc} - // isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive} + isDocumentActive={this.isContentActive} currentTimecode={this.currentTimecode} _timeline={this._timeline} stackedTimeline={this} @@ -702,7 +700,7 @@ interface StackedTimelineAnchorProps { endTag: string; renderDepth: number; layoutDoc: Doc; - isDocumentActive?: () => boolean; + isDocumentActive?: () => boolean | undefined; ScreenToLocalTransform: () => Transform; _timeline: HTMLDivElement | null; focus: DocFocusFunc; @@ -748,7 +746,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> time < NumCast(this.props.mark[this.props.endTag]) && this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5 ) { - LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true); + LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false); } this._lastTimecode = time; } @@ -806,25 +804,6 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> return [resetTitle]; }; - innerStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => { - if (property === StyleProp.Decorations && doc && NumCast(doc.timecodeToHide) - NumCast(doc.timecodeToShow) < 0.0002) { - return ( - <div className="styleProvider-lock"> - <FontAwesomeIcon - icon={'camera'} - style={{ color: 'red' }} - onClick={e => { - LinkFollower.FollowLink(undefined, doc, props as DocumentViewSharedProps, e.altKey); - e.stopPropagation(); - }} - size="lg" - /> - </div> - ); - } - return this.props.styleProvider?.(doc, props, property); - }; - // renders anchor LabelBox renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) { const anchor = observable({ view: undefined as any }); @@ -841,7 +820,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> ref={action((r: DocumentView | null) => (anchor.view = r))} Document={mark} DataDoc={undefined} - styleProvider={this.innerStyleProvider} + styleProvider={this.props.styleProvider} renderDepth={this.props.renderDepth + 1} LayoutTemplate={undefined} LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 175051d5c..acf59b5da 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -22,7 +22,7 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; @@ -241,16 +241,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } - addDocTab = (doc: Doc, where: string) => { - if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]); - return true; - } - return this.props.addDocTab(doc, where); - }; - scrollToBottom = () => { - smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); + smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight, 'ease'); }; // let's dive in and get the actual document we want to drag/move around @@ -263,13 +255,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); } } const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; this.props.focus(this.rootDoc, { - willZoom: options?.willZoom, - scale: options?.scale, + ...options, afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), }); }; @@ -361,7 +352,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection removeDocument={this.props.removeDocument} contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.addDocTab} + addDocTab={this.props.addDocTab} bringToFront={returnFalse} scriptContext={this.props.scriptContext} pinToPres={this.props.pinToPres} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 7bc273d7d..d2074219a 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -111,7 +111,7 @@ export function CollectionSubView<X>(moreProps?: X) { rawdocs = rootDoc && !this.props.isAnnotationOverlay ? [Doc.GetProto(rootDoc)] : []; } - const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate).map(d => d as Doc); + const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.unrendered)).map(d => d as Doc); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; @@ -172,8 +172,6 @@ export function CollectionSubView<X>(moreProps?: X) { // The following conditional detects a recurring bug we've seen on the server if (proto[Id] === Docs.Prototypes.get(DocumentType.COL)[Id]) { alert('COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info...'); - console.log(doc); - console.log(proto); throw new Error(`AHA! You were trying to set a cursor on a collection's proto, which is the original collection proto! Look at the two previously printed lines for document values!`); } let cursors = Cast(proto.cursors, listSpec(CursorField)); diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 3dd9d2d84..a1466bcd0 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,32 +1,32 @@ -import { toUpper } from "lodash"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, Opt, StrListCast } from "../../../fields/Doc"; -import { List } from "../../../fields/List"; -import { ObjectField } from "../../../fields/ObjectField"; -import { RichTextField } from "../../../fields/RichTextField"; -import { listSpec } from "../../../fields/Schema"; -import { ComputedField, ScriptField } from "../../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils"; -import { Docs } from "../../documents/Documents"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentManager } from "../../util/DocumentManager"; -import { ScriptingGlobals } from "../../util/ScriptingGlobals"; -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; -import { EditableView } from "../EditableView"; -import { ViewSpecPrefix } from "../nodes/DocumentView"; -import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines"; -import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; -import { CollectionSubView } from "./CollectionSubView"; -import "./CollectionTimeView.scss"; -import React = require("react"); +import { toUpper } from 'lodash'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, Opt, StrListCast } from '../../../fields/Doc'; +import { List } from '../../../fields/List'; +import { ObjectField } from '../../../fields/ObjectField'; +import { RichTextField } from '../../../fields/RichTextField'; +import { listSpec } from '../../../fields/Schema'; +import { ComputedField, ScriptField } from '../../../fields/ScriptField'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; +import { Docs } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { DocumentManager } from '../../util/DocumentManager'; +import { ScriptingGlobals } from '../../util/ScriptingGlobals'; +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { EditableView } from '../EditableView'; +import { ViewSpecPrefix } from '../nodes/DocumentView'; +import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; +import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; +import { CollectionSubView } from './CollectionSubView'; +import './CollectionTimeView.scss'; +import React = require('react'); @observer export class CollectionTimeView extends CollectionSubView() { _changing = false; - @observable _layoutEngine = "pivot"; + @observable _layoutEngine = computePivotLayout.name; @observable _collapsed: boolean = false; @observable _childClickedScript: Opt<ScriptField>; @observable _viewDefDivClick: Opt<ScriptField>; @@ -35,7 +35,7 @@ export class CollectionTimeView extends CollectionSubView() { getAnchor = () => { const anchor = Docs.Create.HTMLAnchorDocument([], { title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, - annotationOn: this.rootDoc + annotationOn: this.rootDoc, }); // save view spec information for anchor @@ -43,81 +43,103 @@ export class CollectionTimeView extends CollectionSubView() { proto.pivotField = this.pivotField; proto.docFilters = ObjectField.MakeCopy(this.layoutDoc._docFilters as ObjectField) || new List<string>([]); proto.docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField) || new List<string>([]); - proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType; + proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; // store anchor in annotations list of document (not technically needed since these anchors are never drawn) - if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) { - Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor); + if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { + Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor); } else { - this.dataDoc[this.props.fieldKey + "-annotations"] = new List<Doc>([anchor]); + this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]); } return anchor; - } + }; async componentDidMount() { this.props.setContentView?.(this); //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), ""); - ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); "; + ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; useRightSplit(alias, shiftKey); "; runInAction(() => { - this._childClickedScript = ScriptField.MakeScript("openInLightbox(self)", { this: Doc.name }); - this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" }); + this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name }); + this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' }); }); } - get pivotField() { return this._focusPivotField || StrCast(this.layoutDoc._pivotField); } + get pivotField() { + return this._focusPivotField || StrCast(this.layoutDoc._pivotField); + } @action setViewSpec = (anchor: Doc, preview: boolean) => { - if (preview) { // if in preview, then override document's fields with view spec + if (preview) { + // if in preview, then override document's fields with view spec this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters); this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters); this._focusPivotField = StrCast(anchor.pivotField); - } else if (anchor.pivotField !== undefined) { // otherwise set document's fields based on anchor view spec + } else if (anchor.pivotField !== undefined) { + // otherwise set document's fields based on anchor view spec this.layoutDoc._prevFilterIndex = 1; this.layoutDoc._pivotField = StrCast(anchor.pivotField); this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters)); this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters)); } return 0; - } + }; layoutEngine = () => this._layoutEngine; - toggleVisibility = action(() => this._collapsed = !this._collapsed); + toggleVisibility = action(() => (this._collapsed = !this._collapsed)); onMinDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { - const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); - const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); - this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq + (maxReq - minReq) * delta[0] / this.props.PanelWidth(); - this.props.Document[this.props.fieldKey + "-timelineSpan"] = undefined; - return false; - }), returnFalse, emptyFunction); - } + setupMoveUpEvents( + this, + e, + action((e: PointerEvent, down: number[], delta: number[]) => { + const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0)); + const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10)); + this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); + this.props.Document[this.props.fieldKey + '-timelineSpan'] = undefined; + return false; + }), + returnFalse, + emptyFunction + ); + }; onMaxDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { - const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); - const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); - this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq + (maxReq - minReq) * delta[0] / this.props.PanelWidth(); - return false; - }), returnFalse, emptyFunction); - } + setupMoveUpEvents( + this, + e, + action((e: PointerEvent, down: number[], delta: number[]) => { + const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0)); + const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10)); + this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); + return false; + }), + returnFalse, + emptyFunction + ); + }; onMidDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { - const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0)); - const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10)); - this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq - (maxReq - minReq) * delta[0] / this.props.PanelWidth(); - this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq - (maxReq - minReq) * delta[0] / this.props.PanelWidth(); - return false; - }), returnFalse, emptyFunction); - } + setupMoveUpEvents( + this, + e, + action((e: PointerEvent, down: number[], delta: number[]) => { + const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0)); + const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10)); + this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); + this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); + return false; + }), + returnFalse, + emptyFunction + ); + }; goTo = (prevFilterIndex: number) => { - this.layoutDoc._pivotField = this.layoutDoc["_prevPivotFields" + prevFilterIndex]; - this.layoutDoc._docFilters = ObjectField.MakeCopy(this.layoutDoc["_prevDocFilter" + prevFilterIndex] as ObjectField); - this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc["_prevDocRangeFilters" + prevFilterIndex] as ObjectField); + this.layoutDoc._pivotField = this.layoutDoc['_prevPivotFields' + prevFilterIndex]; + this.layoutDoc._docFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocFilter' + prevFilterIndex] as ObjectField); + this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocRangeFilters' + prevFilterIndex] as ObjectField); this.layoutDoc._prevFilterIndex = prevFilterIndex; - } + }; @action contentsDown = (e: React.MouseEvent) => { @@ -127,37 +149,58 @@ export class CollectionTimeView extends CollectionSubView() { } else { this.layoutDoc._docFilters = new List([]); } - } + }; dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF; @computed get contents() { - return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : "none" }} - onClick={this.contentsDown}> - <CollectionFreeFormView {...this.props} - engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }} - fitContentsToBox={returnTrue} - childClickScript={this._childClickedScript} - viewDefDivClick={this._viewDefDivClick} - //dontScaleFilter={this.dontScaleFilter} - layoutEngine={this.layoutEngine} /> - </div>; + return ( + <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}> + <CollectionFreeFormView + {...this.props} + engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }} + fitContentsToBox={returnTrue} + childClickScript={this._childClickedScript} + viewDefDivClick={this._viewDefDivClick} + //dontScaleFilter={this.dontScaleFilter} + layoutEngine={this.layoutEngine} + /> + </div> + ); } public static SyncTimelineToPresentation(doc: Doc) { const fieldKey = Doc.LayoutFieldKey(doc); - doc[fieldKey + "-timelineCur"] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)"); + doc[fieldKey + '-timelineCur'] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)"); } specificMenu = (e: React.MouseEvent) => { const layoutItems: ContextMenuProps[] = []; const doc = this.layoutDoc; - layoutItems.push({ description: "Force Timeline", event: () => { doc._forceRenderEngine = "timeline"; }, icon: "compress-arrows-alt" }); - layoutItems.push({ description: "Force Pivot", event: () => { doc._forceRenderEngine = "pivot"; }, icon: "compress-arrows-alt" }); - layoutItems.push({ description: "Auto Time/Pivot layout", event: () => { doc._forceRenderEngine = undefined; }, icon: "compress-arrows-alt" }); - layoutItems.push({ description: "Sync with presentation", event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: "compress-arrows-alt" }); + layoutItems.push({ + description: 'Force Timeline', + event: () => { + doc._forceRenderEngine = computeTimelineLayout.name; + }, + icon: 'compress-arrows-alt', + }); + layoutItems.push({ + description: 'Force Pivot', + event: () => { + doc._forceRenderEngine = computePivotLayout.name; + }, + icon: 'compress-arrows-alt', + }); + layoutItems.push({ + description: 'Auto Time/Pivot layout', + event: () => { + doc._forceRenderEngine = undefined; + }, + icon: 'compress-arrows-alt', + }); + layoutItems.push({ description: 'Sync with presentation', event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: 'compress-arrows-alt' }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" }); - } + ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); + }; @computed get _allFacets() { const facets = new Set<string>(); this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); @@ -169,37 +212,40 @@ export class CollectionTimeView extends CollectionSubView() { const docItems: ContextMenuProps[] = []; const keySet: Set<string> = new Set(); - this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey => - pair.layout[fieldKey] instanceof RichTextField || - typeof (pair.layout[fieldKey]) === "number" || - typeof (pair.layout[fieldKey]) === "boolean" || - typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] !== "#" || fieldKey === "#") && (fieldKey === "tags" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey))); - Array.from(keySet).map(fieldKey => - docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" })); - docItems.push({ description: ":default", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" }); - ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" }); + this.childLayoutPairs.map(pair => + this._allFacets + .filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof pair.layout[fieldKey] === 'number' || typeof pair.layout[fieldKey] === 'boolean' || typeof pair.layout[fieldKey] === 'string') + .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey[0] !== '#' || fieldKey === '#') && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0])) + .map(fieldKey => keySet.add(fieldKey)) + ); + Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); + docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' }); + ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' }); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y); - ContextMenu.Instance.displayMenu(x, y, ":"); - } + ContextMenu.Instance.displayMenu(x, y, ':'); + }; @computed get pivotKeyUI() { - return <div className={"pivotKeyEntry"}> - <EditableView - GetValue={returnEmptyString} - SetValue={(value: any) => { - if (value?.length) { - this.layoutDoc._pivotField = value; - return true; - } - return false; - }} - toggle={this.toggleVisibility} - background={"#f1efeb"} // this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; - contents={":" + StrCast(this.layoutDoc._pivotField)} - showMenuOnLoad={true} - display={"inline"} - menuCallback={this.menuCallback} /> - </div>; + return ( + <div className={'pivotKeyEntry'}> + <EditableView + GetValue={returnEmptyString} + SetValue={(value: any) => { + if (value?.length) { + this.layoutDoc._pivotField = value; + return true; + } + return false; + }} + toggle={this.toggleVisibility} + background={'#f1efeb'} // this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + contents={':' + StrCast(this.layoutDoc._pivotField)} + showMenuOnLoad={true} + display={'inline'} + menuCallback={this.menuCallback} + /> + </div> + ); } render() { @@ -211,55 +257,62 @@ export class CollectionTimeView extends CollectionSubView() { } }); const forceLayout = StrCast(this.layoutDoc._forceRenderEngine); - const doTimeline = forceLayout ? (forceLayout === "timeline") : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6; - if (doTimeline !== (this._layoutEngine === "timeline")) { + const doTimeline = forceLayout ? forceLayout === computeTimelineLayout.name : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6; + if (doTimeline !== (this._layoutEngine === computeTimelineLayout.name)) { if (!this._changing) { this._changing = true; - setTimeout(action(() => { - this._layoutEngine = doTimeline ? "timeline" : "pivot"; - this._changing = false; - }), 0); + setTimeout( + action(() => { + this._layoutEngine = doTimeline ? computeTimelineLayout.name : computePivotLayout.name; + this._changing = false; + }), + 0 + ); } } - return <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu} - style={{ width: this.props.PanelWidth(), height: "100%" }}> - {this.pivotKeyUI} - {this.contents} - {!this.props.isSelected() || !doTimeline ? (null) : <> - <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} /> - <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} /> - <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} /> - </>} - </div>; + return ( + <div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this.props.PanelWidth(), height: '100%' }}> + {this.pivotKeyUI} + {this.contents} + {!this.props.isSelected() || !doTimeline ? null : ( + <> + <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} /> + <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} /> + <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} /> + </> + )} + </div> + ); } } ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) { - const pivotField = StrCast(pivotDoc._pivotField) || "author"; + const pivotField = StrCast(pivotDoc._pivotField) || 'author'; let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex); const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField)); - pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField); - pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField); - pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotField; + pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField); + pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField); + pivotDoc['_prevPivotFields' + prevFilterIndex] = pivotField; pivotDoc._prevFilterIndex = ++prevFilterIndex; pivotDoc._docFilters = new List(); - setTimeout(action(() => { - const filterVals = (bounds.payload as string[]); - filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, "check")); - const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc); - if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) { - if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) { - pivotDoc._pivotField = filterVals[0]; + setTimeout( + action(() => { + const filterVals = bounds.payload as string[]; + filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, 'check')); + const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc); + if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) { + if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) { + pivotDoc._pivotField = filterVals[0]; + } } - } - const newFilters = StrListCast(pivotDoc._docFilters); - if (newFilters.length && originalFilter.length && - newFilters.lastElement() === originalFilter.lastElement()) { - pivotDoc._prevFilterIndex = --prevFilterIndex; - pivotDoc["_prevDocFilter" + prevFilterIndex] = undefined; - pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = undefined; - pivotDoc["_prevPivotFields" + prevFilterIndex] = undefined; - } - })); -});
\ No newline at end of file + const newFilters = StrListCast(pivotDoc._docFilters); + if (newFilters.length && originalFilter.length && newFilters.lastElement() === originalFilter.lastElement()) { + pivotDoc._prevFilterIndex = --prevFilterIndex; + pivotDoc['_prevDocFilter' + prevFilterIndex] = undefined; + pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = undefined; + pivotDoc['_prevPivotFields' + prevFilterIndex] = undefined; + } + }) + ); +}); diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 3785b7d61..273b08247 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -67,7 +67,8 @@ font-style: italic; font-size: 8pt; margin-left: 3px; - display: none; + opacity: 0; + pointer-events: none; } .collectionTreeView-contents { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 0ff89c5a7..1a265af4a 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -115,7 +115,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', '')); const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()) + 6; this.layoutDoc._autoHeightMargins = bodyHeight; - this.props.setHeight?.(bodyHeight + titleHeight); + !this.props.dontRegisterView && this.props.setHeight?.(bodyHeight + titleHeight); } }; unobserveHeight = (ref: any) => { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 9f63a11aa..a28b1ca19 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -16,6 +16,7 @@ import { InteractionUtils } from '../../util/InteractionUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; @@ -42,6 +43,7 @@ interface CollectionViewProps_ extends FieldViewProps { layoutEngine?: () => string; setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; setBrushViewer?: (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => void; + ignoreUnrendered?: boolean; // property overrides for child documents childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) @@ -54,6 +56,7 @@ interface CollectionViewProps_ extends FieldViewProps { childHideDecorationTitle?: () => boolean; childHideResizeHandles?: () => boolean; childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection + childCanEmbedOnDrag?: boolean; childXPadding?: number; childYPadding?: number; childLayoutString?: string; @@ -173,7 +176,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab vtype => { const newRendition = Doc.MakeAlias(this.rootDoc); newRendition._viewType = vtype; - this.props.addDocTab(newRendition, 'add:right'); + this.props.addDocTab(newRendition, OpenWhere.addRight); return newRendition; }, false @@ -183,17 +186,17 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab const optionItems = options && 'subitems' in options ? options.subitems : []; !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.rootDoc.forceActive = !this.rootDoc.forceActive), icon: 'project-diagram' }) : null; if (this.rootDoc.childLayout instanceof Doc) { - optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, 'add:right'), icon: 'project-diagram' }); + optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) { - optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, 'add:right'), icon: 'project-diagram' }); + optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' }); if (!Doc.noviceMode && false) { optionItems.push({ description: 'Create Branch', - event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), 'add:right'), + event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), OpenWhere.addRight), icon: 'project-diagram', }); optionItems.push({ @@ -224,7 +227,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab event: (obj: any) => { const alias = Doc.MakeAlias(this.rootDoc); DocUtils.makeCustomViewClicked(alias, undefined, func.key); - this.props.addDocTab(alias, 'add:right'); + this.props.addDocTab(alias, OpenWhere.addRight); }, }) ); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 31ed5a83b..cd58319cb 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -11,7 +11,7 @@ import { List } from '../../../fields/List'; import { FieldId } from '../../../fields/RefField'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { DocUtils } from '../../documents/Documents'; @@ -26,7 +26,7 @@ import { DashboardView } from '../DashboardView'; import { Colors, Shadows } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { MainView } from '../MainView'; -import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; import { DashFieldView } from '../nodes/formattedText/DashFieldView'; import { PinProps, PresBox, PresMovement } from '../nodes/trails'; import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; @@ -55,6 +55,11 @@ 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() { let tabColor = StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); if (tabColor === 'transparent') return 'black'; @@ -147,17 +152,15 @@ export class TabDocView extends React.Component<TabDocViewProps> { ReactDOM.createRoot(closeWrap).render(closeIcon); tab.reactComponents = [iconWrap, closeWrap]; tab.element[0].prepend(iconWrap); - tab._disposers.layerDisposer = reaction( - () => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }), - ({ layer, color }) => { - // console.log("TabDocView: " + this.tabColor); - // console.log("lightOrDark: " + lightOrDark(this.tabColor)); + tab._disposers.color = reaction( + () => ({ color: this.tabColor, borderColor: this.tabBorderColor }), + coloring => { const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color titleEle.style.color = textColor; - titleEle.style.backgroundColor = 'transparent'; + titleEle.style.backgroundColor = coloring.borderColor; iconWrap.style.color = textColor; closeWrap.style.color = textColor; - tab.element[0].style.background = !layer ? color : 'dimgrey'; + tab.element[0].style.background = coloring.color; }, { fireImmediately: true } ); @@ -193,11 +196,12 @@ export class TabDocView extends React.Component<TabDocViewProps> { () => SelectionManager.Views().some(v => v.topMost && v.props.Document === doc), action(selected => { if (selected) this._activated = true; - const toggle = tab.element[0].children[1].children[0] as HTMLInputElement; + const toggle = tab.element[0].children[2].children[0] as HTMLInputElement; selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && UndoManager.RunInBatch(() => 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 @@ -226,7 +230,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { * Adds a document to the presentation view **/ @action - public static PinDoc(docs: Doc | Doc[], pinProps?: PinProps) { + public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) { const docList = docs instanceof Doc ? [docs] : docs; const batch = UndoManager.StartBatch('pinning doc'); @@ -243,8 +247,9 @@ export class TabDocView extends React.Component<TabDocViewProps> { alert('Cannot pin presentation document to itself'); return; } - const pinDoc = Doc.MakeAlias(doc); - pinDoc.presentationTargetDoc = doc; + const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(); + const pinDoc = Doc.MakeAlias(anchorDoc ?? doc); + pinDoc.presentationTargetDoc = anchorDoc ?? doc; pinDoc.title = doc.title + ' - Slide'; pinDoc.data = new List<Doc>(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; @@ -268,7 +273,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { pinDoc.presStartTime = NumCast(doc.clipStart); pinDoc.presEndTime = NumCast(doc.clipEnd, duration); } - PresBox.pinDocView(pinDoc, pinProps, doc); + PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, doc); pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); //save position @@ -277,6 +282,11 @@ export class TabDocView extends React.Component<TabDocViewProps> { pinDoc.title = doc.title + ' (move)'; pinDoc.presMovement = PresMovement.Pan; } + if (pinProps?.currentFrame !== undefined) { + pinDoc.presCurrentFrame = pinProps?.currentFrame; + pinDoc.title = doc.title + ' (move)'; + pinDoc.presMovement = PresMovement.Pan; + } if (pinDoc.isInkMask) { pinDoc.presHideAfter = true; pinDoc.presHideBefore = true; @@ -293,8 +303,8 @@ export class TabDocView extends React.Component<TabDocViewProps> { ) { const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []); if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); - CollectionDockingView.AddSplit(curPres, 'right'); - setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things + CollectionDockingView.AddSplit(curPres, OpenWhereMod.right); + setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), { willPan: true }, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things } setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs } @@ -340,31 +350,21 @@ export class TabDocView extends React.Component<TabDocViewProps> { // "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, // "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right // inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack - addDocTab = (doc: Doc, location: string) => { + addDocTab = (doc: Doc, location: OpenWhere) => { SelectionManager.DeselectAll(); - const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':'); - const locationParams = locationFields.length > 1 ? locationFields[1] : ''; - switch (locationFields[0]) { - case 'dashboard': - return DashboardView.openDashboard(doc); - case 'close': - return CollectionDockingView.CloseSplit(doc, locationParams); - case 'fullScreen': - return CollectionDockingView.OpenFullScreen(doc); - case 'replace': - return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); - // case "lightbox": { - // // TabDocView.PinDoc(doc, { hidePresBox: true }); - // return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); - // } - case 'lightbox': - return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); - case 'toggle': - return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack); - case 'inPlace': - case 'add': - default: - return CollectionDockingView.AddSplit(doc, locationParams, this.stack); + const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); + const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; + if (doc.dockingConfig) return DashboardView.openDashboard(doc); + // prettier-ignore + switch (whereFields[0]) { + case OpenWhere.inPlace: // fall through to lightbox + case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); + case OpenWhere.dashboard: return DashboardView.openDashboard(doc); + case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); + case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); + case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack); + case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack); + case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack); } }; remDocTab = (doc: Doc | Doc[]) => { @@ -382,17 +382,10 @@ export class TabDocView extends React.Component<TabDocViewProps> { @action focusFunc = (doc: Doc, options: DocFocusOptions) => { const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap; - if (options?.willZoom !== false && shrinkwrap && this._document) { - const focusSpeed = NumCast(this._document.focusSpeed, 500); + if (options?.willPanZoom !== false && shrinkwrap && this._document) { + const focusSpeed = options.zoomTime ?? 500; shrinkwrap(); - this._document._viewTransition = `transform ${focusSpeed}ms`; - setTimeout( - action(() => { - this._document!._viewTransition = undefined; - options?.afterFocus?.(false); - }), - focusSpeed - ); + this._view?.setViewTransition('transform', focusSpeed, () => options?.afterFocus?.(false)); } else { options?.afterFocus?.(false); } @@ -501,7 +494,7 @@ interface TabMinimapViewProps { document: Doc; hideMinimap: () => boolean; tabView: () => DocumentView | undefined; - addDocTab: (doc: Doc, where: string) => boolean; + addDocTab: (doc: Doc, where: OpenWhere) => boolean; PanelWidth: () => number; PanelHeight: () => number; background: () => string; diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 83fee013a..7eab03e1d 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -173,7 +173,8 @@ .treeView-header:hover { .collectionTreeView-keyHeader { - display: inherit; + opacity: unset; + pointer-events: unset; } .treeView-openRight { diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index ac8562d5a..bd326f917 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -22,7 +22,7 @@ import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; -import { DocumentView, DocumentViewInternal, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; @@ -44,7 +44,7 @@ export interface TreeViewProps { containerCollection: Doc; renderDepth: number; dropAction: dropActionType; - addDocTab: (doc: Doc, where: string) => boolean; + addDocTab: (doc: Doc, where: OpenWhere) => boolean; panelWidth: () => number; panelHeight: () => number; addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; @@ -236,7 +236,7 @@ export class TreeView extends React.Component<TreeViewProps> { const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail); - this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), 'lightbox'); + this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), OpenWhere.lightbox); } }; @@ -416,7 +416,6 @@ export class TreeView extends React.Component<TreeViewProps> { })() ); }; - @computed get expandedField() { const ids: { [key: string]: string } = {}; const rows: JSX.Element[] = []; @@ -428,6 +427,8 @@ export class TreeView extends React.Component<TreeViewProps> { const contents = doc[key]; let contentElement: (JSX.Element | null)[] | JSX.Element = []; + let leftOffset = observable({ width: 0 }); + const expandedWidth = () => this.props.panelWidth() - leftOffset.width; if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) { const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { @@ -452,7 +453,7 @@ export class TreeView extends React.Component<TreeViewProps> { this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive, - this.props.panelWidth, + expandedWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, [...this.props.renderedIds, doc[Id]], @@ -483,15 +484,21 @@ export class TreeView extends React.Component<TreeViewProps> { ); } rows.push( - <div style={{ display: 'flex' }} key={key}> - <span style={{ fontWeight: 'bold' }}>{key + ':'}</span> - + <div style={{ display: 'flex', overflow: 'auto' }} key={key}> + <span + ref={action((r: any) => { + if (r) leftOffset.width = r.getBoundingClientRect().width; + })} + style={{ fontWeight: 'bold' }}> + {key + ':'} + + </span> {contentElement} </div> ); } rows.push( - <div style={{ display: 'flex' }} key={'newKeyValue'}> + <div style={{ display: 'flex', overflow: 'auto' }} key={'newKeyValue'}> <EditableView key="editableView" contents={'+key:value'} @@ -692,6 +699,7 @@ export class TreeView extends React.Component<TreeViewProps> { this.treeViewOpen = true; }; + @observable headerEleWidth = 0; @computed get headerElements() { return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : ( <> @@ -822,7 +830,7 @@ export class TreeView extends React.Component<TreeViewProps> { } return false; }; - titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - 3 * treeBulletWidth(); + titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth(); return18 = () => 18; /** @@ -923,7 +931,7 @@ export class TreeView extends React.Component<TreeViewProps> { }}> {view} </div> - <div className="treeView-rightButtons"> + <div className="treeView-rightButtons" ref={action((r: any) => r && (this.headerEleWidth = r.getBoundingClientRect().width))}> {buttons} {/* hide and lock buttons */} {this.headerElements} </div> @@ -1101,7 +1109,7 @@ export class TreeView extends React.Component<TreeViewProps> { remove: undefined | ((doc: Doc | Doc[]) => boolean), move: DragManager.MoveFunction, dropAction: dropActionType, - addDocTab: (doc: Doc, where: string) => boolean, + addDocTab: (doc: Doc, where: OpenWhere) => boolean, styleProvider: undefined | StyleProviderFunc, screenToLocalXf: () => Transform, isContentActive: (outsideReaction?: boolean) => boolean, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 89cc22d07..7dd9cdb8b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -46,7 +46,7 @@ export interface PoolData { export interface ViewDefResult { ele: JSX.Element; bounds?: ViewDefBounds; - inkMask?: number; //sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transprent (hidden), >0 = mask layer and not hidden + inkMask?: number; //sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transparent (hidden, as in during a presentation when you want to smoothly animate it into being a mask), >0 = mask layer and not hidden } function toLabel(target: FieldResult<Field>) { if (typeof target === 'number' || Number(target)) { @@ -82,7 +82,7 @@ interface PivotColumn { filters: string[]; } -export function computerPassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { +export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const docMap = new Map<string, PoolData>(); childPairs.forEach(({ layout, data }, i) => { docMap.set(layout[Id], { @@ -97,7 +97,7 @@ export function computerPassLayout(poolData: Map<string, PoolData>, pivotDoc: Do return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []); } -export function computerStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { +export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel const docMap = new Map<string, PoolData>(); const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75 diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index 858719a08..b44acfce8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -5,15 +5,8 @@ transition: opacity 0.5s ease-in; fill: transparent; } -.collectionfreeformlinkview-linkCircle { - stroke: rgb(0,0,0); - opacity: 0.5; - pointer-events: all; - cursor: pointer; -} .collectionfreeformlinkview-linkText { - stroke: rgb(0,0,0); - opacity: 0.5; + stroke: rgb(0, 0, 0); pointer-events: all; cursor: move; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index bf9de6760..4c8c65707 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'; import { Doc, Field } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; -import { Cast, NumCast } from '../../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; import { LinkManager } from '../../../util/LinkManager'; import { SelectionManager } from '../../../util/SelectionManager'; @@ -44,7 +44,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo this._start = Date.now(); this._timeout && clearTimeout(this._timeout); this._timeout = setTimeout(this.timeout, 25); - setTimeout(this.placeAnchors); + setTimeout(this.placeAnchors, 10); // when docs are dragged, their transforms will update before a render has been performed. placeanchors needs to come after a render to find things in the dom. a 0 timeout will still come before the render }), { fireImmediately: true } ); @@ -117,12 +117,16 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo return false; }, emptyFunction, - () => { + action(() => { + SelectionManager.DeselectAll(); + SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true); + LinkManager.currentLink = this.props.LinkDocs[0]; + this.toggleProperties(); // OverlayView.Instance.addElement( // <LinkEditor sourceDoc={this.props.A.props.Document} linkDoc={this.props.LinkDocs[0]} // showLinks={action(() => { })} // />, { x: 300, y: 300 }); - } + }) ); }; @@ -163,15 +167,16 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo @action toggleProperties = () => { - if (SettingsManager.propertiesWidth > 0) { - SettingsManager.propertiesWidth = 0; - } else { + if ((SettingsManager.propertiesWidth ?? 0) < 100) { SettingsManager.propertiesWidth = 250; } }; + @action onClickLine = () => { + SelectionManager.DeselectAll(); SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true); + LinkManager.currentLink = this.props.LinkDocs[0]; this.toggleProperties(); }; @@ -201,13 +206,13 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const clipped = aclipped || bclipped; const pt1 = [aleft + a.width / 2, atop + a.height / 2]; const pt2 = [bleft + b.width / 2, btop + b.width / 2]; - const pt1vec = [(bDocBounds.left + bDocBounds.right) / 2 - pt1[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt1[1]]; - const pt2vec = [(aDocBounds.left + aDocBounds.right) / 2 - pt2[0], (aDocBounds.top + aDocBounds.bottom) / 2 - pt2[1]]; + const pt2vec = [(bDocBounds.left + bDocBounds.right) / 2 - pt2[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt2[1]]; + const pt1vec = [(aDocBounds.left + aDocBounds.right) / 2 - pt1[0], (aDocBounds.top + aDocBounds.bottom) / 2 - pt1[1]]; const pt1len = Math.sqrt(pt1vec[0] * pt1vec[0] + pt1vec[1] * pt1vec[1]); const pt2len = Math.sqrt(pt2vec[0] * pt2vec[0] + pt2vec[1] * pt2vec[1]); const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2; - const pt1norm = clipped ? [0, 0] : [(pt1vec[0] / pt1len) * ptlen, (pt1vec[1] / pt1len) * ptlen]; - const pt2norm = clipped ? [0, 0] : [(pt2vec[0] / pt2len) * ptlen, (pt2vec[1] / pt2len) * ptlen]; + const pt1norm = clipped ? [0, 0] : [-(pt1vec[0] / pt1len) * ptlen, -(pt1vec[1] / pt1len) * ptlen]; + const pt2norm = clipped ? [0, 0] : [-(pt2vec[0] / pt2len) * ptlen, -(pt2vec[1] / pt2len) * ptlen]; const pt1normlen = Math.sqrt(pt1norm[0] * pt1norm[0] + pt1norm[1] * pt1norm[1]) || 1; const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1; const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen]; @@ -217,51 +222,82 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].linkOffsetX); const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].linkOffsetY); - return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13], pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13] }; + return { + a, + b, + pt1norm, + pt2norm, + aActive, + bActive, + textX, + textY, + pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13], + pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13], + }; } render() { if (!this.renderData) return null; + const link = this.props.LinkDocs[0]; const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData; - LinkManager.currentLink = this.props.LinkDocs[0]; - const linkRelationship = Field.toString(LinkManager.currentLink?.linkRelationship as any as Field); //get string representing relationship + const linkRelationship = Field.toString(link?.linkRelationship as any as Field); //get string representing relationship const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>; const linkColorList = Doc.UserDoc().linkColorList as List<string>; const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List<number>; const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship); + const linkDescription = Field.toString(link.description as any as Field); const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex]; //access stroke color using index of the relationship in the color list (default black) - const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? 'black' : linkColorList[currRelationshipIndex]; + const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? StrCast(link._backgroundColor, 'black') : linkColorList[currRelationshipIndex]; // const hexStroke = this.rgbToHex(stroke) //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has) //thickness varies linearly from 3px to 12px for increasing link count const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px'; - if (this.props.LinkDocs[0].displayArrow === undefined) { - this.props.LinkDocs[0].displayArrow = false; + if (link.linkDisplayArrow === undefined) { + link.linkDisplayArrow = false; } - return this.props.LinkDocs[0].opacity === 0 || !a.width || !b.width || (!this.props.LinkDocs[0].linkDisplay && !aActive && !bActive) ? null : ( + return link.opacity === 0 || !a.width || !b.width || (!link.linkDisplay && !aActive && !bActive) ? null : ( <> <defs> - <marker id="arrowhead" markerWidth="4" markerHeight="3" refX="0" refY="1.5" orient="auto"> - <polygon points="0 0, 3 1.5, 0 3" fill={Colors.DARK_GRAY} /> + <marker id={`${link[Id] + 'arrowhead'}`} markerWidth="4" markerHeight="3" refX="0" refY="1.5" orient="auto"> + <polygon points="0 0, 3 1.5, 0 3" fill={stroke} /> </marker> + <filter id="outline"> + <feMorphology in="SourceAlpha" result="expanded" operator="dilate" radius="1" /> + <feFlood floodColor={`${Colors.DARK_GRAY}`} /> + <feComposite in2="expanded" operator="in" /> + <feComposite in="SourceGraphic" /> + </filter> + <filter x="0" y="0" width="1" height="1" id={`${link[Id] + 'background'}`}> + <feFlood floodColor={`${StrCast(link._backgroundColor, 'white')}`} result="bg" /> + <feMerge> + <feMergeNode in="bg" /> + <feMergeNode in="SourceGraphic" /> + </feMerge> + </filter> </defs> <path + filter={LinkManager.currentLink === link ? 'url(#outline)' : ''} + fill="pink" + stroke="antiquewhite" + strokeWidth="4" className="collectionfreeformlinkview-linkLine" - style={{ pointerEvents: 'all', opacity: this._opacity, stroke: SelectionManager.SelectedSchemaDoc() === this.props.LinkDocs[0] ? Colors.MEDIUM_BLUE : stroke, strokeWidth }} + style={{ pointerEvents: 'all', opacity: this._opacity, stroke, strokeWidth }} onClick={this.onClickLine} d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} - markerEnd={this.props.LinkDocs[0].displayArrow ? 'url(#arrowhead)' : ''} + markerEnd={link.linkDisplayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''} /> - {textX === undefined ? null : ( - <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}> - {Field.toString(this.props.LinkDocs[0].description as any as Field)} + {textX === undefined || !linkDescription ? null : ( + <text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}> + <tspan> </tspan> + <tspan dy="2">{linkDescription}</tspan> + <tspan dy="2"> </tspan> </text> )} </> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index b8344dc0c..420e6a318 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -2,13 +2,13 @@ import { computed } from 'mobx'; import { observer } from 'mobx-react'; import { Id } from '../../../../fields/FieldSymbols'; import { DocumentManager } from '../../../util/DocumentManager'; +import { LightboxView } from '../../LightboxView'; import './CollectionFreeFormLinksView.scss'; import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView'; import React = require('react'); -import { LightboxView } from '../../LightboxView'; @observer -export class CollectionFreeFormLinksView extends React.Component<React.PropsWithChildren<{}>> { +export class CollectionFreeFormLinksView extends React.Component { @computed get uniqueConnections() { return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)) .filter(c => !LightboxView.LightboxDoc || (LightboxView.IsLightboxDocView(c.a.docViewPath) && LightboxView.IsLightboxDocView(c.b.docViewPath))) @@ -19,7 +19,6 @@ export class CollectionFreeFormLinksView extends React.Component<React.PropsWith return ( <div className="collectionfreeformlinksview-container"> <svg className="collectionfreeformlinksview-svgCanvas">{this.uniqueConnections}</svg> - {this.props.children} </div> ); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index d80fcdfc3..7a7ae3f40 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -96,37 +96,6 @@ border-color: #69a5db; } -.progressivizeButton { - position: absolute; - display: grid; - grid-template-columns: auto 20px auto; - transform: translate(-105%, 0); - align-items: center; - border: black solid 1px; - border-radius: 3px; - justify-content: center; - width: 40; - z-index: 30000; - height: 20; - overflow: hidden; - background-color: #d5dce2; - transition: all 1s; - - .progressivizeButton-prev:hover { - color: #5a9edd; - } - - .progressivizeButton-frame { - justify-self: center; - text-align: center; - width: 15px; - } - - .progressivizeButton-next:hover { - color: #5a9edd; - } -} - .resizable { background: rgba(0, 0, 0, 0.2); width: 100px; @@ -178,25 +147,6 @@ } } -.progressivizeMove-frame { - width: 20px; - border-radius: 2px; - z-index: 100000; - color: white; - text-align: center; - background-color: #5a9edd; - transform: translate(-110%, 110%); -} - -.progressivizeButton:hover { - box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5); - - .progressivizeButton-frame { - background-color: #5a9edd; - color: white; - } -} - .collectionFreeform-customText { position: absolute; text-align: center; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ac3777aa6..dc0eb69f3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,4 +1,5 @@ import { Bezier } from 'bezier-js'; +import { Colors } from 'browndash-components'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; @@ -37,7 +38,7 @@ import { GestureOverlay } from '../../GestureOverlay'; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { PresBox } from '../../nodes/trails/PresBox'; @@ -47,7 +48,7 @@ import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { TabDocView } from '../TabDocView'; -import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; +import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; @@ -71,6 +72,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return 'CollectionFreeFormView(' + this.props.Document.title?.toString() + ')'; } // this makes mobx trace() statements more descriptive + @observable + public static ShowPresPaths = false; + private _lastNudge: any; private _lastX: number = 0; private _lastY: number = 0; @@ -105,7 +109,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables - @observable _viewTransition: 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 _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 _hLines: number[] | undefined; @observable _vLines: number[] | undefined; @observable _firstRender = true; // 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. @@ -118,6 +122,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. @observable _brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 }; // highlighted region of freeform canvas used by presentations to indicate a region + constructor(props: any) { + super(props); + this.props.setContentView?.(this); + } + @computed get views() { 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); @@ -173,6 +182,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform); } + _keyTimer: NodeJS.Timeout | undefined; changeKeyFrame = (back = false) => { const currentFrame = Cast(this.Document._currentFrame, 'number', null); if (currentFrame === undefined) { @@ -180,14 +190,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } if (back) { - CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); + this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice()); this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1); } else { - CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0); + this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0); this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1); this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame)); } }; + @observable _keyframeEditing = false; + @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set); + getKeyFrameEditing = () => this._keyframeEditing; onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); @@ -709,6 +722,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => { + this.props.DocumentView?.().clearViewTransition(); const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true); this._lastX = e.clientX; @@ -792,9 +806,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }, }); const intersects = Array.from(new Set(rawIntersects as (number | string)[])); // convert to more manageable union array type - if (intersects.length) { - console.log(); - } // return tuples of the inkingStroke intersected, and the t value of the intersection intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve } @@ -1008,7 +1019,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; e.stopPropagation(); e.preventDefault(); - switch (Doc.UserDoc().freeformScrollMode) { + switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) { case freeformScrollMode.Pan: // if shift is selected then zoom if (e.ctrlKey) { @@ -1021,8 +1032,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } else if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming } else if (this.props.isContentActive(true) && !this.Document._isGroup) { - const dx = e.deltaX; - const dy = e.deltaY; + const dx = -e.deltaX; + const dy = -e.deltaY; if (e.shiftKey) { !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dy, deltaY: 0 }); } else { @@ -1076,7 +1087,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } } if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) { - this._viewTransition = panTime; + this._panZoomTransition = panTime; const scale = this.getLocalTransform().inverse().Scale; const minScale = NumCast(this.rootDoc._viewScaleMin, 1); const minPanX = NumCast(this.rootDoc._panXMin, 0); @@ -1095,7 +1106,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.setPan(NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true); this._lastNudge && clearTimeout(this._lastNudge); this._lastNudge = setTimeout( - action(() => (this._viewTransition = 0)), + action(() => (this._panZoomTransition = 0)), nudgeTime ); return true; @@ -1124,7 +1135,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) { if (this.Document._isGroup) return; - this._viewTransition = transitionTime; + this._panZoomTransition = transitionTime; const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]); this.layoutDoc[this.scaleFieldKey] = scale; const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]); @@ -1132,9 +1143,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y); this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0]; this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1]; - return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._viewTransition = 0))), this._viewTransition)); // set transition to be smooth, then reset + return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._panZoomTransition = 0))), this._panZoomTransition)); // set transition to be smooth, then reset } + _focusCount = 0; focusDocument = (doc: Doc, options: DocFocusOptions) => { const state = HistoryUtil.getState(); @@ -1154,14 +1166,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) { // SelectionManager.DeselectAll(); // } - if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined) { + if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined || doc.type === DocumentType.MARKER) { this.props.focus(doc, options); } else { const xfToCollection = options?.docTransform ?? Transform.Identity(); - const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoom ? this.Document[this.scaleFieldKey] : undefined }; + const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willPanZoom ? this.Document[this.scaleFieldKey] : undefined }; const newState = HistoryUtil.getState(); const cantTransform = (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc; - const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || 0.75 : undefined); + const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willPanZoom) ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willPanZoom ? options?.zoomScale || 0.75 : undefined); if (!cantTransform) { // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection newState.initializers![this.Document[Id]] = { panX, panY }; @@ -1169,27 +1181,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } // focus on the document in the collection const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale); - const focusSpeed = options?.instant ? 0 : didMove ? NumCast(doc.focusSpeed, 500) : 0; + const focusSpeed = options?.instant ? 0 : didMove ? options.zoomTime ?? 500 : 0; // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... if (didMove) { - scale && (this.Document[this.scaleFieldKey] = scale); + options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale); this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow } + const focusCount = ++this._focusCount; const startTime = Date.now(); // focus on this collection within its parent view. the parent view after focusing determines whether to reset the view change within the collection const endFocus = async (moved: boolean) => { doc.hidden && Doc.UnHighlightDoc(doc); const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing; if (resetView) { - const restoreState = (!LightboxView.LightboxDoc || LightboxView.LightboxDoc === this.props.Document) && savedState; + const restoreState = savedState; if (typeof restoreState !== 'boolean') { this.Document._panX = restoreState.panX; this.Document._panY = restoreState.panY; this.Document[this.scaleFieldKey] = restoreState.scale; } } - runInAction(() => (this._viewTransition = 0)); + this._focusCount === focusCount && didMove && runInAction(() => (this._panZoomTransition = 0)); return resetView; }; const xf = !cantTransform @@ -1269,7 +1282,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; pointerEvents = () => { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); - const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); + const pointerEvents = + this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); return pointerEvents; }; getChildDocView(entry: PoolData) { @@ -1311,6 +1325,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} docViewPath={this.props.docViewPath} styleProvider={this.getClusterColor} + canEmbedOnDrag={this.props.childCanEmbedOnDrag} dataProvider={this.childDataProvider} sizeProvider={this.childSizeProvider} dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} @@ -1324,18 +1339,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection /> ); } - addDocTab = action((doc: Doc, where: string) => { - if (where === 'inParent') { - (doc instanceof Doc ? [doc] : doc).forEach(doc => { - const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); - doc.x = pt[0]; - doc.y = pt[1]; - }); - return this.props.addDocument?.(doc) || false; - } - if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List<Doc>(doc as any as Doc[]); - return true; + addDocTab = action((doc: Doc, where: OpenWhere) => { + switch (where) { + case OpenWhere.inParent: + return this.props.addDocument?.(doc) || false; + case OpenWhere.inParentFromScreen: + return ( + this.props.addDocument?.( + (doc instanceof Doc ? [doc] : doc).map(doc => { + const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); + doc.x = pt[0]; + doc.y = pt[1]; + return doc; + }) + ) || false + ); + case OpenWhere.inPlace: + if (this.layoutDoc.isInPlaceContainer) { + this.dataDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc); + return true; + } } return this.props.addDocTab(doc, where); }); @@ -1353,7 +1376,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection z: Cast(z, 'number'), color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color), backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.props.styleProvider?.(childDoc, this.props, StyleProp.BackgroundColor), - opacity: Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity), + opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity), zIndex: Cast(zIndex, 'number'), width: Cast(childDocLayout._width, 'number'), height: Cast(childDocLayout._height, 'number'), @@ -1453,10 +1476,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const newPool = new Map<string, PoolData>(); // prettier-ignore switch (this.layoutEngine) { - case 'pass': return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; - case 'timeline': return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; - case 'pivot': return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; - case 'starburst': return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) }; + case computePassLayout.name : return { newPool, computedElementData: this.doEngineLayout(newPool, computePassLayout) }; + case computeTimelineLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; + case computePivotLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; + case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) }; } return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } @@ -1496,7 +1519,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection elements.push({ ele: this.getChildDocView(entry[1]), bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica), - inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity) : -1, + inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1, }) ); @@ -1526,11 +1549,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return 0; }; + @action + scrollFocus = (anchor: Doc, options: DocFocusOptions) => { + const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; + return PresBox.restoreTargetDocView( + this.props.DocumentView?.(), // + { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, + anchor, + focusSpeed, + { + pannable: anchor.presPinData ? true : false, + } + ) + ? focusSpeed + : undefined; + }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document + getAnchor = () => { if (this.props.Document.annotationOn) { return this.rootDoc; } - const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc }); + const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc }); + PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc); const proto = Doc.GetProto(anchor); proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]); @@ -1545,7 +1585,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action componentDidMount() { super.componentDidMount?.(); - this.props.setContentView?.(this); this.props.setBrushViewer?.(this.brushView); setTimeout( action(() => { @@ -1568,12 +1607,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection r: cbounds.r - p[0] + c[0], b: cbounds.b - p[1] + c[1], }; - this.layoutDoc._width = pbounds.r - pbounds.x; - this.layoutDoc._height = pbounds.b - pbounds.y; - this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2; - this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2; - this.layoutDoc.x = pbounds.x; - this.layoutDoc.y = pbounds.y; + if (Number.isFinite(pbounds.r - pbounds.x) && Number.isFinite(pbounds.b - pbounds.y)) { + this.layoutDoc._width = pbounds.r - pbounds.x; + this.layoutDoc._height = pbounds.b - pbounds.y; + this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2; + this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2; + this.layoutDoc.x = pbounds.x; + this.layoutDoc.y = pbounds.y; + } } }, { fireImmediately: true } @@ -1703,7 +1744,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection doc.x = scr?.[0]; doc.y = scr?.[1]; }); - this.props.addDocTab(childDocs as any as Doc, 'inParent'); + this.props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen); this.props.ContainingCollectionView?.removeDocument(this.props.Document); }; @@ -1841,6 +1882,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection DragManager.SetSnapLines(horizLines, vertLines); }; + incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0; + incrementalRender = action(() => { if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) { const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); @@ -1866,6 +1909,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); } + showPresPaths = () => (CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.getPaths(this.rootDoc) : null); + @computed get marqueeView() { TraceMobx(); return ( @@ -1912,10 +1957,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable} transform={this.contentTransform} zoomScaling={this.zoomScaling} - presPaths={BoolCast(this.Document.presPathView)} - progressivize={BoolCast(this.Document.editProgressivize)} + presPaths={this.showPresPaths} presPinView={BoolCast(this.Document.presPinView)} - transition={this._viewTransition ? `transform ${this._viewTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)} + transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)} viewDefDivClick={this.props.viewDefDivClick}> {this.children} </CollectionFreeFormViewPannableContents> @@ -1987,19 +2031,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection {this._firstRender ? this.placeholder : this.marqueeView} {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />} - { - // uncomment to show snap lines - <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}> - <svg style={{ width: '100%', height: '100%' }}> - {this._hLines?.map(l => ( - <line x1="0" y1={l} x2="1000" y2={l} stroke="black" /> - ))} - {this._vLines?.map(l => ( - <line y1="0" x1={l} y2="1000" x2={l} stroke="black" /> - ))} - </svg> - </div> - } + {/* // uncomment to show snap lines */} + <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}> + <svg style={{ width: '100%', height: '100%' }}> + {this._hLines?.map(l => ( + <line x1="0" y1={l} x2="1000" y2={l} stroke="black" /> + ))} + {this._vLines?.map(l => ( + <line y1="0" x1={l} y2="1000" x2={l} stroke="black" /> + ))} + </svg> + </div> {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? ( <div @@ -2041,8 +2083,7 @@ interface CollectionFreeFormViewPannableContentsProps { viewDefDivClick?: ScriptField; children: () => JSX.Element[]; transition?: string; - presPaths?: boolean; - progressivize?: boolean; + presPaths: () => JSX.Element | null; presPinView?: boolean; isAnnotationOverlay: boolean | undefined; isAnnotationOverlayScrollable: boolean | undefined; @@ -2095,42 +2136,11 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF return true; }; - // scale: NumCast(targetDoc._viewScale), - @computed get zoomProgressivizeContainer() { - const activeItem = PresBox.Instance.activeItem; - // const targetDoc = PresBox.Instance.targetDoc; - if (activeItem && activeItem.presPinView && activeItem.id) { - const left = NumCast(activeItem.presPinViewX); - const top = NumCast(activeItem.presPinViewY); - const width = 100; - const height = 100; - return !this.props.presPinView ? null : ( - <div key="resizable" className="resizable" onPointerDown={this.onPointerDown} style={{ width, height, top, left, position: 'absolute' }}> - <div className="resizers" key={'resizer' + activeItem.id}> - <div className="resizer top-left" onPointerDown={this.onPointerDown} /> - <div className="resizer top-right" onPointerDown={this.onPointerDown} /> - <div className="resizer bottom-left" onPointerDown={this.onPointerDown} /> - <div className="resizer bottom-right" onPointerDown={this.onPointerDown} /> - </div> - </div> - ); - } - } - - @computed get zoomProgressivize() { - return PresBox.Instance?.activeItem?.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : null; - } - - @computed get progressivize() { - return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : null; - } - @computed get presPaths() { - const presPaths = 'presPaths' + (this.props.presPaths ? '' : '-hidden'); - return !PresBox.Instance || !this.props.presPaths ? null : ( + return !this.props.presPaths() ? null : ( <> - <div key="presorder">{PresBox.Instance.order}</div> - <svg key="svg" className={presPaths}> + <div key="presorder">{PresBox.Instance?.order}</div> + <svg key="svg" className="presPaths"> <defs> <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible"> <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" /> @@ -2142,7 +2152,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" /> </marker> </defs> - {PresBox.Instance.paths} + {this.props.presPaths()} </svg> </> ); @@ -2182,8 +2192,6 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF /> )} {this.presPaths} - {this.progressivize} - {this.zoomProgressivize} </div> ); } @@ -2252,7 +2260,7 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) { SelectionManager.DeselectAll(); dv.props.focus(dv.props.Document, { - willZoom: true, + willPanZoom: true, afterFocus: async didMove => { if (!didMove) { const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; @@ -2272,6 +2280,14 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); +ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) { + const selView = SelectionManager.Views(); + if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent'; + runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.())); +}); ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) { - !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) })); + !readOnly && + SelectionManager.Views().forEach(view => + TabDocView.PinDoc(view.rootDoc, { currentFrame: Cast(view.rootDoc.currentFrame, 'number', null), pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) }) + ); }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index a020b67cd..7c1137292 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -17,8 +17,8 @@ import { SelectionManager } from '../../../util/SelectionManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; +import { OpenWhere } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { PresBox } from '../../nodes/trails/PresBox'; import { VideoBox } from '../../nodes/VideoBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { PreviewCursor } from '../../PreviewCursor'; @@ -120,7 +120,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const cm = ContextMenu.Instance; const [x, y] = this.Transform.transformPoint(this._downX, this._downY); if (e.key === '?') { - cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', useCors: true }), 'add:right')); + cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', useCors: true }), OpenWhere.addRight)); cm.displayMenu(this._downX, this._downY, undefined, true); e.stopPropagation(); @@ -243,7 +243,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque if (!(e.nativeEvent as any).marqueeHit) { (e.nativeEvent as any).marqueeHit = true; // allow marquee if right click OR alt+left click OR in adding presentation slide & left key drag mode - if (e.button === 2 || (e.button === 0 && e.altKey) || (PresBox.startMarquee && e.button === 0)) { + if (e.button === 2 || (e.button === 0 && e.altKey)) { // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) { this.setPreviewCursor(e.clientX, e.clientY, true, false); // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors. @@ -272,9 +272,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this } e.altKey && e.preventDefault(); - if (PresBox.startMarquee) { - e.stopPropagation(); - } }; @action @@ -295,11 +292,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque document.removeEventListener('pointerdown', hideMarquee, true); document.removeEventListener('wheel', hideMarquee, true); }; - if (PresBox.startMarquee) { - this.pinWithView(); - PresBox.startMarquee = false; - } - if (!this._commandExecuted && Math.abs(this.Bounds.height * this.Bounds.width) > 100 && !PresBox.startMarquee) { + if (!this._commandExecuted && Math.abs(this.Bounds.height * this.Bounds.width) > 100) { MarqueeOptionsMenu.Instance.createCollection = this.collection; MarqueeOptionsMenu.Instance.delete = this.delete; MarqueeOptionsMenu.Instance.summarize = this.summary; @@ -540,7 +533,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque d.y = NumCast(d.y) - this.Bounds.top; return d; }); - const summary = Docs.Create.TextDocument('', { backgroundColor: '#e2ad32', x: this.Bounds.left, y: this.Bounds.top, isPushpin: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: 'overview' }); + const summary = Docs.Create.TextDocument('', { backgroundColor: '#e2ad32', x: this.Bounds.left, y: this.Bounds.top, followLinkToggle: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: 'overview' }); const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' }); DocUtils.MakeLink({ doc: summary }, { doc: portal }, 'summary of:summarized by', ''); @@ -685,7 +678,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque className="marqueeView" style={{ overflow: StrCast(this.props.Document._overflow), - cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible || PresBox.startMarquee ? 'crosshair' : 'pointer', + cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer', }} onDragOver={e => e.preventDefault()} onScroll={e => (e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0)} diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 9778fc4fe..a2330c6b2 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -9,7 +9,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields import { emptyFunction, returnEmptyDoclist, returnTrue, StopEvent, Utils } from '../../../../Utils'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; -import { DragManager } from '../../../util/DragManager'; +import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { Colors, Shadows } from '../../global/globalEnums'; import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; @@ -150,7 +150,7 @@ export class CollectionLinearView extends CollectionSubView() { <span className="bottomPopup-text"> Currently playing: {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( - <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> + <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, { willPanZoom: true }, undefined, [])}> {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')} </span> ))} @@ -190,6 +190,7 @@ export class CollectionLinearView extends CollectionSubView() { moveDocument={this.props.moveDocument} addDocTab={this.props.addDocTab} pinToPres={emptyFunction} + dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} rootSelected={this.props.isSelected} removeDocument={this.props.removeDocument} ScreenToLocalTransform={docXf} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 465dbfe6d..b88da5191 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -9,7 +9,7 @@ import { DragManager, dropActionType } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; -import { DocumentView } from '../../nodes/DocumentView'; +import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionMulticolumnView.scss'; import ResizeBar from './MulticolumnResizer'; @@ -234,16 +234,9 @@ export class CollectionMulticolumnView extends CollectionSubView() { onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); - addDocTab = (doc: Doc, where: string) => { - if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]); - return true; - } - return this.props.addDocTab(doc, where); - }; - isContentActive = () => this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => - ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; + focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options); + isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); + isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return ( <DocumentView @@ -267,7 +260,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { hideResizeHandles={this.props.childHideResizeHandles?.()} hideDecorationTitle={this.props.childHideDecorationTitle?.()} fitContentsToBox={this.props.fitContentsToBox} - focus={this.props.focus} + focus={this.focusDocument} docFilters={this.childDocFilters} docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} @@ -278,7 +271,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.addDocTab} + addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} bringToFront={returnFalse} /> diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index f8de4e5de..407deaabd 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -9,7 +9,7 @@ import { DragManager, dropActionType } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; -import { DocumentView } from '../../nodes/DocumentView'; +import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionMultirowView.scss'; import HeightLabel from './MultirowHeightLabel'; @@ -234,16 +234,9 @@ export class CollectionMultirowView extends CollectionSubView() { onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); - addDocTab = (doc: Doc, where: string) => { - if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]); - return true; - } - return this.props.addDocTab(doc, where); - }; - isContentActive = () => this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => - ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; + focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options); + isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); + isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return ( <DocumentView @@ -277,7 +270,7 @@ export class CollectionMultirowView extends CollectionSubView() { moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.addDocTab} + addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} bringToFront={returnFalse} /> diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx index fb93d8b8e..97a6c5c18 100644 --- a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx +++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx @@ -30,6 +30,8 @@ import { DocumentIconContainer } from '../../nodes/DocumentIcon'; import { OverlayView } from '../../OverlayView'; import { CollectionView } from '../CollectionView'; import './CollectionSchemaView.scss'; +import { OpenWhere } from '../../nodes/DocumentView'; +import { PinProps } from '../../nodes/trails'; // intialize cell properties export interface CellProps { @@ -46,8 +48,8 @@ export interface CellProps { // currently unused renderDepth: number; // called when a button is pressed on the node itself - addDocTab: (document: Doc, where: string) => boolean; - pinToPres: (document: Doc) => void; + addDocTab: (document: Doc, where: OpenWhere) => boolean; + pinToPres: (document: Doc, pinProps: PinProps) => void; moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; isFocused: boolean; changeFocusedCellByIndex: (row: number, col: number) => void; @@ -212,7 +214,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc); const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null); // Jump to the this document - DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [], undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc)); + DocumentManager.Instance.jumpToDocument(this._rowDoc, { willPan: true }, emptyFunction, targetContext ? [targetContext] : [], () => this.props.setPreviewDoc(this._rowDoc)); } }; @@ -507,7 +509,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { })} /> </div> - <div onClick={() => this._doc && this.props.addDocTab(this._doc, 'add:right')} className="collectionSchemaView-cellContents-docButton"> + <div onClick={() => this._doc && this.props.addDocTab(this._doc, OpenWhere.addRight)} className="collectionSchemaView-cellContents-docButton"> <FontAwesomeIcon icon="external-link-alt" size="lg" /> </div> </div> diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx index f872637e5..3cb2df7d3 100644 --- a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx +++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx @@ -10,6 +10,7 @@ import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; +import { OpenWhere } from '../../nodes/DocumentView'; import './CollectionSchemaView.scss'; export interface MovableRowProps { @@ -138,7 +139,7 @@ export class MovableRow extends React.Component<React.PropsWithChildren<MovableR <div className="row-option" style={{ cursor: 'grab' }} ref={reference} onPointerDown={onItemDown}> <FontAwesomeIcon icon="grip-vertical" size="sm" /> </div> - <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, 'add:right')}> + <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, OpenWhere.addRight)}> <FontAwesomeIcon icon="external-link-alt" size="sm" /> </div> </div> diff --git a/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx b/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx index dfeee3173..1b4fcf0a4 100644 --- a/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx +++ b/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx @@ -23,7 +23,8 @@ import { undoBatch } from '../../../util/UndoManager'; import '../../../views/DocumentDecorations.scss'; import { ContextMenu } from '../../ContextMenu'; import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; -import { DocumentView } from '../../nodes/DocumentView'; +import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; +import { PinProps } from '../../nodes/trails'; import { DefaultStyleProvider } from '../../StyleProvider'; import { CollectionView } from '../CollectionView'; import { @@ -86,8 +87,8 @@ export interface SchemaTableProps { ScreenToLocalTransform: () => Transform; active: (outsideReaction: boolean | undefined) => boolean | undefined; onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void; - addDocTab: (document: Doc, where: string) => boolean; - pinToPres: (document: Doc) => void; + addDocTab: (document: Doc, where: OpenWhere) => boolean; + pinToPres: (document: Doc, pinProps: PinProps) => void; isSelected: (outsideReaction?: boolean) => boolean; isFocused: (document: Doc, outsideReaction: boolean) => boolean; setFocused: (document: Doc) => void; @@ -625,7 +626,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { }; onOpenClick = () => { - this._showDoc && this.props.addDocTab(this._showDoc, 'add:right'); + this._showDoc && this.props.addDocTab(this._showDoc, OpenWhere.addRight); }; getPreviewTransform = (): Transform => { |
