aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
diff options
context:
space:
mode:
authorMelissa Zhang <mzhang19096@gmail.com>2020-05-15 19:10:07 -0700
committerMelissa Zhang <mzhang19096@gmail.com>2020-05-15 19:10:07 -0700
commit253d5efbbafe4a44c350fef7dda6d553a70a94c9 (patch)
treeb43a55e1efddc86e25df4bb7d90a99f3c7b52a17 /src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
parent36c01920e3874e1484222a7012745b581405f67d (diff)
parent08b6bf8b51ab631c8cfe9c3e12bfb0ae2dd7b4c7 (diff)
merge with master
Diffstat (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx340
1 files changed, 251 insertions, 89 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 28b461313..4be671a76 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,21 +1,21 @@
import { library } from "@fortawesome/fontawesome-svg-core";
-import { faEye } from "@fortawesome/free-regular-svg-icons";
+import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons";
import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
-import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
-import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
-import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkData, InkField, InkTool } from "../../../../new_fields/InkField";
-import { List } from "../../../../new_fields/List";
-import { RichTextField } from "../../../../new_fields/RichTextField";
-import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema";
-import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
-import { TraceMobx } from "../../../../new_fields/util";
+import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../fields/Doc";
+import { documentSchema, collectionSchema } from "../../../../fields/documentSchemas";
+import { Id } from "../../../../fields/FieldSymbols";
+import { InkData, InkField, InkTool, PointData } from "../../../../fields/InkField";
+import { List } from "../../../../fields/List";
+import { RichTextField } from "../../../../fields/RichTextField";
+import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema";
+import { ScriptField, ComputedField } 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, returnOne, Utils, returnZero, returnFalse } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse, numberRange } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { DocServer } from "../../../DocServer";
import { Docs } from "../../../documents/Documents";
@@ -32,7 +32,7 @@ import { ContextMenuProps } from "../../ContextMenuItem";
import { InkingControl } from "../../InkingControl";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView";
-import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
import PDFMenu from "../../pdf/PDFMenu";
import { CollectionDockingView } from "../CollectionDockingView";
@@ -44,6 +44,9 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
import { CollectionViewType } from "../CollectionView";
+import { Timeline } from "../../animationtimeline/Timeline";
+import { SnappingManager } from "../../../util/SnappingManager";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -51,6 +54,7 @@ export const panZoomSchema = createSchema({
_panX: "number",
_panY: "number",
scale: "number",
+ timecode: "number",
arrangeScript: ScriptField,
arrangeInit: ScriptField,
useClusters: "boolean",
@@ -65,11 +69,10 @@ export const panZoomSchema = createSchema({
fitH: "number"
});
-type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
-const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
+type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>;
+const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema);
export type collectionFreeformViewProps = {
forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
- childClickScript?: ScriptField;
viewDefDivClick?: ScriptField;
};
@@ -94,6 +97,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
@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>();
@computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
@computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
@@ -111,8 +115,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
this.Document.scale || 1)
- private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections
- private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
+ private centeringShiftX = () => !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0; // shift so pan position is at center of window for non-overlay collections
+ private centeringShiftY = () => !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 0;// shift so pan position is at center of window for non-overlay collections
private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1);
private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
@@ -121,12 +125,40 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
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);
}
- private addDocument = (newBox: Doc) => {
- const added = this.props.addDocument(newBox);
- added && this.bringToFront(newBox);
- added && this.updateCluster(newBox);
- return added;
+ addDocument = (newBox: Doc | Doc[]) => {
+ if (this.Document.timecode !== undefined) {
+ CollectionFreeFormDocumentView.setupKeyframes((newBox instanceof Doc) ? [newBox] : newBox, this.Document.timecode, this.props.Document);
+ }
+
+ if (newBox instanceof Doc) {
+ const added = this.props.addDocument(newBox);
+ added && this.bringToFront(newBox);
+ added && this.updateCluster(newBox);
+ return added;
+ } else {
+ return this.props.addDocument(newBox);
+ // bcz: deal with clusters
+ }
+ }
+
+ @undoBatch
+ @action
+ nextKeyframe = (): void => {
+ if (this.props.Document.timecode === undefined) {
+ this.props.Document.timecode = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
+ }
+ const timecode = NumCast(this.props.Document.timecode);
+ CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, timecode);
+ this.props.Document.timecode = Math.max(0, timecode + 1);
+ }
+ @undoBatch
+ @action
+ prevKeyframe = (): void => {
+ CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
+ this.props.Document.timecode = Math.max(0, NumCast(this.props.Document.timecode) - 1);
}
+
private selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
@@ -151,6 +183,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const xfo = this.getTransformOverlay();
const [xp, yp] = xf.transformPoint(de.x, de.y);
const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
+ const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
if (super.onInternalDrop(e, de)) {
if (de.complete.docDragData) {
if (de.complete.docDragData.droppedDocuments.length) {
@@ -160,20 +193,29 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const y = (z ? ypo : yp) - de.complete.docDragData.offset[1];
const dropX = NumCast(firstDoc.x);
const dropY = NumCast(firstDoc.y);
- de.complete.docDragData.droppedDocuments.forEach(action((d: Doc) => {
- const layoutDoc = Doc.Layout(d);
- d.x = x + NumCast(d.x) - dropX;
- d.y = y + NumCast(d.y) - dropY;
- if (!NumCast(layoutDoc._width)) {
- layoutDoc._width = 300;
- }
- if (!NumCast(layoutDoc._height)) {
- const nw = NumCast(layoutDoc._nativeWidth);
- const nh = NumCast(layoutDoc._nativeHeight);
- layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
+ const droppedDocs = de.complete.docDragData.droppedDocuments;
+ runInAction(() => {
+ zsorted.forEach((doc, index) => doc.zIndex = index + 1);
+ for (let i = 0; i < droppedDocs.length; i++) {
+ const d = droppedDocs[i];
+ const layoutDoc = Doc.Layout(d);
+ if (this.Document.timecode !== undefined) {
+ CollectionFreeFormDocumentView.setValues(this.Document.timecode, d, x + NumCast(d.x) - dropX, y + NumCast(d.y) - dropY, Cast(d.opacity, "number", null));
+ } else {
+ d.x = x + NumCast(d.x) - dropX;
+ d.y = y + NumCast(d.y) - dropY;
+ }
+ if (!NumCast(layoutDoc._width)) {
+ layoutDoc._width = 300;
+ }
+ if (!NumCast(layoutDoc._height)) {
+ const nw = NumCast(layoutDoc._nativeWidth);
+ const nh = NumCast(layoutDoc._nativeHeight);
+ layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
+ }
+ d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
- d.isBackground === undefined && this.bringToFront(d);
- }));
+ });
(de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments);
}
@@ -326,6 +368,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
if (e.button === 0 && (!e.shiftKey || this._hitCluster) && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+
+ // if (!this.props.Document.aliasOf && !this.props.ContainingCollectionView) {
+ // this.props.addDocTab(this.props.Document, "replace");
+ // e.stopPropagation();
+ // e.preventDefault();
+ // return;
+ // }
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
@@ -381,7 +430,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
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);
- const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
+ const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, InkingControl.Instance.selectedWidth, points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -550,7 +599,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
document.removeEventListener("pointerup", this.onPointerUp);
return;
}
- this.pan(e);
+ (!MarqueeView.DragMarquee || e.altKey) && this.pan(e);
}
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
@@ -696,7 +745,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
zoom = (pointX: number, pointY: number, deltaY: number): void => {
- let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1;
+ let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05;
if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
deltaScale = 1 / this.zoomScaling();
}
@@ -704,7 +753,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const [x, y] = this.getTransform().transformPoint(pointX, pointY);
const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
- if (localTransform.Scale >= 0.15) {
+ if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
this.props.Document.scale = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
@@ -719,7 +768,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
else if (this.props.active(true)) {
e.stopPropagation();
- this.zoom(e.clientX, e.clientY, e.deltaY);
+ if (!e.ctrlKey && MarqueeView.DragMarquee) this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true);
+ else this.zoom(e.clientX, e.clientY, e.deltaY);
}
this.props.Document.targetScale = NumCast(this.props.Document.scale);
}
@@ -758,20 +808,21 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
}
- bringToFront = (doc: Doc, sendToBack?: boolean) => {
+ bringToFront = action((doc: Doc, sendToBack?: boolean) => {
if (sendToBack || doc.isBackground) {
doc.zIndex = 0;
}
else {
const docs = this.childLayoutPairs.map(pair => pair.layout);
- docs.slice().sort((doc1, doc2) => {
- if (doc1 === doc) return 1;
- if (doc2 === doc) return -1;
- return NumCast(doc1.zIndex) - NumCast(doc2.zIndex);
- }).forEach((doc, index) => doc.zIndex = index + 1);
- doc.zIndex = docs.length + 1;
+ docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ let zlast = docs.length ? NumCast(docs[docs.length - 1].zIndex) : 1;
+ if (zlast - docs.length > 100) {
+ for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
+ zlast = docs.length + 1;
+ }
+ doc.zIndex = zlast + 1;
}
- }
+ });
scaleAtPt(docpt: number[], scale: number) {
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
@@ -853,9 +904,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
@computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
- backgroundHalo = () => BoolCast(this.Document.useClusters);
+ @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
@computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
- parentActive = () => this.props.active() || this.backgroundActive ? true : false;
+ backgroundHalo = () => BoolCast(this.Document.useClusters);
+ parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive ? true : false;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
...this.props,
@@ -865,12 +917,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
DataDoc: childData,
Document: childLayout,
LibraryPath: this.libraryPath,
+ LayoutTemplate: this.props.ChildLayoutTemplate,
+ LayoutTemplateString: this.props.ChildLayoutString,
FreezeDimensions: this.props.freezeChildDimensions,
layoutKey: undefined,
+ setupDragLines: this.setupDragLines,
+ dontRegisterView: this.props.dontRegisterView,
rootSelected: childData ? this.rootSelected : returnFalse,
dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
- //onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
onClick: this.onChildClickHandler,
+ onDoubleClick: this.onChildDoubleClickHandler,
ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
renderDepth: this.props.renderDepth + 1,
PanelWidth: childLayout[WidthSym],
@@ -889,14 +945,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
addDocTab = action((doc: Doc, where: string) => {
if (where === "inParent") {
- const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
- doc.x = pt[0];
- doc.y = pt[1];
- this.props.addDocument(doc);
- return true;
+ if (doc instanceof Doc) {
+ const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+ doc.x = pt[0];
+ doc.y = pt[1];
+ return this.props.addDocument(doc);
+ } else {
+ (doc as any as Doc[]).forEach(doc => {
+ const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+ doc.x = pt[0];
+ doc.y = pt[1];
+ });
+ return this.props.addDocument(doc);
+ }
}
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
- this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
+ this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List<Doc>(doc as any as Doc[]);
return true;
}
return this.props.addDocTab(doc, where);
@@ -907,9 +971,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
}
const layoutDoc = Doc.Layout(params.pair.layout);
- const { x, y, z, color, zIndex } = params.pair.layout;
+ const { x, y, opacity } = this.Document.timecode === undefined ? params.pair.layout :
+ CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.timecode);
+ if (this.Document.timecode !== undefined) {
+ const time = this.Document.timecode || 0;
+ }
+ const { z, color, zIndex } = params.pair.layout;
return {
x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
+ transition: StrCast(layoutDoc.transition), opacity: Cast(opacity, "number", null),
width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
};
}
@@ -994,11 +1064,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return { newPool, computedElementData: this.doFreeformLayout(newPool) };
}
- childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
get doLayoutComputation() {
const { newPool, computedElementData } = this.doInternalLayoutComputation;
- runInAction(() =>
- Array.from(newPool.entries()).map(entry => {
+ const array = Array.from(newPool.entries());
+ runInAction(() => {
+ for (const entry of array) {
const lastPos = this._cachedPool.get(entry[0]); // last computed pos
const newPos = entry[1];
if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
@@ -1007,7 +1077,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
}
- }));
+ }
+ });
this._cachedPool.clear();
Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
const elements: ViewDefResult[] = computedElementData.slice();
@@ -1020,7 +1091,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
replica={entry[1].replica}
dataProvider={this.childDataProvider}
sizeProvider={this.childSizeProvider}
- LayoutDoc={this.childLayoutDocFunc}
pointerEvents={
this.backgroundActive ?
true :
@@ -1033,11 +1103,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
}));
+ if (this.props.isAnnotationOverlay) {
+ this.props.Document.scale = Math.max(1, NumCast(this.props.Document.scale));
+ }
+
return elements;
}
+ @action
componentDidMount() {
- super.componentDidMount();
+ super.componentDidMount?.();
this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
@@ -1054,12 +1129,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
promoteCollection = undoBatch(action(() => {
- this.childDocs.forEach(doc => {
+ const childDocs = this.childDocs.slice();
+ childDocs.forEach(doc => {
const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = scr?.[0];
doc.y = scr?.[1];
- this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc);
});
+ this.props.addDocTab(childDocs as any as Doc, "inParent");
this.props.ContainingCollectionView?.removeDocument(this.props.Document);
}));
layoutDocsInGrid = () => {
@@ -1083,22 +1159,36 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
});
}, "arrange contents");
}
+ @undoBatch
+ @action
+ toggleNativeDimensions = () => {
+ Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
+ }
private thumbIdentifier?: number;
onContextMenu = (e: React.MouseEvent) => {
- if (this.props.children && this.props.annotationsKey) return;
+ if (this.props.annotationsKey) return;
+
+ ContextMenu.Instance.addItem({
+ description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
+ this._timelineVisible = !this._timelineVisible;
+ }), icon: this._timelineVisible ? faEyeSlash : faEye
+ });
+ ContextMenu.Instance.addItem({ description: "Advance", event: this.nextKeyframe, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+ ContextMenu.Instance.addItem({ description: "Backup ", event: this.prevKeyframe, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+
const options = ContextMenu.Instance.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
- optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+ optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+ optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
// layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
- optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" });
optionItems.push({
description: "Import document", icon: "upload", event: ({ x, y }) => {
const input = document.createElement("input");
@@ -1126,10 +1216,60 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
input.click();
}
});
-
+ optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+
+ }
+ @observable _timelineVisible = false;
+
+ intersectRect(r1: { left: number, top: number, width: number, height: number },
+ r2: { left: number, top: number, width: number, height: number }) {
+ return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
}
+ @action
+ setupDragLines = () => {
+ const activeDocs = this.getActiveDocuments();
+ if (activeDocs.length > 50) {
+ DragManager.SetSnapLines([], []);
+ return;
+ }
+ const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
+ const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
+ const docDims = (doc: Doc) => ({ 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 }) => {
+ if (this.intersectRect(docDims(doc), rect)) {
+ snappableDocs.push(doc);
+ }
+ };
+ const snappableDocs: Doc[] = []; // the set of documents in the visible viewport that we will try to snap to;
+ const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) };
+ this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
+ !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => isDocInView(doc, selRect)); // if not, see if there are background docs to snap to
+ !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => isDocInView(doc, otherBounds)); // if not, then why not snap to floating docs
+
+ const horizLines: number[] = [];
+ const vertLines: number[] = [];
+ snappableDocs.filter(doc => !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => {
+ const { left, top, width, height } = docDims(doc);
+ const topLeftInScreen = this.getTransform().inverse().transformPoint(left, top);
+ const docSize = this.getTransform().inverse().transformDirection(width, height);
+
+ horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
+ vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]);// right line
+ });
+ DragManager.SetSnapLines(horizLines, vertLines);
+ }
+ onPointerOver = (e: React.PointerEvent) => {
+ if (SnappingManager.GetIsDragging()) {
+ this.setupDragLines();
+ }
+ e.stopPropagation();
+ }
+
+ @observable private _hLines: number[] | undefined;
+ @observable private _vLines: number[] | undefined;
+
private childViews = () => {
const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
return [
@@ -1162,12 +1302,21 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return false;
});
@computed get marqueeView() {
- return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
- addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
+ return <MarqueeView {...this.props}
+ nudge={this.nudge}
+ addDocTab={this.addDocTab}
+ activeDocuments={this.getActiveDocuments}
+ selectDocuments={this.selectDocuments}
+ addDocument={this.addDocument}
+ addLiveTextDocument={this.addLiveTextBox}
+ getContainerTransform={this.getContainerTransform}
+ getTransform={this.getTransform}
+ isAnnotationOverlay={this.isAnnotationOverlay}>
<CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} shifted={!this.nativeHeight && !this.isAnnotationOverlay}
easing={this.easing} viewDefDivClick={this.props.viewDefDivClick} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
{this.children}
</CollectionFreeFormViewPannableContents>
+ {this._timelineVisible ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
</MarqueeView>;
}
@@ -1179,21 +1328,18 @@ 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.layoutDoc.isBackground && SelectionManager.GetIsDragging(); }
+ @computed get backgroundEvents() { return this.layoutDoc.isBackground && SnappingManager.GetIsDragging(); }
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
- // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
- // this.Document.fitX = this.contentBounds && this.contentBounds.x;
- // this.Document.fitY = this.contentBounds && this.contentBounds.y;
- // this.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x);
- // this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
- // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey.
- // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
- return <div className={"collectionfreeformview-container"}
- ref={this.createDashEventsTarget}
- onWheel={this.onPointerWheel} onClick={this.onClick} //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
+ return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget}
+ onPointerOver={this.onPointerOver}
+ onWheel={this.onPointerWheel}
+ onClick={this.onClick}
+ onPointerDown={this.onPointerDown}
+ onPointerMove={this.onCursorMove}
+ onDrop={this.onExternalDrop.bind(this)}
+ onContextMenu={this.onContextMenu}
style={{
pointerEvents: this.backgroundEvents ? "all" : undefined,
transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
@@ -1204,19 +1350,35 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
{!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
this.placeholder : this.marqueeView}
<CollectionFreeFormOverlayView elements={this.elementFunc} />
-
+ {this.isAnnotationOverlay ? (null) :
+ <>
+ <div key="fwd" className="backKeyframe" onClick={this.nextKeyframe}>
+ <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ </div>
+ <div key="fwd" className="numKeyframe" >
+ {NumCast(this.props.Document.timecode)}
+ </div>
+ <div key="back" className="fwdKeyframe" onClick={this.prevKeyframe}>
+ <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+ </div>
+ </>}
<div className={"pullpane-indicator"}
style={{
display: this._pullDirection ? "block" : "none",
top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
- // left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto",
width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
}}>
</div>
-
+ {// uncomment to show snap lines
+ <div className="snapLines" style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none" }}>
+ <svg style={{ width: "100%", height: "100%" }}>
+ {this._hLines?.map(l => <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />)}
+ {this._vLines?.map(l => <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />)}
+ </svg>
+ </div>}
</div >;
}
}