From d8c065935a0cb5a89b6d76407b7729f8584424eb Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 6 Aug 2019 22:03:59 -0400
Subject: pivot view

---
 src/client/views/Main.tsx | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 1cf13aa74..daa778ed3 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -7,6 +7,7 @@ import { Cast } from "../../new_fields/Types";
 import { Doc, DocListCastAsync } from "../../new_fields/Doc";
 import { List } from "../../new_fields/List";
 import { DocServer } from "../DocServer";
+import { PivotView } from "./collections/collectionFreeForm/CollectionFreeFormView";
 
 let swapDocs = async () => {
     let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc);
@@ -33,6 +34,7 @@ let swapDocs = async () => {
     DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
     await Docs.Prototypes.initialize();
     await CurrentUserUtils.loadUserDocument(info);
+    await PivotView.loadLayouts();
     // updates old user documents to prevent chrome on tree view.
     (await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled";
     (await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled";
-- 
cgit v1.2.3-70-g09d2


From adb91b035bd18ff407ce0b2decc07c779282c008 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 7 Aug 2019 12:40:45 -0400
Subject: added zooming to pdfs

---
 src/client/views/Main.tsx                          |  5 ++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 63 ++++++++++------------
 src/client/views/nodes/PDFBox.scss                 |  3 ++
 src/client/views/nodes/PDFBox.tsx                  | 59 ++++++++------------
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 src/client/views/pdf/Page.tsx                      | 29 +++++-----
 6 files changed, 75 insertions(+), 86 deletions(-)

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 1cf13aa74..5fd42c0df 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -39,5 +39,10 @@ let swapDocs = async () => {
     (await Cast(CurrentUserUtils.UserDocument.sidebar, Doc))!.chromeStatus = "disabled";
     CurrentUserUtils.UserDocument.chromeStatus = "disabled";
     await swapDocs();
+    document.getElementById('root')!.addEventListener('wheel', event => {
+        if (event.ctrlKey) {
+            event.preventDefault()
+        }
+    }, true);
     ReactDOM.render(<MainView />, document.getElementById('root'));
 })();
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a08a12426..d347e02b6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -352,12 +352,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
         if (BoolCast(this.props.Document.lockedPosition)) return;
-        // if (!this.props.active()) {
-        //     return;
-        // }
-        if (this.props.Document.type === "pdf") {
+        if (!e.ctrlKey && this.props.Document.scrollY !== undefined) {
+            e.stopPropagation();
             return;
         }
+
         let childSelected = this.childDocs.some(doc => {
             var dv = DocumentManager.Instance.getDocumentView(doc);
             return dv && SelectionManager.IsSelected(dv) ? true : false;
@@ -366,21 +365,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             return;
         }
         e.stopPropagation();
-        const coefficient = 1000;
-
-        if (e.ctrlKey) {
-            let deltaScale = (1 - (e.deltaY / coefficient));
-            let nw = this.nativeWidth * deltaScale;
-            let nh = this.nativeHeight * deltaScale;
-            if (nw && nh) {
-                this.props.Document.nativeWidth = nw;
-                this.props.Document.nativeHeight = nh;
-            }
-            e.stopPropagation();
-            e.preventDefault();
-        } else {
-            // if (modes[e.deltaMode] === 'pixels') coefficient = 50;
-            // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height??
+
+        // bcz: this changes the nativewidth/height, but ImageBox will just revert it back to its defaults.  need more logic to fix.
+        // if (e.ctrlKey && this.props.Document.scrollY === undefined) {
+        //     let deltaScale = (1 - (e.deltaY / coefficient));
+        //     let nw = this.nativeWidth * deltaScale;
+        //     let nh = this.nativeHeight * deltaScale;
+        //     if (nw && nh) {
+        //         this.props.Document.nativeWidth = nw;
+        //         this.props.Document.nativeHeight = nh;
+        //     }
+        //     e.preventDefault();
+        // } 
+        // else 
+        {
             let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1;
             if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
                 deltaScale = 1 / this.zoomScaling();
@@ -392,21 +390,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
             this.props.Document.scale = Math.abs(safeScale);
             this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
-            e.stopPropagation();
+            e.preventDefault();
         }
     }
 
     @action
     setPan(panX: number, panY: number) {
-        if (BoolCast(this.props.Document.lockedPosition)) return;
-        this.props.Document.panTransformType = "None";
-        var scale = this.getLocalTransform().inverse().Scale;
-        const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
-        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;
-        if (this.props.Document.scrollY) {
-            this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym]();
+        if (!BoolCast(this.props.Document.lockedPosition)) {
+            this.props.Document.panTransformType = "None";
+            var scale = this.getLocalTransform().inverse().Scale;
+            const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
+            const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.props.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
+            this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
+            this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
+            if (this.props.Document.scrollHeight !== undefined) this.props.Document.scrollY = this.isAnnotationOverlay ? newPanY : panY;
+            else this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
         }
     }
 
@@ -490,12 +488,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         this.Document.scale = scale;
     }
 
-    getScale = () => {
-        if (this.Document.scale) {
-            return this.Document.scale;
-        }
-        return 1;
-    }
+    getScale = () => this.Document.scale ? this.Document.scale : 1;
 
 
     getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index a1bab0409..c88a94c28 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -5,6 +5,9 @@
     height: 100%;
     overflow-y: scroll;
     overflow-x: hidden;
+    .pdfBox-scrollHack {
+        pointer-events: none;
+    }
 }
 
 .pdfBox-cont {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 56e720bf7..2759b46d4 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -30,10 +30,11 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
     @observable private _flyout: boolean = false;
     @observable private _alt = false;
-    @observable private _scrollY: number = 0;
     @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
+
     @computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; }
     @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+    @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); }
 
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _reactionDisposer?: IReactionDisposer;
@@ -62,16 +63,16 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     }
 
     public GetPage() {
-        return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1;
+        return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.nativeHeight)) + 1;
     }
 
     @action
     public BackPage() {
-        let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1;
+        let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.nativeHeight)) + 1;
         cp = cp - 1;
         if (cp > 0) {
             this.props.Document.curPage = cp;
-            this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.pdfHeight);
+            this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.nativeHeight);
         }
     }
 
