aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2019-11-26 19:50:28 -0500
committerSam Wilkins <samwilkins333@gmail.com>2019-11-26 19:50:28 -0500
commit328de7ab14cc56275082db0b3ffec8ad56258a3e (patch)
treebf697786b7b147944873bd3edaeed2077dd5c7ca /src/client/views/collections/collectionFreeForm
parent96519f4537895edcd1a23f353322459de328a330 (diff)
parente324248724a130a84b459a072dc846f500f8d9b0 (diff)
merge
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx34
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx55
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss11
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx47
4 files changed, 92 insertions, 55 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 42e987a9a..4a32c1647 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -6,6 +6,8 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
import { OverlayView, OverlayElementOptions } from "../../OverlayView";
import { emptyFunction } from "../../../../Utils";
import React = require("react");
+import { ObservableMap, runInAction } from "mobx";
+import { Id } from "../../../../new_fields/FieldSymbols";
import { DateField } from "../../../../new_fields/DateField";
interface PivotData {
@@ -42,8 +44,7 @@ function toLabel(target: FieldResult<Field>) {
return String(target);
}
-export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
- let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
+export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200);
const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
@@ -60,6 +61,8 @@ export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs:
const docMap = new Map<Doc, ViewDefBounds>();
const groupNames: PivotData[] = [];
+ const expander = 1.05;
+ const gap = .15;
let x = 0;
pivotColumnGroups.forEach((val, key) => {
let y = 0;
@@ -69,25 +72,31 @@ export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs:
text: toLabel(key),
x,
y: pivotAxisWidth + 50,
- width: pivotAxisWidth * 1.25 * numCols,
+ width: pivotAxisWidth * expander * numCols,
height: 100,
fontSize: NumCast(pivotDoc.pivotFontSize, 10)
});
for (const doc of val) {
let layoutDoc = Doc.Layout(doc);
+ let wid = pivotAxisWidth;
+ let hgt = layoutDoc.nativeWidth ? (NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
+ if (hgt > pivotAxisWidth) {
+ hgt = pivotAxisWidth;
+ wid = layoutDoc.nativeHeight ? (NumCast(layoutDoc.nativeWidth) / NumCast(layoutDoc.nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
+ }
docMap.set(doc, {
- x: x + xCount * pivotAxisWidth * 1.25,
+ x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2,
y: -y,
- width: pivotAxisWidth,
- height: layoutDoc.nativeWidth ? (NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth
+ width: wid,
+ height: hgt
});
xCount++;
if (xCount >= numCols) {
- xCount = 0;
- y += pivotAxisWidth * 1.25;
+ xCount = (pivotAxisWidth - wid) / 2;
+ y += pivotAxisWidth * expander;
}
}
- x += pivotAxisWidth * 1.25 * (numCols + 1);
+ x += pivotAxisWidth * (numCols * expander + gap);
});
childPairs.map(pair => {
@@ -99,9 +108,12 @@ export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs:
height: NumCast(pair.layout.height)
};
const pos = docMap.get(pair.layout) || defaultPosition;
- layoutPoolData.set(pair, { transition: "transform 1s", ...pos });
+ let data = poolData.get(pair.layout[Id]);
+ if (!data || pos.x !== data.x || pos.y !== data.y || pos.z !== data.z || pos.width !== data.width || pos.height !== data.height) {
+ runInAction(() => poolData.set(pair.layout[Id], { transition: "transform 1s", ...pos }));
+ }
});
- return { map: layoutPoolData, elements: viewDefsToJSX(groupNames) };
+ return { elements: viewDefsToJSX(groupNames) };
}
export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 837413842..73b45edc6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -6,7 +6,8 @@ import "./CollectionFreeFormLinkView.scss";
import React = require("react");
import v5 = require("uuid/v5");
import { DocumentType } from "../../../documents/DocumentTypes";
-import { observable, action } from "mobx";
+import { observable, action, reaction, IReactionDisposer } from "mobx";
+import { StrCast, Cast } from "../../../../new_fields/Types";
export interface CollectionFreeFormLinkViewProps {
A: DocumentView;
@@ -16,33 +17,57 @@ export interface CollectionFreeFormLinkViewProps {
@observer
export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
- @observable _alive: number = 0;
@observable _opacity: number = 1;
+ @observable _update: number = 0;
+ _anchorDisposer: IReactionDisposer | undefined;
@action
componentDidMount() {
- this._alive = 1;
- setTimeout(this.rerender, 50);
- setTimeout(action(() => this._opacity = 0.05), 50);
+ setTimeout(action(() => this._opacity = 0.05), 750);
+ this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform()],
+ () => {
+ let acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
+ let bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
+ let adiv = (acont.length ? acont[0] : this.props.A.ContentDiv!);
+ let bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv!);
+ let a = adiv.getBoundingClientRect();
+ let b = bdiv.getBoundingClientRect();
+ let abounds = adiv.parentElement!.getBoundingClientRect();
+ let bbounds = bdiv.parentElement!.getBoundingClientRect();
+ let apt = Utils.closestPtBetweenRectangles(abounds.left, abounds.top, abounds.width, abounds.height,
+ bbounds.left, bbounds.top, bbounds.width, bbounds.height,
+ a.left + a.width / 2, a.top + a.height / 2);
+ let bpt = Utils.closestPtBetweenRectangles(bbounds.left, bbounds.top, bbounds.width, bbounds.height,
+ abounds.left, abounds.top, abounds.width, abounds.height,
+ apt.point.x, apt.point.y);
+ let afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
+ let bfield = afield === "anchor1" ? "anchor2" : "anchor1";
+ this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
+ this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
+ this.props.A.props.Document[bfield + "_x"] = (bpt.point.x - bbounds.left) / bbounds.width * 100;
+ this.props.A.props.Document[bfield + "_y"] = (bpt.point.y - bbounds.top) / bbounds.height * 100;
+ this._update++;
+ }
+ , { fireImmediately: true });
}
@action
componentWillUnmount() {
- this._alive = 0;
+ this._anchorDisposer?.();
}
- rerender = action(() => {
- if (this._alive) {
- setTimeout(this.rerender, 50);
- this._alive++;
- }
- });
render() {
- let y = this._alive;
+ let y = this._update;
let acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
let bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
let a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect();
let b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect();
- let pt1 = Utils.getNearestPointInPerimeter(a.left, a.top, a.width, a.height, b.left + b.width / 2, b.top + b.height / 2);
- let pt2 = Utils.getNearestPointInPerimeter(b.left, b.top, b.width, b.height, a.left + a.width / 2, a.top + a.height / 2);
+ let apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height,
+ b.left, b.top, b.width, b.height,
+ a.left + a.width / 2, a.top + a.height / 2);
+ let bpt = Utils.closestPtBetweenRectangles(b.left, b.top, b.width, b.height,
+ a.left, a.top, a.width, a.height,
+ apt.point.x, apt.point.y);
+ let pt1 = [apt.point.x, apt.point.y];
+ let pt2 = [bpt.point.x, bpt.point.y];
return (<line key="linkLine" className="collectionfreeformlinkview-linkLine"
style={{ opacity: this._opacity }}
x1={`${pt1[0]}`} y1={`${pt1[1]}`}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index d2731703f..070d4aa65 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,5 +1,6 @@
@import "../../globalCssVariables";
+.collectionfreeformview-none,
.collectionfreeformview-ease {
position: inherit;
top: 0;
@@ -7,16 +8,14 @@
width: 100%;
height: 100%;
transform-origin: left top;
+ border-radius: inherit;
+}
+
+.collectionfreeformview-ease {
transition: transform 1s;
}
.collectionfreeformview-none {
- position: inherit;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- transform-origin: left top;
touch-action: none;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8791a256c..9bbaa20e7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -40,6 +40,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
import { computedFn, keepAlive } from "mobx-utils";
+import { TraceMobx } from "../../../../new_fields/util";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -73,7 +74,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private _layoutPoolData = new ObservableMap<string, any>();
public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
- @observable _layoutElements: ViewDefResult[] = [];
+ @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[])[] = [];
@computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
@@ -488,9 +489,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
let [x, y] = this.getTransform().transformPoint(pointX, pointY);
let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
- 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);
+ if (localTransform.Scale >= 0.15) {
+ 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);
+ }
}
@action
@@ -619,12 +622,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
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);
- const layoutDoc = Doc.Layout(params.doc);
- if (result && result.success) {
+ const result = this.Document.arrangeScript?.script.run(params, console.log);
+ if (result?.success) {
return { ...result, transition: "transform 1s" };
}
+ const layoutDoc = Doc.Layout(params.doc);
return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(layoutDoc.width, "number"), height: Cast(layoutDoc.height, "number") };
}
@@ -651,25 +653,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
- childDataProvider = computedFn((doc: Doc) => this._layoutPoolData.get(doc[Id]));
+ childDataProvider = computedFn(function childDataProvider(doc: Doc) { return (this as any)._layoutPoolData.get(doc[Id]); }.bind(this));
- get doPivotLayout() {
- return computePivotLayout(this.props.Document, this.childDocs,
+ doPivotLayout(poolData: ObservableMap<string, any>) {
+ return computePivotLayout(poolData, this.props.Document, this.childDocs,
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX);
}
- get doFreeformLayout() {
+ doFreeformLayout(poolData: ObservableMap<string, any>) {
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) : [];
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
+ const data = poolData.get(pair.layout[Id]);
const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
state = pos.state === undefined ? state : pos.state;
- let data = this._layoutPoolData.get(pair.layout[Id]);
if (!data || pos.x !== data.x || pos.y !== data.y || pos.z !== data.z || pos.width !== data.width || pos.height !== data.height || pos.transition !== data.transition) {
- runInAction(() => this._layoutPoolData.set(pair.layout[Id], pos));
+ runInAction(() => poolData.set(pair.layout[Id], pos));
}
});
return { elements: elements };
@@ -678,8 +680,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
get doLayoutComputation() {
let computedElementData: { elements: ViewDefResult[] };
switch (this.Document.freeformLayoutEngine) {
- case "pivot": computedElementData = this.doPivotLayout; break;
- default: computedElementData = this.doFreeformLayout; break;
+ case "pivot": computedElementData = this.doPivotLayout(this._layoutPoolData); break;
+ default: computedElementData = this.doFreeformLayout(this._layoutPoolData); break;
}
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
computedElementData.elements.push({
@@ -693,14 +695,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
componentDidMount() {
- this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
+ this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation; },
action((computation: { elements: ViewDefResult[] }) => computation && (this._layoutElements = computation.elements)),
- { fireImmediately: true });
+ { fireImmediately: true, name: "doLayout" });
}
componentWillUnmount() {
this._layoutComputeReaction && this._layoutComputeReaction();
}
- @computed.struct get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
+ @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
elementFunc = () => this._layoutElements;
@action
@@ -843,7 +845,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return eles;
}
render() {
- // trace();
+ TraceMobx();
// 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;
@@ -873,9 +875,8 @@ interface CollectionFreeFormOverlayViewProps {
@observer
class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOverlayViewProps>{
- @computed.struct get overlayViews() { return this.props.elements().filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); }
render() {
- return this.overlayViews;
+ return this.props.elements().filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
}
}
@@ -898,7 +899,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
const panx = -this.props.panX();
const pany = -this.props.panY();
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)` }}>
+ return <div className={freeformclass} style={{ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)` }}>
{this.props.children()}
</div>;
}