diff options
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx | 115 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 25 |
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) })); |
