aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
diff options
context:
space:
mode:
authorgeireann <geireann.lindfield@gmail.com>2022-10-31 14:33:42 -0400
committergeireann <geireann.lindfield@gmail.com>2022-10-31 14:33:42 -0400
commit85f9dd5b114ed44a13c11eeded12666f0d7e7be2 (patch)
treeb6bbdd3319312eb26dce931c7b5ab0c6514e84d3 /src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
parentaaf2c5fb4c0b6c6063f824eda3ff29c3bf18d2c4 (diff)
parent70c998562c8560283a7d6b9a1ae78b9207e3720f (diff)
Merge branch 'master' of https://github.com/brown-dash/Dash-Web
Diffstat (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx161
1 files changed, 91 insertions, 70 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e9d0826cd..2f246e74f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -26,7 +26,7 @@ import { InteractionUtils } from '../../../util/InteractionUtils';
import { ReplayMovements } from '../../../util/ReplayMovements';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { SelectionManager } from '../../../util/SelectionManager';
-import { ColorScheme } from '../../../util/SettingsManager';
+import { ColorScheme, freeformScrollMode } from '../../../util/SettingsManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
@@ -113,7 +113,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _clusterSets: Doc[][] = [];
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
- @observable _marqueeRef = React.createRef<HTMLDivElement>();
+ @observable _marqueeRef: HTMLDivElement | null = null;
@observable _marqueeViewRef = React.createRef<MarqueeView>();
@observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
@observable _brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 }; // highlighted region of freeform canvas used by presentations to indicate a region
@@ -155,8 +155,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
+ const dv = this.props.DocumentView?.();
const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
- return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ // if freeform has a native aspect, then the panel height needs to be adjusted to match it
+ const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
+ return this.props.isAnnotationOverlay ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedGetLocalTransform(): Transform {
return Transform.Identity()
@@ -190,6 +193,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
shrinkWrap = () => {
+ if (this.props.DocumentView?.().nativeWidth) return;
const vals = this.fitToContentVals;
this.layoutDoc._panX = vals.bounds.cx;
this.layoutDoc._panY = vals.bounds.cy;
@@ -564,6 +568,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@undoBatch
onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
switch (ge.gesture) {
+ default:
+ case GestureUtils.Gestures.Line:
+ case GestureUtils.Gestures.Circle:
+ case GestureUtils.Gestures.Rectangle:
+ case GestureUtils.Gestures.Triangle:
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
@@ -593,34 +602,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.addDocument(inkDoc);
e.stopPropagation();
break;
- case GestureUtils.Gestures.Box:
- const lt = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
- const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
- const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] };
- const bWidth = bounds.r - bounds.x;
- const bHeight = bounds.b - bounds.y;
- const sel = this.getActiveDocuments().filter(doc => {
- const l = NumCast(doc.x);
- const r = l + doc[WidthSym]();
- const t = NumCast(doc.y);
- const b = t + doc[HeightSym]();
- const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t);
- if (pass) {
- doc.x = l - bounds.x - bWidth / 2;
- doc.y = t - bounds.y - bHeight / 2;
- }
- return pass;
- });
- this.addDocument(Docs.Create.FreeformDocument(sel, { title: 'nested collection', x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
- sel.forEach(d => this.props.removeDocument?.(d));
- e.stopPropagation();
- break;
- case GestureUtils.Gestures.StartBracket:
- const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
- this._inkToTextStartX = start[0];
- this._inkToTextStartY = start[1];
- break;
- case GestureUtils.Gestures.EndBracket:
+ case GestureUtils.Gestures.Rectangle:
if (this._inkToTextStartX && this._inkToTextStartY) {
const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color);
@@ -719,6 +701,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@action
+ scrollPan = (e: WheelEvent | { deltaX: number; deltaY: number }): void => {
+ const dx = e.deltaX;
+ const dy = e.deltaY;
+ this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+ };
+
+ @action
pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => {
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
@@ -797,15 +786,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
for (var i = 0; i < inkData.length - 3; i += 4) {
- const intersects = Array.from(
- new Set(
- InkField.Segment(inkData, i).intersects({
- // compute all unique intersections
- p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
- p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y },
- }) as (number | string)[]
- )
- ); // convert to more manageable union array type
+ const rawIntersects = InkField.Segment(inkData, i).intersects({
+ // compute all unique intersections
+ p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
+ p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y },
+ });
+ const intersects = Array.from(new Set(rawIntersects as (number | string)[])); // convert to more manageable union array type
+ if (intersects.length) {
+ console.log();
+ }
// return tuples of the inkingStroke intersected, and the t value of the intersection
intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve
}
@@ -854,6 +843,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return segments;
};
+ // for some reason bezier.js doesn't handle the case of intersecting a linear curve, so we wrap the intersection
+ // call in a test for linearity
+ bintersects = (curve: Bezier, otherCurve: Bezier) => {
+ if ((otherCurve as any)._linear) {
+ return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] });
+ }
+ return curve.intersects(otherCurve);
+ };
+
/**
* Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all
* ink strokes in the current collection.
@@ -878,7 +876,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (ink?.Document === otherInk.props.Document && neighboringSegment) continue;
const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
- curve.intersects(otherCurve).forEach((val: string | number, i: number) => {
+ this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => {
// Converting the Bezier.js Split type to a t-value number.
const t = +val.toString().split('/')[0];
if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
@@ -1008,13 +1006,38 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onPointerWheel = (e: React.WheelEvent): void => {
if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
- if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
- // things that can scroll vertically should do that instead of zooming
- e.stopPropagation();
- } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
- e.stopPropagation();
- e.preventDefault();
- !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?
+ e.stopPropagation();
+ e.preventDefault();
+ switch (Doc.UserDoc().freeformScrollMode) {
+ case freeformScrollMode.Pan:
+ // if shift 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) {
+ !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) {
+ const dx = e.deltaX;
+ const dy = e.deltaY;
+ if (e.shiftKey) {
+ !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dy, deltaY: 0 });
+ } else {
+ !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dx, deltaY: dy });
+ }
+ }
+ 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) {
+ !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;
}
};
@@ -1112,7 +1135,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._viewTransition = 0))), this._viewTransition)); // set transition to be smooth, then reset
}
- focusDocument = (doc: Doc, options?: DocFocusOptions) => {
+ focusDocument = (doc: Doc, options: DocFocusOptions) => {
const state = HistoryUtil.getState();
// TODO This technically isn't correct if type !== "doc", as
@@ -1131,13 +1154,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
// SelectionManager.DeselectAll();
// }
- if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined) {
+ if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined) {
this.props.focus(doc, options);
} else {
const xfToCollection = options?.docTransform ?? Transform.Identity();
const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoom ? this.Document[this.scaleFieldKey] : undefined };
const newState = HistoryUtil.getState();
- const cantTransform = /*this.props.isAnnotationOverlay ||*/ (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc;
+ const cantTransform = (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc;
const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || 0.75 : undefined);
if (!cantTransform) {
// only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
@@ -1175,7 +1198,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
? 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 ? doc : this.rootDoc, {
+ 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)))),
@@ -1294,7 +1317,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
dontScaleFilter={this.props.dontScaleFilter}
dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
pointerEvents={this.pointerEvents}
- jitterRotation={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
/>
);
@@ -1323,8 +1346,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
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);
return {
- x: NumCast(x),
- y: NumCast(y),
+ x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
+ y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.props.styleProvider?.(childDoc, this.props, StyleProp.BackgroundColor),
@@ -1525,9 +1548,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
setTimeout(
action(() => {
this._firstRender = false;
-
- this._marqueeRef.current?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
-
this._disposers.groupBounds = reaction(
() => {
if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
@@ -1645,7 +1665,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
- this._marqueeRef.current?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+ this._marqueeRef?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
}
@action
@@ -1658,10 +1678,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
(e as any).handlePan = true;
- if (!this.layoutDoc._noAutoscroll && !this.props.renderDepth && this._marqueeRef?.current) {
+ if (!this.layoutDoc._noAutoscroll && !this.props.renderDepth && this._marqueeRef) {
const dragX = e.detail.clientX;
const dragY = e.detail.clientY;
- const bounds = this._marqueeRef.current?.getBoundingClientRect();
+ const bounds = this._marqueeRef?.getBoundingClientRect();
const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
@@ -1719,7 +1739,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
event: () => (this.Document._fitContentsToBox = !this.fitContentsToBox),
icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt',
});
- appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinDocView: true, panelWidth: this.props.PanelWidth(), panelHeight: this.props.PanelHeight() }), icon: 'map-pin' });
+ appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
//appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
this.props.ContainingCollectionView && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
@@ -1819,10 +1839,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
DragManager.SetSnapLines(horizLines, vertLines);
};
- onPointerOver = (e: React.PointerEvent) => {
- e.stopPropagation();
- };
-
incrementalRender = action(() => {
if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
@@ -1866,7 +1882,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getContainerTransform={this.getContainerTransform}
getTransform={this.getTransform}
isAnnotationOverlay={this.isAnnotationOverlay}>
- <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}>
+ <div
+ className="marqueeView-div"
+ ref={r => {
+ this._marqueeRef = r;
+ r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+ }}
+ style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}>
{this.layoutDoc._backgroundGridShow ? (
<div>
<CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!?
@@ -1942,7 +1964,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
<div
className={'collectionfreeformview-container'}
ref={this.createDashEventsTarget}
- onPointerOver={this.onPointerOver}
onWheel={this.onPointerWheel}
onClick={this.onClick}
onPointerDown={this.onPointerDown}
@@ -2249,6 +2270,6 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
!readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
});
-ScriptingGlobals.add(function pinWithView(readOnly: boolean) {
- !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocView: true, panelWidth: view.props.PanelWidth(), panelHeight: view.props.PanelHeight() }));
+ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) {
+ !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) }));
});