From 600d06a162b4489fd52d5809eb9f5a69f28618fc Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 6 Dec 2019 23:11:51 -0500
Subject: performance tuning for sidebar

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

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 13fd3cde1..1bbc82119 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -32,6 +32,7 @@ import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
 import { InkingStroke } from "../InkingStroke";
 import React = require("react");
+import { TraceMobx } from "../../../new_fields/util";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -57,6 +58,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     hideOnLeave?: boolean
 }> {
     @computed get layout(): string {
+        TraceMobx();
         if (!this.layoutDoc) return "<p>awaiting layout</p>";
         const layout = Cast(this.layoutDoc[this.props.layoutKey], "string");
         if (layout === undefined) {
@@ -92,6 +94,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     render() {
+        TraceMobx();
         return (this.props.renderDepth > 7 || !this.layout) ? (null) :
             <ObserverJsxParser
                 blacklistedAttrs={[]}
-- 
cgit v1.2.3-70-g09d2


From 55823d0cc300f5cdd90ee34013bfdee93b5709e6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 15 Dec 2019 20:11:58 -0500
Subject: added a DocumentBox as a contentfitting container for another
 document.  added a menu item for showing selected document.

---
 src/client/documents/DocumentTypes.ts              |  3 +-
 src/client/documents/Documents.ts                  |  9 ++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../views/nodes/ContentFittingDocumentView.tsx     |  6 +--
 src/client/views/nodes/DocumentBox.scss            |  6 +++
 src/client/views/nodes/DocumentBox.tsx             | 55 ++++++++++++++++++++++
 src/client/views/nodes/DocumentContentsView.tsx    |  3 +-
 src/client/views/nodes/ImageBox.tsx                |  1 -
 src/new_fields/ScriptField.ts                      |  2 +-
 .../authentication/models/current_user_utils.ts    |  1 +
 10 files changed, 80 insertions(+), 8 deletions(-)
 create mode 100644 src/client/views/nodes/DocumentBox.scss
 create mode 100644 src/client/views/nodes/DocumentBox.tsx

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

diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index f6dd0c346..8f96b2fa6 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -25,5 +25,6 @@ export enum DocumentType {
     COLOR = "color",
     DOCULINK = "doculink",
     PDFANNO = "pdfanno",
-    INK = "ink"
+    INK = "ink",
+    DOCUMENT = "document"
 }
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4ba45ac30..50c51ace1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -48,6 +48,7 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { QueryBox } from "../views/nodes/QueryBox";
 import { ColorBox } from "../views/nodes/ColorBox";
 import { DocuLinkBox } from "../views/nodes/DocuLinkBox";
+import { DocumentBox } from "../views/nodes/DocumentBox";
 import { InkingStroke } from "../views/InkingStroke";
 import { InkField } from "../../new_fields/InkField";
 const requestImageSize = require('../util/request-image-size');
@@ -171,6 +172,10 @@ export namespace Docs {
                 layout: { view: KeyValueBox, dataField: data },
                 options: { height: 150 }
             }],
+            [DocumentType.DOCUMENT, {
+                layout: { view: DocumentBox, dataField: data },
+                options: { height: 250 }
+            }],
             [DocumentType.VID, {
                 layout: { view: VideoBox, dataField: data },
                 options: { currentTimecode: 0 },
@@ -482,6 +487,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + ".kvp", ...options });
         }
 
+        export function DocumentDocument(document?: Doc, options: DocumentOptions = {}) {
+            return InstanceFromProto(Prototypes.get(DocumentType.DOCUMENT), document, { title: document ? document.title + "" : "container", ...options });
+        }
+
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform }, id);
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9bb6fa97e..0b18e9f25 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -908,7 +908,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
                 style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
