From f5b6837dba732780930594e76a3676a4264639d7 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 24 Apr 2020 19:56:02 -0400 Subject: fixed problems with PivotView -- docs that show up when they shouldn't and not being able to click the pivot background sometimes. --- .../CollectionFreeFormLayoutEngines.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 566a6788b..9f7919ada 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -136,6 +136,7 @@ export function computePivotLayout( panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { + const docMap = new Map(); const fieldKey = "data"; const pivotColumnGroups = new Map, PivotColumn>(); @@ -145,6 +146,16 @@ export function computePivotLayout( if (val) { !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] }); pivotColumnGroups.get(val)!.docs.push(doc); + } else { + docMap.set(doc, { + type: "doc", + x: 0, + y: 0, + zIndex: -99, + width: 0, + height: 0, + payload: undefined + }) } } let nonNumbers = 0; @@ -193,7 +204,6 @@ export function computePivotLayout( } } - const docMap = new Map(); const groupNames: ViewDefBounds[] = []; const expander = 1.05; @@ -366,10 +376,9 @@ export function computeTimelineLayout( function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map, poolData: Map, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[], extraDocs: Doc[]): ViewDefResult[] { - const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds); const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds); - const aggBounds = aggregateBounds(docEles.concat(grpEles), 0, 0); + const aggBounds = aggregateBounds(docEles.concat(grpEles).filter(e => e.zIndex !== -99), 0, 0); aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x); const wscale = panelDim[0] / (aggBounds.r - aggBounds.x); let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale; -- cgit v1.2.3-70-g09d2 From 896c0fef965458bd23ff098c7c95b09799528033 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 24 Apr 2020 23:00:26 -0400 Subject: changed pivot view to support lists of values for keys (docs can appear mulltiple times as replicas) --- .../CollectionFreeFormLayoutEngines.tsx | 122 +++++++++++++-------- .../collectionFreeForm/CollectionFreeFormView.tsx | 47 ++++---- .../views/nodes/CollectionFreeFormDocumentView.tsx | 5 +- 3 files changed, 100 insertions(+), 74 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 9f7919ada..d8bd18ec7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -9,6 +9,7 @@ import React = require("react"); import { Id, ToString } from "../../../../new_fields/FieldSymbols"; import { ObjectField } from "../../../../new_fields/ObjectField"; import { RefField } from "../../../../new_fields/RefField"; +import { listSpec } from "../../../../new_fields/Schema"; export interface ViewDefBounds { type: string; @@ -24,6 +25,8 @@ export interface ViewDefBounds { highlight?: boolean; color?: string; payload: any; + replica?: string; + pair?: { layout: Doc, data?: Doc }; } export interface PoolData { @@ -36,6 +39,8 @@ export interface PoolData { color?: string; transition?: string; highlight?: boolean; + replica?: string; + pair: { layout: Doc, data?: Doc } } export interface ViewDefResult { @@ -72,100 +77,111 @@ function getTextWidth(text: string, font: string): number { interface PivotColumn { docs: Doc[]; + replicas: string[]; filters: string[]; } export function computerPassLayout( poolData: Map, pivotDoc: Doc, - childDocs: Doc[], filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { - const docMap = new Map(); - childDocs.forEach((doc, i) => { - docMap.set(doc, { + const docMap = new Map(); + childPairs.forEach(({ layout, data }, i) => { + docMap.set(layout[Id], { type: "doc", - x: NumCast(doc.x), - y: NumCast(doc.y), - width: doc[WidthSym](), - height: doc[HeightSym](), - payload: undefined + x: NumCast(layout.x), + y: NumCast(layout.y), + width: layout[WidthSym](), + height: layout[HeightSym](), + payload: undefined, + pair: { layout, data }, + replica: "" }); }); - return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c))); + return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout))); } export function computerStarburstLayout( poolData: Map, pivotDoc: Doc, - childDocs: Doc[], filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { - const docMap = new Map(); + const docMap = new Map(); const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])]; const docScale = NumCast(pivotDoc._starburstDocScale); const docSize = docScale * 100; // assume a icon sized at 100 const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize]; - childDocs.forEach((doc, i) => { - const deg = i / childDocs.length * Math.PI * 2; - docMap.set(doc, { + childPairs.forEach((pair, i) => { + const deg = i / childPairs.length * Math.PI * 2; + docMap.set(pair.layout[Id], { type: "doc", - x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * doc[WidthSym]() / 2, - y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * doc[HeightSym]() / 2, - width: docScale * doc[WidthSym](), - height: docScale * doc[HeightSym](), - payload: undefined + x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * pair.layout[WidthSym]() / 2, + y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * pair.layout[HeightSym]() / 2, + width: docScale * pair.layout[WidthSym](), + height: docScale * pair.layout[HeightSym](), + payload: undefined, + replica: "" }); }); - return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c))); + return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout))); } export function computePivotLayout( poolData: Map, pivotDoc: Doc, - childDocs: Doc[], filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { - const docMap = new Map(); + const docMap = new Map(); const fieldKey = "data"; const pivotColumnGroups = new Map, PivotColumn>(); const pivotFieldKey = toLabel(pivotDoc._pivotField); for (const doc of filterDocs) { + const lval = Cast(doc[pivotFieldKey], listSpec("string"), null); const val = Field.toString(doc[pivotFieldKey] as Field); - if (val) { - !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] }); + if (lval) { + lval.forEach((val, i) => { + !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); + pivotColumnGroups.get(val)!.docs.push(doc); + pivotColumnGroups.get(val)!.replicas.push(i.toString()); + }); + } else if (val) { + !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); pivotColumnGroups.get(val)!.docs.push(doc); + pivotColumnGroups.get(val)!.replicas.push(""); } else { - docMap.set(doc, { + docMap.set(doc[Id], { type: "doc", x: 0, y: 0, zIndex: -99, width: 0, height: 0, - payload: undefined + payload: undefined, + pair: { layout: doc }, + replica: "" }) } } let nonNumbers = 0; - childDocs.map(doc => { - const num = toNumber(doc[pivotFieldKey]); + childPairs.map(pair => { + const num = toNumber(pair.layout[pivotFieldKey]); if (num === undefined || Number.isNaN(num)) { nonNumbers++; } }); - const pivotNumbers = nonNumbers / childDocs.length < .1; + const pivotNumbers = nonNumbers / childPairs.length < .1; if (pivotColumnGroups.size > 10) { const arrayofKeys = Array.from(pivotColumnGroups.keys()); const sortedKeys = pivotNumbers ? arrayofKeys.sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : arrayofKeys.sort(); @@ -177,6 +193,7 @@ export function computePivotLayout( const newgrp = pivotColumnGroups.get(sortedKeys[j])!; curgrp.docs.push(...newgrp.docs); curgrp.filters.push(...newgrp.filters); + curgrp.replicas.push(...newgrp.replicas); pivotColumnGroups.delete(sortedKeys[j]); } } @@ -226,7 +243,7 @@ export function computePivotLayout( fontSize, payload: val }); - for (const doc of val.docs) { + val.docs.forEach((doc, i) => { const layoutDoc = Doc.Layout(doc); let wid = pivotAxisWidth; let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth; @@ -234,27 +251,29 @@ export function computePivotLayout( hgt = pivotAxisWidth; wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth; } - docMap.set(doc, { + docMap.set(doc[Id] + (val.replicas || ""), { type: "doc", x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0), y: -y + (pivotAxisWidth - hgt) / 2, width: wid, height: hgt, - payload: undefined + payload: undefined, + pair: { layout: doc }, + replica: val.replicas[i] }); xCount++; if (xCount >= numCols) { xCount = 0; y += pivotAxisWidth * expander; } - } + }); x += pivotAxisWidth * (numCols * expander + gap); }); const dividers = sortedPivotKeys.map((key, i) => ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters })); groupNames.push(...dividers); - return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c))); + return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childPairs.filter(c => !filterDocs.includes(c.layout))); } function toNumber(val: FieldResult) { @@ -264,7 +283,6 @@ function toNumber(val: FieldResult) { export function computeTimelineLayout( poolData: Map, pivotDoc: Doc, - childDocs: Doc[], filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], @@ -272,7 +290,7 @@ export function computeTimelineLayout( ) { const fieldKey = "data"; const pivotDateGroups = new Map(); - const docMap = new Map(); + const docMap = new Map(); const groupNames: ViewDefBounds[] = []; const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field); const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]); @@ -351,7 +369,7 @@ export function computeTimelineLayout( } const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined }; - return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childDocs.filter(c => !filterDocs.includes(c))); + return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childPairs.filter(c => !filterDocs.includes(c.layout))); function layoutDocsAtTime(keyDocs: Doc[], key: number) { keyDocs.forEach(doc => { @@ -363,43 +381,51 @@ export function computeTimelineLayout( hgt = pivotAxisWidth; wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth; } - docMap.set(doc, { + docMap.set(doc[Id], { type: "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 / (Math.max(stack, 1)), payload: undefined + zIndex: (curTime === key ? 1000 : zind++), + highlight: curTime === key, + width: wid / (Math.max(stack, 1)), + height: hgt / (Math.max(stack, 1)), + payload: undefined, + pair: { layout: doc }, + replica: "" }); stacking[stack] = x + pivotAxisWidth; }); } } -function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map, +function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map, poolData: Map, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[], - extraDocs: Doc[]): ViewDefResult[] { + extraDocs: { data?: Doc, layout: Doc }[]): ViewDefResult[] { const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds); - const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds); + const docEles = Array.from(docMap.entries()).map(ele => ele[1]); const aggBounds = aggregateBounds(docEles.concat(grpEles).filter(e => e.zIndex !== -99), 0, 0); aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x); const wscale = panelDim[0] / (aggBounds.r - aggBounds.x); let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale; if (Number.isNaN(scale)) scale = 1; - childPairs.filter(d => docMap.get(d.layout)).map(pair => { - const newPosRaw = docMap.get(pair.layout); + Array.from(docMap.entries()).filter(ele => ele[1].pair).map(ele => { + const newPosRaw = ele[1]; if (newPosRaw) { const newPos = { x: newPosRaw.x * scale, y: newPosRaw.y * scale, z: newPosRaw.z, + replica: newPosRaw.replica, highlight: newPosRaw.highlight, zIndex: newPosRaw.zIndex, width: (newPosRaw.width || 0) * scale, - height: newPosRaw.height! * scale + height: newPosRaw.height! * scale, + pair: ele[1].pair! }; - poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos }); + poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos }); } }); - extraDocs.map(ed => poolData.set(ed[Id], { x: 0, y: 0, zIndex: -99 })); + extraDocs.map(ed => poolData.set(ed.layout[Id], { x: 0, y: 0, zIndex: -99, pair: ed })); return viewDefsToJSX(extras.concat(groupNames).map(gname => ({ type: gname.type, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b3cf2b97a..5555d9e84 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -85,8 +85,8 @@ export class CollectionFreeFormView extends CollectionSubView(); - private _cachedPool: Map = new Map(); + private _layoutPoolData = new ObservableMap(); + private _cachedPool: Map = new Map(); @observable private _pullCoords: number[] = [0, 0]; @observable private _pullDirection: string = ""; @@ -728,7 +728,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout instanceof Doc).map(pair => pair.layout); - const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc)); + const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc, "")).map(doc => this.childDataProvider(doc, "")); if (measuredDocs.length) { const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content ({ @@ -899,16 +899,16 @@ export class CollectionFreeFormView extends CollectionSubView) { + return this._layoutPoolData.get(doc[Id] + (replica || "")); }.bind(this)); doEngineLayout(poolData: Map, engine: ( poolData: Map, pivotDoc: Doc, - childDocs: Doc[], filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]) { - return engine(poolData, this.props.Document, this.childDocs, this.childDocs, + return engine(poolData, this.props.Document, this.childDocs, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); } @@ -970,14 +969,14 @@ export class CollectionFreeFormView extends CollectionSubView this.isCurrent(pair.layout)).map((pair, i) => { - const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state }); + const pos = this.getCalculatedPositions({ pair, index: i, collection: this.Document, docs: layoutDocs, state }); poolData.set(pair.layout[Id], pos); }); return elements; } @computed get doInternalLayoutComputation() { - const newPool = new Map(); + const newPool = new Map(); const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.(); switch (engine) { case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; @@ -992,22 +991,22 @@ export class CollectionFreeFormView extends CollectionSubView - Array.from(newPool.keys()).map(key => { - const lastPos = this._cachedPool.get(key); // last computed pos - const newPos = newPool.get(key); + Array.from(newPool.entries()).map(entry => { + const lastPos = this._cachedPool.get(entry[0]); // last computed pos + const newPos = entry[1]; 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) { - this._layoutPoolData.set(key, newPos); + this._layoutPoolData.set(entry[0], newPos); } })); this._cachedPool.clear(); - Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k))); + Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1])); const elements: ViewDefResult[] = computedElementData.slice(); - const engine = this.props.layoutEngine?.() || this.props.Document._layoutEngine; - this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair => + Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry => elements.push({ ele: , - bounds: this.childDataProvider(pair.layout) + bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica) })); return elements; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 4b282b0c9..baeac311e 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -12,7 +12,7 @@ import { TraceMobx } from "../../../new_fields/util"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { - dataProvider?: (doc: Doc) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string } | undefined; + dataProvider?: (doc: Doc, replica?: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined; x?: number; y?: number; z?: number; @@ -23,6 +23,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { jitterRotation: number; transition?: string; fitToBox?: boolean; + replica?: string; } @observer @@ -46,7 +47,7 @@ export class CollectionFreeFormDocumentView extends DocComponent Date: Sat, 25 Apr 2020 03:42:51 -0400 Subject: hopefully final cleanup of pile up collection view. added doubleTap support in setupMoveUpEvents --- src/Utils.ts | 12 ++- src/client/documents/Documents.ts | 4 + .../views/collections/CollectionPileView.scss | 8 ++ .../views/collections/CollectionPileView.tsx | 97 ++++++++++++++++++++++ src/client/views/collections/CollectionSubView.tsx | 4 +- .../views/collections/CollectionTimeView.tsx | 3 +- src/client/views/collections/CollectionView.tsx | 5 +- .../CollectionFreeFormLayoutEngines.tsx | 95 ++++++++++----------- .../collectionFreeForm/CollectionFreeFormView.tsx | 19 +++-- .../collections/collectionFreeForm/MarqueeView.tsx | 16 ++-- src/new_fields/Doc.ts | 35 ++------ 11 files changed, 189 insertions(+), 109 deletions(-) create mode 100644 src/client/views/collections/CollectionPileView.scss create mode 100644 src/client/views/collections/CollectionPileView.tsx (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx') diff --git a/src/Utils.ts b/src/Utils.ts index 9acdc8731..ad12c68a1 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -504,8 +504,9 @@ export function setupMoveUpEvents( e: React.PointerEvent, moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean, upEvent: (e: PointerEvent) => void, - clickEvent: (e: PointerEvent) => void, - stopPropagation: boolean = true + clickEvent: (e: PointerEvent, doubleTap?: boolean) => void, + stopPropagation: boolean = true, + stopMovePropagation: boolean = true ) { (target as any)._downX = (target as any)._lastX = e.clientX; (target as any)._downY = (target as any)._lastY = e.clientY; @@ -520,12 +521,15 @@ export function setupMoveUpEvents( } (target as any)._lastX = e.clientX; (target as any)._lastY = e.clientY; - e.stopPropagation(); + stopMovePropagation && e.stopPropagation(); }; + (target as any)._doubleTap = false; const _upEvent = (e: PointerEvent): void => { + (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < 300); + (target as any)._lastTap = Date.now(); upEvent(e); if (Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4) { - clickEvent(e); + clickEvent(e, (target as any)._doubleTap); } document.removeEventListener("pointermove", _moveEvent); document.removeEventListener("pointerup", _upEvent); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 578d337d6..1268c97b0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -612,6 +612,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Freeform }, id); } + export function PileDocument(documents: Array, options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Pile }, id); + } + export function LinearDocument(documents: Array, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Linear }, id); } diff --git a/src/client/views/collections/CollectionPileView.scss b/src/client/views/collections/CollectionPileView.scss new file mode 100644 index 000000000..ac874b663 --- /dev/null +++ b/src/client/views/collections/CollectionPileView.scss @@ -0,0 +1,8 @@ +.collectionPileView { + display: flex; + flex-direction: row; + position: absolute; + height: 100%; + width: 100%; + overflow: visible; +} diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx new file mode 100644 index 000000000..511efe0a7 --- /dev/null +++ b/src/client/views/collections/CollectionPileView.tsx @@ -0,0 +1,97 @@ +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types"; +import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from "../ContextMenuItem"; +import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; +import { CollectionSubView } from "./CollectionSubView"; +import "./CollectionPileView.scss"; +import React = require("react"); +import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils"; +import { SelectionManager } from "../../util/SelectionManager"; + +@observer +export class CollectionPileView extends CollectionSubView(doc => doc) { + _lastTap = 0; + _doubleTap: boolean | undefined = false; + _originalChrome: string = ""; + @observable _contentsActive = true; + @observable _layoutEngine = "pass"; + @observable _collapsed: boolean = false; + @observable _childClickedScript: Opt; + componentDidMount() { + this._originalChrome = StrCast(this.layoutDoc._chromeStatus); + this.layoutDoc._chromeStatus = "disabled"; + this.layoutDoc.hideFilterView = true; + } + componentWillUnmount() { + this.layoutDoc.hideFilterView = false; + this.layoutDoc._chromeStatus = this._originalChrome; + } + + layoutEngine = () => this._layoutEngine; + + @computed get contents() { + return
+ +
; + } + + specificMenu = (e: React.MouseEvent) => { + const layoutItems: ContextMenuProps[] = []; + const doc = this.props.Document; + + ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" }); + } + + toggleStarburst = action(() => { + if (this._layoutEngine === 'starburst') { + const defaultSize = 110; + this.layoutDoc.overflow = undefined; + this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2; + this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2; + this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize); + this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize); + this._layoutEngine = 'pass'; + } else { + const defaultSize = 25; + this.layoutDoc.overflow = 'visible'; + !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500); + !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5); + if (this._layoutEngine === 'pass') { + this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2; + this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2; + this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym](); + this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym](); + } + this.layoutDoc._width = this.layoutDoc._height = defaultSize; + this._layoutEngine = 'starburst'; + } + }); + + pointerDown = (e: React.PointerEvent) => { + // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header + setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false, false); // this sets _doubleTap + } + + onClick = (e: React.MouseEvent) => { + if (e.button === 0 && (this._doubleTap || this.layoutEngine() === "starburst")) { + SelectionManager.DeselectAll(); + this.toggleStarburst(); + e.stopPropagation(); + } else if (this.layoutEngine() === "pass") { + runInAction(() => this._contentsActive = false); + setTimeout(action(() => this._contentsActive = true), 300); + } + } + + render() { + + return
+ {this.contents} +
; + } +} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 78ba3d6e6..c530b24fb 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -400,9 +400,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: if (generatedDocuments.length) { const set = generatedDocuments.length > 1 && generatedDocuments.map(d => Doc.iconify(d)); if (set) { - const pile = Docs.Create.FreeformDocument(generatedDocuments, { ...options, title: "pile", _LODdisable: true, }); - Doc.pileup(pile, generatedDocuments); - addDocument(pile); + addDocument(Doc.pileup(generatedDocuments, options.x!, options.y!)); } else { generatedDocuments.forEach(addDocument); } diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 63498cea1..045134225 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -19,7 +19,6 @@ const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; import React = require("react"); -import { DocumentView } from "../nodes/DocumentView"; @observer export class CollectionTimeView extends CollectionSubView(doc => doc) { @@ -84,7 +83,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { } @computed get contents() { - return
+ return
; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 47e79f088..f82c6d8a6 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -46,6 +46,7 @@ import { InteractionUtils } from '../../util/InteractionUtils'; import { ObjectField } from '../../../new_fields/ObjectField'; import CollectionMapView from './CollectionMapView'; import { Transform } from 'prosemirror-transform'; +import { CollectionPileView } from './CollectionPileView'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -67,7 +68,8 @@ export enum CollectionViewType { Carousel = "carousel", Linear = "linear", Staff = "staff", - Map = "map" + Map = "map", + Pile = "pileup" } export interface CollectionRenderProps { @@ -169,6 +171,7 @@ export class CollectionView extends Touchable { case CollectionViewType.Multicolumn: return (); case CollectionViewType.Multirow: return (); case CollectionViewType.Linear: { return (); } + case CollectionViewType.Pile: { return (); } case CollectionViewType.Carousel: { return (); } case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index d8bd18ec7..9ca747113 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -13,10 +13,11 @@ import { listSpec } from "../../../../new_fields/Schema"; export interface ViewDefBounds { type: string; - text?: string; + payload: any; x: number; y: number; z?: number; + text?: string; zIndex?: number; width?: number; height?: number; @@ -24,14 +25,13 @@ export interface ViewDefBounds { fontSize?: number; highlight?: boolean; color?: string; - payload: any; replica?: string; pair?: { layout: Doc, data?: Doc }; } export interface PoolData { - x?: number; - y?: number; + x: number; + y: number; z?: number; zIndex?: number; width?: number; @@ -40,7 +40,7 @@ export interface PoolData { transition?: string; highlight?: boolean; replica?: string; - pair: { layout: Doc, data?: Doc } + pair: { layout: Doc, data?: Doc }; } export interface ViewDefResult { @@ -84,96 +84,88 @@ interface PivotColumn { export function computerPassLayout( poolData: Map, pivotDoc: Doc, - filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { - const docMap = new Map(); + const docMap = new Map(); childPairs.forEach(({ layout, data }, i) => { docMap.set(layout[Id], { - type: "doc", x: NumCast(layout.x), y: NumCast(layout.y), width: layout[WidthSym](), height: layout[HeightSym](), - payload: undefined, pair: { layout, data }, replica: "" }); }); - return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout))); + return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []); } export function computerStarburstLayout( poolData: Map, pivotDoc: Doc, - filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { - const docMap = new Map(); + const docMap = new Map(); const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])]; const docScale = NumCast(pivotDoc._starburstDocScale); const docSize = docScale * 100; // assume a icon sized at 100 const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize]; - childPairs.forEach((pair, i) => { + childPairs.forEach(({ layout, data }, i) => { const deg = i / childPairs.length * Math.PI * 2; - docMap.set(pair.layout[Id], { - type: "doc", - x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * pair.layout[WidthSym]() / 2, - y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * pair.layout[HeightSym]() / 2, - width: docScale * pair.layout[WidthSym](), - height: docScale * pair.layout[HeightSym](), - payload: undefined, + docMap.set(layout[Id], { + x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * layout[WidthSym]() / 2, + y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * layout[HeightSym]() / 2, + width: docScale * layout[WidthSym](), + height: docScale * layout[HeightSym](), + pair: { layout, data }, replica: "" }); }); - return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout))); + return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []); } export function computePivotLayout( poolData: Map, pivotDoc: Doc, - filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { - const docMap = new Map(); + const docMap = new Map(); const fieldKey = "data"; const pivotColumnGroups = new Map, PivotColumn>(); const pivotFieldKey = toLabel(pivotDoc._pivotField); - for (const doc of filterDocs) { - const lval = Cast(doc[pivotFieldKey], listSpec("string"), null); - const val = Field.toString(doc[pivotFieldKey] as Field); + childPairs.map(pair => { + const lval = Cast(pair.layout[pivotFieldKey], listSpec("string"), null); + const val = Field.toString(pair.layout[pivotFieldKey] as Field); if (lval) { lval.forEach((val, i) => { !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); - pivotColumnGroups.get(val)!.docs.push(doc); + pivotColumnGroups.get(val)!.docs.push(pair.layout); pivotColumnGroups.get(val)!.replicas.push(i.toString()); }); } else if (val) { !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); - pivotColumnGroups.get(val)!.docs.push(doc); + pivotColumnGroups.get(val)!.docs.push(pair.layout); pivotColumnGroups.get(val)!.replicas.push(""); } else { - docMap.set(doc[Id], { - type: "doc", + docMap.set(pair.layout[Id], { x: 0, y: 0, zIndex: -99, width: 0, height: 0, - payload: undefined, - pair: { layout: doc }, + pair, replica: "" - }) + }); } - } + }); let nonNumbers = 0; childPairs.map(pair => { const num = toNumber(pair.layout[pivotFieldKey]); @@ -252,12 +244,10 @@ export function computePivotLayout( wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth; } docMap.set(doc[Id] + (val.replicas || ""), { - type: "doc", x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0), y: -y + (pivotAxisWidth - hgt) / 2, width: wid, height: hgt, - payload: undefined, pair: { layout: doc }, replica: val.replicas[i] }); @@ -273,7 +263,7 @@ export function computePivotLayout( const dividers = sortedPivotKeys.map((key, i) => ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters })); groupNames.push(...dividers); - return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childPairs.filter(c => !filterDocs.includes(c.layout))); + return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []); } function toNumber(val: FieldResult) { @@ -283,14 +273,13 @@ function toNumber(val: FieldResult) { export function computeTimelineLayout( poolData: Map, pivotDoc: Doc, - filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { const fieldKey = "data"; const pivotDateGroups = new Map(); - const docMap = new Map(); + const docMap = new Map(); const groupNames: ViewDefBounds[] = []; const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field); const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]); @@ -306,11 +295,11 @@ export function computeTimelineLayout( let minTime = minTimeReq === undefined ? Number.MAX_VALUE : minTimeReq; let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq; - filterDocs.map(doc => { - const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey]))); + childPairs.forEach(pair => { + const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey]))); if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) { !pivotDateGroups.get(num) && pivotDateGroups.set(num, []); - pivotDateGroups.get(num)!.push(doc); + pivotDateGroups.get(num)!.push(pair.layout); minTime = Math.min(num, minTime); maxTime = Math.max(num, maxTime); } @@ -369,7 +358,7 @@ export function computeTimelineLayout( } const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined }; - return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childPairs.filter(c => !filterDocs.includes(c.layout))); + return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]); function layoutDocsAtTime(keyDocs: Doc[], key: number) { keyDocs.forEach(doc => { @@ -382,13 +371,11 @@ export function computeTimelineLayout( wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth; } docMap.set(doc[Id], { - type: "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 / (Math.max(stack, 1)), - payload: undefined, pair: { layout: doc }, replica: "" }); @@ -397,12 +384,19 @@ export function computeTimelineLayout( } } -function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map, - poolData: Map, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[], - extraDocs: { data?: Doc, layout: Doc }[]): ViewDefResult[] { +function normalizeResults( + panelDim: number[], + fontHeight: number, + docMap: Map, + poolData: Map, + viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], + groupNames: ViewDefBounds[], + minWidth: number, + extras: ViewDefBounds[] +): ViewDefResult[] { const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds); const docEles = Array.from(docMap.entries()).map(ele => ele[1]); - const aggBounds = aggregateBounds(docEles.concat(grpEles).filter(e => e.zIndex !== -99), 0, 0); + const aggBounds = aggregateBounds(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" }))).filter(e => e.zIndex !== -99), 0, 0); aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x); const wscale = panelDim[0] / (aggBounds.r - aggBounds.x); let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale; @@ -420,12 +414,11 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { zIndex: newPosRaw.zIndex, width: (newPosRaw.width || 0) * scale, height: newPosRaw.height! * scale, - pair: ele[1].pair! + pair: ele[1].pair }; poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos }); } }); - extraDocs.map(ed => poolData.set(ed.layout[Id], { x: 0, y: 0, zIndex: -99, pair: ed })); return viewDefsToJSX(extras.concat(groupNames).map(gname => ({ type: gname.type, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5555d9e84..af9d3c5be 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -902,7 +902,7 @@ export class CollectionFreeFormView extends CollectionSubView, pivotDoc: Doc, - filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], - viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]) { - return engine(poolData, this.props.Document, this.childDocs, - this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); + viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[] + ) { + return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); } doFreeformLayout(poolData: Map) { @@ -977,7 +976,7 @@ export class CollectionFreeFormView extends CollectionSubView(); - const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.(); + const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); switch (engine) { case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; @@ -1001,6 +1000,7 @@ export class CollectionFreeFormView extends CollectionSubView this._cachedPool.set(k[0], k[1])); const elements: ViewDefResult[] = computedElementData.slice(); + const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry => elements.push({ ele: , @@ -1082,7 +1083,7 @@ export class CollectionFreeFormView extends CollectionSubView this.props.Document.jitterRotation = (this.props.Document.jitterRotation ? 0 : 10)), icon: "paint-brush" }); + optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" }); optionItems.push({ description: "Import document", icon: "upload", event: ({ x, y }) => { const input = document.createElement("input"); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ab8174ba1..cd8166309 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,12 +1,12 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_fields/Doc"; +import { Doc, DocListCast, DataSym, WidthSym, HeightSym, Opt } from "../../../../new_fields/Doc"; import { InkField, InkData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types"; import { Utils } from "../../../../Utils"; -import { Docs, DocUtils } from "../../../documents/Documents"; +import { Docs, DocUtils, DocumentOptions } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; @@ -310,11 +310,10 @@ export class MarqueeView extends React.Component { + getCollection = (selected: Doc[], creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => { const bounds = this.Bounds; // const inkData = this.ink ? this.ink.inkData : undefined; - const creator = asTemplate ? Docs.Create.StackingDocument : Docs.Create.FreeformDocument; - const newCollection = creator(selected, { + const newCollection = (creator || Docs.Create.FreeformDocument)(selected, { x: bounds.left, y: bounds.top, _panX: 0, @@ -338,8 +337,7 @@ export class MarqueeView extends React.Component this.props.removeDocument(d)); - const newCollection = this.getCollection(selected, false); - Doc.pileup(newCollection, selected); + const newCollection = Doc.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2); this.props.addDocument(newCollection); this.props.selectDocuments([newCollection], []); MarqueeOptionsMenu.Instance.fadeOut(true); @@ -359,7 +357,7 @@ export class MarqueeView extends React.Component { - const newCollection = this.getCollection([], false, true); + const newCollection = this.getCollection([], undefined, true); this.props.addDocument(newCollection); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 18ffc38f5..337274774 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -976,7 +976,8 @@ export namespace Doc { if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", ""); } - export function pileup(newCollection: Doc, selected: Doc[]) { + export function pileup(selected: Doc[], x: number, y: number) { + const newCollection = Docs.Create.PileDocument(selected, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true }); let w = 0, h = 0; selected.forEach((d, i) => { Doc.iconify(d); @@ -993,12 +994,10 @@ export namespace Doc { newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55; newCollection._width = newCollection._height = 110; //newCollection.borderRounding = "40px"; - newCollection.jitterRotation = 10; + newCollection._jitterRotation = 10; newCollection._backgroundColor = "gray"; - - newCollection._layoutEngine = "pass"; newCollection.overflow = "visible"; - newCollection.onClick = ScriptField.MakeScript("toggleStarburst(self, this)", { self: Doc.name, this: Doc.name }); + return newCollection; } @@ -1055,28 +1054,4 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo return docs.length ? new List(docs) : prevValue; }); Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: "check" | "x" | undefined) { Doc.setDocFilter(container, key, value, modifiers); }); -Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); }); -Scripting.addGlobal(function toggleStarburst(self: Doc, tLayout: Doc) { - if (self._layoutEngine === 'starburst') { - const defaultSize = 110; - tLayout.overflow = undefined; - self.x = NumCast(self.x) + tLayout[WidthSym]() / 2 - NumCast(tLayout._starburstPileWidth, defaultSize) / 2; - self.y = NumCast(self.y) + tLayout[HeightSym]() / 2 - NumCast(tLayout._starburstPileHeight, defaultSize) / 2; - tLayout._width = NumCast(tLayout._starburstPileWidth, defaultSize); - tLayout._height = NumCast(tLayout._starburstPileHeight, defaultSize); - self._layoutEngine = 'pass'; - } else { - const defaultSize = 25; - tLayout.overflow = 'visible'; - !tLayout._starburstRadius && (tLayout._starburstRadius = 500); - !tLayout._starburstDocScale && (tLayout._starburstDocScale = 2.5); - if (self._layoutEngine === 'pass') { - self.x = NumCast(self.x) + tLayout[WidthSym]() / 2 - defaultSize / 2; - self.y = NumCast(self.y) + tLayout[HeightSym]() / 2 - defaultSize / 2; - tLayout._starburstPileWidth = tLayout[WidthSym](); - tLayout._starburstPileHeight = tLayout[HeightSym](); - } - tLayout._width = tLayout._height = defaultSize; - self._layoutEngine = 'starburst'; - } -}); \ No newline at end of file +Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From c6d8ac7eef3480a7e19687b066456d9d34519587 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 25 Apr 2020 20:25:50 -0400 Subject: several fixes for performance and for working with isBackground objects. --- .../CollectionStackingViewFieldColumn.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 2 +- .../CollectionFreeFormLayoutEngines.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 21 ++++++++++++++------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 21 ++++++++++++++------- src/client/views/nodes/ImageBox.scss | 9 +-------- src/client/views/nodes/ImageBox.tsx | 8 ++++---- src/new_fields/util.ts | 2 +- 9 files changed, 39 insertions(+), 32 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx') diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 5d926b7c7..8992de9d1 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -364,7 +364,7 @@ export class CollectionStackingViewFieldColumn extends React.Component diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 2bd37bda5..801704673 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -493,7 +493,7 @@ export class CollectionView extends Touchable { {!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) :
} - {this.filterView} + {this.facetWidth() < 10 ? (null) : this.filterView}
); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 9ca747113..9a864078a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -39,7 +39,7 @@ export interface PoolData { color?: string; transition?: string; highlight?: boolean; - replica?: string; + replica: string; pair: { layout: Doc, data?: Doc }; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6a3764bb7..a13c81a15 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -508,7 +508,7 @@ export class CollectionFreeFormView extends CollectionSubView BoolCast(this.Document.useClusters); - + @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); } + parentActive = () => this.props.active() || this.backgroundActive ? true : false; getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { ...this.props, @@ -879,7 +880,7 @@ export class CollectionFreeFormView extends CollectionSubView) { + childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) { return this._layoutPoolData.get(doc[Id] + (replica || "")); }.bind(this)); @@ -975,6 +976,9 @@ export class CollectionFreeFormView extends CollectionSubView(); const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); switch (engine) { @@ -1009,8 +1013,10 @@ export class CollectionFreeFormView extends CollectionSubView { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined; + dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined; x?: number; y?: number; z?: number; @@ -23,7 +23,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { jitterRotation: number; transition?: string; fitToBox?: boolean; - replica?: string; + replica: string; } @observer diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f9d9ca9a1..7ae77fc65 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -281,12 +281,12 @@ export class DocumentView extends DocComponent(Docu } } - onClick = (e: React.MouseEvent | React.PointerEvent) => { + onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { let stopPropagate = true; let preventDefault = true; - this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document); + !this.props.Document.isBackground && this.props.bringToFront(this.props.Document); if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click if (!(e.nativeEvent as any).formattedHandled) { const fullScreenAlias = Doc.MakeAlias(this.props.Document); @@ -329,7 +329,7 @@ export class DocumentView extends DocComponent(Docu stopPropagate && e.stopPropagation(); preventDefault && e.preventDefault(); } - } + }); // follows a link - if the target is on screen, it highlights/pans to it. // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place @@ -347,8 +347,12 @@ export class DocumentView extends DocComponent(Docu this.props.addDocTab(doc, where) && this.props.focus(doc, BoolCast(this.Document.followLinkZoom, true), undefined, hackToCallFinishAfterFocus); // add the target and focus on it. return where !== "inPlace"; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target) }; - // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target - this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus); + if (!this.Document.followLinkZoom) { + targetFocusAfterDocFocus(); + } else { + // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target + this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus); + } }; await DocumentManager.Instance.FollowLink(undefined, this.props.Document, createViewFunc, shiftKey, this.props.ContainingCollectionDoc, batch.end, altKey ? true : undefined); } @@ -642,6 +646,7 @@ export class DocumentView extends DocComponent(Docu const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" }); DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to"); } + this.Document.followLinkZoom = true; this.Document.isLinkButton = true; } @@ -1069,7 +1074,7 @@ export class DocumentView extends DocComponent(Docu const titleView = (!showTitle ? (null) :
(Docu
; } @computed get ignorePointerEvents() { - return this.props.pointerEvents === false || (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None); + return this.props.pointerEvents === false || + (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || + (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None); } @undoBatch @action diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 49425c2c2..15148d01d 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -1,5 +1,4 @@ -.imageBox, -.imageBox-dragging { +.imageBox { border-radius: inherit; width: 100%; height: 100%; @@ -11,12 +10,6 @@ } } -.imageBox-dragging { - .imageBox-fader { - pointer-events: none; - } -} - #upload-icon { position: absolute; bottom: 0; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index e189f5814..14b367868 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -360,7 +360,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url.href).filter(url => url); // access the primary layout data of the alternate documents + const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; return paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; } @@ -438,8 +439,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent [this.content]; render() { TraceMobx(); - const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging"; - return (