From 52dc823c6a4c4b3e8deb994358a10acca4b6bd6c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 24 Jul 2019 12:31:07 -0400
Subject: Added transitions

---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index b09538d1a..389b9f68b 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -82,6 +82,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     render() {
+        const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
         return (
             <div className="collectionFreeFormDocumentView-container"
                 style={{
@@ -90,7 +91,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     backgroundColor: "transparent",
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
-                    transition: StrCast(this.props.Document.transition),
+                    transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
                     width: this.width,
                     height: this.height,
                     zIndex: this.Document.zIndex || 0,
-- 
cgit v1.2.3-70-g09d2


From cc806ebbe8d48d1c5a4c3c49231a5d38d6f39943 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 27 Jul 2019 04:12:07 -0400
Subject: fixed a bunch of template and stacking view issues among others.

---
 src/client/views/DocumentDecorations.tsx           |  4 +-
 src/client/views/GlobalKeyHandler.ts               |  1 +
 src/client/views/MainView.tsx                      |  2 +-
 .../views/collections/CollectionDockingView.tsx    | 39 +++++--------
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 .../views/collections/CollectionStackingView.tsx   | 68 +++++++++-------------
 src/client/views/collections/CollectionView.tsx    |  2 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 22 +++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 16 +----
 src/client/views/nodes/DocumentView.tsx            | 14 +++--
 src/new_fields/Doc.ts                              | 13 ++++-
 11 files changed, 75 insertions(+), 108 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 255855b45..c08a84742 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -533,7 +533,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     proto.nativeHeight = nheight = doc.height || 0;
                     proto.ignoreAspect = true;
                 }
-                if (nwidth > 0 && nheight > 0) {
+                if (nwidth > 0 && nheight > 0 && !BoolCast(proto.ignoreAspect)) {
                     if (Math.abs(dW) > Math.abs(dH)) {
                         if (!fixedAspect) {
                             Doc.SetInPlace(element.props.Document, "nativeWidth", actualdW / (doc.width || 1) * (doc.nativeWidth || 0), true);
@@ -553,7 +553,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 } else {
                     dW && (doc.width = actualdW);
                     dH && (doc.height = actualdH);
-                    Doc.SetInPlace(element.props.Document, "autoHeight", undefined, true);
+                    dH && Doc.SetInPlace(element.props.Document, "autoHeight", undefined, true);
                 }
             }
         });
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 7477c5b4f..5050f34ab 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -67,6 +67,7 @@ export default class KeyManager {
                     }
                 }
                 MainView.Instance.toggleColorPicker(true);
+                SelectionManager.DeselectAll();
                 break;
             case "delete":
             case "backspace":
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 61a013963..bfb50bc75 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -407,7 +407,7 @@ export class MainView extends React.Component {
                             </button>
                         </div></li>)}
                     <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
-                    <li key="color"><button className="add-button round-button" title="Select Color" onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
+                    <li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
                         <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
                             <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
                         </div>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ba7903419..0865058be 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -539,17 +539,17 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
     }
 
+    panelWidth = () => Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth()));
+    panelHeight = () => Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), NumCast(this._document!.nativeHeight, this._panelHeight)));
+
+    nativeWidth = () => !BoolCast(this._document!.ignoreAspect) ? NumCast(this._document!.nativeWidth, this._panelWidth) : 0;
+    nativeHeight = () => !BoolCast(this._document!.ignoreAspect) ? NumCast(this._document!.nativeHeight, this._panelHeight) : 0;
 
-    nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
-    nativeHeight = () => {
-        let nh = NumCast(this._document!.nativeHeight, this._panelHeight);
-        let res = BoolCast(this._document!.ignoreAspect) ? this._panelHeight : nh;
-        return res;
-    }
     contentScaling = () => {
         const nativeH = this.nativeHeight();
         const nativeW = this.nativeWidth();
-        let wscale = this._panelWidth / nativeW;
+        if (!nativeW || !nativeH) return 1;
+        let wscale = this.panelWidth() / nativeW;
         return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
     }
 
@@ -561,18 +561,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
         return Transform.Identity();
     }
