diff options
Diffstat (limited to 'src')
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) })); |