From 2faf9b1e8d8f0fbaecb767f552b9d16a6b49a8de Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 27 Aug 2023 14:33:39 -0400
Subject: from last

---
 src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 3 ---
 1 file changed, 3 deletions(-)

(limited to 'src/client/views/collections/CollectionStackingViewFieldColumn.tsx')

diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index ebb4ba5a1..7a22d4871 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -22,9 +22,6 @@ import { EditableView } from '../EditableView';
 import './CollectionStackingView.scss';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { Id } from '../../../fields/FieldSymbols';
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
 
 // So this is how we are storing a column
 interface CSVFieldColumnProps {
-- 
cgit v1.2.3-70-g09d2


From 44c66891f727093248a5e4cdeeec25a04cc772cc Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 6 Sep 2023 23:00:29 -0400
Subject: disabled expand button for header context linear menu. moved input
 for stacking view to right-hand side to avoid overlap with undo/redo

---
 src/client/util/CurrentUserUtils.ts                        |  4 ++--
 src/client/views/collections/CollectionStackingView.scss   |  9 ++++++++-
 .../collections/CollectionStackingViewFieldColumn.tsx      | 14 +++++++++-----
 3 files changed, 19 insertions(+), 8 deletions(-)

(limited to 'src/client/views/collections/CollectionStackingViewFieldColumn.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 836160265..aa5f2658a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -756,7 +756,7 @@ export class CurrentUserUtils {
 
     /// Initializes all the default buttons for the top bar context menu
     static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
-        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: true, _height: 35 };
+        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
         const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
         const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
         return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
@@ -784,7 +784,7 @@ export class CurrentUserUtils {
         const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs));
         const dockBtnsReqdOpts:DocumentOptions = {
             title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
-            childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true
+            childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: false, ignoreClick: true
         };
         return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
     }
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 255bc3889..dddb3ec71 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -395,16 +395,23 @@
     }
 
     .collectionStackingView-addDocumentButton {
-        font-size: 75%;
         letter-spacing: 2px;
         cursor: pointer;
 
+        .editableView-container-editing {
+            text-align: right;
+        }
         .editableView-input {
             outline-color: black;
             letter-spacing: 2px;
             color: grey;
             border: 0px;
+            background: yellow;
+            text-align: right;
             padding-top: 10px; // : 12px 10px 11px 10px;
+            input {
+                text-align: right;
+            }
         }
     }
 
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 7a22d4871..3aadeffcd 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -212,15 +212,16 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         const layoutItems: ContextMenuProps[] = [];
         const docItems: ContextMenuProps[] = [];
         const dataDoc = this.props.DataDoc || this.props.Document;
-
+        const width = this._ele ? Number(getComputedStyle(this._ele).width.replace('px', '')) : 0;
+        const height = this._ele ? Number(getComputedStyle(this._ele).height.replace('px', '')) : 0;
         DocUtils.addDocumentCreatorMenuItems(
             doc => {
                 FormattedTextBox.SelectOnLoad = doc[Id];
                 return this.props.addDocument?.(doc);
             },
             this.props.addDocument,
-            x,
-            y,
+            0,
+            0,
             true
         );
 
@@ -272,8 +273,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 this.props.addDocument?.(created);
             }
         });
-        const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y);
-        ContextMenu.Instance.displayMenu(x, y, undefined, true);
+        const pt = this.props
+            .screenToLocalTransform()
+            .inverse()
+            .transformPoint(width - 30, height);
+        ContextMenu.Instance.displayMenu(pt[0], pt[1], undefined, true);
     };
 
     @computed get innards() {
-- 
cgit v1.2.3-70-g09d2


From cfef242e9b76ba9caca2fc1871af74af6775538f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 12 Sep 2023 20:43:20 -0400
Subject: dropping link button on same collection makes a pushpin. fixed broken
 undo typing to crate doc in sidebar. fixed min/max scaling for cropped images
 and made annotationOverlays start to use it.   Fixed nested text boxes to
 stopPropagation on pointer down to enable editing of translations in sidebar.
 moved sidebar filters onto doc's filters.  Added metadata filters back to
 sidebar.  Added an -any- option to filtersPanel.  fixed schema view preview
 window,  added buttons and sliders.

---
 package-lock.json                                  | 119 ++++++++++++++++-----
 package.json                                       |   4 +-
 src/client/documents/Documents.ts                  |   7 +-
 src/client/util/CurrentUserUtils.ts                |  10 +-
 src/client/views/FilterPanel.tsx                   |  20 ++--
 src/client/views/PropertiesView.tsx                |   7 +-
 src/client/views/SidebarAnnos.tsx                  |  30 +++---
 src/client/views/StyleProvider.tsx                 |   2 +-
 .../views/collections/CollectionStackingView.tsx   |   2 +-
 .../CollectionStackingViewFieldColumn.tsx          |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  44 +++++---
 .../collectionSchema/CollectionSchemaView.scss     |   8 +-
 .../collectionSchema/CollectionSchemaView.tsx      |   1 +
 .../collections/collectionSchema/SchemaRowBox.tsx  |   4 +-
 .../collectionSchema/SchemaTableCell.tsx           |  26 ++++-
 src/client/views/global/globalScripts.ts           |   8 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   1 -
 src/client/views/nodes/ImageBox.tsx                |  10 +-
 src/client/views/nodes/PDFBox.tsx                  |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  51 ++++++---
 src/fields/Doc.ts                                  |   1 +
 21 files changed, 255 insertions(+), 110 deletions(-)

(limited to 'src/client/views/collections/CollectionStackingViewFieldColumn.tsx')

diff --git a/package-lock.json b/package-lock.json
index e0e54769a..fbfdf0136 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7244,12 +7244,97 @@
                                         "strip-ansi": "^7.0.1"
                                     }
                                 },
