aboutsummaryrefslogtreecommitdiff
path: root/src
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
parente34620f6ded8afd6bb3f96c2c2889c9165f64569 (diff)
added collectionTime view
Diffstat (limited to 'src')
-rw-r--r--src/client/views/collections/CollectionPivotView.tsx1
-rw-r--r--src/client/views/collections/CollectionTimeView.scss88
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx174
-rw-r--r--src/client/views/collections/CollectionView.tsx5
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx115
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx25
7 files changed, 408 insertions, 6 deletions
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index 1a84a34a0..992018a3f 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -19,7 +19,6 @@ import React = require("react");
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { RichTextField } from "../../../new_fields/RichTextField";
-import { ImageField } from "../../../new_fields/URLField";
@observer
export class CollectionPivotView extends CollectionSubView(doc => doc) {
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
new file mode 100644
index 000000000..4e06fdb84
--- /dev/null
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -0,0 +1,88 @@
+.collectionTimeView {
+ display: flex;
+ flex-direction: row;
+ position: absolute;
+ height: 100%;
+ width: 100%;
+
+ .collectionTimeView-flyout {
+ width: 400px;
+ height: 300px;
+ display: inline-block;
+
+ .collectionTimeView-flyout-item {
+ background-color: lightgray;
+ text-align: left;
+ display: inline-block;
+ position: relative;
+ width: 100%;
+ }
+ }
+
+ .pivotKeyEntry {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ z-index: 10;
+ pointer-events: all;
+ padding: 5px;
+ border: 1px solid black;
+ }
+
+ .collectionTimeView-treeView {
+ display: flex;
+ flex-direction: column;
+ width: 200px;
+ height: 100%;
+
+ .collectionTimeView-addfacet {
+ display: inline-block;
+ width: 200px;
+ height: 30px;
+ background: darkGray;
+ text-align: center;
+
+ .collectionTimeView-button {
+ align-items: center;
+ display: flex;
+ width: 100%;
+ height: 100%;
+
+ .collectionTimeView-span {
+ margin: auto;
+ }
+ }
+
+ >div,
+ >div>div {
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ }
+ }
+
+ .collectionTimeView-tree {
+ display: inline-block;
+ width: 100%;
+ height: calc(100% - 30px);
+ }
+ }
+
+ .collectionTimeView-pivot {
+ display: inline-block;
+ width: calc(100% - 200px);
+ height: 100%;
+ }
+
+ .collectionTimeView-dragger {
+ background-color: lightgray;
+ height: 40px;
+ width: 20px;
+ position: absolute;
+ border-radius: 10px;
+ top: 55%;
+ border: 1px black solid;
+ z-index: 2;
+ left: -10px;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
new file mode 100644
index 000000000..dfcf93ef6
--- /dev/null
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -0,0 +1,174 @@
+import { faEdit } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, IReactionDisposer, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Set } from "typescript-collections";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
+import { Cast, StrCast } from "../../../new_fields/Types";
+import { Docs } from "../../documents/Documents";
+import { EditableView } from "../EditableView";
+import { anchorPoints, Flyout } from "../TemplateMenu";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import "./CollectionTimeView.scss";
+import { CollectionSubView } from "./CollectionSubView";
+import { CollectionTreeView } from "./CollectionTreeView";
+import React = require("react");
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { RichTextField } from "../../../new_fields/RichTextField";
+
+@observer
+export class CollectionTimeView extends CollectionSubView(doc => doc) {
+ componentDidMount() {
+ this.props.Document._freeformLayoutEngine = "timeline";
+ const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
+ if (!this.props.Document._facetCollection) {
+ const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true });
+ facetCollection.target = this.props.Document;
+ this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilter"]);
+
+ const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
+ const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailed'); useRightSplit(alias); ";
+ facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "boolean", checked: "boolean", containingTreeView: Doc.name });
+ this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "boolean", containingCollection: Doc.name });
+ this.props.Document._facetCollection = facetCollection;
+ this.props.Document._fitToBox = true;
+ }
+ }
+ bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth;
+ getTransform = () => this.props.ScreenToLocalTransform().translate(-this._facetWidth, 0);
+
+ @computed get _allFacets() {
+ const facets = new Set<string>();
+ this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+ return facets.toArray();
+ }
+
+ /**
+ * Responds to clicking the check box in the flyout menu
+ */
+ facetClick = (facetHeader: string) => {
+ const facetCollection = this.props.Document._facetCollection;
+ if (facetCollection instanceof Doc) {
+ const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
+ if (found !== -1) {
+ (facetCollection.data as List<Doc>).splice(found, 1);
+ const docFilter = Cast(this.props.Document._docFilter, listSpec("string"));
+ if (docFilter) {
+ let index: number;
+ while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
+ docFilter.splice(index, 3);
+ }
+ }
+ } else {
+ const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
+ const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
+ const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
+ newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
+ Doc.AddDocToList(facetCollection, "data", newFacet);
+ }
+ }
+ }
+ _canClick = false;
+ _facetWidthOnDown = 0;
+ @observable _facetWidth = 200;
+ onPointerDown = (e: React.PointerEvent) => {
+ this._canClick = true;
+ this._facetWidthOnDown = e.screenX;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+
+ @action
+ onPointerMove = (e: PointerEvent) => {
+ this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
+ Math.abs(e.movementX) > 6 && (this._canClick = false);
+ }
+ @action
+ onPointerUp = (e: PointerEvent) => {
+ if (Math.abs(e.screenX - this._facetWidthOnDown) < 6 && this._canClick) {
+ this._facetWidth = this._facetWidth < 15 ? 200 : 0;
+ }
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ menuCallback = (x: number, y: number) => {
+ ContextMenu.Instance.clearItems();
+ const docItems: ContextMenuProps[] = [];
+ const keySet: Set<string> = new Set();
+
+ this.childLayoutPairs.map(pair =>
+ Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof (pair.layout[fieldKey]) === "string").map(fieldKey =>
+ keySet.add(fieldKey)));
+ keySet.toArray().map(fieldKey =>
+ docItems.push({ description: ":" + fieldKey, event: () => this.props.Document.pivotField = fieldKey, icon: "compress-arrows-alt" }));
+ docItems.push({ description: ":(null)", event: () => this.props.Document.pivotField = undefined, icon: "compress-arrows-alt" })
+ ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
+ const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+ ContextMenu.Instance.displayMenu(x, y, ":");
+ }
+
+ @observable private collapsed: boolean = false;
+ private toggleVisibility = action(() => this.collapsed = !this.collapsed);
+
+ render() {
+ const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
+ const flyout = (
+ <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}` }}>
+ {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
+ <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} />
+ <span className="checkmark" />
+ {facet}
+ </label>)}
+ </div>
+ );
+ const newEditableViewProps = {
+ GetValue: () => "",
+ SetValue: (value: any) => {
+ if (value?.length) {
+ this.props.Document.pivotField = value;
+ return true;
+ }
+ return false;
+ },
+ showMenuOnLoad: true,
+ contents: ":" + StrCast(this.props.Document.pivotField),
+ toggle: this.toggleVisibility,
+ color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+ };
+ return !facetCollection ? (null) :
+ <div className="collectionTimeView" style={{ height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+ <div className={"pivotKeyEntry"}>
+ <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} />
+ </div>
+ <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
+ <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
+ </div>
+ <div className="collectionTimeView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
+ <div className="collectionTimeView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
+ <div className="collectionTimeView-button">
+ <span className="collectionTimeView-span">Facet Filters</span>
+ <FontAwesomeIcon icon={faEdit} size={"lg"} />
+ </div>
+ </Flyout>
+ </div>
+ <div className="collectionTimeView-tree" key="tree">
+ <CollectionTreeView {...this.props} Document={facetCollection} />
+ </div>
+ </div>
+ <div className="collectionTimeView-pivot" key="pivot" style={{ width: this.bodyPanelWidth() }}>
+ <CollectionFreeFormView {...this.props} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index dab0ce08e..027cf2404 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -36,6 +36,7 @@ import { CollectionStaffView } from './CollectionStaffView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import { CollectionTimeView } from './CollectionTimeView';
export const COLLECTION_BORDER_WIDTH = 2;
const path = require('path');
library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -50,6 +51,7 @@ export enum CollectionViewType {
Masonry,
Multicolumn,
Pivot,
+ Time,
Carousel,
Linear,
Staff,
@@ -67,6 +69,7 @@ export namespace CollectionViewType {
["masonry", CollectionViewType.Masonry],
["multicolumn", CollectionViewType.Multicolumn],
["pivot", CollectionViewType.Pivot],
+ ["time", CollectionViewType.Time],
["carousel", CollectionViewType.Carousel],
["linear", CollectionViewType.Linear],
]);
@@ -185,6 +188,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Pivot: { return (<CollectionPivotView key="collview" {...props} />); }
+ case CollectionViewType.Time: { return (<CollectionTimeView key="collview" {...props} />); }
case CollectionViewType.Freeform:
default: { this.props.Document._freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
}
@@ -226,6 +230,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
subItems.push({ description: "Masonry", event: () => this.props.Document._viewType = CollectionViewType.Masonry, icon: "columns" });
subItems.push({ description: "Carousel", event: () => this.props.Document._viewType = CollectionViewType.Carousel, icon: "columns" });
subItems.push({ description: "Pivot", event: () => this.props.Document._viewType = CollectionViewType.Pivot, icon: "columns" });
+ subItems.push({ description: "Time", event: () => this.props.Document._viewType = CollectionViewType.Time, icon: "columns" });
switch (this.props.Document._viewType) {
case CollectionViewType.Freeform: {
subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 073a30330..9cb668d20 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -71,6 +71,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
case CollectionViewType.Masonry: return this._stacking_commands;
case CollectionViewType.Freeform: return this._freeform_commands;
case CollectionViewType.Pivot: return this._freeform_commands;
+ case CollectionViewType.Time: return this._freeform_commands;
case CollectionViewType.Carousel: return this._freeform_commands;
}
return [];
@@ -418,8 +419,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry</option>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">Multicolumn</option>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="8">Pivot</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="9">Carousel</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="10">Linear</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="9">Time</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="10">Carousel</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="11">Linear</option>
</select>
<div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
<div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.openViewSpecs} >
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)
}));