@@ -79,7 +80,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     public GotoPage(p: number) {
         if (p > 0 && p <= NumCast(this.props.Document.numPages)) {
             this.props.Document.curPage = p;
-            this.props.Document.scrollY = (p - 1) * NumCast(this.dataDoc.pdfHeight);
+            this.props.Document.scrollY = (p - 1) * NumCast(this.dataDoc.nativeHeight);
         }
     }
 
@@ -88,31 +89,16 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
         let cp = this.GetPage() + 1;
         if (cp <= NumCast(this.props.Document.numPages)) {
             this.props.Document.curPage = cp;
-            this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.pdfHeight);
+            this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.nativeHeight);
         }
     }
 
-    scrollTo = (y: number) => {
-        this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" });
-    }
-
     @action
     setPanY = (y: number) => {
         this.containingCollectionDocument && (this.containingCollectionDocument.panY = y);
     }
 
-    private newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this._keyValue = e.currentTarget.value;
-    }
-
-    private newValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this._valueValue = e.currentTarget.value;
-    }
-
-    private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this._scriptValue = e.currentTarget.value;
-    }
-
+    @action
     private applyFilter = () => {
         let scriptText = this._scriptValue.length > 0 ? this._scriptValue :
             this._keyValue.length > 0 && this._valueValue.length > 0 ?
@@ -121,7 +107,10 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
         script.compiled && (this.props.Document.filterScript = new ScriptField(script));
     }
 
-    @action
+    scrollTo = (y: number) => {
+        this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" });
+    }
+
     private resetFilters = () => {
         this._keyValue = this._valueValue = "";
         this._scriptValue = "return true";
@@ -130,6 +119,9 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
         this._scriptRef.current && (this._scriptRef.current.value = "");
         this.applyFilter();
     }