+                                "string-width-cjs": {
+                                    "version": "npm:string-width@4.2.3",
+                                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                                    "requires": {
+                                        "emoji-regex": "^8.0.0",
+                                        "is-fullwidth-code-point": "^3.0.0",
+                                        "strip-ansi": "^6.0.1"
+                                    },
+                                    "dependencies": {
+                                        "ansi-regex": {
+                                            "version": "5.0.1",
+                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                                        },
+                                        "emoji-regex": {
+                                            "version": "8.0.0",
+                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                                        },
+                                        "strip-ansi": {
+                                            "version": "6.0.1",
+                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                                            "requires": {
+                                                "ansi-regex": "^5.0.1"
+                                            }
+                                        }
+                                    }
+                                },
                                 "strip-ansi": {
                                     "version": "7.1.0",
                                     "bundled": true,
                                     "requires": {
                                         "ansi-regex": "^6.0.1"
                                     }
+                                },
+                                "strip-ansi-cjs": {
+                                    "version": "npm:strip-ansi@6.0.1",
+                                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                                    "requires": {
+                                        "ansi-regex": "^5.0.1"
+                                    },
+                                    "dependencies": {
+                                        "ansi-regex": {
+                                            "version": "5.0.1",
+                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                                        }
+                                    }
+                                },
+                                "wrap-ansi-cjs": {
+                                    "version": "npm:wrap-ansi@7.0.0",
+                                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+                                    "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+                                    "requires": {
+                                        "ansi-styles": "^4.0.0",
+                                        "string-width": "^4.1.0",
+                                        "strip-ansi": "^6.0.0"
+                                    },
+                                    "dependencies": {
+                                        "ansi-regex": {
+                                            "version": "5.0.1",
+                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                                        },
+                                        "emoji-regex": {
+                                            "version": "8.0.0",
+                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                                        },
+                                        "string-width": {
+                                            "version": "4.2.3",
+                                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                                            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                                            "requires": {
+                                                "emoji-regex": "^8.0.0",
+                                                "is-fullwidth-code-point": "^3.0.0",
+                                                "strip-ansi": "^6.0.1"
+                                            }
+                                        },
+                                        "strip-ansi": {
+                                            "version": "6.0.1",
+                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                                            "requires": {
+                                                "ansi-regex": "^5.0.1"
+                                            }
+                                        }
+                                    }
                                 }
                             }
                         },
@@ -8754,7 +8839,7 @@
                             }
                         },
                         "string-width-cjs": {
-                            "version": "npm:string-width@4.2.3",
+                            "version": "npm:string-width-cjs@4.2.3",
                             "bundled": true,
                             "requires": {
                                 "emoji-regex": "^8.0.0",
@@ -8777,7 +8862,7 @@
                             }
                         },
                         "strip-ansi-cjs": {
-                            "version": "npm:strip-ansi@6.0.1",
+                            "version": "npm:strip-ansi-cjs@6.0.1",
                             "bundled": true,
                             "requires": {
                                 "ansi-regex": "^5.0.1"
@@ -8936,7 +9021,7 @@
                             }
                         },
                         "wrap-ansi-cjs": {
-                            "version": "npm:wrap-ansi@7.0.0",
+                            "version": "npm:wrap-ansi-cjs@7.0.0",
                             "bundled": true,
                             "requires": {
                                 "ansi-styles": "^4.0.0",
@@ -14742,6 +14827,11 @@
                 "node-forge": "^0.10.0"
             }
         },
