aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2019-10-09 23:30:36 -0400
committerBob Zeleznik <zzzman@gmail.com>2019-10-09 23:30:36 -0400
commit5617124a794b8d256e3d24b143b8dd2403cce993 (patch)
tree673a288cdbc853d697c2d8294312f563ae9f4e60 /src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
parent2cc7ca6b267d9dd6b1683d59b6966a021339bc18 (diff)
parent3b1345616bf2f7101a21c182e0c9719b1e31f899 (diff)
merged
Diffstat (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx932
1 files changed, 351 insertions, 581 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0a2dcbe3b..38488f033 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,22 +1,23 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
+import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faFileUpload } from "@fortawesome/free-solid-svg-icons";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils";
+import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+import { aggregateBounds, emptyFunction, intersectRect, returnEmptyString, returnOne, Utils } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { DocServer } from "../../../DocServer";
import { Docs } from "../../../documents/Documents";
import { DocumentType } from "../../../documents/DocumentTypes";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
-import { CompileScript } from "../../../util/Scripting";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch, UndoManager } from "../../../util/UndoManager";
@@ -24,22 +25,21 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"
import { ContextMenu } from "../../ContextMenu";
import { ContextMenuProps } from "../../ContextMenuItem";
import { InkingCanvas } from "../../InkingCanvas";
-import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
+import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentContentsView } from "../../nodes/DocumentContentsView";
-import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
+import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView";
+import { FormattedTextBox } from "../../nodes/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
-import { OverlayElementOptions, OverlayView } from "../../OverlayView";
import PDFMenu from "../../pdf/PDFMenu";
-import { ScriptBox } from "../../ScriptBox";
import { CollectionSubView } from "../CollectionSubView";
+import { computePivotLayout, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
-import { DocServer } from "../../../DocServer";
-library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
+library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
export const panZoomSchema = createSchema({
panX: "number",
@@ -47,245 +47,80 @@ export const panZoomSchema = createSchema({
scale: "number",
arrangeScript: ScriptField,
arrangeInit: ScriptField,
+ useClusters: "boolean",
+ isRuleProvider: "boolean",
+ fitToBox: "boolean",
+ panTransformType: "string",
});
-export interface ViewDefBounds {
- x: number;
- y: number;
- z?: number;
- width: number;
- height: number;
-}
-
-export interface ViewDefResult {
- ele: JSX.Element;
- bounds?: ViewDefBounds;
-}
-
-export namespace PivotView {
-
- export interface PivotData {
- type: string;
- text: string;
- x: number;
- y: number;
- width: number;
- height: number;
- fontSize: number;
- }
-
- export const elements = (target: CollectionFreeFormView) => {
- let collection = target.Document;
- const field = StrCast(collection.pivotField) || "title";
- const width = NumCast(collection.pivotWidth) || 200;
- const groups = new Map<FieldResult<Field>, Doc[]>();
-
- for (const doc of target.childDocs) {
- const val = doc[field];
- if (val === undefined) continue;
-
- const l = groups.get(val);
- if (l) {
- l.push(doc);
- } else {
- groups.set(val, [doc]);
- }
- }
-
- let minSize = Infinity;
-
- groups.forEach((val, key) => minSize = Math.min(minSize, val.length));
-
- const numCols = NumCast(collection.pivotNumColumns) || Math.ceil(Math.sqrt(minSize));
- const fontSize = NumCast(collection.pivotFontSize);
-
- const docMap = new Map<Doc, ViewDefBounds>();
- const groupNames: PivotData[] = [];
-
- let x = 0;
- groups.forEach((val, key) => {
- let y = 0;
- let xCount = 0;
- groupNames.push({
- type: "text",
- text: String(key),
- x,
- y: width + 50,
- width: width * 1.25 * numCols,
- height: 100, fontSize: fontSize
- });
- for (const doc of val) {
- docMap.set(doc, {
- x: x + xCount * width * 1.25,
- y: -y,
- width,
- height: width
- });
- xCount++;
- if (xCount >= numCols) {
- xCount = 0;
- y += width * 1.25;
- }
- }
- x += width * 1.25 * (numCols + 1);
- });
-
- let elements = target.viewDefsToJSX(groupNames);
- let docViews = target.childDocs.reduce((prev, doc) => {
- let minim = BoolCast(doc.isMinimized);
- if (minim === undefined || !minim) {
- let defaultPosition = (): ViewDefBounds => {
- return {
- x: NumCast(doc.x),
- y: NumCast(doc.y),
- z: NumCast(doc.z),
- width: NumCast(doc.width),
- height: NumCast(doc.height)
- };
- };
- const pos = docMap.get(doc) || defaultPosition();
- prev.push({
- ele: <CollectionFreeFormDocumentView
- key={doc[Id]}
- x={pos.x}
- y={pos.y}
- width={pos.width}
- height={pos.height}
- {...target.getChildDocumentViewProps(doc)}
- />,
- bounds: {
- x: pos.x,
- y: pos.y,
- z: pos.z,
- width: NumCast(pos.width),
- height: NumCast(pos.height)
- }
- });
- }
- return prev;
- }, elements);
-
- target.resetSelectOnLoaded();
-
- return docViews;
- };
-
-}
-
-type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>;
-const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema);
+type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
+const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
@observer
export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
- private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
private _lastX: number = 0;
private _lastY: number = 0;
- private get _pwidth() { return this.props.PanelWidth(); }
- private get _pheight() { return this.props.PanelHeight(); }
- private inkKey = "ink";
- private _childLayoutDisposer?: IReactionDisposer;
-
- componentDidMount() {
- this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)],
- async (args) => {
- this.childDocs.filter(doc => args[1] instanceof Doc || doc.layout instanceof Doc).map(async doc => {
- if (!Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc)) {
- Doc.ApplyTemplateTo(args[1] as Doc, (await doc), undefined);
- }
- });
- });
- }
- componentWillUnmount() {
- this._childLayoutDisposer && this._childLayoutDisposer();
- }
-
- get parentScaling() {
- return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;
- }
-
- ComputeContentBounds(boundsList: { x: number, y: number, width: number, height: number }[]) {
- let bounds = boundsList.reduce((bounds, b) => {
- var [sptX, sptY] = [b.x, b.y];
- let [bptX, bptY] = [sptX + NumCast(b.width, 1), sptY + NumCast(b.height, 1)];
- return {
- x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
- r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
- };
- }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
- return bounds;
- }
-
- @computed get contentBounds() {
- let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
- let res = {
- panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
- panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
- scale: (bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1) / this.parentScaling
- };
- if (res.scale === 0) res.scale = 1;
- return res;
- }
-
- @computed get fitToBox() { return this.props.fitToBox || this.props.Document.fitToBox; }
- @computed get nativeWidth() { return this.fitToBox ? 0 : this.Document.nativeWidth || 0; }
- @computed get nativeHeight() { return this.fitToBox ? 0 : this.Document.nativeHeight || 0; }
- public get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt')
+ private _clusterDistance: number = 75;
+ private _hitCluster = false;
+ @observable _clusterSets: (Doc[])[] = [];
+
+ @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
+ @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
+ @computed get contentBounds() { return aggregateBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); }
+ @computed get nativeWidth() { return this.fitToContent ? 0 : this.Document.nativeWidth || 0; }
+ @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
+ private get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt')
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
- private panX = () => this.contentBounds.panX;
- private panY = () => this.contentBounds.panY;
- private zoomScaling = () => this.contentBounds.scale;
- private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections
- private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
+ private easing = () => this.props.Document.panTransformType === "Ease";
+ private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document.panX || 0;
+ private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document.panY || 0;
+ private zoomScaling = () => (1 / this.parentScaling) * (this.fitToContent ?
+ Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), 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 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);
private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
private addLiveTextBox = (newBox: Doc) => {
- this._selectOnLoaded = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ 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
+ let maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
+ let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0;
+ if (heading === 0) {
+ let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
+ DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
+ heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
+ }
+ !this.Document.isRuleProvider && (newBox.heading = heading);
this.addDocument(newBox, false);
}
private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
- this.props.addDocument(newBox, false);
- this.bringToFront(newBox);
- this.updateClusters();
- return true;
+ let added = this.props.addDocument(newBox, false);
+ added && this.bringToFront(newBox);
+ added && this.updateCluster(newBox);
+ return added;
}
private selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
- SelectionManager.SelectDoc(dv!, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
}
+ public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
+
public getActiveDocuments = () => {
- const curPage = FieldValue(this.Document.curPage, -1);
- return this.childDocs.filter(doc => {
- var page = NumCast(doc.page, -1);
- return page === curPage || page === -1;
- });
+ return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
}
@computed get fieldExtensionDoc() {
- return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true");
- }
-
- 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);
- }
- _clusterDistance = 75;
- boundsOverlap(doc: Doc, doc2: Doc) {
- var x2 = NumCast(doc2.x) - this._clusterDistance;
- var y2 = NumCast(doc2.y) - this._clusterDistance;
- var w2 = NumCast(doc2.width) + this._clusterDistance;
- var h2 = NumCast(doc2.height) + this._clusterDistance;
- var x = NumCast(doc.x) - this._clusterDistance;
- var y = NumCast(doc.y) - this._clusterDistance;
- var w = NumCast(doc.width) + this._clusterDistance;
- var h = NumCast(doc.height) + this._clusterDistance;
- if (doc.z === doc2.z && this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
- return true;
- }
- return false;
+ return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
+ }
+
+ @action
+ onDrop = (e: React.DragEvent): Promise<void> => {
+ var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
+ return super.onDrop(e, { x: pt[0], y: pt[1] });
}
+
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
@@ -297,11 +132,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
if (de.data instanceof DragManager.DocumentDragData) {
if (de.data.droppedDocuments.length) {
let z = NumCast(de.data.droppedDocuments[0].z);
- let x = (z ? xpo : xp) - de.data.xOffset;
- let y = (z ? ypo : yp) - de.data.yOffset;
+ let x = (z ? xpo : xp) - de.data.offset[0];
+ let y = (z ? ypo : yp) - de.data.offset[1];
let dropX = NumCast(de.data.droppedDocuments[0].x);
let dropY = NumCast(de.data.droppedDocuments[0].y);
- de.data.droppedDocuments.forEach(d => {
+ de.data.droppedDocuments.forEach(action((d: Doc) => {
d.x = x + NumCast(d.x) - dropX;
d.y = y + NumCast(d.y) - dropY;
if (!NumCast(d.width)) {
@@ -313,16 +148,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
d.height = nw && nh ? nh / nw * NumCast(d.width) : 300;
}
this.bringToFront(d);
- });
+ }));
- this.updateClusters();
+ de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]);
}
}
else if (de.data instanceof DragManager.AnnotationDragData) {
if (de.data.dropDocument) {
let dragDoc = de.data.dropDocument;
- let x = xp - de.data.xOffset;
- let y = yp - de.data.yOffset;
+ let x = xp - de.data.offset[0];
+ let y = yp - de.data.offset[1];
let dropX = NumCast(de.data.dropDocument.x);
let dropY = NumCast(de.data.dropDocument.y);
dragDoc.x = x + NumCast(dragDoc.x) - dropX;
@@ -336,20 +171,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return false;
}
- tryDragCluster(e: PointerEvent) {
- let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
- let cluster = this.childDocs.reduce((cluster, cd) => {
+ pickCluster(probe: number[]) {
+ return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
let cx = NumCast(cd.x) - this._clusterDistance;
let cy = NumCast(cd.y) - this._clusterDistance;
let cw = NumCast(cd.width) + 2 * this._clusterDistance;
let ch = NumCast(cd.height) + 2 * this._clusterDistance;
- if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) {
- return NumCast(cd.cluster);
- }
- return cluster;
+ return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ?
+ NumCast(cd.cluster) : cluster;
}, -1);
+ }
+ tryDragCluster(e: PointerEvent) {
+ let cluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
if (cluster !== -1) {
- let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
+ let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster);
// hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view
let prevSelected = SelectionManager.SelectedDocuments();
@@ -358,13 +193,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
SelectionManager.DeselectAll();
prevSelected.map(dv => SelectionManager.SelectDoc(dv, true));
- let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
+ let de = new DragManager.DocumentDragData(eles);
de.moveDocument = this.props.moveDocument;
const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
- const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top);
+ de.offset = this.getTransform().transformDirection(e.x - left, e.y - top);
de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
- de.xOffset = xoff;
- de.yOffset = yoff;
DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {
handlers: { dragComplete: action(emptyFunction) },
hideSource: !de.dropAction
@@ -374,56 +207,68 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return false;
}
- @observable sets: (Doc[])[] = [];
+
+ @undoBatch
+ updateClusters(useClusters: boolean) {
+ this.props.Document.useClusters = useClusters;
+ this._clusterSets.length = 0;
+ this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
+ }
+
+ @undoBatch
@action
- updateClusters() {
- this.sets.length = 0;
- this.childDocs.map(c => {
- let included = [];
- for (let i = 0; i < this.sets.length; i++) {
- for (let member of this.sets[i]) {
- if (this.boundsOverlap(c, member)) {
- included.push(i);
- break;
- }
+ updateCluster(doc: Doc) {
+ let childLayouts = this.childLayoutPairs.map(pair => pair.layout);
+ if (this.props.Document.useClusters) {
+ this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
+ let preferredInd = NumCast(doc.cluster);
+ doc.cluster = -1;
+ this._clusterSets.map((set, i) => set.map(member => {
+ if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+ doc.cluster = i;
}
+ }));
+ if (doc.cluster === -1 && preferredInd !== -1 && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+ doc.cluster = preferredInd;
}
- if (included.length === 0) {
- this.sets.push([c]);
- } else if (included.length === 1) {
- this.sets[included[0]].push(c);
- } else {
- this.sets[included[0]].push(c);
- for (let s = 1; s < included.length; s++) {
- this.sets[included[0]].push(...this.sets[included[s]]);
- this.sets[included[s]].length = 0;
+ this._clusterSets.map((set, i) => {
+ if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
+ doc.cluster = i;
}
+ });
+ if (doc.cluster === -1) {
+ doc.cluster = this._clusterSets.length;
+ this._clusterSets.push([doc]);
+ } else {
+ for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
+ this._clusterSets[doc.cluster].push(doc);
}
- });
- this.sets.map((set, i) => set.map(member => member.cluster = i));
+ }
}
getClusterColor = (doc: Doc) => {
- if (this.props.Document.useClusters) {
- let cluster = NumCast(doc.cluster);
- if (this.sets.length <= cluster) {
- setTimeout(() => this.updateClusters(), 0);
- return;
+ let clusterColor = "";
+ let cluster = NumCast(doc.cluster);
+ if (this.Document.useClusters) {
+ if (this._clusterSets.length <= cluster) {
+ setTimeout(() => this.updateCluster(doc), 0);
+ } else {
+ // choose a cluster color from a palette
+ let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
+ clusterColor = colors[cluster % colors.length];
+ let set = this._clusterSets[cluster] && this._clusterSets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor));
+ // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
+ set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
+ set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
}
- let set = this.sets.length > cluster ? this.sets[cluster] : undefined;
- let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
- let clusterColor = colors[cluster % colors.length];
- set && set.filter(s => !s.isBackground).map(s =>
- s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor)));
- set && set.filter(s => s.isBackground).map(s =>
- s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor)));
- return clusterColor;
}
- return "";
+ return clusterColor;
}
@action
onPointerDown = (e: React.PointerEvent): void => {
+ if (e.nativeEvent.cancelBubble) return;
+ this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -441,8 +286,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerMove = (e: PointerEvent): void => {
- if (!e.cancelBubble) {
- if (this.props.Document.useClusters && this.tryDragCluster(e)) {
+ if (!e.cancelBubble && !this.isAnnotationOverlay) {
+ if (this._hitCluster && this.tryDragCluster(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();
document.removeEventListener("pointermove", this.onPointerMove);
@@ -451,7 +296,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
let x = this.Document.panX || 0;
let y = this.Document.panY || 0;
- let docs = this.childDocs || [];
+ let docs = this.childLayoutPairs.map(pair => pair.layout);
let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
if (!this.isAnnotationOverlay) {
PDFMenu.Instance.fadeOut(true);
@@ -476,14 +321,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
});
}
- let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling(),
- this._pheight / this.zoomScaling());
- let panelwidth = panelDim[0];
- let panelheight = panelDim[1];
- if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2;
- if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2;
- if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2;
- if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2;
+ let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
+ let panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale,
+ this.props.PanelHeight() / this.zoomScaling() * cscale);
+ if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2;
+ if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2;
+ if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2;
+ if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2;
}
this.setPan(x - dx, y - dy);
this._lastX = e.pageX;
@@ -495,34 +339,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (BoolCast(this.props.Document.lockedPosition)) return;
+ if (this.props.Document.lockedPosition || this.props.Document.inOverlay || this.isAnnotationOverlay) return;
if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
e.stopPropagation();
- return;
- }
-
- let childSelected = this.childDocs.some(doc => {
- var dv = DocumentManager.Instance.getDocumentView(doc);
- return dv && SelectionManager.IsSelected(dv) ? true : false;
- });
- if (!this.props.isSelected() && !childSelected && this.props.renderDepth > 0) {
- return;
}
- e.stopPropagation();
-
- // bcz: this changes the nativewidth/height, but ImageBox will just revert it back to its defaults. need more logic to fix.
- // if (e.ctrlKey && this.props.Document.scrollHeight === undefined) {
- // let deltaScale = (1 - (e.deltaY / coefficient));
- // let nw = this.nativeWidth * deltaScale;
- // let nh = this.nativeHeight * deltaScale;
- // if (nw && nh) {
- // this.props.Document.nativeWidth = nw;
- // this.props.Document.nativeHeight = nh;
- // }
- // e.preventDefault();
- // }
- // else
- {
+ else if (this.props.active()) {
+ e.stopPropagation();
let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1;
if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
deltaScale = 1 / this.zoomScaling();
@@ -534,13 +356,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
let 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);
- e.preventDefault();
}
}
@action
setPan(panX: number, panY: number) {
- if (!BoolCast(this.props.Document.lockedPosition)) {
+ if (!this.props.Document.lockedPosition || this.props.Document.inOverlay) {
this.props.Document.panTransformType = "None";
var scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
@@ -550,125 +371,109 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
- @action
- onDrop = (e: React.DragEvent): void => {
- var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
- super.onDrop(e, { x: pt[0], y: pt[1] });
- }
-
- onDragOver = (): void => {
- }
-
bringToFront = (doc: Doc, sendToBack?: boolean) => {
if (sendToBack || doc.isBackground) {
doc.zIndex = 0;
- return;
}
- const docs = this.childDocs;
- 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;
- }
-
- focusDocument = (doc: Doc, willZoom: boolean, scale?: number) => {
- const panX = this.Document.panX;
- const panY = this.Document.panY;
- const id = this.Document[Id];
+ 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;
+ }
+ }
+
+ focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {
const state = HistoryUtil.getState();
- state.initializers = state.initializers || {};
// TODO This technically isn't correct if type !== "doc", as
// currently nothing is done, but we should probably push a new state
- if (state.type === "doc" && panX !== undefined && panY !== undefined) {
- const init = state.initializers[id];
+ if (state.type === "doc" && this.Document.panX !== undefined && this.Document.panY !== undefined) {
+ const init = state.initializers![this.Document[Id]];
if (!init) {
- state.initializers[id] = {
- panX, panY
- };
+ state.initializers![this.Document[Id]] = { panX: this.Document.panX, panY: this.Document.panY };
HistoryUtil.pushState(state);
- } else if (init.panX !== panX || init.panY !== panY) {
- init.panX = panX;
- init.panY = panY;
+ } else if (init.panX !== this.Document.panX || init.panY !== this.Document.panY) {
+ init.panX = this.Document.panX;
+ init.panY = this.Document.panY;
HistoryUtil.pushState(state);
}
}
SelectionManager.DeselectAll();
- const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
- const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
- const newState = HistoryUtil.getState();
- (newState.initializers || (newState.initializers = {}))[id] = { panX: newPanX, panY: newPanY };
- HistoryUtil.pushState(newState);
- this.setPan(newPanX, newPanY);
-
- this.props.Document.panTransformType = "Ease";
- this.props.focus(this.props.Document);
- if (willZoom) {
- this.setScaleToZoom(doc, scale);
+ if (this.props.Document.scrollHeight) {
+ let annotOn = Cast(doc.annotationOn, Doc) as Doc;
+ if (!annotOn) {
+ this.props.focus(doc);
+ } else {
+ let contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn.height);
+ let offset = annotOn && (contextHgt / 2 * 96 / 72);
+ this.props.Document.scrollY = NumCast(doc.y) - offset;
+ }
+ } else {
+ const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
+ const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
+ const newState = HistoryUtil.getState();
+ newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
+ HistoryUtil.pushState(newState);
+
+ let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
+
+ this.setPan(newPanX, newPanY);
+ this.Document.panTransformType = "Ease";
+ Doc.BrushDoc(this.props.Document);
+ this.props.focus(this.props.Document);
+ willZoom && this.setScaleToZoom(doc, scale);
+
+ afterFocus && setTimeout(() => {
+ if (afterFocus && afterFocus()) {
+ this.Document.panX = savedState.px;
+ this.Document.panY = savedState.py;
+ this.Document.scale = savedState.s;
+ this.Document.panTransformType = savedState.pt;
+ }
+ }, 1000);
}
}
setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
- let p = this.props;
- let PanelHeight = p.PanelHeight();
- let panelWidth = p.PanelWidth();
-
- let docHeight = NumCast(doc.height);
- let docWidth = NumCast(doc.width);
- let targetHeight = scale * PanelHeight;
- let targetWidth = scale * panelWidth;
-
- let maxScaleX: number = targetWidth / docWidth;
- let maxScaleY: number = targetHeight / docHeight;
- let maxApplicableScale = Math.min(maxScaleX, maxScaleY);
- this.Document.scale = maxApplicableScale;
+ this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc.width), this.props.PanelHeight() / NumCast(doc.height));
}
zoomToScale = (scale: number) => {
this.Document.scale = scale;
}
- getScale = () => this.Document.scale ? this.Document.scale : 1;
+ getScale = () => this.Document.scale || 1;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
+ ...this.props,
DataDoc: childData,
Document: childLayout,
- addDocument: this.props.addDocument,
- removeDocument: this.props.removeDocument,
- moveDocument: this.props.moveDocument,
- onClick: this.props.onClick,
+ ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves
+ 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
ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
renderDepth: this.props.renderDepth + 1,
- selectOnLoad: childLayout[Id] === this._selectOnLoaded,
PanelWidth: childLayout[WidthSym],
PanelHeight: childLayout[HeightSym],
ContentScaling: returnOne,
ContainingCollectionView: this.props.CollectionView,
+ ContainingCollectionDoc: this.props.Document,
focus: this.focusDocument,
backgroundColor: this.getClusterColor,
parentActive: this.props.active,
- whenActiveChanged: this.props.whenActiveChanged,
bringToFront: this.bringToFront,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
zoomToScale: this.zoomToScale,
getScale: this.getScale
};
}
getDocumentViewProps(layoutDoc: Doc): DocumentViewProps {
return {
- DataDoc: this.props.DataDoc,
- Document: this.props.Document,
- addDocument: this.props.addDocument,
- removeDocument: this.props.removeDocument,
- moveDocument: this.props.moveDocument,
- onClick: this.props.onClick,
+ ...this.props,
ScreenToLocalTransform: this.getTransform,
- renderDepth: this.props.renderDepth,
- selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
PanelWidth: layoutDoc[WidthSym],
PanelHeight: layoutDoc[HeightSym],
ContentScaling: returnOne,
@@ -676,206 +481,202 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
focus: this.focusDocument,
backgroundColor: returnEmptyString,
parentActive: this.props.active,
- whenActiveChanged: this.props.whenActiveChanged,
bringToFront: this.bringToFront,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
zoomToScale: this.zoomToScale,
getScale: this.getScale
};
}
- getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } {
- const result = script.script.run(params);
- if (!result.success) {
- return {};
+ getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } {
+ const script = this.Document.arrangeScript;
+ const result = script && script.script.run(params, console.log);
+ if (result && result.success) {
+ return { ...result, transition: "transform 1s" };
}
- let doc = params.doc;
- return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result;
+ return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") };
}
viewDefsToJSX = (views: any[]) => {
- let elements: ViewDefResult[] = [];
- if (Array.isArray(views)) {
- elements = views.reduce<typeof elements>((prev, ele) => {
- const jsx = this.viewDefToJSX(ele);
- jsx && prev.push(jsx);
- return prev;
- }, elements);
- }
- return elements;
+ return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
}
private viewDefToJSX(viewDef: any): Opt<ViewDefResult> {
if (viewDef.type === "text") {
- const text = Cast(viewDef.text, "string");
+ const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
const x = Cast(viewDef.x, "number");
const y = Cast(viewDef.y, "number");
const z = Cast(viewDef.z, "number");
const width = Cast(viewDef.width, "number");
const height = Cast(viewDef.height, "number");
const fontSize = Cast(viewDef.fontSize, "number");
- if ([text, x, y, width, height].some(val => val === undefined)) {
- return undefined;
- }
-
- return {
- ele: <div className="collectionFreeform-customText" style={{
- transform: `translate(${x}px, ${y}px)`,
- width, height, fontSize
- }}>{text}</div>, bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
- };
+ return [text, x, y, width, height].some(val => val === undefined) ? undefined :
+ {
+ ele: <div className="collectionFreeform-customText" style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+ {text}
+ </div>,
+ bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
+ };
}
}
- @computed.struct
- get elements() {
- if (this.Document.usePivotLayout) return PivotView.elements(this);
- let curPage = FieldValue(this.Document.curPage, -1);
- const initScript = this.Document.arrangeInit;
- const script = this.Document.arrangeScript;
- let state: any = undefined;
- const docs = this.childDocs;
- let elements: ViewDefResult[] = [];
- if (initScript) {
- const initResult = initScript.script.run({ docs, collection: this.Document });
- if (initResult.success) {
- const result = initResult.result;
- const { state: scriptState, views } = result;
- state = scriptState;
- elements = this.viewDefsToJSX(views);
- }
+ lookupLayout = (doc: Doc, dataDoc?: Doc) => {
+ let data: any = undefined;
+ let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] };
+ switch (this.Document.freeformLayoutEngine) {
+ case "pivot": computedElementData = this.doPivotLayout; break;
+ default: computedElementData = this.doFreeformLayout; break;
}
- let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => {
- var page = NumCast(doc.page, -1);
- if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) {
- let minim = BoolCast(doc.isMinimized);
- if (minim === undefined || !minim) {
- const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) :
- { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
- state = pos.state === undefined ? state : pos.state;
- let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, doc);
- if (pair.layout && !(pair.data instanceof Promise)) {
- prev.push({
- ele: <CollectionFreeFormDocumentView key={doc[Id]}
- x={script ? pos.x : undefined} y={script ? pos.y : undefined}
- width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
- bounds: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined
- });
- }
- }
+ computedElementData.map.forEach((value: any, key: { layout: Doc, data?: Doc }) => {
+ if (key.layout === doc && key.data === dataDoc) {
+ data = value;
}
- return prev;
- }, elements);
-
- this.resetSelectOnLoaded();
+ });
+ return data && { x: data.x, y: data.y, z: data.z, width: data.width, height: data.height, transition: data.transition };
+ }
- return docviews;
+ @computed
+ get doPivotLayout() {
+ return computePivotLayout(this.props.Document, this.childDocs,
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX);
}
- resetSelectOnLoaded = () => setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
+ @computed
+ get doFreeformLayout() {
+ let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
+ let layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
+ const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
+ let state = initResult && initResult.success ? initResult.result.scriptState : undefined;
+ let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
- @computed.struct
- get views() {
- let source = this.elements;
- return source.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
+ const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
+ state = pos.state === undefined ? state : pos.state;
+ layoutPoolData.set(pair, pos);
+ });
+ return { map: layoutPoolData, elements: elements };
}
- @computed.struct
- get overlayViews() {
- return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
+
+ @computed
+ get doLayoutComputation() {
+ let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] };
+ switch (this.Document.freeformLayoutEngine) {
+ case "pivot": computedElementData = this.doPivotLayout; break;
+ default: computedElementData = this.doFreeformLayout; break;
+ }
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
+ computedElementData.elements.push({
+ ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.lookupLayout}
+ ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
+ jitterRotation={NumCast(this.props.Document.jitterRotation)} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
+ bounds: this.lookupLayout(pair.layout, pair.data)
+ }));
+
+ return computedElementData;
}
+ @computed.struct get elements() { return this.doLayoutComputation.elements; }
+ @computed.struct get views() { return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
+ @computed.struct get overlayViews() { return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); }
@action
onCursorMove = (e: React.PointerEvent) => {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
- fitToContainer = async () => this.props.Document.fitToBox = !this.fitToBox;
-
- arrangeContents = async () => {
- const docs = await DocListCastAsync(this.Document[this.props.fieldKey]);
+ layoutDocsInGrid = () => {
UndoManager.RunInBatch(() => {
- if (docs) {
- let startX = this.Document.panX || 0;
- let x = startX;
- let y = this.Document.panY || 0;
- let i = 0;
- const width = Math.max(...docs.map(doc => NumCast(doc.width)));
- const height = Math.max(...docs.map(doc => NumCast(doc.height)));
- for (const doc of docs) {
- doc.x = x;
- doc.y = y;
- x += width + 20;
- if (++i === 6) {
- i = 0;
- x = startX;
- y += height + 20;
- }
+ const docs = DocListCast(this.Document[this.props.fieldKey]);
+ let startX = this.Document.panX || 0;
+ let x = startX;
+ let y = this.Document.panY || 0;
+ let i = 0;
+ const width = Math.max(...docs.map(doc => NumCast(doc.width)));
+ const height = Math.max(...docs.map(doc => NumCast(doc.height)));
+ for (const doc of docs) {
+ doc.x = x;
+ doc.y = y;
+ x += width + 20;
+ if (++i === 6) {
+ i = 0;
+ x = startX;
+ y += height + 20;
}
}
}, "arrange contents");
}
+ autoFormat = () => {
+ this.Document.isRuleProvider = !this.Document.isRuleProvider;
+ // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading.
+ this.Document.isRuleProvider && this.childLayoutPairs.map(pair =>
+ // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template)
+ DocListCast(pair.layout.layout instanceof Doc ? pair.layout.layout.data : pair.layout.data).map(heading => {
+ let headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading);
+ let headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout;
+ if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) {
+ Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor;
+ }
+ })
+ );
+ }
+
analyzeStrokes = async () => {
- let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
- if (!data) {
- return;
+ let data = Cast(this.fieldExtensionDoc.ink, InkField);
+ if (data) {
+ CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, ["inkAnalysis", "handwriting"], data.inkData);
}
- let relevantKeys = ["inkAnalysis", "handwriting"];
- CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, relevantKeys, data.inkData);
}
onContextMenu = (e: React.MouseEvent) => {
let layoutItems: ContextMenuProps[] = [];
+
+ if (this.childDocs.some(d => BoolCast(d.isTemplate))) {
+ layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
+ }
+ layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+ layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+ layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+ layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" });
+ layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
+ layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
+ layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
layoutItems.push({
- description: "Import document", icon: "upload", event: () => {
+ description: "Import document", icon: "upload", event: ({ x, y }) => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".zip";
input.onchange = async _e => {
- const files = input.files;
- if (!files) return;
- const file = files[0];
- let formData = new FormData();
- formData.append('file', file);
- formData.append('remap', "true");
const upload = Utils.prepend("/uploadDoc");
- const response = await fetch(upload, { method: "POST", body: formData });
- const json = await response.json();
- if (json === "error") {
- return;
- }
- const doc = await DocServer.GetRefField(json);
- if (!doc || !(doc instanceof Doc)) {
- return;
+ let formData = new FormData();
+ const file = input.files && input.files[0];
+ if (file) {
+ formData.append('file', file);
+ formData.append('remap', "true");
+ const response = await fetch(upload, { method: "POST", body: formData });
+ const json = await response.json();
+ if (json !== "error") {
+ const doc = await DocServer.GetRefField(json);
+ if (doc instanceof Doc) {
+ const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
+ doc.x = xx, doc.y = yy;
+ this.props.addDocument && this.props.addDocument(doc, false);
+ }
+ }
}
- const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY);
- doc.x = x, doc.y = y;
- this.props.addDocument &&
- this.props.addDocument(doc, false);
};
input.click();
}
});
- layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
- layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
- layoutItems.push({
- description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`,
- event: async () => {
- Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes
- Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white";
- this.props.Document.useClusters = !this.props.Document.useClusters;
- },
- icon: !this.props.Document.useClusters ? "braille" : "braille"
- });
+
layoutItems.push({
- description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`,
- event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground,
- icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard"
+ description: "Add Note ...",
+ subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({
+ description: (i + 1) + ": " + StrCast(note.title),
+ event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument({ width: 200, height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })),
+ icon: "eye"
+ })) as ContextMenuProps[],
+ icon: "eye"
});
- layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
- layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
}
@@ -884,49 +685,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
<CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
...this.views
]
- private overlayChildViews = () => {
- return [...this.overlayViews];
- }
-
- public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
- return () => {
- let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
- let overlayDisposer: () => void = emptyFunction;
- const script = Cast(doc[key], ScriptField);
- let originalText: string | undefined = undefined;
- if (script) originalText = script.script.originalScript;
- // tslint:disable-next-line: no-unnecessary-callback-wrapper
- let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
- const script = CompileScript(text, {
- params,
- requiredType,
- typecheck: false
- });
- if (!script.compiled) {
- onError(script.errors.map(error => error.messageText).join("\n"));
- return;
- }
- doc[key] = new ScriptField(script);
- overlayDisposer();
- }} />;
- overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
- };
- addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
- addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
- };
- }
-
render() {
- const easing = () => this.props.Document.panTransformType === "Ease";
- Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
+ // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
+ this.props.Document.fitX = this.contentBounds && this.contentBounds.x;
+ this.props.Document.fitY = this.contentBounds && this.contentBounds.y;
+ this.props.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x);
+ this.props.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
+ // if fieldExt 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
+ Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey);
return (
<div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onContextMenu={this.onContextMenu}>
+ style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (NumCast(this.props.Document.scrollHeight) ? NumCast(this.props.Document.scrollHeight) : "100%") : this.props.PanelHeight() }}
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
<MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
- addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
- getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
+ addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} setPreviewCursor={this.props.setPreviewCursor}
+ getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
<CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
- easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
<CollectionFreeFormLinksView {...this.props} key="freeformLinks">
<InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={"ink"} >
{this.childViews}
@@ -935,7 +711,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
</CollectionFreeFormViewPannableContents>
</MarqueeView>
- {this.overlayChildViews()}
+ {this.overlayViews}
<CollectionFreeFormOverlayView {...this.props} {...this.getDocumentViewProps(this.props.Document)} />
</div>
);
@@ -944,24 +720,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@observer
class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
- @computed get overlayView() {
- return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
- renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
- }
render() {
- return this.overlayView;
+ return <DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
+ renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />;
}
}
@observer
class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
- @computed get backgroundView() {
- let props = this.props;
- return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
- renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
- }
render() {
- return this.props.Document.backgroundLayout ? this.backgroundView : (null);
+ return !this.props.Document.backgroundLayout ? (null) :
+ (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
+ renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
}
}
@@ -982,8 +752,8 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
const ceny = this.props.centeringShiftY();
const panx = -this.props.panX();
const pany = -this.props.panY();
- const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
- return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
+ const zoom = this.props.zoomScaling();
+ return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)` }}>
{this.props.children}
</div>;
}