+    private newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => this._keyValue = e.currentTarget.value;
+    private newValueChange = (e: React.ChangeEvent<HTMLInputElement>) => this._valueValue = e.currentTarget.value;
+    private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => this._scriptValue = e.currentTarget.value;
 
     settingsPanel() {
         return !this.props.active() ? (null) :
@@ -176,12 +168,12 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
     loaded = (nw: number, nh: number, np: number) => {
         this.dataDoc.numPages = np;
-        if (!this.dataDoc.nativeWidth || !this.dataDoc.nativeHeight) {
+        if (!this.dataDoc.nativeWidth || !this.dataDoc.nativeHeight || !this.dataDoc.scrollHeight) {
             let oldaspect = NumCast(this.dataDoc.nativeHeight) / NumCast(this.dataDoc.nativeWidth, 1);
             this.dataDoc.nativeWidth = nw;
             this.dataDoc.nativeHeight = this.dataDoc.nativeHeight ? nw * oldaspect : nh;
-            this.containingCollectionDocument && (this.containingCollectionDocument.pdfHeight = nh);
-            this.dataDoc.height = nh * (this.dataDoc[WidthSym]() / nw);
+            this.dataDoc.height = this.dataDoc[WidthSym]() * (nh / nw);
+            this.dataDoc.scrollHeight = np * this.dataDoc.nativeHeight;
         }
     }
 
@@ -189,14 +181,10 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     onScroll = (e: React.UIEvent<HTMLDivElement>) => {
         if (e.currentTarget && this.containingCollectionDocument) {
             this.containingCollectionDocument.panTransformType = "None";
-            this.containingCollectionDocument.scrollY = this._scrollY = e.currentTarget.scrollTop;
+            this.containingCollectionDocument.scrollY = e.currentTarget.scrollTop;
         }
     }
 
-    @computed get fieldExtensionDoc() {
-        return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true");
-    }
-
     render() {
         const pdfUrl = Cast(this.props.Document.data, PdfField);
         let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
@@ -205,14 +193,13 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
             <div className={classname}
                 onScroll={this.onScroll}
                 style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}
-                ref={this._mainCont}
-                onWheel={(e: React.WheelEvent) => { e.stopPropagation(); }}>
-                <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} scrollY={this._scrollY}
+                ref={this._mainCont}>
+                <div className="pdfBox-scrollHack" style={{ height: NumCast(this.props.Document.scrollHeight) + (NumCast(this.props.Document.nativeHeight) - NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.scale)), width: "100%" }} />
+                <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} scrollY={NumCast(this.props.Document.scrollY)}
                     Document={this.props.Document} DataDoc={this.props.DataDoc}
                     addDocTab={this.props.addDocTab} setPanY={this.setPanY}
                     fieldKey={this.props.fieldKey} fieldExtensionDoc={this.fieldExtensionDoc} />
                 {this.settingsPanel()}
-            </div>
-        );
+            </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 7c445c373..021c04723 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -160,7 +160,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 }))));
             this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages);
 