-    get scaleToFitMultiplier() {
-        let docWidth = NumCast(this._document!.width);
-        let docHeight = NumCast(this._document!.height);
-        if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1;
-        if (StrCast(this._document!.layout).indexOf("Collection") === -1 ||
-            !BoolCast(this._document!.fitToContents, false) ||
-            NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1;
-        let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ?
-            this._panelHeight / docHeight : this._panelWidth / docWidth);
-        return scaling;
-    }
-    get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
+    get previewPanelCenteringOffset() { return this.nativeWidth && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
 
     addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => {
         if (doc.dockingConfig) {
@@ -588,6 +577,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             return (null);
         }
         let resolvedDataDoc = this._document.layout instanceof Doc ? this._document : this._dataDoc;
+        console.log("pw = " + this.panelWidth() + "," + this.panelHeight() + " " + this.contentScaling());
         return <DocumentView key={this._document[Id]}
             Document={this._document}
             DataDoc={resolvedDataDoc}
@@ -595,8 +585,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             addDocument={undefined}
             removeDocument={undefined}
             ContentScaling={this.contentScaling}
-            PanelWidth={this.nativeWidth}
-            PanelHeight={this.nativeHeight}
+            PanelWidth={this.panelWidth}
+            PanelHeight={this.panelHeight}
             ScreenToLocalTransform={this.ScreenToLocalTransform}
             renderDepth={0}
             selectOnLoad={false}
@@ -610,18 +600,15 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     }
 
     @computed get content() {
-        if (!this._document) {
-            return (null);
-        }
         return (
             <div className="collectionDockingView-content" ref={this._mainCont}
-                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier})` }}>
+                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
                 {this.docView}
             </div >);
     }
 
     render() {
-        if (!this._isActive) return null;
+        if (!this._isActive || !this._document) return null;
         let theContent = this.content;
         return !this._document ? (null) :
             <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 119aa7c19..8ce3b40e5 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -563,7 +563,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         height: "100%"
                     }}>
                     <DocumentView
-                        DataDoc={this.props.Document.layout instanceof Doc ? this.props.Document : this.props.DataDocument}
+                        DataDoc={this.props.DataDocument}
                         Document={this.props.Document}
                         fitToBox={this.props.fitToBox}
                         renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 5a123bf65..3da13bff5 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -46,14 +46,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return fields;
     }
     componentDidMount() {
-        this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
+        this._heightDisposer = reaction(() => [this.yMargin, this.props.Document[WidthSym](), this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
             () => {
-                if (this.singleColumn) {
+                if (this.singleColumn && BoolCast(this.props.Document.autoHeight)) {
                     let hgt = this.Sections.size * 50 + this.filteredChildren.reduce((height, d, i) => {
                         let xhgt = height + this.getDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap);
                         return xhgt;
                     }, this.yMargin);
-                    this.layoutDoc.height = hgt;
+                    this.layoutDoc.height = hgt * (this.props as any).ContentScaling();
                 }
             }, { fireImmediately: true });
     }
@@ -74,14 +74,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return doc.type === DocumentType.IMG || doc.type === DocumentType.VID ? { title: "title", caption: "caption" } : {};
     }
 
-    getDisplayDoc(layoutDoc: Doc, d: Doc, dxf: () => Transform) {
-        let resolvedDataDoc = !this.layoutDoc.isTemplate && this.props.DataDoc !== this.layoutDoc ? this.props.DataDoc : undefined;
-        let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
+    getDisplayDoc(layoutDoc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
         let height = () => this.getDocHeight(layoutDoc);
         let finalDxf = () => dxf().scale(this.columnWidth / layoutDoc[WidthSym]());
         return <CollectionSchemaPreview
             Document={layoutDoc}
-            DataDocument={resolvedDataDoc}
+            DataDocument={dataDoc}
             showOverlays={this.overlays}
             renderDepth={this.props.renderDepth}
             fitToBox={true}
@@ -102,51 +100,37 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     getDocHeight(d: Doc) {
         let nw = NumCast(d.nativeWidth);
         let nh = NumCast(d.nativeHeight);
-        let aspect = nw && nh ? nh / nw : 1;
-        let wid = Math.min(d[WidthSym](), this.columnWidth);
-        return (nw && nh) ? wid * aspect : d[HeightSym]();
+        if (!BoolCast(d.ignoreAspect) && nw && nh) {
+            let aspect = nw && nh ? nh / nw : 1;
+            let wid = Math.min(d[WidthSym](), this.columnWidth);
+            return wid * aspect;
+        }
+        return d[HeightSym]();
     }
 
-    offsetTransform(doc: Doc, translateX: number, translateY: number) {
+    getDocTransform(doc: Doc, dref: HTMLDivElement) {
+        let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
         let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
         let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
         return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.columnWidth);
     }
-    getDocTransform(doc: Doc, dref: HTMLDivElement) {
-        let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
-        return this.offsetTransform(doc, translateX, translateY);
-    }
-
-    getSingleDocTransform(doc: Doc, ind: number, width: number) {
-        let localY = this.filteredChildren.reduce((height, d, i) =>
-            height + (i < ind ? this.getDocHeight(Doc.expandTemplateLayout(d, this.props.DataDoc)) + this.gridGap : 0), this.yMargin);
-        let translate = this.props.ScreenToLocalTransform().inverse().transformPoint((this.props.PanelWidth() - width) / 2, localY);
-        return this.offsetTransform(doc, translate[0], translate[1]);
-    }
 
     children(docs: Doc[]) {
         this._docXfs.length = 0;
         return docs.map((d, i) => {
-            let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
-            let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
-            let height = () => this.getDocHeight(layoutDoc);
-            if (this.singleColumn) {
-                //have to add the height of all previous single column sections or the doc decorations will be in the wrong place.
-                let dxf = () => this.getSingleDocTransform(layoutDoc, i, width());
-                let rowHgtPcnt = height();
-                this._docXfs.push({ dxf: dxf, width: width, height: height });
-                return <div className="collectionStackingView-columnDoc" key={d[Id]} style={{ width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: `${rowHgtPcnt}` }} >
-                    {this.getDisplayDoc(layoutDoc, d, dxf)}
-                </div>;
-            } else {
-                let dref = React.createRef<HTMLDivElement>();
-                let dxf = () => this.getDocTransform(layoutDoc, dref.current!);
-                let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
-                this._docXfs.push({ dxf: dxf, width: width, height: height });
-                return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
-                    {this.getDisplayDoc(layoutDoc, d, dxf)}
-                </div>;
-            }
+            let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d)
+            let width = () => d.nativeWidth && !BoolCast(d.ignoreAspect) ? Math.min(pair.layout[WidthSym](), this.columnWidth) : this.columnWidth;
+            let height = () => this.getDocHeight(pair.layout);
+            let dref = React.createRef<HTMLDivElement>();
+            let dxf = () => this.getDocTransform(pair.layout, dref.current!);
+            this._docXfs.push({ dxf: dxf, width: width, height: height });
+            let rowHgtPcnt = height();
+            let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
+            let style = this.singleColumn ? { width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: `${rowHgtPcnt}` } : { gridRowEnd: `span ${rowSpan}` };
+
+            return <div className={`collectionStackingView-${this.singleColumn ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
+                {this.getDisplayDoc(pair.layout, pair.data, dxf, width)}
+            </div>;
         });
     }
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7781b26d9..cd137cd60 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -15,8 +15,6 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionTreeView } from "./CollectionTreeView";
-import { StrCast, PromiseValue } from '../../../new_fields/Types';
-import { DocumentType } from '../../documents/Documents';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5d61aefc7..f83d02271 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -61,11 +61,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private inkKey = "ink";
 
     get parentScaling() {
-        return (this.props as any).ContentScaling && this.Document.nativeWidth && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;
+        return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;
     }
 
     @computed get contentBounds() {
-        let bounds = this.fitToBox && !this.nativeWidth && !this.isAnnotationOverlay ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
+        let bounds = this.fitToBox && !this.isAnnotationOverlay ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
         return {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
@@ -370,24 +370,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
         let self = this;
-        let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
-        let layoutDoc = childDocLayout;
-        if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
-            Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
-            let fieldExtensionDoc = Doc.resolvedFieldDataDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy");
-            layoutDoc = Doc.expandTemplateLayout(childDocLayout, fieldExtensionDoc !== resolvedDataDoc ? fieldExtensionDoc : undefined);
-        } else layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
+        let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout);
         return {
-            DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : undefined,
-            Document: layoutDoc,
+            DataDoc: pair.data,
+            Document: pair.layout,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
             ScreenToLocalTransform: this.getTransform,
             renderDepth: this.props.renderDepth + 1,
-            selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
-            PanelWidth: layoutDoc[WidthSym],
-            PanelHeight: layoutDoc[HeightSym],
+            selectOnLoad: pair.layout[Id] === this._selectOnLoaded,
+            PanelWidth: pair.layout[WidthSym],
+            PanelHeight: pair.layout[HeightSym],
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 389b9f68b..7ffd760e0 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -35,21 +35,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); }
     @computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); }
     @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); }