+        "google-translate-api-browser": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/google-translate-api-browser/-/google-translate-api-browser-3.0.1.tgz",
+            "integrity": "sha512-KTLodkyGBWMK9IW6QIeJ2zCuju4Z0CLpbkADKo+yLhbSTD4l+CXXpQ/xaynGVAzeBezzJG6qn8MLeqOq3SmW0A=="
+        },
         "googleapis": {
             "version": "40.0.1",
             "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-40.0.1.tgz",
@@ -27327,29 +27417,6 @@
             "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
             "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
         },
-        "translate-google-api": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/translate-google-api/-/translate-google-api-1.0.4.tgz",
-            "integrity": "sha512-KVXmo4+64/H1vIbnzf2zNiJ2JLeEB3jrEnNRP2EFNAGNqna/5bmw/Cps3pCHu0n3BzTOoWh9u6wFvrRYdzQ6Iw==",
-            "requires": {
-                "axios": "^0.20.0"
-            },
-            "dependencies": {
-                "axios": {
-                    "version": "0.20.0",
-                    "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz",
-                    "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==",
-                    "requires": {
-                        "follow-redirects": "^1.10.0"
-                    }
-                },
-                "follow-redirects": {
-                    "version": "1.15.2",
-                    "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
-                    "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
-                }
-            }
-        },
         "traverse-chain": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
diff --git a/package.json b/package.json
index 67fdedeb2..d013ad74d 100644
--- a/package.json
+++ b/package.json
@@ -164,6 +164,7 @@
         "@types/three": "^0.126.2",
         "@types/web": "0.0.53",
         "@webscopeio/react-textarea-autocomplete": "^4.9.1",
+        "D": "^1.0.0",
         "adm-zip": "^0.4.16",
         "archiver": "^3.1.1",
         "array-batcher": "^1.2.3",
@@ -196,7 +197,6 @@
         "cors": "^2.8.5",
         "csv-parser": "^3.0.0",
         "csv-stringify": "^6.3.0",
-        "D": "^1.0.0",
         "d3": "^7.6.1",
         "depcheck": "^0.9.2",
         "equation-editor-react": "github:bobzel/equation-editor-react#useLocally",
@@ -219,6 +219,7 @@
         "golden-layout": "^1.5.9",
         "google-auth-library": "^4.2.4",
         "google-maps-react": "^2.0.6",
+        "google-translate-api-browser": "^3.0.1",
         "googleapis": "^40.0.0",
         "googlephotos": "^0.2.5",
         "got": "^12.0.1",
@@ -327,7 +328,6 @@
         "textarea-caret": "^3.1.0",
         "three": "^0.127.0",
         "tough-cookie": "^4.0.0",