-            let startY = NumCast(this.props.Document.startY);
+            let startY = NumCast(this.props.Document.startY, NumCast(this.props.Document.scrollY));
             this.props.setPanY && this.props.setPanY(startY);
             this.props.Document.scrollY = startY + 1;
         }
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index a15bed255..6de2db427 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -143,6 +143,7 @@ export default class Page extends React.Component<IPageProps> {
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         // if alt+left click, drag and annotate
+        if (this.props.Document.scale !== 1) return;
         if (e.altKey && e.button === 0) {
             e.stopPropagation();
         }
@@ -197,21 +198,21 @@ export default class Page extends React.Component<IPageProps> {
     onSelectEnd = (e: PointerEvent): void => {
         if (this._marqueeing) {
             this._marqueeing = false;
-            if (this._marquee.current) { // make a copy of the marquee
-                let copy = document.createElement("div");
-                let style = this._marquee.current.style;
-                copy.style.left = style.left;
-                copy.style.top = style.top;
-                copy.style.width = style.width;
-                copy.style.height = style.height;
-                copy.style.border = style.border;
-                copy.style.opacity = style.opacity;
-                copy.className = "pdfPage-annotationBox";
-                this.props.createAnnotation(copy, this.props.page);
-                this._marquee.current.style.opacity = "0";
-            }
-
             if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
+                if (this._marquee.current) { // make a copy of the marquee
+                    let copy = document.createElement("div");
+                    let style = this._marquee.current.style;
+                    copy.style.left = style.left;
+                    copy.style.top = style.top;
+                    copy.style.width = style.width;
+                    copy.style.height = style.height;
+                    copy.style.border = style.border;
+                    copy.style.opacity = style.opacity;
+                    copy.className = "pdfPage-annotationBox";
+                    this.props.createAnnotation(copy, this.props.page);
+                    this._marquee.current.style.opacity = "0";
+                }
+
                 if (!e.ctrlKey) {
                     PDFMenu.Instance.Status = "snippet";
                     PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight };
-- 
cgit v1.2.3-70-g09d2


From 50d7ffb3303cf06ced84b9788b95578fff2e92da Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 7 Aug 2019 20:14:53 -0400
Subject: pivot viewer native code

---
 src/client/views/Main.tsx                          |   2 +-
 src/client/views/collections/CollectionView.tsx    |   5 +-
 .../views/collections/CollectionViewChromes.tsx    |  33 +++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 203 +++++++++++++++++----
 4 files changed, 205 insertions(+), 38 deletions(-)

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index daa778ed3..8be633f80 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -34,7 +34,7 @@ let swapDocs = async () => {
     DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
     await Docs.Prototypes.initialize();
     await CurrentUserUtils.loadUserDocument(info);
-    await PivotView.loadLayouts();
+    // await PivotView.loadLayouts();
     // updates old user documents to prevent chrome on tree view.
     (await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled";
     (await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled";
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 60ede17e7..7a402798e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -89,8 +89,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
             subItems.push({
                 description: "Freeform", event: () => {
                     this.props.Document.viewType = CollectionViewType.Freeform;
-                    delete this.props.Document.arrangeInit;
-                    delete this.props.Document.arrangeScript;
+                    delete this.props.Document.usePivotLayout;
                 }, icon: "signature"
             });
             if (CollectionBaseView.InSafeMode()) {
@@ -103,7 +102,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
             switch (this.props.Document.viewType) {
                 case CollectionViewType.Freeform: {
                     subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) });
-                    subItems.push({ description: "Pivot", icon: "copy", event: () => CollectionFreeFormView.SetPivotLayout(this.props.Document) });
+                    subItems.push({ description: "Pivot", icon: "copy", event: () => this.props.Document.usePivotLayout = true });
                     break;
                 }
             }
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 1b2561953..e1ac79da6 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -3,7 +3,7 @@ import { CollectionView } from "./CollectionView";
 import "./CollectionViewChromes.scss";
 import { CollectionViewType } from "./CollectionBaseView";
 import { undoBatch } from "../../util/UndoManager";
-import { action, observable, runInAction, computed, IObservable, IObservableValue } from "mobx";
+import { action, observable, runInAction, computed, IObservable, IObservableValue, reaction, autorun } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { DocLike } from "../MetadataEntryMenu";
@@ -187,6 +187,36 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         }
     }
 
+    private get document() {
+        return this.props.CollectionView.props.Document;
+    }
+
+    private get pivotKey() {
+        return StrCast(this.document.pivotField);
+    }
+
+    private set pivotKey(value: string) {
+        this.document.pivotField = value;
+    }
+
+    @observable private pivotKeyDisplay = "";
+    getPivotInput = () => {
+        if (!this.document.usePivotLayout) {
+            return (null);
+        }
+        return (<input className="collectionViewBaseChrome-viewSpecsInput"
+            placeholder="PIVOT ON..."
+            value={this.pivotKeyDisplay}
+            onChange={action((e: React.ChangeEvent<HTMLInputElement>) => this.pivotKeyDisplay = e.currentTarget.value)}
+            onKeyPress={action((e: React.KeyboardEvent<HTMLInputElement>) => {
+                let value = e.currentTarget.value;
+                if (e.which === 13) {
+                    this.pivotKey = value;
+                    this.pivotKeyDisplay = "";
+                }
+            })} />);
+    }
+
     render() {
         return (
             <div className="collectionViewChrome-cont" style={{ top: this._collapsed ? -70 : 0 }}>
@@ -219,6 +249,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                                 value={this.filterValue ? this.filterValue.script.originalScript : ""}
                                 onChange={(e) => { }}
                                 onPointerDown={this.openViewSpecs} />
+                            {this.getPivotInput()}
                             <div className="collectionViewBaseChrome-viewSpecsMenu"
                                 onPointerDown={this.openViewSpecs}
                                 style={{
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 110ac2f25..7d3c0569d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -3,7 +3,7 @@ import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faChalkboard, faBraille } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast, FieldResult, Field, Opt } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
@@ -38,6 +38,7 @@ import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { DocumentType, Docs } from "../../../documents/Documents";
 import { RouteStore } from "../../../../server/RouteStore";
+import { string, number, elementType } from "prop-types";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
 
@@ -49,14 +50,143 @@ export const panZoomSchema = createSchema({
     arrangeInit: ScriptField,
 });
 
+export interface ViewDefBounds {
+    x: number;
+    y: number;
+    z?: number;
+    width: number;
+    height: number;
+}
+
+export interface ViewDefResult {
+    ele: JSX.Element;
+    bounds?: ViewDefBounds;
+}
+
 export namespace PivotView {
 
-    export let scripts: { arrangeInit: string, arrangeScript: string };
+    // export let scripts: { arrangeInit: string, arrangeScript: string };
 
-    export async function loadLayouts() {
-        scripts = JSON.parse(await (await fetch(Utils.prepend("/layoutscripts"))).text());
+    // export async function loadLayouts() {
+    //     scripts = JSON.parse(await (await fetch(Utils.prepend("/layoutscripts"))).text());
+    // }
+
+    export interface PivotData {
+        type: string;
+        text: string;
+        x: number;
+        y: number;
+        width: number;
+        height: number;
+        fontSize: number;
     }
 
+    export const elements = (target: CollectionFreeFormView) => {
+        let collection = target.Document;
+        const field = StrCast(collection.pivotField) || "title";
+        const width = NumCast(collection.pivotWidth) || 200;
+
+        const groups = new Map<FieldResult<Field>, Doc[]>();
+
+        for (const doc of target.childDocs) {
+            const val = doc[field];
+            if (val === undefined) continue;
+
+            const l = groups.get(val);
+            if (l) {
+                l.push(doc);
+            } else {
+                groups.set(val, [doc]);
+            }
+
+        }
+
+        let minSize = Infinity;
+
+        groups.forEach((val, key) => {
+            minSize = Math.min(minSize, val.length);
+        });
+
+        const numCols = NumCast(collection.pivotNumColumns) || Math.ceil(Math.sqrt(minSize));
+        const fontSize = NumCast(collection.pivotFontSize);
+
+        const docMap = new Map<Doc, ViewDefBounds>();
+        const groupNames: PivotData[] = [];
+
+        let x = 0;
+        groups.forEach((val, key) => {
+            let y = 0;
+            let xCount = 0;
+            groupNames.push({
+                type: "text",
+                text: String(key),
+                x,
+                y: width + 50,
+                width: width * 1.25 * numCols,
+                height: 100, fontSize: fontSize
+            });
+            for (const doc of val) {
+                docMap.set(doc, {
+                    x: x + xCount * width * 1.25,
+                    y: -y,
+                    width,
+                    height: width
+                });
+                xCount++;
+                if (xCount >= numCols) {
+                    xCount = 0;
+                    y += width * 1.25;
+                }
+            }
+            x += width * 1.25 * (numCols + 1);
+        });
+
+        let elements = target.viewDefsToJSX(groupNames);
+        let curPage = FieldValue(target.Document.curPage, -1);
+
+        let docViews = target.childDocs.filter(doc => doc instanceof Doc).reduce((prev, doc) => {
+            var page = NumCast(doc.page, -1);
+            if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) {
+                let minim = BoolCast(doc.isMinimized);
+                if (minim === undefined || !minim) {
+                    let defaultPosition = (): ViewDefBounds => {
+                        return {
+                            x: NumCast(doc.x),
+                            y: NumCast(doc.y),
+                            z: NumCast(doc.z),
+                            width: NumCast(doc.width),
+                            height: NumCast(doc.height)
+                        };
+                    };
+                    const pos = docMap.get(doc) || defaultPosition();
+                    prev.push({
+                        ele: (
+                            <CollectionFreeFormDocumentView
+                                key={doc[Id]}
+                                x={pos.x}
+                                y={pos.y}
+                                width={pos.width}
+                                height={pos.height}
+                                {...target.getChildDocumentViewProps(doc)}
+                            />),
+                        bounds: {
+                            x: pos.x,
+                            y: pos.y,
+                            z: pos.z,
+                            width: NumCast(pos.width),
+                            height: NumCast(pos.height)
+                        }
+                    });
+                }
+            }
+            return prev;
+        }, elements);
+
+        target.resetSelectOnLoaded();
+
+        return docViews;
+    };
+
 }
 
 type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>;
@@ -508,9 +638,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return 1;
     }
 
-
     getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
-        let self = this;
         let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout);
         return {
             DataDoc: pair.data,
@@ -569,7 +697,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         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, z?: number, width: number, height: number } } | undefined {
+    viewDefsToJSX = (views: any[]) => {
+        let elements: ViewDefResult[] = [];
+        if (Array.isArray(views)) {
+            elements = views.reduce<typeof elements>((prev, ele) => {
+                const jsx = this.viewDefToJSX(ele);
+                jsx && prev.push(jsx);
+                return prev;
+            }, elements);
+        }
+        return elements;
+    }
+
+    private viewDefToJSX(viewDef: any): Opt<ViewDefResult> {
         if (viewDef.type === "text") {
             const text = Cast(viewDef.text, "string");
             const x = Cast(viewDef.x, "number");
@@ -598,20 +738,14 @@ 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, z?: number, width: number, height: number } }[] = [];
+        let elements: ViewDefResult[] = [];
         if (initScript) {
             const initResult = initScript.script.run({ docs, collection: this.Document });
             if (initResult.success) {
                 const result = initResult.result;
                 const { state: scriptState, views } = result;
                 state = scriptState;
-                if (Array.isArray(views)) {
-                    elements = views.reduce<typeof elements>((prev, ele) => {
-                        const jsx = this.viewDefToJSX(ele);
-                        jsx && prev.push(jsx);
-                        return prev;
-                    }, elements);
-                }
+                elements = this.viewDefsToJSX(views);
             }
         }
         let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => {
@@ -633,14 +767,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             return prev;
         }, elements);
 
-        setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
+        this.resetSelectOnLoaded();
 
         return docviews;
     }
 
+    resetSelectOnLoaded = () => setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
+
     @computed.struct
     get views() {
-        return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
+        let source = this.Document.usePivotLayout === true ? PivotView.elements(this) : this.elements;
+        return source.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
     }
     @computed.struct
     get overlayViews() {
@@ -798,22 +935,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         };
     }
 