-                {!BoolCast(this.Document.LODdisable) && this.props.renderDepth > 0 && this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale < NumCast(this.Document.LODarea, 100000) ? this.placeholder : this.marqueeView}
+                {!BoolCast(this.Document.LODdisable) && !this.props.isAnnotationOverlay && this.props.renderDepth > 0 && this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale < NumCast(this.Document.LODarea, 100000) ? this.placeholder : this.marqueeView}
                 <CollectionFreeFormOverlayView elements={this.elementFunc} />
             </div>;
     }
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 5e3306a11..2565fd932 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -31,9 +31,9 @@ interface ContentFittingDocumentViewProps {
     CollectionDoc?: Doc;
     onClick?: ScriptField;
     getTransform: () => Transform;
-    addDocument: (document: Doc) => boolean;
-    moveDocument: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
-    removeDocument: (document: Doc) => boolean;
+    addDocument?: (document: Doc) => boolean;
+    moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
+    removeDocument?: (document: Doc) => boolean;
     active: (outsideReaction: boolean) => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
diff --git a/src/client/views/nodes/DocumentBox.scss b/src/client/views/nodes/DocumentBox.scss
new file mode 100644
index 000000000..6a2f98166
--- /dev/null
+++ b/src/client/views/nodes/DocumentBox.scss
@@ -0,0 +1,6 @@
+.documentBox-container {
+    width: 100%;
+    height: 100%;
+    pointer-events: all;
+    background: gray;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
new file mode 100644
index 000000000..2f2cba485
--- /dev/null
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -0,0 +1,55 @@
+import { observer } from "mobx-react";
+import { Doc } from "../../../new_fields/Doc";
+import { documentSchema } from "../../../new_fields/documentSchemas";
+import { makeInterface } from "../../../new_fields/Schema";
+import { ComputedField } from "../../../new_fields/ScriptField";
+import { emptyFunction, emptyPath } from "../../../Utils";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { DocComponent } from "../DocComponent";
+import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
+import "./DocumentBox.scss";
+import { FieldView, FieldViewProps } from "./FieldView";
+import React = require("react");
+
+type DocBoxSchema = makeInterface<[typeof documentSchema]>;
+const DocBoxDocument = makeInterface(documentSchema);
+
+@observer
+export class DocumentBox extends DocComponent<FieldViewProps, DocBoxSchema>(DocBoxDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocumentBox, fieldKey); }
+
+
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const funcs: ContextMenuProps[] = [];
+        funcs.push({ description: "Auto Show Selected", event: () => Doc.GetProto(this.props.Document).data = ComputedField.MakeFunction("selectedDocs(this,true,_last_)?.[0]"), icon: "expand-arrows-alt" });
+
+        ContextMenu.Instance.addItem({ description: "DocumentBox Funcs...", subitems: funcs, icon: "asterisk" });
+    }
+    render() {
+        const containedDoc = this.props.Document[this.props.fieldKey] as Doc;
+        return <div className="documentBox-container" onContextMenu={this.specificContextMenu}>
+            {!containedDoc ? (null) : <ContentFittingDocumentView
+                Document={containedDoc}
+                DataDocument={undefined}
+                LibraryPath={emptyPath}
+                fitToBox={this.props.fitToBox}
+                addDocument={this.props.addDocument}
+                moveDocument={this.props.moveDocument}
+                removeDocument={this.props.removeDocument}
+                ruleProvider={this.props.ruleProvider}
+                addDocTab={this.props.addDocTab}
+                pinToPres={this.props.pinToPres}
+                getTransform={this.props.ScreenToLocalTransform}
+                renderDepth={this.props.renderDepth - 1}
+                PanelWidth={this.props.PanelWidth}
+                PanelHeight={this.props.PanelHeight}
+                focus={this.props.focus}
+                active={this.props.active}
+                whenActiveChanged={this.props.whenActiveChanged}
+                setPreviewScript={emptyFunction}
+                previewScript={undefined}
+            />}
+        </div>;
+    }
+}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 1bbc82119..8f6bfc8e1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -14,6 +14,7 @@ import { LinkFollowBox } from "../linking/LinkFollowBox";
 import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
 import { AudioBox } from "./AudioBox";
 import { ButtonBox } from "./ButtonBox";
+import { DocumentBox } from "./DocumentBox";
 import { DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
 import { FontIconBox } from "./FontIconBox";
@@ -102,7 +103,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                     FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView,
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
                     PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox,
-                    ColorBox, DocuLinkBox, InkingStroke
+                    ColorBox, DocuLinkBox, InkingStroke, DocumentBox
                 }}
                 bindings={this.CreateBindings()}
                 jsx={this.layout}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f6aa47f15..09e627078 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -18,7 +18,6 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../../views/ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocAnnotatableComponent } from '../DocComponent';