-        "translate-google-api": "^1.0.4",
         "typescript-collections": "^1.3.3",
         "typescript-language-server": "^0.4.0",
         "url-loader": "^1.1.2",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index bb60cb329..85ee4ef59 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -145,7 +145,6 @@ class CTypeInfo extends FInfo {
 class DTypeInfo extends FInfo {
     fieldType? = 'enumeration';
     values? = Array.from(Object.keys(DocumentType));
-    readOnly = true;
 }
 class DateInfo extends FInfo {
     fieldType? = 'date';
@@ -338,11 +337,13 @@ export class DocumentOptions {
 
     // freeform properties
     _freeform_backgroundGrid?: BOOLt = new BoolInfo('whether background grid is shown on freeform collections');
+    _freeform_scale_min?: NUMt = new NumInfo('how far out a view can zoom (used by image/videoBoxes that are clipped');
+    _freeform_scale_max?: NUMt = new NumInfo('how far in a view can zoom (used by sidebar freeform views');
     _freeform_scale?: NUMt = new NumInfo('how much a freeform view has been scaled (zoomed)');
     _freeform_panX?: NUMt = new NumInfo('horizontal pan location of a freeform view');
     _freeform_panY?: NUMt = new NumInfo('vertical pan location of a freeform view');
     _freeform_noAutoPan?: BOOLt = new BoolInfo('disables autopanning when this item is dragged');
-    _freeform_noZoom?: BOOLt = new BoolInfo('disables zooming');
+    _freeform_noZoom?: BOOLt = new BoolInfo('disables zooming (used by Pile docs)');
 
     //BUTTONS
     buttonText?: string;
@@ -426,7 +427,7 @@ export class DocumentOptions {
     treeView_FreezeChildren?: STRt = new StrInfo('set (add, remove, add|remove) to disable adding, removing or both from collection');
 
     sidebar_color?: string; // background color of text sidebar
-    sidebar_collectionType?: string; // collection type of text sidebar
+    sidebar_type_collection?: string; // collection type of text sidebar
 
     data_dashboards?: List<any>; // list of dashboards used in shareddocs;
     text?: string;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 8bedea562..527f251f9 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -627,6 +627,11 @@ export class CurrentUserUtils {
             { title: "Z order", icon: "z",                  toolTip: "Keep Z order on Drag", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: '{ return toggleRaiseOnDrag(_readOnly_);}'}}, // Only when floating document is selected in freeform
         ]
     }
+    static stackTools(): Button[] {
+        return [
+            { title: "Center",    icon: "align-center",        toolTip: "Center Align Stack",  btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center",     funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+        ]
+    }
     static viewTools(): Button[] {
         return [
             { title: "Snap",      icon: "th",                  toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines",  funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
@@ -682,8 +687,8 @@ export class CurrentUserUtils {
 
     static schemaTools():Button[] {
         return [
-            {title: "Show preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} },
-            {title: "Single Lines", toolTip: "Single Line Rows",       btnType: ButtonType.ToggleButton, buttonText: "Single Line",  icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
+            {title: "Preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, icon: "portrait", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} },
+            {title: "1 Line",toolTip: "Single Line Rows",       btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
         ];
     }
 
@@ -713,6 +718,7 @@ export class CurrentUserUtils {
             { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {                                                                             linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
             { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
             { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+            { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
             { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected
            ];
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 0d4f4df5a..a601706ce 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -235,7 +235,7 @@ export class FilterPanel extends React.Component<filterProps> {
     facetValues = (facetHeader: string) => {
         const allCollectionDocs = new Set<Doc>();
         SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
-        const set = new Set<string>([String.fromCharCode(127) + '--undefined--']);
+        const set = new Set<string>([String.fromCharCode(127) + '--undefined--', Doc.FilterAny]);
         if (facetHeader === 'tags')
             allCollectionDocs.forEach(child =>
                 StrListCast(child[facetHeader])
@@ -257,24 +257,16 @@ export class FilterPanel extends React.Component<filterProps> {
     };
 
     render() {
-        // console.log('this is frist one today ' + this._allFacets);
-        this._allFacets.forEach(element => console.log(element));
-        // const options = Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => pair[1].filterable ).map(facet => value: facet, label: facet) //this._allFacets.filter(facet => this.activeFacetHeaders.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet }));
-        // console.log('HEELLLLLL ' + DocumentOptions);
-
-        let filteredOptions: string[] = ['author', 'tags', 'text', 'acl-Guest'];
+        let filteredOptions: string[] = ['author', 'tags', 'text', 'acl-Guest', ...this._allFacets.filter(facet => facet[0] === facet.charAt(0).toUpperCase())];
 
         Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => {
             if (pair[1].filterable) {
                 filteredOptions.push(pair[0]);
-                console.log('THIS IS FILTERABLE ALKDJFIIEII' + filteredOptions);
             }
         });
 
         let options = filteredOptions.map(facet => ({ value: facet, label: facet }));
 
-        //  Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => console.log('this is first piar ' + pair[0] + ' this is second piar ' + pair[1].filterable));
-
         return (
             <div className="filterBox-treeView">
                 <div className="filterBox-select">
@@ -398,13 +390,13 @@ export class FilterPanel extends React.Component<filterProps> {
                         <div>
                             <input
                                 style={{ width: 20, marginLeft: 20 }}
-                                checked={
+                                checked={['check', 'exists'].includes(
                                     StrListCast(this.targetDoc._childFilters)
                                         .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue)
-                                        ?.split(Doc.FilterSep)[2] === 'check'
-                                }
+                                        ?.split(Doc.FilterSep)[2] ?? ''
+                                )}
                                 type={type}
-                                onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')}
+                                onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? (fval === Doc.FilterAny ? 'exists' : 'check') : 'remove'), 'set filter')}
                             />
                             {facetValue}
                         </div>
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 699aafe80..21b6bfac5 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -18,7 +18,7 @@ import { ComputedField } from '../../fields/ScriptField';
 import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { GetEffectiveAcl, normalizeEmail, SharingPermissions } from '../../fields/util';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
-import { DocumentType } from '../documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocumentManager } from '../util/DocumentManager';
 import { GroupManager } from '../util/GroupManager';
 import { LinkManager } from '../util/LinkManager';
@@ -122,6 +122,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @computed get isInk() {
         return this.selectedDoc?.type === DocumentType.INK;
     }
+    @computed get isStack() {
+        return this.selectedDoc?.type_collection === CollectionViewType.Stacking;
+    }
 
     rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[Width](), this.props.width - 20));
     rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[Width]() ? Math.min(this.selectedDoc?.[Height](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
@@ -1096,6 +1099,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @computed get transformEditor() {
         return (
             <div className="transform-editor">
+                {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), (val: number) => !isNaN(val) && (this.selectedDoc!.gridGap = val))}
+                {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.xMargin = val))}
                 {this.isInk ? this.controlPointsButton : null}
                 {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)}
                 {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, (val: number) => !isNaN(val) && (this.shapeHgt = val), 1000, 1)}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index ff347d65f..1e1b8e0e6 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -62,12 +62,9 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
         return Array.from(keys.keys()).sort();
     }
-    get filtersKey() {
-        return '_' + this.sidebarKey + '_childFilters';
-    }
 
     anchorMenuClick = (anchor: Doc, filterExlusions?: string[]) => {
-        const startup = StrListCast(this.props.rootDoc.childFilters)
+        const startup = this.childFilters()
             .map(filter => filter.split(':')[0])
             .join(' ');
         const target = Docs.Create.TextDocument(startup, {
@@ -151,8 +148,9 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     };
     makeDocUnfiltered = (doc: Doc) => {
         if (DocListCast(this.props.rootDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
-            if (this.props.layoutDoc[this.filtersKey]) {
-                this.props.layoutDoc[this.filtersKey] = new List<string>();
+            if (this.childFilters()) {
+                // if any child filters exist, get rid of them
+                this.props.layoutDoc._childFilters = new List<string>();
             }
             return true;
         }
@@ -178,7 +176,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey);
     moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
     removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
-    childFilters = () => [...StrListCast(this.props.layoutDoc._childFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
+    childFilters = () => StrListCast(this.props.layoutDoc._childFilters);
     layout_showTitle = () => 'title';
     setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight());
     sortByLinkAnchorY = (a: Doc, b: Doc) => {
@@ -188,25 +186,25 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     };
     render() {
         const renderTag = (tag: string) => {
-            const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`tags::${tag}::check`);
+            const active = this.childFilters().includes(`tags${Doc.FilterSep}${tag}${Doc.FilterSep}check`);
             return (
-                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'tags', tag, 'check', true, this.sidebarKey, e.shiftKey)}>
+                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'tags', tag, 'check', true, undefined, e.shiftKey)}>
                     {tag}
                 </div>
             );
         };
         const renderMeta = (tag: string, dflt: FieldResult<Field>) => {
-            const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${dflt}:exists`);
+            const active = this.childFilters().includes(`${tag}${Doc.FilterSep}${Doc.FilterAny}${Doc.FilterSep}exists`);
             return (
-                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, dflt, 'exists', true, this.sidebarKey, e.shiftKey)}>
+                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
                     {tag}
                 </div>
             );
         };
         const renderUsers = (user: string) => {
-            const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`author:${user}:check`);
+            const active = this.childFilters().includes(`author:${user}:check`);
             return (
-                <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'author', user, 'check', true, this.sidebarKey, e.shiftKey)}>
+                <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'author', user, 'check', true, undefined, e.shiftKey)}>
                     {user}
                 </div>
             );
@@ -224,11 +222,11 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                     height: '100%',
                 }}>
                 <div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
-                    {this.allUsers.map(renderUsers)}
+                    {this.allUsers.length > 1 ? this.allUsers.map(renderUsers) : null}
                     {this.allHashtags.map(renderTag)}
-                    {/* {Array.from(this.allMetadata.keys())
+                    {Array.from(this.allMetadata.keys())
                         .sort()
-                        .map(key => renderMeta(key, this.allMetadata.get(key)))} */}
+                        .map(key => renderMeta(key, this.allMetadata.get(key)))}
                 </div>
 
                 <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 85aa5ad83..0a14b93f7 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -317,7 +317,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                                 "this view inherits filters from one of its parents"}
                             color={SettingsManager.userColor}
                             background={showFilterIcon}
-                            items={[...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])].map(dv => ({
+                            items={[ DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard)!, ...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])].map(dv => ({
                                 text: StrCast(dv.rootDoc.title),
                                 val: dv as any,
                                 style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor},
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9ba4cb6cf..8c40567d3 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -138,7 +138,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             // assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns
             const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
             // just getting the style
-            const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
+            const style = this.isStackingView ? { margin: this.rootDoc._stacking_alignCenter ? 'auto' : undefined, width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
             // So we're choosing whether we're going to render a column or a masonry doc
             return (
                 <div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}>
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 3aadeffcd..3598d548a 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -361,7 +361,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                             //TODO: would be great if there was additional space beyond the frame, so that you can actually see your
                             // bottom note
                             //TODO: ok, so we are using a single column, and this is it!
-                            <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" style={{ width: 'calc(100% - 25px)', maxWidth: this.props.columnWidth / this.props.numGroupColumns - 25, marginBottom: 10 }}>
+                            <div
+                                key={`${heading}-add-document`}
+                                onKeyDown={e => e.stopPropagation()}
+                                className="collectionStackingView-addDocumentButton"
+                                style={{ width: 'calc(100% - 25px)', maxWidth: this.props.columnWidth / this.props.numGroupColumns - 25, marginBottom: 10 }}>
                                 <EditableView
                                     GetValue={returnEmptyString}
                                     SetValue={this.addNewTextDoc}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7768ca547..5f65aa563 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -52,6 +52,7 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
 import React = require('react');
+import { FollowLinkScript } from '../../../util/LinkFollower';
 
 export type collectionFreeformViewProps = {
     NativeWidth?: () => number;
@@ -421,11 +422,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) {
             let added = false;
             //  do nothing if link is dropped into any freeform view parent of dragged document
-            if (!linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.rootDoc) {
-                const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
-                added = this.props.addDocument?.(source) ? true : false;
-                de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
-            }
+            const source =
+                !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.rootDoc
+                    ? Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' })
+                    : Docs.Create.FontIconDocument({
+                          title: 'anchor',
+                          icon_label: '',
+                          followLinkToggle: true,
+                          icon: 'map-pin',
+                          x: xp,
+                          y: yp,
+                          backgroundColor: '#ACCEF7',
+                          layout_hideAllLinks: true,
+                          layout_hideLinkButton: true,
+                          _width: 15,
+                          _height: 15,
+                          _xPadding: 0,
+                          onClick: FollowLinkScript(),
+                      });
+            added = this.props.addDocument?.(source) ? true : false;
+            de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
+
             e.stopPropagation();
             !added && e.preventDefault();
             return added;
@@ -1024,7 +1041,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     zoom = (pointX: number, pointY: number, deltaY: number): void => {
-        if (this.Document._isGroup || this.Document._freeform_noZoom) return;
+        if (this.Document._isGroup || this.Document[this.scaleFieldKey + '_noZoom']) return;
         let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
         if (deltaScale < 0) deltaScale = -deltaScale;
         const [x, y] = this.getTransform().transformPoint(pointX, pointY);
@@ -1032,12 +1049,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (deltaScale * invTransform.Scale > 20) {
             deltaScale = 20 / invTransform.Scale;
         }
-        if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) {
+        if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc[this.scaleFieldKey + '_min'])) {
             this.setPan(0, 0);
             return;
         }
-        if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) {
-            deltaScale = NumCast(this.rootDoc._freeform_scale_min, 1) / invTransform.Scale;
+        if (deltaScale * invTransform.Scale > NumCast(this.rootDoc[this.scaleFieldKey + '_max'], Number.MAX_VALUE)) {
+            deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_max'], 1) / invTransform.Scale;
+        }
+        if (deltaScale * invTransform.Scale < NumCast(this.rootDoc[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0)) {
+            deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_min'], 1) / invTransform.Scale;
         }
 
         const localTransform = invTransform.scaleAbout(deltaScale, x, y);
@@ -1527,12 +1547,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 });
             });
 
-        if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) {
-            // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
-            if (this.props.viewField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
-            else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey]));
-        }
-
         this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
         return elements;
     }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 9f8ef763a..76bd392a5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -193,6 +193,12 @@
     flex-direction: row;
     height: 100%;
     overflow: auto;
+
+    .schemaSelectionCell {
+        align-self: center;
+        width: 100%;
+        display: flex;
+    }
 }
 
 .schema-header-row > .schema-column-header:nth-child(2) > .left {
@@ -205,7 +211,7 @@
     overflow-x: hidden;
     overflow-y: auto;
     padding: 5px;
-    display: inline-block;
+    display: inline-flex;
 }
 
 .schema-row {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 5c7dcc1a4..d757d5349 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -836,6 +836,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                 <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }}></div>
                 <div
                     className="schema-table"
+                    style={{ width: `calc(100% - ${this.previewWidth + 4}px)` }}
                     onWheel={e => this.props.isContentActive() && e.stopPropagation()}
                     ref={r => {
                         // prevent wheel events from passively propagating up through containers
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 5b4fc34bb..4e418728f 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -16,7 +16,7 @@ import { CollectionSchemaView } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 import { SchemaTableCell } from './SchemaTableCell';
 import { Transform } from '../../../util/Transform';
-import { IconButton } from 'browndash-components';
+import { IconButton, Size } from 'browndash-components';
 import { CgClose } from 'react-icons/cg';
 import { FaExternalLinkAlt } from 'react-icons/fa';
 import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
@@ -117,6 +117,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     <IconButton
                         tooltip="close"
                         icon={<CgClose size={'16px'} />}
+                        size={Size.XSMALL}
                         onPointerDown={e =>
                             setupMoveUpEvents(
                                 this,
@@ -133,6 +134,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     <IconButton
                         tooltip="open preview"
                         icon={<FaExternalLinkAlt />}
+                        size={Size.XSMALL}
                         onPointerDown={e =>
                             setupMoveUpEvents(
                                 this,
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 1c9c0de53..e18f27fb0 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -325,10 +325,34 @@ export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps>
         const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
         const options = this.props.options?.map(facet => ({ value: facet, label: facet }));
         return (
-            <div className="schemaSelectionCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}>
+            <div className="schemaSelectionCell" style={{ color, textDecoration, cursor, pointerEvents }}>
                 <div style={{ width: '100%' }}>
                     <Select
                         styles={{
+                            container: base => ({
+                                ...base,
+                                height: 20,
+                            }),
+                            control: base => ({
+                                ...base,
+                                height: 20,
+                                minHeight: 20,
+                            }),
+                            placeholder: base => ({
+                                ...base,
+                                top: '40%',
+                            }),
+                            input: base => ({
+                                ...base,
+                                height: 20,
+                                minHeight: 20,
+                                margin: 0,
+                            }),
+                            indicatorsContainer: base => ({
+                                ...base,
+                                height: 20,
+                                transform: 'scale(0.75)',
+                            }),
                             menuPortal: base => ({
                                 ...base,
                                 left: 0,
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 9b9cc6d24..d8ed93bd7 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -85,10 +85,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'viewAllPersist', checkResult?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'viewAllPersist', checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'viewAllPersist', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
+    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'viewAllPersist', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
         ['grid', {
             checkResult: (doc:Doc) => BoolCast(doc._freeform_backgroundGrid, false),
             setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -101,6 +101,10 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snapli
             checkResult: (doc:Doc) => BoolCast(doc._freeform_fitContentsToBox, false),
             setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
         }],
+        ['center', {
+            checkResult: (doc:Doc) => BoolCast(doc._stacking_alignCenter, false),
+            setDoc: (doc:Doc) => doc._stacking_alignCenter = !doc._stacking_alignCenter,
+        }],
         ['viewAllPersist', {
             checkResult: (doc:Doc) => false,
             setDoc: (doc:Doc) => doc.fitContentOnce = true
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 6710cee63..4a438f826 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -40,7 +40,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         { key: 'opacity', val: 1 },
         { key: '_currentFrame' },
         { key: 'freeform_scale', val: 1 },
-        { key: 'freeform_scale', val: 1 },
         { key: 'freeform_panX' },
         { key: 'freeform_panY' },
     ]; // fields that are configured to be animatable using animation frames
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 9eaebfc8c..d2508e7cf 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -231,13 +231,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto['data_nativeWidth'] = anchw;
         croppingProto['data_nativeHeight'] = anchh;
         croppingProto.freeform_scale = viewScale;
-        croppingProto.freeform_scaleMin = viewScale;
+        croppingProto.freeform_scale_min = viewScale;
         croppingProto.freeform_panX = anchx / viewScale;
         croppingProto.freeform_panY = anchy / viewScale;
-        croppingProto.freeform_panXMin = anchx / viewScale;
-        croppingProto.freeform_panXMax = anchw / viewScale;
-        croppingProto.freeform_panYMin = anchy / viewScale;
-        croppingProto.freeform_panYMax = anchh / viewScale;
+        croppingProto.freeform_panX_min = anchx / viewScale;
+        croppingProto.freeform_panX_max = anchw / viewScale;
+        croppingProto.freeform_panY_min = anchy / viewScale;
+        croppingProto.freeform_panY_max = anchh / viewScale;
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
             cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[Width]();
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 6f9d96a33..5cec8fc24 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -438,7 +438,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
     };
     @undoBatch
-    toggleSidebarType = () => (this.rootDoc.sidebar_collectionType = this.rootDoc.sidebar_collectionType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
+    toggleSidebarType = () => (this.rootDoc[this.SidebarKey + '_type_collection'] = this.rootDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
     specificContextMenu = (e: React.MouseEvent): void => {
         const cm = ContextMenu.Instance;
         const options = cm.findByDescription('Options...');
@@ -554,7 +554,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         };
         return (
             <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: '100%', right: 0, backgroundColor: `white` }}>
-                {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))}
+                {renderComponent(StrCast(this.layoutDoc[this.SidebarKey + '_type_collection']))}
             </div>
         );
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 42cc14f9c..7eab1ea4e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -72,7 +72,9 @@ import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
 import { media_state } from '../AudioBox';
-const translateGoogleApi = require('translate-google-api');
+import { setCORS } from 'google-translate-api-browser';
+// setting up cors-anywhere server address
+const translate = setCORS('http://cors-anywhere.herokuapp.com/');
 export const GoogleRef = 'googleDocId';
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
@@ -94,6 +96,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     static _userStyleSheet: any = addStyleSheet();
     static _hadSelection: boolean = false;
     private _sidebarRef = React.createRef<SidebarAnnos>();
+    private _sidebarTagRef = React.createRef<React.Component>();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _editorView: Opt<EditorView>;
@@ -706,7 +709,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     toggleSidebar = (preview: boolean = false) => {
         const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', ''));
         if (preview) this._showSidebar = true;
-        else this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
+        else {
+            this.layoutDoc[this.SidebarKey + '_freeform_scale_max'] = 1;
+            this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
+        }
 
         this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
     };
@@ -1580,7 +1586,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     onPointerDown = (e: React.PointerEvent): void => {
         if ((e.nativeEvent as any).handledByInnerReactInstance) {
-            return e.stopPropagation();
+            return; //e.stopPropagation();
         } else (e.nativeEvent as any).handledByInnerReactInstance = true;
 
         if (this.Document.forceActive) e.stopPropagation();
@@ -1612,10 +1618,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         FormattedTextBoxComment.textBox = this;
         if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) {
             if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
-                // stop propagation if not in sidebar
-                // bcz: Change. drag selecting requires that preventDefault is NOT called.  This used to happen in DocumentView,
-                //      but that's changed, so this shouldn't be needed.
-                //e.stopPropagation();  // if the text box is selected, then it consumes all down events
+                // stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
+                e.stopPropagation(); // if the text box's content is active, then it consumes all down events
                 document.addEventListener('pointerup', this.onSelectEnd);
             }
         }
@@ -1777,13 +1781,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
         const state = this._editorView!.state;
         const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
-        if (this.layoutDoc.sidebar_collectionType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
+        if (this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
             try {
-                translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => {
+                translate(curText, { from: 'en', to: 'es' }).then((result1: any) => {
                     setTimeout(
                         () =>
-                            translateGoogleApi(result1[0], { from: 'es', to: 'en' }).then((result: any) => {
-                                this.dataDoc[this.fieldKey + '_translation'] = result1 + '\r\n\r\n' + result[0];
+                            translate(result1.text, { from: 'es', to: 'en' }).then((result: any) => {
+                                const tb = this._sidebarTagRef.current as FormattedTextBox;
+                                tb._editorView?.dispatch(tb._editorView!.state.tr.insertText(result1.text + '\r\n\r\n' + result.text));
                             }),
                         1000
                     );
@@ -1967,6 +1972,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
                     <ComponentTag
                         {...this.props}
+                        ref={this._sidebarTagRef as any}
                         setContentView={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
@@ -1989,14 +1995,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         fitContentsToBox={this.fitContentsToBox}
                         noSidebar={true}
                         treeViewHideTitle={true}
-                        fieldKey={this.layoutDoc.sidebar_collectionType === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
+                        fieldKey={this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
                     />
                 </div>
             );
         };
         return (
             <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))}
+                {renderComponent(StrCast(this.layoutDoc[this.SidebarKey + '_type_collection']))}
             </div>
         );
     }
@@ -2044,9 +2050,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     }
     @observable _isHovering = false;
     onPassiveWheel = (e: WheelEvent) => {
+        if (e.clientX > this.ProseRef!.getBoundingClientRect().right) {
+            if (this.rootDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform) {
+                // if the scrolled freeform is a child of the sidebar component, we need to let the event go through
+                // so react can let the freeform view handle it. We prevent default to stop any containing views from scrolling
+                e.preventDefault();
+            }
+            return;
+        }
+
         // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
         if (this.props.isContentActive() && !this.props.allowScroll) {
-            if (!NumCast(this.layoutDoc._layout_scrollTop) && e.deltaY <= 0) e.preventDefault();
+            // prevent default if selected || child is active  but this doc isn't scrollable
+            if (
+                (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil(Number(this.layoutDoc._height)) && //
+                (this.props.isSelected() || this.isAnyChildContentActive())
+            ) {
+                e.preventDefault();
+            }
             e.stopPropagation();
         }
     };
@@ -2120,7 +2141,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         className="formattedTextBox-outer"
                         ref={this._scrollRef}
                         style={{
-                            width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+                            width: this.props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
                             overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
                         }}
                         onScroll={this.onScroll}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index dd3f11444..a8e2440ca 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1513,6 +1513,7 @@ export namespace Doc {
     }
 
     export const FilterSep = '::';
+    export const FilterAny = '--any--';
 
     // filters document in a container collection:
     // all documents with the specified value for the specified key are included/excluded
-- 
cgit v1.2.3-70-g09d2