-    public static SetPivotLayout = (target: Doc) => {
-        let setSpecifiedLayoutField = (originalText: string, key: string, params: Record<string, string>, requiredType?: string) => {
-            const script = CompileScript(originalText, {
-                params,
-                requiredType,
-                typecheck: false
-            });
-            if (!script.compiled) {
-                console.log(script.errors.map(error => error.messageText).join("\n"));
-                return;
-            }
-            target[key] = new ScriptField(script);
-        };
-        setSpecifiedLayoutField(PivotView.scripts.arrangeInit, "arrangeInit", { collection: "Doc", docs: "Doc[]" }, undefined);
-        setSpecifiedLayoutField(PivotView.scripts.arrangeScript, "arrangeScript", { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
-    }
+    // public static SetPivotLayout = (target: Doc) => {
+    //     let setSpecifiedLayoutField = (originalText: string, key: string, params: Record<string, string>, requiredType?: string) => {
+    //         const script = CompileScript(originalText, {
+    //             params,
+    //             requiredType,
+    //             typecheck: false
+    //         });
+    //         if (!script.compiled) {
+    //             console.log(script.errors.map(error => error.messageText).join("\n"));
+    //             return;
+    //         }
+    //         target[key] = new ScriptField(script);
+    //     };
+    //     setSpecifiedLayoutField(PivotView.scripts.arrangeInit, "arrangeInit", { collection: "Doc", docs: "Doc[]" }, undefined);
+    //     setSpecifiedLayoutField(PivotView.scripts.arrangeScript, "arrangeScript", { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
+    // }
 
     render() {
         const easing = () => this.props.Document.panTransformType === "Ease";
-- 
cgit v1.2.3-70-g09d2

