aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm
diff options
context:
space:
mode:
authorbob <bcz@cs.brown.edu>2020-01-31 15:46:42 -0500
committerbob <bcz@cs.brown.edu>2020-01-31 15:46:42 -0500
commit07e53a4a244eff804e47c6b48b8eb4f2a5096b0e (patch)
treeb8d3c7d0f59e81d18c72a4a92de7d4e94a109a2e /src/client/views/collections/collectionFreeForm
parente34620f6ded8afd6bb3f96c2c2889c9165f64569 (diff)
added collectionTime view
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx115
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx25
2 files changed, 137 insertions, 3 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 2069cf832..8a1147c61 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -10,6 +10,7 @@ import { ObservableMap, runInAction } from "mobx";
import { Id, ToString } from "../../../../new_fields/FieldSymbols";
import { ObjectField } from "../../../../new_fields/ObjectField";
import { RefField } from "../../../../new_fields/RefField";
+import { createPromiseCapability } from "../../../../../deploy/assets/pdf.worker";
interface PivotData {
type: string;
@@ -136,6 +137,120 @@ export function computePivotLayout(
};
}
+
+export function computeTimelineLayout(
+ poolData: ObservableMap<string, any>,
+ pivotDoc: Doc,
+ childDocs: Doc[],
+ childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: any) => ViewDefResult[]
+) {
+ const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200);
+ const pivotDateGroups = new Map<number, Doc[]>();
+
+ const timelineFieldKey = Field.toString(pivotDoc.pivotField as Field);
+ let minTime = Number.MAX_VALUE, maxTime = Number.MIN_VALUE;
+ for (const doc of childDocs) {
+ const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey])));
+ if (Number.isNaN(num)) continue;
+ if (num) {
+ !pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
+ pivotDateGroups.get(num)!.push(doc);
+ }
+ minTime = Math.min(num, minTime);
+ maxTime = Math.max(num, maxTime);
+ }
+
+ const docMap = new Map<Doc, ViewDefBounds>();
+ const groupNames: PivotData[] = [];
+
+ const scaling = panelDim[0] / (maxTime - minTime);
+ const expander = 1.05;
+ let x = 0;
+ let prevKey = minTime;
+ const sortedKeys = Array.from(pivotDateGroups.keys()).sort();
+ let stacking: number[] = [];
+ for (let i = 0; i < sortedKeys.length; i++) {
+ const key = sortedKeys[i];
+ const val = pivotDateGroups.get(key)!;
+ val.forEach(d => d.isMinimized = key < minTime || key > maxTime);
+ if (key < minTime || key > maxTime) {
+ continue;
+ }
+ x += Math.max(25, scaling * (key - prevKey));
+ let stack = 0;
+ for (; stack < stacking.length; stack++) {
+ if (stacking[stack] === undefined || stacking[stack] < x)
+ break;
+ }
+ prevKey = key;
+ groupNames.push({
+ type: "text",
+ text: toLabel(key),
+ x: x,
+ y: stack * 25,
+ width: pivotAxisWidth * expander,
+ height: 35,
+ fontSize: NumCast(pivotDoc.pivotFontSize, 20)
+ });
+ val.forEach((doc, i) => {
+ let stack = 0;
+ for (; stack < stacking.length; stack++) {
+ if (stacking[stack] === undefined || stacking[stack] < x)
+ break;
+ }
+ const 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,
+ y: - Math.sqrt(stack) * pivotAxisWidth - pivotAxisWidth,
+ width: wid,
+ height: hgt
+ });
+ stacking[stack] = x + pivotAxisWidth;
+ });
+ }
+
+ const grpEles = groupNames.map(gn => { return { x: gn.x, y: gn.y, width: gn.width, height: gn.height } as PivotData; });
+ const docEles = childPairs.filter(d => !d.layout.isMinimized).map(pair =>
+ docMap.get(pair.layout) || { x: NumCast(pair.layout.x), y: NumCast(pair.layout.y), width: NumCast(pair.layout._width), height: NumCast(pair.layout._height) } // new pos is computed pos, or pos written to the document's fields
+ );
+ const aggBounds = aggregateBounds(docEles.concat(grpEles), 0, 0);
+ const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
+ let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
+ const centerY = (panelDim[1] - (aggBounds.b - aggBounds.y) * scale) / 2;
+ const centerX = (panelDim[0] - (aggBounds.r - aggBounds.x) * scale) / 2;
+ if (Number.isNaN(scale)) scale = 1;
+
+ childPairs.map(pair => {
+ const fallbackPos = {
+ x: NumCast(pair.layout.x),
+ y: NumCast(pair.layout.y),
+ z: NumCast(pair.layout.z),
+ width: NumCast(pair.layout._width),
+ height: NumCast(pair.layout._height)
+ };
+ const newPosRaw = docMap.get(pair.layout) || fallbackPos; // new pos is computed pos, or pos written to the document's fields
+ const newPos = { x: newPosRaw.x * scale, y: newPosRaw.y * scale, z: newPosRaw.z, width: newPosRaw.width * scale, height: newPosRaw.height! * scale };
+ const lastPos = poolData.get(pair.layout[Id]); // last computed pos
+ if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.width !== lastPos.width || newPos.height !== lastPos.height) {
+ runInAction(() => poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos }));
+ }
+ });
+ return {
+ elements: viewDefsToJSX([
+ { type: "text", text: "", x: -centerX, y: aggBounds.y * scale - centerY, width: panelDim[0], height: panelDim[1], fontSize: 1 },
+ { type: "div", color: "black", x: -centerX, y: 0, width: panelDim[0], height: 1 }
+ ].concat(groupNames.map(gname => {
+ return { type: gname.type, text: gname.text, x: gname.x * scale, y: gname.y * scale, width: gname.width * scale, height: gname.height! * scale, fontSize: gname.fontSize };
+ })))
+ };
+}
+
export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
return () => {
const addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 21826ecc5..990a2f3ba 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -32,7 +32,7 @@ import { FormattedTextBox } from "../../nodes/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
import PDFMenu from "../../pdf/PDFMenu";
import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
+import { computePivotLayout, ViewDefResult, computeTimelineLayout } from "./CollectionFreeFormLayoutEngines";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
@@ -41,7 +41,6 @@ import React = require("react");
import { computedFn } from "mobx-utils";
import { TraceMobx } from "../../../../new_fields/util";
import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -765,6 +764,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
</div>,
bounds: { x: x!, y: y!, z: z, width: width!, height: height }
};
+ } else if (viewDef.type === "div") {
+ const x = Cast(viewDef.x, "number");
+ const y = Cast(viewDef.y, "number");
+ const z = Cast(viewDef.z, "number");
+ const backgroundColor = Cast(viewDef.color, "string");
+ const width = Cast(viewDef.width, "number");
+ const height = Cast(viewDef.height, "number");
+ const fontSize = Cast(viewDef.fontSize, "number");
+ return [x, y, width].some(val => val === undefined) ? undefined :
+ {
+ ele: <div className="collectionFreeform-customDiv" key={"div" + x + y + z} style={{ width, height, fontSize, backgroundColor, transform: `translate(${x}px, ${y}px)` }}>
+ </div>,
+ bounds: { x: x!, y: y!, z: z, width: width!, height: height }
+ };
}
}
@@ -775,6 +788,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return this._layoutPoolData.get(doc[Id]);
}.bind(this));
+ doTimelineLayout(poolData: ObservableMap<string, any>) {
+ return computeTimelineLayout(poolData, this.props.Document, this.childDocs,
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
+ }
+
doPivotLayout(poolData: ObservableMap<string, any>) {
return computePivotLayout(poolData, this.props.Document, this.childDocs,
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
@@ -800,6 +818,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
get doLayoutComputation() {
let computedElementData: { elements: ViewDefResult[] };
switch (this.Document._freeformLayoutEngine) {
+ case "timeline": computedElementData = this.doTimelineLayout(this._layoutPoolData); break;
case "pivot": computedElementData = this.doPivotLayout(this._layoutPoolData); break;
default: computedElementData = this.doFreeformLayout(this._layoutPoolData); break;
}
@@ -808,7 +827,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} {...this.getChildDocumentViewProps(pair.layout, pair.data)}
dataProvider={this.childDataProvider}
jitterRotation={NumCast(this.props.Document.jitterRotation)}
- fitToBox={this.props.fitToBox || this.Document._freeformLayoutEngine === "pivot"} />,
+ fitToBox={this.props.fitToBox || this.Document._freeformLayoutEngine === "pivot" || this.Document._freeformLayoutEngine === "timeline"} />,
bounds: this.childDataProvider(pair.layout)
}));