diff options
| author | Sophie Zhang <sophie_zhang@brown.edu> | 2023-03-06 16:33:39 -0500 |
|---|---|---|
| committer | Sophie Zhang <sophie_zhang@brown.edu> | 2023-03-06 16:33:39 -0500 |
| commit | 3a6f179aedf8e148fe9828426b43aa31ca87bcb1 (patch) | |
| tree | 9f49ba8d54bda361826c17cfe7f3cb0a37854ace /src/client/views/collections/collectionFreeForm | |
| parent | c6425a0469727305f76d00e3f8c545e04aad61cc (diff) | |
| parent | 4c2584baf8bae0cde714c832b0768d3c08864422 (diff) | |
Merge branch 'master' into pres-trail-sophie
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
5 files changed, 156 insertions, 177 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 7dd9cdb8b..fa0695fb2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -14,6 +14,7 @@ export interface ViewDefBounds { x: number; y: number; z?: number; + rotation?: number; text?: string; zIndex?: number; width?: number; @@ -31,6 +32,7 @@ export interface PoolData { x: number; y: number; z?: number; + rotation?: number; zIndex?: number; width?: number; height?: number; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 9811c239b..7f1e15c2f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -136,6 +136,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo height = rect.height; var el = el.parentNode; while (el && el !== document.body) { + if (el.className === 'tabDocView-content') break; rect = el.getBoundingClientRect?.(); if (rect?.width) { if (top <= rect.bottom === false && getComputedStyle(el).overflow === 'hidden') return rect.bottom; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 7a7ae3f40..c6419885b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -33,7 +33,7 @@ } .collectionfreeformview-mask { mix-blend-mode: multiply; - background-color: rgba(0, 0, 0, 0.7); + background-color: rgba(0, 0, 0, 0.8); } .collectionfreeformview-viewdef { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7aae5ed5f..63b566f28 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -4,11 +4,10 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../../fields/Doc'; +import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; -import { ObjectField } from '../../../../fields/ObjectField'; import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; @@ -22,7 +21,6 @@ import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; -import { HistoryUtil } from '../../../util/History'; import { InteractionUtils } from '../../../util/InteractionUtils'; import { ReplayMovements } from '../../../util/ReplayMovements'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; @@ -34,15 +32,15 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss'; import { Timeline } from '../../animationtimeline/Timeline'; import { ContextMenu } from '../../ContextMenu'; +import { DocumentDecorations } from '../../DocumentDecorations'; 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, OpenWhere, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { PresBox } from '../../nodes/trails/PresBox'; -import { VideoBox } from '../../nodes/VideoBox'; +import { PinProps, PresBox } from '../../nodes/trails/PresBox'; import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; @@ -61,6 +59,7 @@ export type collectionFreeformViewProps = { scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; + getScrollHeight?: () => number | undefined; dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. // However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents. @@ -148,7 +147,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] } : this.props.contentBounds?.() ?? aggregateBounds( - this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), + this._layoutElements.filter(e => e.bounds && e.bounds.width && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10) ); @@ -183,6 +182,32 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } _keyTimer: NodeJS.Timeout | undefined; + + public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) { + if (timer) clearTimeout(timer); + return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true); + } + public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) { + if (timer) clearTimeout(timer); + const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true); + const timecode = Math.round(time); + docs.forEach(doc => { + CollectionFreeFormDocumentView.animFields.forEach(val => { + const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); + }); + CollectionFreeFormDocumentView.animStringFields.forEach(val => { + const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as string); + }); + CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => { + const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any); + }); + }); + return newTimer; + } + changeKeyFrame = (back = false) => { const currentFrame = Cast(this.Document._currentFrame, 'number', null); if (currentFrame === undefined) { @@ -190,10 +215,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } if (back) { - this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice()); + this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], 1000); this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1); } else { - this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0); + this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], currentFrame || 0); this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1); this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame)); } @@ -272,6 +297,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return dispTime === -1 || curTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime); } + focus = (anchor: Doc, options: DocFocusOptions) => { + const xfToCollection = options?.docTransform ?? Transform.Identity(); + const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; + const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); + const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale || 0.75 : undefined); + + // focus on the document in the collection + const didMove = !cantTransform && !anchor.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale); + // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... + if (didMove) { + const focusTime = options?.instant ? 0 : options.zoomTime ?? 500; + options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale); + this.setPan(panX, panY, focusTime, 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 + return focusTime; + } + }; + + getView = async (doc: Doc): Promise<Opt<DocumentView>> => { + return new Promise<Opt<DocumentView>>(res => { + const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); + findDoc(dv => res(dv)); + }); + }; + @action internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) { if (!de.embedKey && !this.ChildDrag && this.rootDoc._isGroup) return false; @@ -363,10 +412,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1); if (grouping !== -1) { const layoutDoc = Doc.Layout(cd); - const cx = NumCast(cd.x) - this._clusterDistance; - const cy = NumCast(cd.y) - this._clusterDistance; - const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance; - const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance; + const cx = NumCast(cd.x) - this._clusterDistance / 2; + const cy = NumCast(cd.y) - this._clusterDistance / 2; + const cw = NumCast(layoutDoc._width) + this._clusterDistance; + const ch = NumCast(layoutDoc._height) + this._clusterDistance; return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster; } return cluster; @@ -494,6 +543,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } } break; + case StyleProp.FillColor: + if (doc && this.Document._currentFrame !== undefined) { + return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor; + } } return styleProp; }; @@ -545,7 +598,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection case InkTool.None: if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) { this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)); - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false); + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false); } break; } @@ -771,7 +824,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } else if (!e.cancelBubble) { if (this.tryDragCluster(e, this._hitCluster)) { return true; - } else this.pan(e); + } + this.pan(e); } return false; }; @@ -1004,6 +1058,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (deltaScale * invTransform.Scale > 20) { deltaScale = 20 / invTransform.Scale; } + if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) { + return; + } if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) { deltaScale = NumCast(this.rootDoc._viewScaleMin, 1) / invTransform.Scale; } @@ -1012,29 +1069,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) { const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20); this.props.Document[this.scaleFieldKey] = Math.abs(safeScale); - this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); + this.setPan(-localTransform.TranslateX / safeScale, NumCast(this.props.Document.scrollTop) * safeScale || -localTransform.TranslateY / safeScale); } }; @action onPointerWheel = (e: React.WheelEvent): void => { + if (this.Document._isGroup) return; // group style collections neither pan nor zoom PresBox.Instance?.pauseAutoPres(); if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; e.stopPropagation(); e.preventDefault(); switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) { case freeformScrollMode.Pan: - // if shift is selected then zoom + // if ctrl is selected then zoom if (e.ctrlKey) { - 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) { + if (this.props.isContentActive(true)) { !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? } - // otherwise pan - } 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) { + } // otherwise pan + else if (this.props.isContentActive(true)) { const dx = -e.deltaX; const dy = -e.deltaY; if (e.shiftKey) { @@ -1046,9 +1100,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection break; default: case freeformScrollMode.Zoom: - 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) { + if (this.props.isContentActive(true)) { !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? } break; @@ -1096,7 +1148,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const minPanX = NumCast(this.rootDoc._panXMin, 0); const minPanY = NumCast(this.rootDoc._panYMin, 0); const newPanX = Math.min(minPanX + (1 - minScale / scale) * NumCast(this.rootDoc._panXMax, this.nativeWidth), Math.max(minPanX, panX)); - const newPanY = Math.min(this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : minPanY + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, this.nativeHeight), Math.max(minPanY, panY)); + const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale; + const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight; + const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight(); + const maxPanY = + minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by fitWidth + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, nativeHeight) + + (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning + let newPanY = Math.max(minPanY, Math.min(maxPanY, panY)); + if (NumCast(this.rootDoc.scrollTop) && NumCast(this.rootDoc._viewScale, minScale) !== minScale) { + const relTop = NumCast(this.rootDoc.scrollTop) / maxScrollTop; + this.rootDoc.scrollTop = undefined; + newPanY = minPanY + relTop * (maxPanY - minPanY); + } else if (fitYscroll && this.rootDoc.scrollTop === undefined && NumCast(this.rootDoc._viewScale, minScale) === minScale) { + const maxPanY = minPanY + fitYscroll; + const relTop = (panY - minPanY) / (maxPanY - minPanY); + setTimeout(() => { + this.rootDoc.scrollTop = relTop * maxScrollTop; + }, 10); + newPanY = minPanY; + } !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX); !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY); } @@ -1149,79 +1220,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection 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(); - - // TODO This technically isn't correct if type !== "doc", as - // currently nothing is done, but we should probably push a new state - if (state.type === 'doc' && this.Document._panX !== undefined && this.Document._panY !== undefined) { - const init = state.initializers![this.Document[Id]]; - if (!init) { - state.initializers![this.Document[Id]] = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY) }; - HistoryUtil.pushState(state); - } else if (init.panX !== this.Document._panX || init.panY !== this.Document._panY) { - init.panX = NumCast(this.Document._panX); - init.panY = NumCast(this.Document._panY); - HistoryUtil.pushState(state); - } - } - // 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 || 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?.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 || (!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 }; - HistoryUtil.pushState(newState); - } - // 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 ? 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) { - 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 = savedState; - if (typeof restoreState !== 'boolean') { - this.Document._panX = restoreState.panX; - this.Document._panY = restoreState.panY; - this.Document[this.scaleFieldKey] = restoreState.scale; - } - } - this._focusCount === focusCount && didMove && runInAction(() => (this._panZoomTransition = 0)); - return resetView; - }; - const xf = !cantTransform - ? Transform.Identity() - : this.props.isAnnotationOverlay - ? new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc)) - : new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1); - - this.props.focus(!cantTransform ? this.rootDoc : doc, { - ...options, - docTransform: xf, - afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))), - }); - } - }; - calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => { const layoutdoc = Doc.Layout(doc); const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y)); @@ -1286,7 +1284,9 @@ 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 === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); + this.props.isContentActive() === false || DocumentDecorations.Instance.Interacting + ? 'none' + : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); return pointerEvents; }; getChildDocView(entry: PoolData) { @@ -1319,7 +1319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection searchFilterDocs={this.searchFilterDocs} isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} isContentActive={emptyFunction} - focus={this.focusDocument} + focus={this.isAnnotationOverlay ? this.props.focus : this.focus} addDocTab={this.addDocTab} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} @@ -1337,12 +1337,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection dontScaleFilter={this.props.dontScaleFilter} dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView} pointerEvents={this.pointerEvents} - rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0} + //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0} //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this /> ); } addDocTab = action((doc: Doc, where: OpenWhere) => { + if (this.props.isAnnotationOverlay) return this.props.addDocTab(doc, where); switch (where) { case OpenWhere.inParent: return this.props.addDocument?.(doc) || false; @@ -1360,9 +1361,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection (!docContext || this.props.removeDocument?.(docContext))) || false ); - case OpenWhere.inPlace: - if (this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc); + case undefined: + case OpenWhere.lightbox: + if (this.layoutDoc._isLightbox) { + // _isLightbox docs have a script that will unset this overlay onClick + this.layoutDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc); return true; } } @@ -1370,22 +1373,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }); getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData { - const curFrame = Cast(this.Document._currentFrame, 'number'); const childDoc = params.pair.layout; const childDocLayout = Doc.Layout(childDoc); + const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed const { z, zIndex } = childDoc; - const { backgroundColor, color } = curFrame === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame); - const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame); + const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber); + const { x, y, _width, _height, opacity, _rotation } = + layoutFrameNumber === undefined + ? { _width: Cast(childDocLayout._width, 'number'), _height: Cast(childDocLayout._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } + : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber); return { x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x), y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y), z: Cast(z, 'number'), + rotation: Cast(_rotation, 'number'), color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color), backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor), 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'), + width: _width, + height: _height, transition: StrCast(childDocLayout.dataTransition), pair: params.pair, replica: '', @@ -1506,6 +1514,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || + newPos.rotation !== lastPos.rotation || newPos.zIndex !== lastPos.zIndex || newPos.transition !== lastPos.transition ) { @@ -1521,13 +1530,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const elements = computedElementData.slice(); Array.from(newPool.entries()) .filter(entry => this.isCurrent(entry[1].pair.layout)) - .forEach((entry, i) => + .forEach((entry, i) => { + const childData: ViewDefBounds = this.childDataProvider(entry[1].pair.layout, entry[1].replica); + const childSize = this.childSizeProvider(entry[1].pair.layout, entry[1].replica); elements.push({ ele: this.getChildDocView(entry[1]), - bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica), + bounds: childData.opacity === 0 ? { ...childData, width: 0, height: 0 } : { ...childData, width: childSize.width, height: childSize.height }, inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1, - }) - ); + }); + }); if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) { // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar @@ -1539,47 +1550,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return elements; } - @action - setViewSpec = (anchor: Doc, preview: boolean) => { - if (preview) { - this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters); - this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters); - } else { - if (anchor.docFilters) { - this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters)); - } - if (anchor.docRangeFilters) { - this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters)); - } - } - return 0; - }; + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { + // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type) + const anchor = Docs.Create.CollectionAnchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), unrendered: true, presTransition: 500, annotationOn: this.rootDoc }); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true, viewType: true, filters: true } }, this.rootDoc); - @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 = (addAsAnnotation: boolean) => { - if (this.props.Document.annotationOn) { - return 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>([]); if (addAsAnnotation) { if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor); @@ -1706,7 +1681,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const nativeHeight = height; return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight) .then(async (data_url: any) => { - const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); + const returnedFilename = await Utils.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); cb(returnedFilename as string, nativeWidth, nativeHeight); }) .catch(function (error: any) { @@ -1966,7 +1941,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection zoomScaling={this.zoomScaling} presPaths={this.showPresPaths} presPinView={BoolCast(this.Document.presPinView)} - transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)} + transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))} viewDefDivClick={this.props.viewDefDivClick}> {this.children} </CollectionFreeFormViewPannableContents> @@ -2033,7 +2008,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection : (this.props.pointerEvents?.() as any), transform: `scale(${this.nativeDimScaling || 1})`, width: `${100 / (this.nativeDimScaling || 1)}%`, - height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.nativeDimScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() + height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`, }}> {this._firstRender ? this.placeholder : this.marqueeView} {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />} @@ -2265,20 +2240,21 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor } export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) { + const browseTransitionTime = 500; SelectionManager.DeselectAll(); - dv.props.focus(dv.props.Document, { - willPanZoom: true, - afterFocus: async didMove => { - if (!didMove) { - const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; - const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview - await ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5); - } - return ViewAdjustment.doNothing; - }, - }); - Doc.linkFollowHighlight(dv?.props.Document, false); + if ( + dv.props.focus(dv.props.Document, { + willZoomCentered: true, + zoomScale: 0.8, + zoomTime: browseTransitionTime, + }) === undefined + ) { + const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; + const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview + ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime); + Doc.linkFollowHighlight(dv?.props.Document, false); + } } ScriptingGlobals.add(CollectionBrowseClick); ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 0b7854926..a73627165 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -19,7 +19,6 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { OpenWhere } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { VideoBox } from '../../nodes/VideoBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { PreviewCursor } from '../../PreviewCursor'; import { SubCollectionViewProps } from '../CollectionSubView'; @@ -167,7 +166,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque pasteImageBitmap((data: any, error: any) => { error && console.log(error); data && - VideoBox.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => { + Utils.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => { this.props.Document['thumb-frozen'] = new ImageField(returnedfilename); }); }) @@ -397,6 +396,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque newCollection.forceActive = makeGroup; newCollection.x = this.Bounds.left; newCollection.y = this.Bounds.top; + newCollection.fitwidth = true; selected.forEach(d => (d.context = newCollection)); this.hideMarquee(); return newCollection; @@ -431,8 +431,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @undoBatch @action - collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => { - const selected = this.marqueeSelect(false); + collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => { + const selected = selection ?? this.marqueeSelect(false); const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined); if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) { this.props.removeDocument?.(selected); |
