diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 340 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 6 | ||||
-rw-r--r-- | src/fields/Doc.ts | 9 |
3 files changed, 165 insertions, 190 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 18cf825a3..9041d9b21 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -13,7 +13,7 @@ import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { TraceMobx } from "../../../../fields/util"; import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; -import { aggregateBounds, intersectRect, returnFalse, setupMoveUpEvents, Utils, emptyFunction } from "../../../../Utils"; +import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; @@ -58,24 +58,20 @@ export const panZoomSchema = createSchema({ _timecodeToShow: "number", _currentFrame: "number", _useClusters: "boolean", - fitToBox: "boolean", + _viewTransition: "string", _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set _yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set - _viewTransition: "string", - scrollHeight: "number", - fitX: "number", - fitY: "number", - fitW: "number", - fitH: "number" + _fitToBox: "boolean", + scrollHeight: "number" // this will be set when the collection is an annotation overlay for a PDF/Webpage }); type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>; const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema); export type collectionFreeformViewProps = { + parentActive: (outsideReaction: boolean) => boolean; forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox) viewDefDivClick?: ScriptField; childPointerEvents?: boolean; - parentActive: (outsideReaction: boolean) => boolean; scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; @@ -94,29 +90,32 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P private _wordPalette: Map<string, string> = new Map<string, string>(); private _clusterDistance: number = 75; private _hitCluster: number = -1; - private _layoutComputeReaction: IReactionDisposer | undefined; - private _boundsReaction: IReactionDisposer | undefined; + private _disposers: { [name: string]: IReactionDisposer } = {}; private _layoutPoolData = new ObservableMap<string, PoolData>(); private _layoutSizeData = new ObservableMap<string, { width?: number, height?: number }>(); private _cachedPool: Map<string, PoolData> = new Map(); private _lastTap = 0; private _nudgeTime = 0; - private _thumbIdentifier?: number; + private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } + private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; } + private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } + + @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables @observable _hLines: number[] | undefined; @observable _vLines: number[] | undefined; @observable _pullCoords: number[] = [0, 0]; @observable _pullDirection: string = ""; @observable _showAnimTimeline = false; - @observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. - - @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables @observable _clusterSets: (Doc[])[] = []; @observable _timelineRef = React.createRef<Timeline>(); @observable _marqueeRef = React.createRef<HTMLDivElement>(); @observable _focusFilters: Opt<string[]>; // docFilters that are overridden when previewing a link to an anchor which has docFilters set on it @observable _focusRangeFilters: Opt<string[]>; // docRangeFilters that are overridden when previewing a link to an anchor which has docRangeFilters set on it + @observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. + @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } + @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); } @computed get fitToContentVals() { return { @@ -131,13 +130,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); } @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); } @computed get nativeHeight() { return this.fitToContent ? 0 : Doc.NativeHeight(this.Document); } - private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } - private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; } - private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } - private panX = () => this.freeformData()?.panX ?? NumCast(this.Document._panX); - private panY = () => this.freeformData()?.panY ?? NumCast(this.Document._panY); - private zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)); - private contentTransform = () => `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)` @computed get cachedCenteringShiftX(): number { const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling; return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections @@ -156,16 +148,31 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return this.getTransformOverlay().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform); } - private getTransform = () => this.cachedGetTransform.copy(); - private getLocalTransform = () => this.cachedGetLocalTransform.copy(); - private getContainerTransform = () => this.cachedGetContainerTransform.copy(); - private getTransformOverlay = () => this.getContainerTransform().translate(1, 1); - private addLiveTextBox = (newBox: Doc) => { + onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); + onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); + parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.props.parentActive?.(outsideReaction) || this.backgroundActive || this.layoutDoc._viewType === CollectionViewType.Pile ? true : false; + elementFunc = () => this._layoutElements; + freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined; + freeformDocFilters = () => this._focusFilters || this.docFilters(); + freeformRangeDocFilters = () => this._focusRangeFilters || this.docRangeFilters(); + panX = () => this.freeformData()?.panX ?? NumCast(this.Document._panX); + panY = () => this.freeformData()?.panY ?? NumCast(this.Document._panY); + zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)); + contentTransform = () => `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)` + getTransform = () => this.cachedGetTransform.copy(); + getLocalTransform = () => this.cachedGetLocalTransform.copy(); + getContainerTransform = () => this.cachedGetContainerTransform.copy(); + getTransformOverlay = () => this.getContainerTransform().translate(1, 1); + getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); + addLiveTextBox = (newBox: Doc) => { FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed this.addDocument(newBox); } - - addDocument = action((newBox: Doc | Doc[]) => { + selectDocuments = (docs: Doc[]) => { + SelectionManager.DeselectAll(); + docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true)); + } + addDocument = (newBox: Doc | Doc[]) => { let retVal = false; if (newBox instanceof Doc) { retVal = this.props.addDocument?.(newBox) || false; @@ -191,25 +198,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } return retVal; - }); - - private selectDocuments = (docs: Doc[]) => { - SelectionManager.DeselectAll(); - docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true)); - } - public isCurrent(doc: Doc) { - const dispTime = NumCast(doc._timecodeToShow, -1); - const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5); - const curTime = NumCast(this.Document._currentTimecode, -1); - return dispTime === -1 || ((curTime - dispTime) >= -1e-4 && curTime <= endTime); - } - - public getActiveDocuments = () => { - return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); - } - - onExternalDrop = (e: React.DragEvent) => { - return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY)); } updateGroupBounds = () => { @@ -231,6 +219,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.layoutDoc.y = pbounds.y; } + isCurrent(doc: Doc) { + const dispTime = NumCast(doc._timecodeToShow, -1); + const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5); + const curTime = NumCast(this.Document._currentTimecode, -1); + return dispTime === -1 || ((curTime - dispTime) >= -1e-4 && curTime <= endTime); + } + @action internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) { if (!this.ChildDrag && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false; @@ -271,7 +266,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } @undoBatch - @action internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) { const dragDoc = annoDragData.dropDocument!; const dropPos = [NumCast(dragDoc.x), NumCast(dragDoc.y)]; @@ -282,7 +276,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } @undoBatch - @action internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) { if (linkDragData.linkSourceDocument === this.props.Document || this.props.Document.annotationOn) return false; if (!linkDragData.linkSourceDocument.context || StrCast(Cast(linkDragData.linkSourceDocument.context, Doc, null)?.type) === DocumentType.COL) { @@ -299,7 +292,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } - @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { const [xp, yp] = this.getTransform().transformPoint(de.x, de.y); if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); @@ -308,6 +300,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return false; } + onExternalDrop = (e: React.DragEvent) => { + return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY)); + } + pickCluster(probe: number[]) { return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1); @@ -435,6 +431,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return styleProp; } + trySelectCluster = (addToSel: boolean) => { + if (this._hitCluster !== -1) { + !addToSel && SelectionManager.DeselectAll(); + const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === this._hitCluster); + this.selectDocuments(eles); + return true; + } + return false; + } + @action onPointerDown = (e: React.PointerEvent): void => { if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) { @@ -774,10 +780,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P cleanUpInteractions = () => { switch (this._pullDirection) { - case "left": - case "right": - case "top": - case "bottom": + case "left": case "right": case "top": case "bottom": CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), this._pullDirection); } @@ -868,7 +871,21 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } - bringToFront = action((doc: Doc, sendToBack?: boolean) => { + @action + nudge = (x: number, y: number) => { + if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || + this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out... + this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(), + NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "transform 500ms", true); + this._nudgeTime = Date.now(); + setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document._viewTransition = undefined), 500); + return true; + } + return false; + } + + @action + bringToFront = (doc: Doc, sendToBack?: boolean) => { if (sendToBack || StrListCast(doc.layers).includes(StyleLayers.Background)) { doc.zIndex = 0; } else if (doc.isInkMask) { @@ -883,7 +900,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } doc.zIndex = zlast + 1; } - }); + } scaleAtPt(docpt: number[], scale: number) { if (this.Document._isGroup) return; @@ -923,7 +940,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey], transition: this.Document._viewTransition }; const newState = HistoryUtil.getState(); const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); - const { panX, panY, scale } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined); + const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .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: panX, panY: panY }; HistoryUtil.pushState(newState); @@ -960,14 +977,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1); this.props.focus(cantTransform ? doc : this.rootDoc, { - ...options, docTransform: xf, + ...options, + docTransform: xf, afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))) }); } } - setPanIntoView = (doc: Doc, xf: Transform, scale?: number) => { + calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => { const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1); const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1); const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y)); @@ -1000,43 +1018,51 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } - onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); - onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); - parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.props.parentActive?.(outsideReaction) || this.backgroundActive || this.layoutDoc._viewType === CollectionViewType.Pile ? true : false; - getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { - return { - addDocument: this.props.addDocument, - removeDocument: this.props.removeDocument, - moveDocument: this.props.moveDocument, - pinToPres: this.props.pinToPres, - whenActiveChanged: this.props.whenActiveChanged, - parentActive: this.parentActive, - docViewPath: this.props.docViewPath, - DataDoc: childData, - Document: childLayout, - ContainingCollectionView: this.props.CollectionView, - ContainingCollectionDoc: this.props.Document, - LayoutTemplate: childLayout.z ? undefined : this.props.childLayoutTemplate, - LayoutTemplateString: childLayout.z ? undefined : this.props.childLayoutString, - rootSelected: childData ? this.rootSelected : returnFalse, - onClick: this.onChildClickHandler, - onDoubleClick: this.onChildDoubleClickHandler, - ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, - PanelWidth: childLayout[WidthSym], - PanelHeight: childLayout[HeightSym], - docFilters: this.freeformDocFilters, - docRangeFilters: this.freeformRangeDocFilters, - searchFilterDocs: this.searchFilterDocs, - focus: this.focusDocument, - styleProvider: this.getClusterColor, - layerProvider: this.props.layerProvider, - freezeDimensions: this.props.childFreezeDimensions, - dropAction: StrCast(this.props.Document.childDropAction) as dropActionType, - bringToFront: this.bringToFront, - addDocTab: this.addDocTab, - renderDepth: this.props.renderDepth + 1, - dontRegisterView: this.props.dontRegisterView, - }; + getChildDocView(entry: PoolData) { + const childLayout = entry.pair.layout; + const childData = entry.pair.data; + const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); + return <CollectionFreeFormDocumentView key={childLayout[Id] + (entry.replica || "")} + DataDoc={childData} + Document={childLayout} + renderDepth={this.props.renderDepth + 1} + replica={entry.replica} + ContainingCollectionView={this.props.CollectionView} + ContainingCollectionDoc={this.props.Document} + CollectionFreeFormView={this} + LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate} + LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString} + rootSelected={childData ? this.rootSelected : returnFalse} + onClick={this.onChildClickHandler} + onDoubleClick={this.onChildDoubleClickHandler} + ScreenToLocalTransform={childLayout.z ? this.getTransformOverlay : this.getTransform} + PanelWidth={childLayout[WidthSym]} + PanelHeight={childLayout[HeightSym]} + docFilters={this.freeformDocFilters} + docRangeFilters={this.freeformRangeDocFilters} + searchFilterDocs={this.searchFilterDocs} + focus={this.focusDocument} + addDocTab={this.addDocTab} + addDocument={this.props.addDocument} + removeDocument={this.props.removeDocument} + moveDocument={this.props.moveDocument} + pinToPres={this.props.pinToPres} + whenActiveChanged={this.props.whenActiveChanged} + parentActive={this.parentActive} + docViewPath={this.props.docViewPath} + styleProvider={this.getClusterColor} + layerProvider={this.props.layerProvider} + dataProvider={this.childDataProvider} + sizeProvider={this.childSizeProvider} + freezeDimensions={this.props.childFreezeDimensions} + dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} + bringToFront={this.bringToFront} + dontRegisterView={this.props.dontRegisterView} + pointerEvents={this.backgroundActive || this.props.childPointerEvents ? "all" : + (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined} + jitterRotation={NumCast(this.props.Document._jitterRotation) || ((Doc.UserDoc().renderStyle === "comic" ? 10 : 0))} + //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this + />; } addDocTab = action((doc: Doc, where: string) => { @@ -1075,15 +1101,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P }; } - viewDefsToJSX = (views: ViewDefBounds[]) => { - return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!); - } - onViewDefDivClick = (e: React.MouseEvent, payload: any) => { (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload }); e.stopPropagation(); } - private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> { + + viewDefsToJSX = (views: ViewDefBounds[]) => { + return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!); + } + + viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> { const { x, y, z } = viewDef; const color = StrCast(viewDef.color); const width = Cast(viewDef.width, "number"); @@ -1165,24 +1192,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this._cachedPool.clear(); Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1])); const elements = computedElementData.slice(); - const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry => elements.push({ - ele: <CollectionFreeFormDocumentView - key={entry[1].pair.layout[Id] + (entry[1].replica || "")} - {...this.getChildDocumentViewProps(entry[1].pair.layout, entry[1].pair.data)} - replica={entry[1].replica} - CollectionFreeFormView={this} - dataProvider={this.childDataProvider} - sizeProvider={this.childSizeProvider} - layerProvider={this.props.layerProvider} - pointerEvents={this.backgroundActive || this.props.childPointerEvents ? - "all" : - (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined} - jitterRotation={NumCast(this.props.Document._jitterRotation) || ((Doc.UserDoc().renderStyle === "comic" ? 10 : 0))} - //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this - freezeDimensions={BoolCast(this.props.childFreezeDimensions)} - />, + ele: this.getChildDocView(entry[1]), bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica) })); @@ -1195,8 +1207,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return elements; } - freeformDocFilters = () => this._focusFilters || this.docFilters(); - freeformRangeDocFilters = () => this._focusRangeFilters || this.docRangeFilters(); @action setViewSpec = (anchor: Doc, preview: boolean) => { if (preview) { @@ -1226,16 +1236,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } return anchor; } - freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined; + @action componentDidMount() { super.componentDidMount?.(); this.props.setContentView?.(this); - this._layoutComputeReaction = reaction(() => this.doLayoutComputation, + this._disposers.layoutComputation = reaction(() => this.doLayoutComputation, (elements) => this._layoutElements = elements || [], { fireImmediately: true, name: "doLayout" }); if (!this.props.isAnnotationOverlay) { - this._boundsReaction = reaction(() => this.contentBounds, + this._disposers.contentBounds = reaction(() => this.contentBounds, bounds => (!this.fitToContent && this._layoutElements?.length) && setTimeout(() => { const rbounds = Cast(this.Document._renderContentBounds, listSpec("number"), [0, 0, 0, 0]); if (rbounds[0] !== bounds.x || rbounds[1] !== bounds.y || rbounds[2] !== bounds.r || rbounds[3] !== bounds.b) { @@ -1248,14 +1258,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } componentWillUnmount() { - this._layoutComputeReaction?.(); - this._boundsReaction?.(); + Object.values(this._disposers).forEach(disposer => disposer?.()); this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); } - @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } - elementFunc = () => this._layoutElements; - @action onCursorMove = (e: React.PointerEvent) => { // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); @@ -1281,7 +1287,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P e.stopPropagation(); } - promoteCollection = undoBatch(action(() => { + @undoBatch + promoteCollection = () => { const childDocs = this.childDocs.slice(); childDocs.forEach(doc => { const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y)); @@ -1290,10 +1297,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P }); this.props.addDocTab(childDocs as any as Doc, "inParent"); this.props.ContainingCollectionView?.removeDocument(this.props.Document); - })); + } @undoBatch - layoutDocsInGrid = action(() => { + layoutDocsInGrid = () => { const docs = this.childLayoutPairs; const startX = this.Document._panX || 0; let x = startX; @@ -1311,19 +1318,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P y += height + 20; } }); - }); + } @undoBatch - @action - toggleNativeDimensions = () => { - Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight); - } + toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight); @undoBatch @action - toggleLockTransform = (): void => { - this.layoutDoc._lockedTransform = this.layoutDoc._lockedTransform ? undefined : true; - } + toggleLockTransform = () => this.layoutDoc._lockedTransform = this.layoutDoc._lockedTransform ? undefined : true; onContextMenu = (e: React.MouseEvent) => { if (this.props.isAnnotationOverlay || this.props.Document.annotationOn || !ContextMenu.Instance) return; @@ -1430,24 +1432,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />]; } - @computed get placeholder() { - return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}> - <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span> - </div>; - } - - nudge = action((x: number, y: number) => { - if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || - this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out... - this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(), - NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "transform 500ms", true); - this._nudgeTime = Date.now(); - setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document._viewTransition = undefined), 500); - return true; - } - return false; - }); - chooseGridSpace = (gridSpace: number): number => { const divisions = this.props.PanelWidth() / this.zoomScaling() / gridSpace + 3; return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10); @@ -1486,14 +1470,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P }} />; } - trySelectCluster = (addToSel: boolean) => { - if (this._hitCluster !== -1) { - !addToSel && SelectionManager.DeselectAll(); - const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === this._hitCluster); - this.selectDocuments(eles); - return true; - } - return false; + @computed get placeholder() { + return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}> + <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span> + </div>; } @computed get marqueeView() { @@ -1536,7 +1516,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const wscale = nw ? this.props.PanelWidth() / nw : 1; return wscale < hscale ? wscale : hscale; } - @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } render() { TraceMobx(); @@ -1555,7 +1534,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P transform: this.contentScaling ? `scale(${this.contentScaling})` : "", transformOrigin: this.contentScaling ? "left top" : "", width: this.contentScaling ? `${100 / this.contentScaling}%` : "", - height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() + height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}> {this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0 ? this.placeholder : this.marqueeView} @@ -1667,17 +1646,17 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF const activeItem = PresBox.Instance.activeItem; // const targetDoc = PresBox.Instance.targetDoc; if (activeItem && activeItem.presPinView && activeItem.id) { - const vfLeft: number = NumCast(activeItem.presPinViewX); - const vfTop: number = NumCast(activeItem.presPinViewY); - const vfWidth: number = 100; - const vfHeight: number = 100; + 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: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}> + <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> - <div className='resizer top-right' onPointerDown={this.onPointerDown}></div> - <div className='resizer bottom-left' onPointerDown={this.onPointerDown}></div> - <div className='resizer bottom-right' onPointerDown={this.onPointerDown}></div> + <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>; } @@ -1697,16 +1676,13 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF <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"> + <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" /> </marker> - <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" - orient="auto" overflow="visible"> + <marker id="markerSquareFilled" 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="#69a6db" /> </marker> - <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" - orient="auto" overflow="visible"> + <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible"> <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" /> </marker> </defs> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b79c1d474..e31101459 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -378,8 +378,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps startDragging(x: number, y: number, dropAction: dropActionType) { if (this._mainCont.current) { - const ffview = this.props.CollectionFreeFormDocumentView; - ffview && runInAction(() => (ffview().props.CollectionFreeFormView.ChildDrag = this.props.DocumentView())); const dragData = new DragManager.DocumentDragData([this.props.Document]); const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0); dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top); @@ -387,8 +385,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps dragData.treeViewDoc = this.props.treeViewDoc; dragData.removeDocument = this.props.removeDocument; dragData.moveDocument = this.props.moveDocument; + const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView())); DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.layoutDoc.onDragStart }, - () => setTimeout(action(() => ffview && (ffview().props.CollectionFreeFormView.ChildDrag = undefined)))); // this needs to happen after the drop event is processed. + () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed. } } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 4ae436083..ce5b08440 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -25,6 +25,7 @@ import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { AudioField, ImageField, PdfField, VideoField, WebField } from "./URLField"; import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util"; import JSZip = require("jszip"); +import { prefix } from "@fortawesome/free-regular-svg-icons"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -230,10 +231,11 @@ export class Doc extends RefField { const sameAuthor = this.author === Doc.CurrentUserEmail; if (set) { for (const key in set) { - if (!key.startsWith("fields.")) { + const fprefix = "fields."; + if (!key.startsWith(fprefix)) { continue; } - const fKey = key.substring(7); + const fKey = key.substring(fprefix.length); const fn = async () => { const value = await SerializationHelper.Deserialize(set[key]); const prev = GetEffectiveAcl(this); @@ -246,9 +248,6 @@ export class Doc extends RefField { if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) { DocServer.GetRefField(this[Id], true); } - // if (prev !== AclPrivate && GetEffectiveAcl(this) === AclPrivate) { - // this[FieldsSym](true); - // } }; if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; |