From 7fcf4c54c42b7eaa427ea88c0b8586a78d7f1859 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 12 Nov 2023 14:34:09 -0500 Subject: cleaning up freeformview code. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 303 ++++----------------- 1 file changed, 58 insertions(+), 245 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 27c3eaa93..e46a7bed7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -9,10 +9,9 @@ import { DocData, Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; -import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; @@ -31,7 +30,6 @@ import { freeformScrollMode } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; 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 { GestureOverlay } from '../../GestureOverlay'; @@ -48,7 +46,9 @@ import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { TabDocView } from '../TabDocView'; +import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; +import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents'; import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; @@ -82,9 +82,6 @@ export class CollectionFreeFormView extends CollectionSubView = new Map(); private _clusterDistance: number = 75; private _hitCluster: number = -1; private _disposers: { [name: string]: IReactionDisposer } = {}; @@ -111,9 +108,6 @@ export class CollectionFreeFormView extends CollectionSubView(); @observable _marqueeViewRef = React.createRef(); - @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region + @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. @computed get views() { const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele); @@ -171,19 +165,15 @@ export class CollectionFreeFormView extends CollectionSubView this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1)); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1)); zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1)); - contentTransform = () => + PanZoomCenterXf = () => this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `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(); + ScreenToLocalXf = () => this.screenToLocalXf.copy(); getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); isAnyChildContentActive = () => this.props.isAnyChildContentActive(); addLiveTextBox = (newBox: Doc) => { @@ -261,7 +249,7 @@ export class CollectionFreeFormView extends CollectionSubView { SelectionManager.DeselectAll(); - docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).map(dv => dv && SelectionManager.SelectView(dv, true)); + docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true)); }; addDocument = (newBox: Doc | Doc[]) => { let retVal = false; @@ -332,19 +320,18 @@ export class CollectionFreeFormView extends CollectionSubView> => { - return new Promise>(res => { + getView = async (doc: Doc): Promise> => + new Promise>(res => { if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false; 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 (!super.onInternalDrop(e, de)) return false; const refDoc = docDragData.droppedDocuments[0]; - const [xpo, ypo] = this.getContainerTransform().transformPoint(de.x, de.y); + const [xpo, ypo] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); const z = NumCast(refDoc.z); const x = (z ? xpo : xp) - docDragData.offset[0]; const y = (z ? ypo : yp) - docDragData.offset[1]; @@ -452,14 +439,14 @@ export class CollectionFreeFormView extends CollectionSubView { - const [xp, yp] = this.getTransform().transformPoint(de.x, de.y); + const [xp, yp] = this.screenToLocalXf.transformPoint(de.x, de.y); if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp); else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp); return false; }; - onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.getTransform().transformPoint(e.pageX, e.pageY)); + onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.screenToLocalXf.transformPoint(e.pageX, e.pageY)); static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) { const doc2Layout = Doc.Layout(doc2); @@ -500,7 +487,7 @@ export class CollectionFreeFormView extends CollectionSubView v.ContentDiv!), de, @@ -597,7 +584,7 @@ export class CollectionFreeFormView extends CollectionSubView, props: Opt, property: string) => { + clusterStyleProvider = (doc: Opt, props: Opt, property: string) => { let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 switch (property) { case StyleProp.BackgroundColor: @@ -658,16 +645,16 @@ export class CollectionFreeFormView extends CollectionSubView p.X)), Math.max(...ge.points.map(p => p.Y))); - const setDocs = this.getActiveDocuments().filter(s => DocCast(s.proto)?.type === DocumentType.RTF && s.color); - const sets = setDocs.map(sd => Cast(sd.text, RichTextField)?.Text as string); - if (sets.length && sets[0]) { - this._wordPalette.clear(); - const colors = setDocs.map(sd => FieldValue(sd.color) as string); - sets.forEach((st: string, i: number) => st.split(',').forEach(word => this._wordPalette.set(word, colors[i]))); - } - const inks = this.getActiveDocuments().filter(doc => { - if (doc.type === 'ink') { - const l = NumCast(doc.x); - const r = l + NumCast(doc._width); - const t = NumCast(doc.y); - const b = t + NumCast(doc._height); - const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t); - return pass; - } - return false; - }); - // const inkFields = inks.map(i => Cast(i.data, InkField)); - const strokes: InkData[] = []; - inks.forEach(i => { - const d = Cast(i.data, InkField); - const x = NumCast(i.x); - const y = NumCast(i.y); - const left = Math.min(...(d?.inkData.map(pd => pd.X) ?? [0])); - const top = Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0])); - if (d) { - strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top }))); - } + const strokes = this.getActiveDocuments() + .filter(doc => doc.type === DocumentType.INK) + .map(i => { + const d = Cast(i.stroke, InkField); + const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0])); + const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0])); + return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y })); }); - CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => { - const wordResults = results.filter((r: any) => r.category === 'inkWord'); - for (const word of wordResults) { - const indices: number[] = word.strokeIds; - indices.forEach(i => { - const otherInks: Doc[] = []; - indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2])); - inks[i].relatedInks = new List(otherInks); - const uniqueColors: string[] = []; - Array.from(this._wordPalette.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c)); - inks[i].alternativeColors = new List(uniqueColors); - if (this._wordPalette.has(word.recognizedText.toLowerCase())) { - inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase()); - } else if (word.alternates) { - for (const alt of word.alternates) { - if (this._wordPalette.has(alt.recognizedString.toLowerCase())) { - inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase()); - break; - } - } - } - }); - } - }); - this._inkToTextStartX = end[0]; - } + CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {}); break; case GestureUtils.Gestures.Text: if (ge.text) { - const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y); + const B = this.screenToLocalXf.transformPoint(ge.points[0].X, ge.points[0].Y); this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] })); e.stopPropagation(); } @@ -824,7 +761,7 @@ export class CollectionFreeFormView extends CollectionSubView 0 ? 1 / 1.05 : 1.05; if (deltaScale < 0) deltaScale = -deltaScale; - const [x, y] = this.getTransform().transformPoint(pointX, pointY); - const invTransform = this.getLocalTransform().inverse(); + const [x, y] = this.screenToLocalXf.transformPoint(pointX, pointY); + const invTransform = this.panZoomXf.inverse(); if (deltaScale * invTransform.Scale > 20) { deltaScale = 20 / invTransform.Scale; } @@ -1093,7 +1030,7 @@ export class CollectionFreeFormView extends CollectionSubView { - const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); + const pt = this.screenToLocalXf.transformPoint(NumCast(doc.x), NumCast(doc.y)); doc.x = pt[0]; doc.y = pt[1]; return doc; @@ -1424,7 +1361,7 @@ export class CollectionFreeFormView extends CollectionSubView, computedElementData: ViewDefResult[]) => { const array = Array.from(newPool.entries()); - this._lastPoolSize = array.length; for (const entry of array) { const lastPos = this._cachedPool.get(entry[0]); // last computed pos const newPos = entry[1]; @@ -1753,7 +1688,7 @@ export class CollectionFreeFormView extends CollectionSubView { const childDocs = this.childDocs.slice(); childDocs.forEach(doc => { - const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y)); + const scr = this.screenToLocalXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y)); doc.x = scr?.[0]; doc.y = scr?.[1]; }); @@ -1876,7 +1811,7 @@ export class CollectionFreeFormView extends CollectionSubView ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }); const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect); @@ -1892,7 +1827,7 @@ export class CollectionFreeFormView extends CollectionSubView !doc._isGroup && (snapToDraggedDoc || (SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc)))) .forEach(doc => { @@ -1927,7 +1862,7 @@ export class CollectionFreeFormView extends CollectionSubView +
{this.props.Document.annotationOn ? '' : this.props.Document.title?.toString()}
); @@ -1959,15 +1894,15 @@ export class CollectionFreeFormView extends CollectionSubView {this.children} - + ); } @computed get marqueeView() { @@ -1985,8 +1920,8 @@ export class CollectionFreeFormView extends CollectionSubView @@ -2109,128 +2044,6 @@ class CollectionFreeFormOverlayView extends React.Component string; - brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined; -} - -@observer -class CollectionFreeFormViewPannableContents extends React.Component { - @computed get presPaths() { - return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.rootDoc) : null; - } - // rectangle highlight used when following trail/link to a region of a collection that isn't a document - showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) => - !viewport ? null : ( -
- ); - - render() { - return ( -
{ - const target = e.target as any; - if (getComputedStyle(target)?.overflow === 'visible') { - target.scrollTop = target.scrollLeft = 0; // if collection is visible, scrolling messes things up since there are no scroll bars - } - }} - style={{ - transform: this.props.transform(), - transition: this.props.transition, - width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection - }}> - {this.props.children} - {this.presPaths} - {this.showViewport(this.props.brushedView())} -
- ); - } -} - -interface CollectionFreeFormViewBackgroundGridProps { - panX: () => number; - panY: () => number; - PanelWidth: () => number; - PanelHeight: () => number; - color: () => string; - isAnnotationOverlay?: boolean; - nativeDimScaling: () => number; - zoomScaling: () => number; - layoutDoc: Doc; - cachedCenteringShiftX: number; - cachedCenteringShiftY: number; -} -@observer -class CollectionFreeFormBackgroundGrid extends React.Component { - chooseGridSpace = (gridSpace: number): number => { - if (!this.props.zoomScaling()) return gridSpace; - const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace; - return divisions < 90 ? gridSpace : this.chooseGridSpace(gridSpace * 2); - }; - render() { - const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50)); - const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling(); - const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling(); - const renderGridSpace = gridSpace * this.props.zoomScaling(); - const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace; - const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace; - const strokeStyle = this.props.color(); - return !this.props.nativeDimScaling() ? null : ( - { - const ctx = el?.getContext('2d'); - if (ctx) { - const Cx = this.props.cachedCenteringShiftX % renderGridSpace; - const Cy = this.props.cachedCenteringShiftY % renderGridSpace; - ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling())); - ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]); - ctx.clearRect(0, 0, w, h); - if (ctx) { - ctx.strokeStyle = strokeStyle; - ctx.fillStyle = strokeStyle; - ctx.beginPath(); - if (this.props.zoomScaling() > 1) { - for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) { - ctx.moveTo(x, Cy - h); - ctx.lineTo(x, Cy + h); - } - for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { - ctx.moveTo(Cx - w, y); - ctx.lineTo(Cx + w, y); - } - } else { - for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) - for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { - ctx.fillRect(Math.round(x), Math.round(y), 1, 1); - } - } - ctx.stroke(); - } - } - }} - /> - ); - } -} - export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) { const browseTransitionTime = 500; SelectionManager.DeselectAll(); @@ -2245,7 +2058,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY } while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview - ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime); + ffview?.zoomSmoothlyAboutPt(ffview.screenToLocalXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime); Doc.linkFollowHighlight(dv?.props.Document, false); } }); -- cgit v1.2.3-70-g09d2