diff options
Diffstat (limited to 'src')
4 files changed, 84 insertions, 24 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 1dc09b517..f08c2506e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -1,5 +1,5 @@ import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../new_fields/Doc"; -import { NumCast, StrCast, Cast, DateCast } from "../../../../new_fields/Types"; +import { NumCast, StrCast, Cast, DateCast, BoolCast } from "../../../../new_fields/Types"; import { ScriptBox } from "../../ScriptBox"; import { CompileScript } from "../../../util/Scripting"; import { ScriptField } from "../../../../new_fields/ScriptField"; @@ -16,18 +16,35 @@ interface PivotData { text: string; x: number; y: number; + zIndex?: number; width?: number; height?: number; fontSize: number; + color?: string; } export interface ViewDefBounds { x: number; y: number; z?: number; + zIndex?: number; width?: number; height?: number; transition?: string; + highlight?: boolean; +} + +export interface PoolData { + x?: number, + y?: number, + z?: number, + zIndex?: number, + width?: number, + height?: number, + color?: string, + transition?: string, + highlight?: boolean, + state?: any } export interface ViewDefResult { @@ -43,7 +60,7 @@ function toLabel(target: FieldResult<Field>) { } export function computePivotLayout( - poolData: ObservableMap<string, any>, + poolData: ObservableMap<string, PoolData>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], @@ -116,7 +133,7 @@ export function computePivotLayout( export function computeTimelineLayout( - poolData: ObservableMap<string, any>, + poolData: ObservableMap<string, PoolData>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], @@ -128,9 +145,10 @@ export function computeTimelineLayout( const docMap = new Map<Doc, ViewDefBounds>(); const groupNames: PivotData[] = []; const timelineFieldKey = Field.toString(pivotDoc.pivotField as Field); - const minTimeReq = Cast(pivotDoc[fieldKey + "-timelineMinReq"], "number", null); - const maxTimeReq = Cast(pivotDoc[fieldKey + "-timelineMaxReq"], "number", null); const curTime = Cast(pivotDoc[fieldKey + "-timelineCur"], "number", null); + const curTimeSpan = Cast(pivotDoc[fieldKey + "-timelineSpan"], "number", null); + const minTimeReq = curTime === undefined ? Cast(pivotDoc[fieldKey + "-timelineMinReq"], "number", null) : curTimeSpan && (curTime - curTimeSpan); + const maxTimeReq = curTime === undefined ? Cast(pivotDoc[fieldKey + "-timelineMaxReq"], "number", null) : curTimeSpan && (curTime + curTimeSpan); const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3)); const fontHeight = panelDim[1] > 58 ? 30 : panelDim[1] / 2; const findStack = (time: number, stack: number[]) => { @@ -151,6 +169,13 @@ export function computeTimelineLayout( maxTime = Math.max(num, maxTime); } }); + if (curTime !== undefined) { + if (curTime > maxTime || curTime - minTime > maxTime - curTime) { + maxTime = curTime + (curTime - minTime); + } else { + minTime = curTime - (maxTime - curTime); + } + } pivotDoc[fieldKey + "-timelineMin"] = minTime = minTimeReq ? Math.min(minTimeReq, minTime) : minTime; pivotDoc[fieldKey + "-timelineMax"] = maxTime = maxTimeReq ? Math.max(maxTimeReq, maxTime) : maxTime; @@ -163,16 +188,23 @@ export function computeTimelineLayout( if (sortedKeys.length && scaling * (sortedKeys[0] - prevKey) > 25) { groupNames.push({ type: "text", text: prevKey.toString(), x: x, y: 0, height: fontHeight, fontSize }); } + if (!sortedKeys.length && curTime !== undefined) { + groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, zIndex: 1000, color: "orange", y: 0, height: fontHeight, fontSize }); + } const pivotAxisWidth = NumCast(pivotDoc.pivotTimeWidth, panelDim[1] / 2.5); let stacking: number[] = []; + let zind = 0; sortedKeys.forEach(key => { + if (curTime !== undefined && curTime > prevKey && curTime <= key) { + groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize }); + } const keyDocs = pivotDateGroups.get(key)!; keyDocs.forEach(d => d.isMinimized = false); x += scaling * (key - prevKey); const stack = findStack(x, stacking); prevKey = key; - !stack && groupNames.push({ type: "text", text: key.toString(), x: x, y: stack * 25, height: fontHeight, fontSize }); + !stack && Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth && groupNames.push({ type: "text", text: key.toString(), x: x, y: stack * 25, height: fontHeight, fontSize }); keyDocs.forEach(doc => { const stack = findStack(x, stacking); const layoutDoc = Doc.Layout(doc); @@ -182,10 +214,17 @@ export function computeTimelineLayout( hgt = pivotAxisWidth; wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth; } - docMap.set(doc, { x: x, y: - Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2, width: wid, height: hgt }); + docMap.set(doc, { + x: x, y: - Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2, + zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt + }); stacking[stack] = x + pivotAxisWidth; }); }); + if (sortedKeys.length && curTime > sortedKeys[sortedKeys.length - 1]) { + x = (curTime - minTime) * scaling; + groupNames.push({ type: "text", text: curTime.toString(), x: x, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize }); + } if (Math.ceil(maxTime - minTime) * scaling > x + 25) { groupNames.push({ type: "text", text: Math.ceil(maxTime).toString(), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize }); } @@ -194,8 +233,8 @@ export function computeTimelineLayout( return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]); } -function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: any, - poolData: any, viewDefsToJSX: any, groupNames: PivotData[], minWidth: number, extras: any[]) { +function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>, + poolData: ObservableMap<string, PoolData>, viewDefsToJSX: (views: any) => ViewDefResult[], groupNames: PivotData[], minWidth: number, extras: PivotData[]) { const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, height: gn.height }) as PivotData); const docEles = childPairs.filter(d => !d.layout.isMinimized).map(pair => @@ -212,13 +251,18 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { x: NumCast(pair.layout.x), y: NumCast(pair.layout.y), z: NumCast(pair.layout.z), + highlight: undefined, + zIndex: NumCast(pair.layout.zIndex), 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 || 0) * scale, height: newPosRaw.height! * scale }; + const newPos = { + x: newPosRaw.x * scale, y: newPosRaw.y * scale, z: newPosRaw.z, zIndex: newPosRaw.zIndex, highlight: newPosRaw.highlight, + width: (newPosRaw.width || 0) * 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) { + if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) { runInAction(() => poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos })); } }); @@ -229,6 +273,7 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { text: gname.text, x: gname.x * scale, y: gname.y * scale, + color: gname.color, width: gname.width === undefined ? undefined : gname.width * scale, height: Math.max(fontHeight, gname.height! * scale), // height: gname.height === undefined ? undefined : gname.height * scale, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 050dca596..f1a239050 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, computeTimelineLayout } from "./CollectionFreeFormLayoutEngines"; +import { computePivotLayout, ViewDefResult, computeTimelineLayout, PoolData } from "./CollectionFreeFormLayoutEngines"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; @@ -735,13 +735,16 @@ 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 } { + getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData { 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") }; + return { + x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), color: Cast(params.doc.color, "string"), + zIndex: Cast(params.doc.zIndex, "number"), width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number") + }; } viewDefsToJSX = (views: any[]) => { @@ -752,6 +755,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const x = Cast(viewDef.x, "number"); const y = Cast(viewDef.y, "number"); const z = Cast(viewDef.z, "number"); + const highlight = Cast(viewDef.highlight, "boolean"); + const zIndex = Cast(viewDef.zIndex, "number"); + const color = Cast(viewDef.color, "string"); const width = Cast(viewDef.width, "number", null); const height = Cast(viewDef.height, "number", null); if (viewDef.type === "text") { @@ -759,17 +765,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const fontSize = Cast(viewDef.fontSize, "number"); return [text, x, y].some(val => val === undefined) ? undefined : { - ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z} style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}> + ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color} + style={{ width, height, color, fontSize, transform: `translate(${x}px, ${y}px)` }}> {text} </div>, - bounds: { x: x!, y: y!, z: z, width, height: height } + bounds: { x: x!, y: y!, z, zIndex, width, height } }; } else if (viewDef.type === "div") { const backgroundColor = Cast(viewDef.color, "string"); return [x, y].some(val => val === undefined) ? undefined : { - ele: <div className="collectionFreeform-customDiv" key={"div" + x + y + z} style={{ width, height, backgroundColor, transform: `translate(${x}px, ${y}px)` }} />, - bounds: { x: x!, y: y!, z: z, width: width!, height: height } + ele: <div className="collectionFreeform-customDiv" key={"div" + x + y + z} + style={{ width, height, backgroundColor, transform: `translate(${x}px, ${y}px)` }} />, + bounds: { x: x!, y: y!, z, zIndex, width, height } }; } } @@ -801,7 +809,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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; - 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) { + if (!data || pos.x !== data.x || pos.y !== data.y || pos.z !== data.z || pos.zIndex !== data.zIndex || + pos.width !== data.width || pos.height !== data.height || pos.transition !== data.transition) { runInAction(() => poolData.set(pair.layout[Id], pos)); } }); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 09fcc5362..ea7ed1d54 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -15,9 +15,12 @@ import { returnFalse } from "../../../Utils"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { - dataProvider?: (doc: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; + dataProvider?: (doc: Doc) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string } | undefined; x?: number; y?: number; + z?: number; + zIndex?: number; + highlight?: boolean; width?: number; height?: number; jitterRotation: number; @@ -30,8 +33,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF @observable _animPos: number[] | undefined = undefined; get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${anime.random(-1, 1) * this.props.jitterRotation}deg)`; } - get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); } - get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); } + get X() { return this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); } + get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); } + get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); } + get Highlight() { return this.dataProvider?.highlight; } get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); } get height() { const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym](); @@ -78,11 +83,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF this.layoutDoc.isBackground ? undefined : // if it's a background & has a cluster color, make the shadow spread really big StrCast(this.layoutDoc.boxShadow, ""), borderRadius: StrCast(Doc.Layout(this.layoutDoc).borderRounding), + outline: this.Highlight ? "orange solid 2px" : "", transform: this.transform, transition: this.Document.isAnimating ? ".5s ease-in" : this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition), width: this.width, height: this.height, - zIndex: this.Document.zIndex || 0, + zIndex: this.ZInd, }} > {!this.props.fitToBox ? <DocumentView {...this.props} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e9c0a6df3..0f517197a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -919,7 +919,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const titleView = (!showTitle ? (null) : <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{ position: showTextTitle ? "relative" : "absolute", - pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all", + pointerEvents: SelectionManager.GetIsDragging() || this.onClickHandler || this.Document.ignoreClick ? "none" : "all", }}> <EditableView ref={this._titleRef} contents={(this.props.DataDoc || this.props.Document)[showTitle]?.toString()} |