-
-    set width(w: number) {
-        this.Document.width = w;
-        if (this.nativeWidth && this.nativeHeight) {
-            this.Document.height = this.nativeHeight / this.nativeWidth * w;
-        }
-    }
-    set height(h: number) {
-        this.Document.height = h;
-        if (this.nativeWidth && this.nativeHeight) {
-            this.Document.width = this.nativeWidth / this.nativeHeight * h;
-        }
-    }
     @computed get scaleToOverridingWidth() { return this.width / NumCast(this.props.Document.width, this.width); }
-    contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
+
+    contentScaling = () => this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? this.width / this.nativeWidth : 1;
     panelWidth = () => this.props.PanelWidth();
     panelHeight = () => this.props.PanelHeight();
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bae0b5b96..3152d6a2a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -507,12 +507,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @action
     freezeNativeDimensions = (): void => {
         let proto = this.props.Document.isTemplate ? this.props.Document : Doc.GetProto(this.props.Document);
-        if (proto.ignoreAspect === undefined && !proto.nativeWidth) {
+        this.props.Document.autoHeight = proto.autoHeight = false;
+        proto.ignoreAspect = !BoolCast(proto.ignoreAspect);
+        if (!BoolCast(proto.ignoreAspect) && !proto.nativeWidth) {
             proto.nativeWidth = this.props.PanelWidth();
             proto.nativeHeight = this.props.PanelHeight();
-            proto.ignoreAspect = true;
         }
-        proto.ignoreAspect = !BoolCast(proto.ignoreAspect, false);
     }
     @undoBatch
     @action
@@ -546,7 +546,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" });
         subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
         cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
-        cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "edit" });
+        cm.addItem({ description: !BoolCast(this.props.Document.ignoreAspect) && this.props.Document.nativeWidth && this.props.Document.nativeHeight ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" });
         cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
         cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Pos" : "Lock Pos", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
         cm.addItem({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
@@ -642,7 +642,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let self = this;
         let backgroundColor = StrCast(this.layoutDoc.backgroundColor);
         let foregroundColor = StrCast(this.layoutDoc.color);
-        var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
+        var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
         let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined;
         let showTitle = showOverlays && showOverlays.title !== "undefined" ? showOverlays.title : StrCast(this.layoutDoc.showTitle);
@@ -665,6 +665,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     outlineStyle: "dashed",
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
+                    marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+                        `${-1 * this.props.ScreenToLocalTransform().Scale}px` : undefined,
+                    marginTop: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+                        `${-1 * this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     borderRadius: "inherit",
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 1a00db1c1..dbb0dc505 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -389,11 +389,22 @@ export namespace Doc {
         }
         if (expandedTemplateLayout === undefined) {
             setTimeout(() =>
-                dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "["+templateLayoutDoc.title + "]"), 0);
+                dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]"), 0);
         }
         return templateLayoutDoc; // use the templateLayout when it's not a template or the expandedTemplate is pending.
     }
 
+    export function GetLayoutDataDocPair(doc: Doc, dataDoc: Doc | undefined, fieldKey: string, childDocLayout: Doc) {
+        let layoutDoc = childDocLayout;
+        let resolvedDataDoc = !doc.isTemplate && dataDoc !== doc ? dataDoc : undefined;
+        if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
+            Doc.UpdateDocumentExtensionForField(resolvedDataDoc, fieldKey);
+            let fieldExtensionDoc = Doc.resolvedFieldDataDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy");
+            layoutDoc = Doc.expandTemplateLayout(childDocLayout, fieldExtensionDoc !== resolvedDataDoc ? fieldExtensionDoc : undefined);
+        } else layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
+        return { layout: layoutDoc, data: resolvedDataDoc };
+    }
+
     export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc {
         const copy = new Doc;
         Object.keys(doc).forEach(key => {
-- 
cgit v1.2.3-70-g09d2


From 6f3d4a7015e15e0523fc194f7f911f6d45259165 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 1 Aug 2019 13:57:59 -0400
Subject: added floating docs

---
 src/client/util/DragManager.ts                     |  7 +++-
 src/client/views/TemplateMenu.tsx                  | 44 ++++++++++++++++++++-
 .../collectionFreeForm/CollectionFreeFormView.scss |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 46 +++++++++++++++-------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  1 +
 src/client/views/nodes/DocumentView.tsx            | 42 +++++++++-----------
 6 files changed, 100 insertions(+), 41 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index abcc3a4e1..0b5c785a4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -10,6 +10,7 @@ import { LinkManager } from "./LinkManager";
 import { SelectionManager } from "./SelectionManager";
 import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
 import { DocumentDecorations } from "../views/DocumentDecorations";
+import { NumberLiteralType } from "typescript";
 
 export type dropActionType = "alias" | "copy" | undefined;
 export function SetupDrag(
@@ -140,6 +141,10 @@ export namespace DragManager {
         dragHasStarted?: () => void;
 
         withoutShiftDrag?: boolean;
+
+        offsetX?: number;
+
+        offsetY?: number;
     }
 
     export interface DragDropDisposer {
@@ -423,7 +428,7 @@ export namespace DragManager {
             lastX = e.pageX;
             lastY = e.pageY;
             dragElements.map((dragElement, i) => (dragElement.style.transform =
-                `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`)
+                `translate(${(xs[i] += moveX) + (options ? (options.offsetX || 0) : 0)}px, ${(ys[i] += moveY) + (options ? (options.offsetY || 0) : 0)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`)
             );
         };
 
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 1b32f0ddd..c413650f0 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -7,6 +7,10 @@ import { DocumentView } from "./nodes/DocumentView";
 import { Template } from "./Templates";
 import React = require("react");
 import { undoBatch } from "../util/UndoManager";
+import { DocumentManager } from "../util/DocumentManager";
+import { NumCast } from "../../new_fields/Types";
+import { DragManager } from "../util/DragManager";
+import { SelectionManager } from "../util/SelectionManager";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -35,11 +39,46 @@ export interface TemplateMenuProps {
 @observer
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
+    dragRef = React.createRef<HTMLDivElement>();
 
     constructor(props: TemplateMenuProps) {
         super(props);
     }
 
+    toggleFloat = (e: React.MouseEvent): void => {
+        SelectionManager.DeselectAll();
+        let topDocView = this.props.docs[0];
+        let topDoc = topDocView.props.Document;
+        let xf = topDocView.props.ScreenToLocalTransform();
+        let ex = e.clientX;
+        let ey = e.clientY;
+        undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
+        if (!topDoc.z) {
+            setTimeout(() => {
+                let newDocView = DocumentManager.Instance.getDocumentView(topDoc);
+                if (newDocView) {
+                    let de = new DragManager.DocumentDragData([topDoc], [undefined]);
+                    de.moveDocument = topDocView.props.moveDocument;
+                    let xf = newDocView.ContentDiv!.getBoundingClientRect();
+                    console.log("ex = " + ex + " " + xf.left + " " + (ex - xf.left));
+                    DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, {
+                        offsetX: (ex - xf.left), offsetY: (ey - xf.top),
+                        handlers: {
+                            dragComplete: () => { },
+                        },
+                        hideSource: false
+                    });
+                }
+            }, 10);
+        } else if (topDocView.props.ContainingCollectionView) {
+            let collView = topDocView.props.ContainingCollectionView!;
+            let [sx, sy] = xf.inverse().transformPoint(0, 0);
+            let [x, y] = collView.props.ScreenToLocalTransform().transformPoint(sx, sy);
+            topDoc.x = x;
+            topDoc.y = y;
+        }
+    }
+
     @undoBatch
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
@@ -89,9 +128,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         return (
             <div className="templating-menu" >
                 <div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
-                <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
+                <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}>
                     {templateMenu}
-                    <button style={{ display: this._hidden ? "none" : "block" }} onClick={this.clearTemplates}>Clear</button>
+                    <button onClick={this.toggleFloat}>Float</button>
+                    <button onClick={this.clearTemplates}>Clear</button>
                 </ul>
             </div>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index cca199afa..c4311fa52 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -46,6 +46,7 @@
     border-radius: inherit;
     box-sizing: border-box;
     position: absolute;
+    overflow: hidden;
 
     .marqueeView {
         overflow: hidden;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 29f9b1429..9cbc652b9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -77,7 +77,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @computed get contentBounds() {
-        let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds).map(e => e.bounds!)) : undefined;
+        let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
         let res = {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
@@ -98,6 +98,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
     private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
+    private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1);
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
@@ -131,12 +132,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         let xf = this.getTransform();
+        let xfo = this.getTransformOverlay();
         let [xp, yp] = xf.transformPoint(de.x, de.y);
+        let [xpo, ypo] = xfo.transformPoint(de.x, de.y);
         if (super.drop(e, de)) {
             if (de.data instanceof DragManager.DocumentDragData) {
                 if (de.data.droppedDocuments.length) {
-                    let x = xp - de.data.xOffset;
-                    let y = yp - de.data.yOffset;
+                    let z = NumCast(de.data.draggedDocuments[0].z);
+                    let x = (z ? xpo : xp) - de.data.xOffset;
+                    let y = (z ? ypo : yp) - de.data.yOffset;
                     let dropX = NumCast(de.data.droppedDocuments[0].x);
                     let dropY = NumCast(de.data.droppedDocuments[0].y);
                     de.data.droppedDocuments.forEach(d => {
@@ -292,8 +296,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
         this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
         this.props.Document.panY = this.isAnnotationOverlay && StrCast(this.props.Document.backgroundLayout).indexOf("PDFBox") === -1 ? newPanY : panY;
-        // this.props.Document.panX = panX;
-        // this.props.Document.panY = panY;
         if (this.props.Document.scrollY) {
             this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym]();
         }
@@ -396,7 +398,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
-            ScreenToLocalTransform: this.getTransform,
+            ScreenToLocalTransform: pair.layout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
             selectOnLoad: pair.layout[Id] === this._selectOnLoaded,
             PanelWidth: pair.layout[WidthSym],
@@ -436,23 +438,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         };
     }
 
-    getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, width?: number, height?: number, state?: any } {
+    getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } {
         const result = script.script.run(params);
         if (!result.success) {
             return {};
         }
-        return result.result === undefined ? {} : result.result;
+        let doc = params.doc;
+        return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result;
     }
 
-    private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } } | undefined {
+    private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } } | undefined {
         if (viewDef.type === "text") {
             const text = Cast(viewDef.text, "string");
             const x = Cast(viewDef.x, "number");
             const y = Cast(viewDef.y, "number");
+            const z = Cast(viewDef.z, "number");
             const width = Cast(viewDef.width, "number");
             const height = Cast(viewDef.height, "number");
             const fontSize = Cast(viewDef.fontSize, "number");
-            if ([text, x, y, width, height].some(val => val === undefined)) {
+            if ([text, x, y, z, width, height].some(val => val === undefined)) {
                 return undefined;
             }
 
@@ -460,7 +464,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 ele: <div className="collectionFreeform-customText" style={{
                     transform: `translate(${x}px, ${y}px)`,
                     width, height, fontSize
-                }}>{text}</div>, bounds: { x: x!, y: y!, width: width!, height: height! }
+                }}>{text}</div>, bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
             };
         }
     }
@@ -472,7 +476,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const script = this.Document.arrangeScript;
         let state: any = undefined;
         const docs = this.childDocs;
-        let elements: { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } }[] = [];
+        let elements: { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } }[] = [];
         if (initScript) {
             const initResult = initScript.script.run({ docs, collection: this.Document });
             if (initResult.success) {
@@ -494,13 +498,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 let minim = BoolCast(doc.isMinimized);
                 if (minim === undefined || !minim) {
                     const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) :
-                        { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
+                        { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
                     state = pos.state === undefined ? state : pos.state;
                     prev.push({
                         ele: <CollectionFreeFormDocumentView key={doc[Id]}
                             x={script ? pos.x : undefined} y={script ? pos.y : undefined}
                             width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />,
-                        bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, width: pos.width, height: pos.height } : undefined
+                        bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: pos.width, height: pos.height } : undefined
                     });
                 }
             }
@@ -514,8 +518,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed.struct
     get views() {
-        return this.elements.map(ele => ele.ele);
+        return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
     }
+    @computed.struct
+    get overlayViews() {
+        return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
+    }
+
 
     @action
     onCursorMove = (e: React.PointerEvent) => {
@@ -603,6 +612,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
         ...this.views
     ]
+    private overlayChildViews = () => {
+        console.log(this.overlayViews.length);
+        return [
+            ...this.overlayViews
+        ];
+    }
 
     public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
         return () => {
@@ -651,6 +666,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
                     </CollectionFreeFormViewPannableContents>
                 </MarqueeView>
+                {this.overlayChildViews()}
                 <CollectionFreeFormOverlayView  {...this.props} {...this.getDocumentViewProps(this.props.Document)} />
             </div>
         );
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7ffd760e0..3b6c443c2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -77,6 +77,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transformOrigin: "left top",
                     position: "absolute",
                     backgroundColor: "transparent",
+                    boxShadow: this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` : undefined,
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
                     transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d4adcfca1..45b5c328c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,19 +1,24 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, trace, observable, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
+import * as rp from "request-promise";
+import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Copy, Id } from '../../../new_fields/FieldSymbols';
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
-import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, StrCast, NumCast, PromiseValue } from "../../../new_fields/Types";
+import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { emptyFunction, Utils, returnFalse, returnTrue } from "../../../Utils";
+import { RouteStore } from '../../../server/RouteStore';
+import { emptyFunction, returnTrue, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
-import { Docs, DocUtils, DocumentType } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { ClientUtils } from '../../util/ClientUtils';
+import DictationManager from '../../util/DictationManager';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../util/DragManager";
-import { SearchUtil } from "../../util/SearchUtil";
+import { LinkManager } from '../../util/LinkManager';
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -22,27 +27,17 @@ import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from "../DocComponent";
+import { EditableView } from '../EditableView';
+import { OverlayView } from '../OverlayView';
 import { PresentationView } from "../presentationview/PresentationView";
-import { Template, Templates } from "./../Templates";
+import { ScriptingRepl } from '../ScriptingRepl';
+import { Template } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
-import * as rp from "request-promise";
 import "./DocumentView.scss";
-import React = require("react");
-import { Id, Copy } from '../../../new_fields/FieldSymbols';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { list, object, createSimpleSchema } from 'serializr';
-import { LinkManager } from '../../util/LinkManager';
-import { RouteStore } from '../../../server/RouteStore';
 import { FormattedTextBox } from './FormattedTextBox';
-import { OverlayView } from '../OverlayView';
-import { ScriptingRepl } from '../ScriptingRepl';
-import { ClientUtils } from '../../util/ClientUtils';
-import { EditableView } from '../EditableView';
-import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons';
-import { DocumentDecorations } from '../DocumentDecorations';
-import { CognitiveServices } from '../../cognitive_services/CognitiveServices';
-import DictationManager from '../../util/DictationManager';
+import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -121,6 +116,7 @@ export const positionSchema = createSchema({
     height: "number",
     x: "number",
     y: "number",
+    z: "number",
 });
 
 export type PositionDocument = makeInterface<[typeof positionSchema]>;
-- 
cgit v1.2.3-70-g09d2


From 753615c80d4cf08605ebaaeeaf0a44a0fd88d898 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 3 Aug 2019 15:21:10 -0400
Subject: working version of clustering

---
 src/Utils.ts                                       |   2 +
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/DragManager.ts                     |   8 +-
 src/client/views/MainView.tsx                      |   4 +-
 src/client/views/TemplateMenu.tsx                  |   5 +-
 .../views/collections/CollectionDockingView.tsx    |   3 +-
 .../views/collections/CollectionSchemaView.tsx     |   3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 142 +++++++++++++--------
 .../collections/collectionFreeForm/MarqueeView.tsx |   1 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  12 +-
 src/client/views/nodes/DocumentView.tsx            |  17 +--
 .../views/presentationview/PresentationElement.tsx |   3 +-
 src/client/views/search/SearchItem.tsx             |   3 +-
 13 files changed, 125 insertions(+), 81 deletions(-)

(limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 8df67df5d..502540eb0 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -140,6 +140,8 @@ export function returnOne() { return 1; }
 
 export function returnZero() { return 0; }
 
+export function returnEmptyString() { return ""; }
+
 export function emptyFunction() { }
 
 export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 09bafcf43..07e38a4c0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -84,6 +84,7 @@ export interface DocumentOptions {
     templates?: List<string>;
     viewType?: number;
     backgroundColor?: string;
+    defaultBackgroundColor?: string;
     dropAction?: dropActionType;
     backgroundLayout?: string;
     chromeStatus?: string;
@@ -124,7 +125,7 @@ export namespace Docs {
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.TEXT, {
                 layout: { view: FormattedTextBox },
-                options: { height: 150, backgroundColor: "#f1efeb" }
+                options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" }
             }],
             [DocumentType.HIST, {
                 layout: { view: HistogramBox, collectionView: [CollectionView, data] as CollectionViewType },
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0b5c785a4..a7aaaed7c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -404,7 +404,8 @@ export namespace DragManager {
                 hideSource = options.hideSource();
             }
         }
-        eles.map(ele => (ele.hidden = hideSource));
+        eles.map(ele => (ele.hidden = hideSource) &&
+            (ele.parentElement && ele.parentElement.className.indexOf("collectionFreeFormDocumentView") !== -1 && (ele.parentElement.hidden = hideSource)));
 
         let lastX = downX;
         let lastY = downY;
@@ -434,7 +435,10 @@ export namespace DragManager {
 
         let hideDragElements = () => {
             dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
-            eles.map(ele => (ele.hidden = false));
+            eles.map(ele => {
+                ele.hidden = false;
+                (ele.parentElement && ele.parentElement.className.indexOf("collectionFreeFormDocumentView") !== -1 && (ele.parentElement.hidden = false));
+            });
         };
         let endDrag = () => {
             document.removeEventListener("pointermove", moveHandler, true);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2ecf5fd85..53f589684 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -15,7 +15,7 @@ import { listSpec } from '../../new_fields/Schema';
 import { Cast, FieldValue, NumCast, BoolCast, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnOne, returnTrue, Utils } from '../../Utils';
+import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils';
 import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { SetupDrag } from '../util/DragManager';
@@ -270,6 +270,7 @@ export class MainView extends React.Component {
                             PanelWidth={this.getPWidth}
                             PanelHeight={this.getPHeight}
                             renderDepth={0}
+                            backgroundColor={returnEmptyString}
                             selectOnLoad={false}
                             focus={emptyFunction}
                             parentActive={returnTrue}
@@ -334,6 +335,7 @@ export class MainView extends React.Component {
             renderDepth={0}
             selectOnLoad={false}
             focus={emptyFunction}
+            backgroundColor={returnEmptyString}
             parentActive={returnTrue}
             whenActiveChanged={emptyFunction}
             bringToFront={emptyFunction}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e654a0644..6dd908445 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -60,12 +60,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                     let de = new DragManager.DocumentDragData([topDoc], [undefined]);
                     de.moveDocument = topDocView.props.moveDocument;
                     let xf = newDocView.ContentDiv!.getBoundingClientRect();
-                    console.log("ex = " + ex + " " + xf.left + " " + (ex - xf.left));
                     DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, {
                         offsetX: (ex - xf.left), offsetY: (ey - xf.top),
-                        handlers: {
-                            dragComplete: () => { },
-                        },
+                        handlers: { dragComplete: () => { }, },
                         hideSource: false
                     });
                 }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 588102f01..f559480ed 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -10,7 +10,7 @@ import { Id } from '../../../new_fields/FieldSymbols';
 import { FieldId } from "../../../new_fields/RefField";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { emptyFunction, returnTrue, Utils, returnOne } from "../../../Utils";
+import { emptyFunction, returnTrue, Utils, returnOne, returnEmptyString } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
@@ -607,6 +607,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             parentActive={returnTrue}
             whenActiveChanged={emptyFunction}
             focus={emptyFunction}
+            backgroundColor={returnEmptyString}
             addDocTab={this.addDocTab}
             ContainingCollectionView={undefined}
             zoomToScale={emptyFunction}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9efd0d3ec..8218877ba 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -6,7 +6,7 @@ import { action, computed, observable, trace, untracked } from "mobx";
 import { observer } from "mobx-react";
 import ReactTable, { CellInfo, ComponentPropsGetterR, Column, RowInfo, ResizedChangeFunction, Resize } from "react-table";
 import "react-table/react-table.css";
-import { emptyFunction, returnOne } from "../../../Utils";
+import { emptyFunction, returnOne, returnEmptyString } from "../../../Utils";
 import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
@@ -999,6 +999,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         PanelHeight={this.PanelHeight}
                         ContainingCollectionView={this.props.CollectionView}
                         focus={emptyFunction}
+                        backgroundColor={returnEmptyString}
                         parentActive={this.props.active}
                         whenActiveChanged={this.props.whenActiveChanged}
                         bringToFront={emptyFunction}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bc1aee159..ee21c5a4d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,7 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
@@ -9,7 +9,7 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnOne, Utils } from "../../../../Utils";
+import { emptyFunction, returnOne, Utils, returnFalse, returnEmptyString } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { DocumentManager } from "../../../util/DocumentManager";
@@ -37,6 +37,8 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
+import { setScheduler } from "bluebird";
+import { DocumentType, Docs } from "../../../documents/Documents";
 
 library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
 
@@ -132,17 +134,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         r2: { left: number, top: number, width: number, height: number }) {
         return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
     }
-    _groupingBorder = 100;
+    _clusterDistance = 75;
     bounsdSelect(doc: Doc, doc2: Doc) {
-        var x2 = NumCast(doc2.x) - this._groupingBorder;
-        var y2 = NumCast(doc2.y) - this._groupingBorder;
-        var w2 = NumCast(doc2.width) + this._groupingBorder;
-        var h2 = NumCast(doc2.height) + this._groupingBorder;
-        var x = NumCast(doc.x) - this._groupingBorder;
-        var y = NumCast(doc.y) - this._groupingBorder;
-        var w = NumCast(doc.width) + this._groupingBorder;
-        var h = NumCast(doc.height) + this._groupingBorder;
-        if (this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
+        var x2 = NumCast(doc2.x) - this._clusterDistance;
+        var y2 = NumCast(doc2.y) - this._clusterDistance;
+        var w2 = NumCast(doc2.width) + this._clusterDistance;
+        var h2 = NumCast(doc2.height) + this._clusterDistance;
+        var x = NumCast(doc.x) - this._clusterDistance;
+        var y = NumCast(doc.y) - this._clusterDistance;
+        var w = NumCast(doc.width) + this._clusterDistance;
+        var h = NumCast(doc.height) + this._clusterDistance;
+        if (doc.z === doc2.z && this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
             return true;
         }
         return false;
@@ -197,36 +199,83 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return false;
     }
 
+    tryDragCluster(e: PointerEvent) {
+        let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
+        let cluster = this.childDocs.reduce((cluster, cd) => {
+            let cx = NumCast(cd.x) - this._clusterDistance;
+            let cy = NumCast(cd.y) - this._clusterDistance;
+            let cw = NumCast(cd.width) + 2 * this._clusterDistance;
+            let ch = NumCast(cd.height) + 2 * this._clusterDistance;
+            if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
+                return NumCast(cd.cluster);
+            return cluster;
+        }, -1);
+        if (cluster !== -1) {
+            let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
+            this.selectDocuments(eles);
+            let clusterDocs = SelectionManager.SelectedDocuments();
+            SelectionManager.DeselectAll();
+            let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
+            de.moveDocument = this.props.moveDocument;
+            const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
+            const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top);
+            de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
+            de.xOffset = xoff;
+            de.yOffset = yoff;
+            DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {
+                handlers: { dragComplete: action(emptyFunction) },
+                hideSource: !de.dropAction
+            });
+            return true;
+        }
+
+        return false;
+    }
+    @observable sets: (Doc[])[] = [];
     @action
     updateClusters() {
-        let sets: (Doc[])[] = []
+        this.sets.length = 0;
         this.childDocs.map(c => {
             let included = []
-            for (let i = 0; i < sets.length; i++) {
-                for (let j = 0; j < sets[i].length; j++) {
-                    if (this.bounsdSelect(c, sets[i][j])) {
+            for (let i = 0; i < this.sets.length; i++) {
+                for (let j = 0; j < this.sets[i].length; j++) {
+                    if (this.bounsdSelect(c, this.sets[i][j])) {
                         included.push(i);
                         break;
                     }
                 }
             }
             if (included.length === 0)
-                sets.push([c]);
+                this.sets.push([c]);
             else if (included.length === 1)
-                sets[included[0]].push(c);
+                this.sets[included[0]].push(c);
             else {
-                sets[included[0]].push(c);
+                this.sets[included[0]].push(c);
                 for (let s = 1; s < included.length; s++) {
-                    sets[included[0]].push(...sets[included[s]]);
-                    sets[included[s]].length = 0;
+                    this.sets[included[0]].push(...this.sets[included[s]]);
+                    this.sets[included[s]].length = 0;
                 }
             }
         });
-        for (let s = 0; s < sets.length; s++) {
-            for (let i = 0; i < sets[s].length; i++) {
-                Doc.GetProto(sets[s][i]).cluster = s;
+        for (let s = 0; s < this.sets.length; s++) {
+            for (let i = 0; i < this.sets[s].length; i++) {
+                this.sets[s][i].cluster = s;
+            }
+        }
+    }
+
+    getClusterColor = (doc: Doc) => {
+        if (this.props.Document.useClusters) {
+            let cluster = NumCast(doc.cluster);
+            let set = this.sets.length > cluster ? this.sets[NumCast(doc.cluster)] : undefined;
+            let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
+            let clusterColor = colors[cluster % colors.length];
+            for (let i = 0; set && i < set.length; i++) {
+                if (set[i].backgroundColor && set[i].backgroundColor !== set[i].defaultBackgroundColor) clusterColor = StrCast(set[i].backgroundColor);
             }
+            return clusterColor;
         }
+        return "";
     }
 
     @action
@@ -249,34 +298,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     onPointerMove = (e: PointerEvent): void => {
         if (!e.cancelBubble) {
-            let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
-            let cluster = this.childDocs.reduce((cluster, cd) => {
-                let cx = NumCast(cd.x) - this._groupingBorder;
-                let cy = NumCast(cd.y) - this._groupingBorder;
-                let cw = NumCast(cd.width) + 2 * this._groupingBorder;
-                let ch = NumCast(cd.height) + 2 * this._groupingBorder;
-                if (this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
-                    return NumCast(cd.cluster);
-                return cluster;
-            }, -1);
-            if (cluster !== -1) {
-                let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
-                this.selectDocuments(eles);
-                let clusterDocs = SelectionManager.SelectedDocuments();
-                SelectionManager.DeselectAll();
-                let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
-                de.moveDocument = this.props.moveDocument;
-                const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
-                const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top);
-                de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
-                de.xOffset = xoff;
-                de.yOffset = yoff;
-                DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {
-                    handlers: {
-                        dragComplete: action(emptyFunction)
-                    },
-                    hideSource: !de.dropAction
-                });
+            if (this.props.Document.useClusters && this.tryDragCluster(e)) {
                 e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                 e.preventDefault();
                 document.removeEventListener("pointermove", this.onPointerMove);
@@ -493,6 +515,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
+            backgroundColor: this.getClusterColor,
             parentActive: this.props.active,
             whenActiveChanged: this.props.whenActiveChanged,
             bringToFront: this.bringToFront,
@@ -516,6 +539,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
+            backgroundColor: returnEmptyString,
             parentActive: this.props.active,
             whenActiveChanged: this.props.whenActiveChanged,
             bringToFront: this.bringToFront,
@@ -625,6 +649,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             event: async () => this.props.Document.fitToBox = !this.fitToBox,
             icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt"
         });
+        layoutItems.push({
+            description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`,
+            event: async () => {
+                Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb";
+                Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white";
+                this.props.Document.useClusters = !this.props.Document.useClusters;
+            },
+            icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt"
+        });
         layoutItems.push({
             description: "Arrange contents in grid",
             icon: "table",
@@ -700,10 +733,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ...this.views
     ]
     private overlayChildViews = () => {
-        console.log(this.overlayViews.length);
-        return [
-            ...this.overlayViews
-        ];
+        return [...this.overlayViews];
     }
 
     public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b9ee588dd..ff96bd993 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -278,6 +278,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 panX: 0,
                 panY: 0,
                 backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
+                defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
                 width: bounds.width,
                 height: bounds.height,
                 title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection",
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3b6c443c2..ee596c841 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -8,6 +8,7 @@ import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"
 import "./DocumentView.scss";
 import React = require("react");
 import { Doc } from "../../../new_fields/Doc";
+import { returnEmptyString } from "../../../Utils";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     x?: number;
@@ -69,6 +70,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return undefined;
     }
 
+    @computed
+    get clusterColor() { return this.props.backgroundColor(this.props.Document); }
+
+    clusterColorFunc = (doc: Doc) => this.clusterColor;
+
     render() {
         const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
         return (
@@ -77,7 +83,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transformOrigin: "left top",
                     position: "absolute",
                     backgroundColor: "transparent",
-                    boxShadow: this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` : undefined,
+                    boxShadow: this.props.Document.z ? `#9c9396  ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` :
+                        this.clusterColor ? (
+                            this.props.Document.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` :
+                                `${this.clusterColor} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) : undefined,
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
                     transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
@@ -88,6 +97,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 <DocumentView {...this.props}
                     ContentScaling={this.contentScaling}
                     ScreenToLocalTransform={this.getTransform}
+                    backgroundColor={this.clusterColorFunc}
                     PanelWidth={this.panelWidth}
                     PanelHeight={this.panelHeight}
                     animateBetweenIcon={this.animateBetweenIcon}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 16ae5471d..ae6a01ab4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
 import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -95,6 +95,7 @@ export interface DocumentViewProps {
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
     collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
     zoomToScale: (scale: number) => void;
+    backgroundColor: (doc: Doc) => string;
     getScale: () => number;
     animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void;
     ChromeHeight?: () => number;
@@ -675,12 +676,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string.
         return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
     }
+
     render() {
-        if (this.Document.hidden) {
-            return null;
-        }
-        let self = this;
-        let backgroundColor = StrCast(this.layoutDoc.backgroundColor);
+        let backgroundColor = this.props.backgroundColor(this.props.Document) || StrCast(this.layoutDoc.backgroundColor);
         let foregroundColor = StrCast(this.layoutDoc.color);
         var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
@@ -695,8 +693,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
-        let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
-        let groupCol = colors[NumCast(this.props.Document.cluster) % colors.length];
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -705,9 +701,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    boxShadow: this.layoutDoc.isBackground ?
-                        `0px 0px 50px 50px ${groupCol}` :
-                        `${groupCol} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
@@ -717,7 +710,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     borderRadius: "inherit",
-                    background: this.layoutDoc.isBackground ? groupCol : backgroundColor,
+                    background: backgroundColor,
                     width: nativeWidth,
                     height: nativeHeight,
                     transform: `scale(${this.props.ContentScaling()})`,
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 11f3eb846..e2d8daea9 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -9,7 +9,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils, returnFalse, emptyFunction, returnOne } from "../../../Utils";
+import { Utils, returnFalse, emptyFunction, returnOne, returnEmptyString } from "../../../Utils";
 import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
 import { ContextMenu } from "../ContextMenu";
@@ -843,6 +843,7 @@ export default class PresentationElement extends React.Component<PresentationEle
                     PanelWidth={() => 350}
                     PanelHeight={() => 90}
                     focus={emptyFunction}
+                    backgroundColor={returnEmptyString}
                     selectOnLoad={false}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 0390359b3..1b9bba5c6 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
+import { emptyFunction, returnFalse, returnOne, Utils, returnEmptyString } from "../../../Utils";
 import { DocumentType } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
 import { SetupDrag, DragManager } from "../../util/DragManager";
@@ -223,6 +223,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     PanelWidth={returnXDimension}
                     PanelHeight={returnYDimension}
                     focus={emptyFunction}
+                    backgroundColor={returnEmptyString}
                     selectOnLoad={false}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
-- 
cgit v1.2.3-70-g09d2