-import { InkingControl } from '../InkingControl';
 import FaceRectangles from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index 0cadb60ed..b5ad4a7f6 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -103,7 +103,7 @@ export class ScriptField extends ObjectField {
     }
     public static CompileScript(script: string, params: object = {}, addReturn = false) {
         const compiled = CompileScript(script, {
-            params: { this: Doc.name, ...params },
+            params: { this: Doc.name, _last_: "any", ...params },
             typecheck: false,
             editable: true,
             addReturn: addReturn
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 50bfb4832..fd01a291d 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -47,6 +47,7 @@ export class CurrentUserUtils {
         doc.activePen = doc;
         const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
             { title: "collection", icon: "folder", ignoreClick: true, drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' },
+            { title: "preview", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(undefined, { width: 250, height: 250, title: "container" })' },
             { title: "todo item", icon: "check", ignoreClick: true, drag: 'getCopy(this.dragFactory, true)', dragFactory: notes[notes.length - 1] },
             { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
             { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' },
-- 
cgit v1.2.3-70-g09d2


From 15f2148f40c351589e18c1f9ac3b39a8de366a40 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 14 Jan 2020 17:15:36 -0500
Subject: fixes for stacking views to honor panelwidth/height -- allows
 stacking panels to  be placed in Content fitting document views.

---
 src/client/documents/Documents.ts                  |  2 +-
 .../views/collections/CollectionStackingView.tsx   | 31 +++++-----
 src/client/views/nodes/DocumentContentsView.tsx    |  2 +-
 src/client/views/nodes/ImageBox.tsx                | 69 ++++++++++------------
 src/new_fields/Doc.ts                              | 18 +++---
 5 files changed, 60 insertions(+), 62 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e149963b9..be678d765 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -355,7 +355,7 @@ export namespace Docs {
 
             AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "link to audio: " + d.title));
 
-            return Doc.assign(viewDoc, delegateProps);
+            return Doc.assign(viewDoc, delegateProps, true);
         }
 
         /**
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 03c757a51..3ab0326e2 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -39,7 +39,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
     @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
     @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
-    @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
+    @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized).map(d => (Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d).layout as Doc) || d); }
     @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
     @computed get yMargin() { return Math.max(this.props.Document.showTitle ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); }
     @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
@@ -52,22 +52,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
 
-    childDocHeight(child: Doc) { return this.getDocHeight(Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, child).layout); }
-
     children(docs: Doc[]) {
         this._docXfs.length = 0;
         return docs.map((d, i) => {
-            const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d);
-            const layoutDoc = pair.layout ? Doc.Layout(pair.layout) : d;
-            const width = () => Math.min(layoutDoc.nativeWidth && !layoutDoc.ignoreAspect && !this.props.Document.fillColumn ? layoutDoc[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
-            const height = () => this.getDocHeight(layoutDoc);
+            const width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+            const height = () => this.getDocHeight(d);
             const dref = React.createRef<HTMLDivElement>();
-            const dxf = () => this.getDocTransform(layoutDoc, dref.current!);
+            const dxf = () => this.getDocTransform(d, dref.current!);
             this._docXfs.push({ dxf: dxf, width: width, height: height });
             const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
             const style = this.isStackingView ? { width: width(), marginTop: i === 0 ? 0 : this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
             return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
-                {this.getDisplayDoc(pair.layout || d, pair.data, dxf, width)}
+                {this.getDisplayDoc(d, (d.resolvedDataDoc as Doc) || d, dxf, width)}
             </div>;
         });
     }
@@ -114,7 +110,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
                         const r1 = Math.max(maxHght,
                             (this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => {
-                                const val = height + this.childDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
+                                const val = height + this.getDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
                                 return val;
                             }, this.yMargin));
                         return r1;
@@ -291,7 +287,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
         const key = this.sectionFilter;
         let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
-        const types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
+        const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
             type = types[0];
         }
@@ -316,15 +312,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         const y = this._scroll; // required for document decorations to update when the text box container is scrolled
         const { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
         const outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
+        const scaling = 1 / Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]());
         const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
-        return this.props.ScreenToLocalTransform().
-            translate(offset[0], offset[1] + (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0));
+        const offsetx = (doc[WidthSym]() - doc[WidthSym]() / scaling) / 2;
+        const offsety = (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0);
+        return this.props.ScreenToLocalTransform().translate(offset[0] - offsetx, offset[1] + offsety).scale(scaling);
     }
 
     sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
         const key = this.sectionFilter;
         let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
-        const types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
+        const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
             type = types[0];
         }
@@ -396,6 +394,11 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             <div className="collectionStackingMasonry-cont" >
                 <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
                     ref={this.createRef}
+                    style={{
+                        transform: `scale(${Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]())})`,
+                        height: `${100 * 1 / Math.min(this.props.PanelWidth() / this.layoutDoc[WidthSym](), this.props.PanelHeight() / this.layoutDoc[HeightSym]())}%`,
+                        transformOrigin: "top"
+                    }}
                     onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
                     onDrop={this.onDrop.bind(this)}
                     onContextMenu={this.onContextMenu}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 8f6bfc8e1..66886165e 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -82,7 +82,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         return this.props.DataDoc;
     }
     get layoutDoc() {
-        return this.props.DataDoc === undefined ? Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document) : Doc.Layout(this.props.Document);
+        return Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document);
     }
 
     CreateBindings(): JsxBindings {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 09e627078..634555012 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -214,37 +214,23 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
     }
     _curSuffix = "_m";
 
-    _resized = false;
-    resize = (srcpath: string) => {
-        requestImageSize(srcpath)
+    _resized = "";
+    resize = (imgPath: string) => {
+        requestImageSize(imgPath)
             .then((size: any) => {
                 const rotation = NumCast(this.dataDoc.rotation) % 180;
                 const realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
                 const aspect = realsize.height / realsize.width;
                 if (this.Document.width && (Math.abs(1 - NumCast(this.Document.height) / NumCast(this.Document.width) / (realsize.height / realsize.width)) > 0.1)) {
                     setTimeout(action(() => {
-                        this._resized = true;
-                        this.Document.height = this.Document[WidthSym]() * aspect;
-                        this.Document.nativeHeight = realsize.height;
-                        this.Document.nativeWidth = realsize.width;
+                        if (this.pathInfos.srcpath === imgPath && (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc)) {
+                            this._resized = imgPath;
+                            this.Document.height = this.Document[WidthSym]() * aspect;
+                            this.Document.nativeHeight = realsize.height;
+                            this.Document.nativeWidth = realsize.width;
+                        }
                     }), 0);
-                } else this._resized = true;
-            })
-            .catch((err: any) => console.log(err));
-    }
-    fadesize = (srcpath: string) => {
-        requestImageSize(srcpath)
-            .then((size: any) => {
-                const rotation = NumCast(this.dataDoc.rotation) % 180;
-                const realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
-                const aspect = realsize.height / realsize.width;
-                if (this.Document.width && (Math.abs(1 - NumCast(this.Document.height) / NumCast(this.Document.width) / (realsize.height / realsize.width)) > 0.1)) {
-                    setTimeout(action(() => {
-                        this.Document.height = this.Document[WidthSym]() * aspect;
-                        this.Document.nativeHeight = realsize.height;
-                        this.Document.nativeWidth = realsize.width;
-                    }), 0);
-                }
+                } else this._resized = imgPath;
             })
             .catch((err: any) => console.log(err));
     }
@@ -285,18 +271,16 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         return !tags ? (null) : (<img id={"google-tags"} src={"/assets/google_tags.png"} />);
     }
 
-    @computed get content() {
-        TraceMobx();
-        const extensionDoc = this.extensionDoc;
-        if (!extensionDoc) return (null);
-        // let transform = this.props.ScreenToLocalTransform().inverse();
+    @computed get nativeSize() {
         const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
-        // var [sptX, sptY] = transform.transformPoint(0, 0);
-        // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight());
-        // let w = bptX - sptX;
-
         const nativeWidth = (this.Document.nativeWidth || pw);
         const nativeHeight = (this.Document.nativeHeight || 1);
+        return { nativeWidth, nativeHeight };
+    }
+
+    @computed get pathInfos() {
+        const extensionDoc = this.extensionDoc!;
+        const { nativeWidth, nativeHeight } = this.nativeSize;
         let paths = [[Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png"), nativeWidth / nativeHeight]];
         // this._curSuffix = "";
         // if (w > 20) {
@@ -308,15 +292,24 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         // else if (this._largeRetryCount < 10) this._curSuffix = "_l";
         if (field instanceof ImageField) paths = [[this.choosePath(field.url), nativeWidth / nativeHeight]];
         paths.push(...altpaths);
-        // }
-        const rotation = NumCast(this.Document.rotation, 0);
-        const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
-        const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
         const srcpath = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][0] as string;
         const srcaspect = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][1] as number;
         const fadepath = paths[Math.min(paths.length - 1, 1)][0] as string;
+        return { srcpath, srcaspect, fadepath };
+    }
+
+    @computed get content() {
+        TraceMobx();
+        const extensionDoc = this.extensionDoc;
+        if (!extensionDoc) return (null);
+
+        const { srcpath, srcaspect, fadepath } = this.pathInfos;
+        const { nativeWidth, nativeHeight } = this.nativeSize;
+        const rotation = NumCast(this.Document.rotation, 0);
+        const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
+        const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
 
-        !this.Document.ignoreAspect && !this._resized && this.resize(srcpath);
+        !this.Document.ignoreAspect && this._resized !== srcpath && this.resize(srcpath);
 
         return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
             <div className="imageBox-fader" >
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index c809ad17a..d4634ba4b 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -290,14 +290,13 @@ export namespace Doc {
      * @param fields the fields to project onto the target. Its type signature defines a mapping from some string key
      * to a potentially undefined field, where each entry in this mapping is optional. 
      */
-    export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>) {
+    export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>, skipUndefineds: boolean = false) {
         for (const key in fields) {
             if (fields.hasOwnProperty(key)) {
                 const value = fields[key];
-                // Do we want to filter out undefineds?
-                // if (value !== undefined) {
-                doc[key] = value;
-                // }
+                if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds?
+                    doc[key] = value;
+                }
             }
         }
         return doc;
@@ -406,8 +405,9 @@ export namespace Doc {
     }
     export function MakeAlias(doc: Doc) {
         const alias = !GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeCopy(doc) : Doc.MakeDelegate(doc);
-        if (alias.layout instanceof Doc) {
-            alias.layout = Doc.MakeAlias(alias.layout);
+        let layout = Doc.Layout(alias);
+        if (layout instanceof Doc && layout !== alias) {
+            Doc.SetLayout(alias, Doc.MakeAlias(layout));
         }
         const aliasNumber = Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1;
         alias.title = ComputedField.MakeFunction(`renameAlias(this, ${aliasNumber})`);
@@ -455,6 +455,7 @@ export namespace Doc {
         if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
             const extensionDoc = fieldExtensionDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)));
             layoutDoc = Doc.expandTemplateLayout(childDocLayout, extensionDoc !== resolvedDataDoc ? extensionDoc : undefined);
+            layoutDoc && (layoutDoc.resolvedDataDoc = resolvedDataDoc);
         } else layoutDoc = childDocLayout;
         return { layout: layoutDoc, data: resolvedDataDoc };
     }
@@ -468,7 +469,7 @@ export namespace Doc {
     //
     export function fieldExtensionDoc(doc: Doc, fieldKey: string) {
         const extension = doc[fieldKey + "_ext"];
-        if (extension === undefined) {
+        if (doc instanceof Doc && extension === undefined) {
             setTimeout(() => CreateDocumentExtensionForField(doc, fieldKey), 0);
         }
         return extension ? extension as Doc : undefined;
@@ -657,6 +658,7 @@ export namespace Doc {
     // the document containing the view layout information - will be the Document itself unless the Document has
     // a layout field.  In that case, all layout information comes from there unless overriden by Document  
     export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? doc[StrCast(doc.layoutKey, "layout")] as Doc : doc; }
+    export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; }
     export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; }
     const manager = new DocData();
     export function SearchQuery(): string { return manager._searchQuery; }
-- 
cgit v1.2.3-70-g09d2

