From 9d6c7f8100de3a952d20ad41ab20872737cb909e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 27 Aug 2023 20:25:55 -0400
Subject: lots of cleanup to streamline import orderings (ie packages should
 not mutually import each other directly or via a chain).  change
 raiseWhenDragged to be keepZWhenDragged and got rid of system wide default.

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f5cc1eb53..16d6f1270 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -371,7 +371,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
             layoutDoc._width = NumCast(layoutDoc._width, 300);
             layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? (nd[1] / nd[0]) * NumCast(layoutDoc._width) : 300);
-            (d._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
+            !d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront
         }
 
         if (this.layoutDoc._autoArrange || de.metaKey) {
@@ -1267,7 +1267,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     @computed get _pointerEvents() {
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
-        const pointerEvents = DocumentDecorations.Instance.Interacting
+        const pointerEvents = DocumentView.Interacting
             ? 'none'
             : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.());
         return pointerEvents;
-- 
cgit v1.2.3-70-g09d2


From b5605e12c760af341516f5bc0b0fe4afb14a14ca Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 5 Sep 2023 11:11:11 -0400
Subject: change getAnchor to honor addAsAnnotation so that pinning docs
 doesn't add to annotation list.  fixed dragging anchor menu icons so that
 they appear.

---
 src/client/util/DocumentManager.ts                 |   5 +-
 src/client/views/DocComponent.tsx                  |   3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 src/client/views/nodes/DocumentView.tsx            |   1 +
 src/client/views/nodes/ImageBox.tsx                |   7 +-
 src/client/views/nodes/WebBox.tsx                  |   6 +-
 src/client/views/pdf/AnchorMenu.tsx                | 122 +++++----------------
 7 files changed, 39 insertions(+), 106 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index bfe0e1b48..b9f6059f4 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -11,7 +11,6 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie
 import { TabDocView } from '../views/collections/TabDocView';
 import { LightboxView } from '../views/LightboxView';
 import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
-import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
 import { KeyValueBox } from '../views/nodes/KeyValueBox';
 import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { PresBox } from '../views/nodes/trails';
@@ -320,7 +319,9 @@ export class DocumentManager {
     @action
     restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: DocFocusOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
         if (viewSpec && docView) {
-            if (docView.ComponentView instanceof FormattedTextBox) docView.ComponentView?.focus(viewSpec, options);
+            //if (docView.ComponentView instanceof FormattedTextBox)
+            //viewSpec !== docView.rootDoc &&
+            docView.ComponentView?.focus?.(viewSpec, options);
             PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
             Doc.linkFollowHighlight(viewSpec ? [docView.rootDoc, viewSpec] : docView.rootDoc, undefined, options.effect);
             if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.rootDoc._layout_currentTimecode));
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 6a1efdf9c..483b92957 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -139,7 +139,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             const indocs = doc instanceof Doc ? [doc] : doc;
             const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
 
-            docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
+            // docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
             const targetDataDoc = this.dataDoc;
             const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
             const toRemove = value.filter(v => docs.includes(v));
@@ -194,6 +194,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                     added.forEach(doc => {
                         doc._dragOnlyWithinContainer = undefined;
                         if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
+                        else Doc.GetProto(doc).annotationOn = undefined;
                         Doc.SetContainer(doc, this.rootDoc);
                         inheritParentAcls(targetDataDoc, doc, true);
                     });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 16d6f1270..7768ca547 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -311,6 +311,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     focus = (anchor: Doc, options: DocFocusOptions) => {
         if (this._lightboxDoc) return;
+        if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return;
         const xfToCollection = options?.docTransform ?? Transform.Identity();
         const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
         const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f7773ff18..ab99f3c6d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -121,6 +121,7 @@ export interface DocComponentView {
     reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
     shrinkWrap?: () => void; // requests a document to display all of its contents with no white space.  currently only implemented (needed?) for freeform views
     select?: (ctrlKey: boolean, shiftKey: boolean) => void;
+    focus?: (textAnchor: Doc, options: DocFocusOptions) => Opt<number>;
     menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
     isAnyChildContentActive?: () => boolean; // is any child content of the document active
     onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f5c6a9273..a19cc3555 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -85,8 +85,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     };
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+        const visibleAnchor = this._getAnchor?.(this._savedAnnotations, false); // use marquee anchor, otherwise, save zoom/pan as anchor
         const anchor =
-            this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor
+            visibleAnchor ??
             Docs.Create.ConfigDocument({
                 title: 'ImgAnchor:' + this.rootDoc.title,
                 config_panX: NumCast(this.layoutDoc._freeform_panX),
@@ -96,8 +97,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             });
         if (anchor) {
             if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
-            /* addAsAnnotation &&*/ this.addDocument(anchor);
-            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true } }, this.rootDoc);
+            (addAsAnnotation || visibleAnchor) && this.addDocument(anchor);
+            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: visibleAnchor ? false : true } }, this.rootDoc);
             return anchor;
         }
         return this.rootDoc;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f5db2ccfd..5f891e9a0 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -330,8 +330,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 ele.append(contents);
             }
         } catch (e) {}
+        const visibleAnchor = this._getAnchor(this._savedAnnotations, false);
         const anchor =
-            this._getAnchor(this._savedAnnotations, false) ??
+            visibleAnchor ??
             Docs.Create.ConfigDocument({
                 title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._layout_scrollTop),
                 y: NumCast(this.layoutDoc._layout_scrollTop),
@@ -340,8 +341,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.rootDoc);
         anchor.text = ele?.textContent ?? '';
         anchor.text_html = ele?.innerHTML;
-        //addAsAnnotation &&
-        this.addDocumentWrapper(anchor);
+        (addAsAnnotation || visibleAnchor) && this.addDocumentWrapper(anchor);
         return anchor;
     };
 
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 6a8b06377..a52250145 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -3,7 +3,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { EditorView } from 'prosemirror-view';
 import { ColorState } from 'react-color';
 import { Doc, Opt } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
@@ -15,68 +14,24 @@ import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
 import { LinkPopup } from '../linking/LinkPopup';
 import './AnchorMenu.scss';
 import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
+import { SettingsManager } from '../../util/SettingsManager';
 
 @observer
 export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     static Instance: AnchorMenu;
 
     private _disposer: IReactionDisposer | undefined;
-    private _disposer2: IReactionDisposer | undefined;
-    private _commentCont = React.createRef<HTMLButtonElement>();
-    private _palette = [
-        'rgba(208, 2, 27, 0.8)',
-        'rgba(238, 0, 0, 0.8)',
-        'rgba(245, 166, 35, 0.8)',
-        'rgba(248, 231, 28, 0.8)',
-        'rgba(245, 230, 95, 0.616)',
-        'rgba(139, 87, 42, 0.8)',
-        'rgba(126, 211, 33, 0.8)',
-        'rgba(65, 117, 5, 0.8)',
-        'rgba(144, 19, 254, 0.8)',
-        'rgba(238, 169, 184, 0.8)',
-        'rgba(224, 187, 228, 0.8)',
-        'rgba(225, 223, 211, 0.8)',
-        'rgba(255, 255, 255, 0.8)',
-        'rgba(155, 155, 155, 0.8)',
-        'rgba(0, 0, 0, 0.8)',
-    ];
+    private _commentRef = React.createRef<HTMLDivElement>();
+    private _cropRef = React.createRef<HTMLDivElement>();
 
     @observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
 
     @observable public Status: 'marquee' | 'annotation' | '' = '';
 
     // GPT additions
-    @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY;
     @observable private selectedText: string = '';
-    @observable private editorView?: EditorView;
-    @observable private textDoc?: Doc;
-    @observable private highlightRange: number[] | undefined;
-    private selectionRange: number[] | undefined;
-
-    @action
-    setGPTMode = (mode: GPTPopupMode) => {
-        this.GPTMode = mode;
-    };
-
     @action
-    setHighlightRange(r: number[] | undefined) {
-        this.highlightRange = r;
-    }
-
-    @action
-    public setSelectedText = (txt: string) => {
-        this.selectedText = txt;
-    };
-
-    @action
-    public setEditorView = (editor: EditorView) => {
-        this.editorView = editor;
-    };
-
-    @action
-    public setTextDoc = (textDoc: Doc) => {
-        this.textDoc = textDoc;
-    };
+    public setSelectedText = (txt: string) => (this.selectedText = txt);
 
     public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
 
@@ -105,20 +60,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     componentWillUnmount() {
         this._disposer?.();
-        this._disposer2?.();
     }
 
     componentDidMount() {
-        this._disposer2 = reaction(
-            () => this._opacity,
-            opacity => {},
-            { fireImmediately: true }
-        );
         this._disposer = reaction(
             () => SelectionManager.Views().slice(),
-            selected => {
-                AnchorMenu.Instance.fadeOut(true);
-            }
+            selected => AnchorMenu.Instance.fadeOut(true)
         );
     }
 
@@ -129,17 +76,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     gptSummarize = async (e: React.PointerEvent) => {
         // move this logic to gptpopup, need to implement generate again
         GPTPopup.Instance.setVisible(true);
-        this.setHighlightRange(undefined);
         GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
         GPTPopup.Instance.setLoading(true);
 
         try {
             const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
-            if (res) {
-                GPTPopup.Instance.setText(res);
-            } else {
-                GPTPopup.Instance.setText('Something went wrong.');
-            }
+            GPTPopup.Instance.setText(res || 'Something went wrong.');
         } catch (err) {
             console.error(err);
         }
@@ -151,7 +93,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             this,
             e,
             (e: PointerEvent) => {
-                this.StartDrag(e, this._commentCont.current!);
+                this.StartDrag(e, this._commentRef.current!);
                 return true;
             },
             returnFalse,
@@ -168,7 +110,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             this,
             e,
             (e: PointerEvent) => {
-                this.StartCropDrag(e, this._commentCont.current!);
+                this.StartCropDrag(e, this._cropRef.current!);
                 return true;
             },
             returnFalse,
@@ -214,37 +156,21 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * all selected text available to summarize but its only supported for pdf and web ATM.
      * @returns Whether the GPT icon for summarization should appear
      */
-    canSummarize = (): boolean => {
-        const docs = SelectionManager.Docs();
-        if (docs.length > 0) {
-            return docs.some(doc => doc.type === DocumentType.PDF || doc.type === DocumentType.WEB);
-        }
-        return false;
-    };
-
-    /**
-     * Returns whether the selected text can be edited.
-     * @returns Whether the GPT icon for summarization should appear
-     */
-    canEdit = (): boolean => {
-        const docs = SelectionManager.Docs();
-        if (docs.length > 0) {
-            return docs.some(doc => doc.type === 'rtf');
-        }
-        return false;
-    };
+    canSummarize = () => SelectionManager.Docs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
 
     render() {
         const buttons =
             this.Status === 'marquee' ? (
                 <>
                     {this.highlighter}
-                    <IconButton
-                        tooltip="Drag to Place Annotation" //
-                        onPointerDown={this.pointerDown}
-                        icon={<FontAwesomeIcon icon="comment-alt" />}
-                        color={StrCast(Doc.UserDoc().userColor)}
-                    />
+                    <div ref={this._commentRef}>
+                        <IconButton
+                            tooltip="Drag to Place Annotation" //
+                            onPointerDown={this.pointerDown}
+                            icon={<FontAwesomeIcon icon="comment-alt" />}
+                            color={SettingsManager.userColor}
+                        />
+                    </div>
                     {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
                     {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
                         <IconButton
@@ -270,12 +196,14 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         color={StrCast(Doc.UserDoc().userColor)}
                     />
                     {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
-                        <IconButton
-                            tooltip="Click/Drag to create cropped image" //
-                            onPointerDown={this.cropDown}
-                            icon={<FontAwesomeIcon icon="image" />}
-                            color={StrCast(Doc.UserDoc().userColor)}
-                        />
+                        <div ref={this._cropRef}>
+                            <IconButton
+                                tooltip="Click/Drag to create cropped image" //
+                                onPointerDown={this.cropDown}
+                                icon={<FontAwesomeIcon icon="image" />}
+                                color={StrCast(Doc.UserDoc().userColor)}
+                            />
+                        </div>
                     )}
                 </>
             ) : (
-- 
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/collectionFreeForm/CollectionFreeFormView.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


From a41fd88ba86a7f30e460f7e957080970d1429677 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 13 Sep 2023 10:50:26 -0400
Subject: fixed explore mode when restoring zoom level of freeform overlays

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5f65aa563..86b2a3e43 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -98,7 +98,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     private _brushtimer: any;
     private _brushtimer1: any;
 
-    private get isAnnotationOverlay() {
+    public get isAnnotationOverlay() {
         return this.props.isAnnotationOverlay;
     }
     public get scaleFieldKey() {
@@ -2300,7 +2300,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
             }
             while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
             const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
-            ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime);
+            ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
             Doc.linkFollowHighlight(dv?.props.Document, false);
         }
     });
-- 
cgit v1.2.3-70-g09d2


From d6172adc218cc8ebb3a545cd6cdf8cc4f2a36e98 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 13 Sep 2023 12:06:34 -0400
Subject: prevent piles from zooming.

---
 src/client/documents/Documents.ts                                       | 2 +-
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index dbd607dd4..055950e6f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1723,7 +1723,7 @@ export namespace DocUtils {
             });
         });
         if (create) {
-            const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true, _layout_fitWidth: false });
+            const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', _freeform_noZoom: true, x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true, _layout_fitWidth: false });
             newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
             newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
             newCollection._width = newCollection._height = size * 2;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 86b2a3e43..91669cf70 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1041,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[this.scaleFieldKey + '_noZoom']) return;
+        if (this.Document._isGroup || this.Document[(this.props.viewField ?? '_') + 'freeform_noZoom']) return;
         let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
         if (deltaScale < 0) deltaScale = -deltaScale;
         const [x, y] = this.getTransform().transformPoint(pointX, pointY);
-- 
cgit v1.2.3-70-g09d2


From 542f641ccd026c99912e864b47186f189410b280 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 13 Sep 2023 12:29:19 -0400
Subject: fixed region highlight when dragging minimap

---
 src/client/views/collections/TabDocView.tsx        | 53 ++++++++++++----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 2 files changed, 30 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index d9faab063..dab53b671 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -502,6 +502,18 @@ interface TabMinimapViewProps {
     PanelHeight: () => number;
     background: () => string;
 }
+interface TabMiniThumbProps {
+    miniWidth: () => number;
+    miniHeight: () => number;
+    miniTop: () => number;
+    miniLeft: () => number;
+}
+@observer
+class TabMiniThumb extends React.Component<TabMiniThumbProps> {
+    render() {
+        return <div className="miniThumb" style={{ width: `${this.props.miniWidth()}% `, height: `${this.props.miniHeight()}% `, left: `${this.props.miniLeft()}% `, top: `${this.props.miniTop()}% ` }} />;
+    }
+}
 @observer
 export class TabMinimapView extends React.Component<TabMinimapViewProps> {
     static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
@@ -513,25 +525,17 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     return 'none';
                 case StyleProp.DocContents:
                     const background = ((type: DocumentType) => {
+                        // prettier-ignore
                         switch (type) {
-                            case DocumentType.PDF:
-                                return 'pink';
-                            case DocumentType.AUDIO:
-                                return 'lightgreen';
-                            case DocumentType.WEB:
-                                return 'brown';
-                            case DocumentType.IMG:
-                                return 'blue';
-                            case DocumentType.MAP:
-                                return 'orange';
-                            case DocumentType.VID:
-                                return 'purple';
-                            case DocumentType.RTF:
-                                return 'yellow';
-                            case DocumentType.COL:
-                                return undefined;
-                            default:
-                                return 'gray';
+                            case DocumentType.PDF:    return 'pink';
+                            case DocumentType.AUDIO:  return 'lightgreen';
+                            case DocumentType.WEB:    return 'brown';
+                            case DocumentType.IMG:    return 'blue';
+                            case DocumentType.MAP:    return 'orange';
+                            case DocumentType.VID:    return 'purple';
+                            case DocumentType.RTF:    return 'yellow';
+                            case DocumentType.COL:    return undefined;
+                            default:                  return 'gray';
                         }
                     })(doc.type as DocumentType);
                     return !background ? undefined : <div style={{ width: doc[Width](), height: doc[Height](), position: 'absolute', display: 'block', background }} />;
@@ -551,13 +555,13 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
     returnMiniSize = () => NumCast(this.props.document._miniMapSize, 150);
     miniDown = (e: React.PointerEvent) => {
         const doc = this.props.document;
-        const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
         const miniSize = this.returnMiniSize();
         doc &&
             setupMoveUpEvents(
                 this,
                 e,
                 action((e: PointerEvent, down: number[], delta: number[]) => {
+                    const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
                     doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
                     doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
                     return false;
@@ -568,10 +572,11 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
     };
     popup = () => {
         if (!this.renderBounds) return <></>;
-        const miniWidth = (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100;
-        const miniHeight = (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100;
-        const miniLeft = 50 + ((NumCast(this.props.document._freeform_panX) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2;
-        const miniTop = 50 + ((NumCast(this.props.document._freeform_panY) - this.renderBounds.cy) / this.renderBounds.dim) * 100 - miniHeight / 2;
+        const renderBounds = this.renderBounds;
+        const miniWidth = () => (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+        const miniHeight = () => (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+        const miniLeft = () => 50 + ((NumCast(this.props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
+        const miniTop = () => 50 + ((NumCast(this.props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
         const miniSize = this.returnMiniSize();
         return (
             <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
@@ -607,7 +612,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     fitContentsToBox={returnTrue}
                 />
                 <div className="miniOverlay" onPointerDown={this.miniDown}>
-                    <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% ` }} />
+                    <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
                 </div>
             </div>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 91669cf70..3a4719854 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -156,7 +156,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
             : this.props.contentBounds?.() ??
                   aggregateBounds(
-                      this._layoutElements.filter(e => e.bounds && e.bounds.width && !e.bounds.z).map(e => e.bounds!),
+                      this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
                       NumCast(this.layoutDoc._xPadding, 10),
                       NumCast(this.layoutDoc._yPadding, 10)
                   );
-- 
cgit v1.2.3-70-g09d2


From 3929187a6a9ed4031fded51d9c652f705b0ef499 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 13 Sep 2023 16:44:26 -0400
Subject: made grid and snap lines appear on dark backgrounds.  fixed showing
 snap lines based on what's dragged not what's selected.

---
 src/client/views/MainView.tsx                                | 12 ++++++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx            | 10 ++++++++--
 2 files changed, 16 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2b4f4d5f7..78f6038f9 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -10,13 +10,14 @@ import 'normalize.css';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { DocCast, StrCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
+import { emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { CaptureManager } from '../util/CaptureManager';
 import { DocumentManager } from '../util/DocumentManager';
+import { DragManager } from '../util/DragManager';
 import { GroupManager } from '../util/GroupManager';
 import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
@@ -911,14 +912,17 @@ export class MainView extends React.Component {
         );
     }
     @computed get snapLines() {
-        return !SelectionManager.Views().some(dv => dv.rootDoc.freeform_snapLines) ? null : (
+        SnappingManager.GetIsDragging();
+        const dragged = DragManager.docsBeingDragged[0];
+        const dragPar = dragged ? DocumentManager.Instance.getDocumentView(dragged)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView : undefined;
+        return !dragPar?.rootDoc.freeform_snapLines ? null : (
             <div className="mainView-snapLines">
                 <svg style={{ width: '100%', height: '100%' }}>
                     {SnappingManager.horizSnapLines().map((l, i) => (
-                        <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
+                        <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.rootDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
                     ))}
                     {SnappingManager.vertSnapLines().map((l, i) => (
-                        <line key={i} y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
+                        <line key={i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.rootDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
                     ))}
                 </svg>
             </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3a4719854..8a812c671 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -16,7 +16,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
 import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, lightOrDark, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -1900,6 +1900,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     showPresPaths = () => (CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.getPaths(this.rootDoc) : null);
 
     brushedView = () => this._brushedView;
+    gridColor = () => {
+        const backColor = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
+        return lightOrDark(backColor);
+    };
     @computed get marqueeView() {
         TraceMobx();
         return (
@@ -1932,6 +1936,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                                 PanelHeight={this.props.PanelHeight}
                                 panX={this.panX}
                                 panY={this.panY}
+                                color={this.gridColor}
                                 nativeDimScaling={this.nativeDim}
                                 zoomScaling={this.zoomScaling}
                                 layoutDoc={this.layoutDoc}
@@ -2231,6 +2236,7 @@ interface CollectionFreeFormViewBackgroundGridProps {
     panY: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
+    color: () => string;
     isAnnotationOverlay?: boolean;
     nativeDimScaling: () => number;
     zoomScaling: () => number;
@@ -2252,7 +2258,7 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
         const renderGridSpace = gridSpace * this.props.zoomScaling();
         const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
         const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
-        const strokeStyle = Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)';
+        const strokeStyle = this.props.color();
         return !this.props.nativeDimScaling() ? null : (
             <canvas
                 className="collectionFreeFormView-grid"
-- 
cgit v1.2.3-70-g09d2


From 34f3bab0f4d55b2f55f38e451bfd024994ecff42 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 20 Sep 2023 13:20:53 -0400
Subject: added carousel3D to novice and fixed it's button positions and its
 focus so that it works with trails. added dragging documents within single
 collection for rearranging order of docs in a tab collection.  fixed freeform
 views set to fitWidth to show decorations properly. turned off scaling of
 tree views.  updated lightbox buttons.  fixed tooltips on properties toggles.

---
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/util/DragManager.ts                     |   2 +-
 src/client/views/LightboxView.scss                 |  11 +--
 src/client/views/LightboxView.tsx                  | 106 +++++++++++++--------
 src/client/views/PropertiesButtons.tsx             |  12 ++-
 .../views/collections/CollectionCarousel3DView.tsx |  17 +++-
 src/client/views/collections/CollectionSubView.tsx |   5 +-
 .../views/collections/CollectionTreeView.tsx       |   4 +-
 src/client/views/collections/TreeView.tsx          |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   9 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |   2 +-
 11 files changed, 109 insertions(+), 69 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 259c028cc..2ea5972ee 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -492,8 +492,8 @@ export class CurrentUserUtils {
         const childContextMenuIcons = ["tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
         const reqdOpts:DocumentOptions = {
             title: "My Dashboards", childHideLinkButton: true, treeView_FreezeChildren: "remove|add", treeView_HideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
-            dropAction: "same", treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true,  treeView_TruncateTitleWidth: 350, ignoreClick: true, 
-            layout_headerButton: newDashboardButton, childDragAction: "none",
+            dropAction: "inSame", treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true,  treeView_TruncateTitleWidth: 350, ignoreClick: true, 
+            layout_headerButton: newDashboardButton, childDragAction: "inSame",
             _layout_showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
             contextMenuLabels:new List<string>(contextMenuLabels),
             contextMenuIcons:new List<string>(contextMenuIcons),
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ed22e70bd..f86f9a3e5 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -14,7 +14,7 @@ import { SelectionManager } from './SelectionManager';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
 
-export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
+export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
 
 /**
  * Initialize drag
diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss
index f86a1d211..9a9b8a437 100644
--- a/src/client/views/LightboxView.scss
+++ b/src/client/views/LightboxView.scss
@@ -5,7 +5,6 @@
     top: 10;
     background: transparent;
     border-radius: 8;
-    color: white;
     opacity: 0.7;
     width: 25;
     flex-direction: column;
@@ -17,11 +16,10 @@
 .lightboxView-tabBtn {
     margin: auto;
     position: absolute;
-    right: 38;
+    right: 45;
     top: 10;
     background: transparent;
     border-radius: 8;
-    color: white;
     opacity: 0.7;
     width: 25;
     flex-direction: column;
@@ -33,11 +31,10 @@
 .lightboxView-penBtn {
     margin: auto;
     position: absolute;
-    right: 70;
+    right: 80;
     top: 10;
     background: transparent;
     border-radius: 8;
-    color: white;
     opacity: 0.7;
     width: 25;
     flex-direction: column;
@@ -49,11 +46,10 @@
 .lightboxView-exploreBtn {
     margin: auto;
     position: absolute;
-    right: 100;
+    right: 115;
     top: 10;
     background: transparent;
     border-radius: 8;
-    color: white;
     opacity: 0.7;
     width: 25;
     flex-direction: column;
@@ -68,7 +64,6 @@
     left: 0;
     width: 100%;
     height: 100%;
-    background: #000000bb;
     z-index: 1000;
     .lightboxView-contents {
         position: absolute;
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 8f081b321..93eaec959 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,15 +1,17 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { InkTool } from '../../fields/InkField';
-import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils';
 import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
+import { SettingsManager } from '../util/SettingsManager';
 import { Transform } from '../util/Transform';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
@@ -118,7 +120,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
                     bottom,
                 }}>
-                <div className="lightboxView-navBtn" title={color} style={{ top, color: color ? 'red' : 'white', background: color ? 'white' : undefined }} onClick={click}>
+                <div className="lightboxView-navBtn" title={color} style={{ top, color: SettingsManager.userColor, background: undefined }} onClick={click}>
                     <div style={{ height: 10 }}>{color}</div>
                     <FontAwesomeIcon icon={icon as any} size="3x" />
                 </div>
@@ -229,6 +231,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     downx = e.clientX;
                     downy = e.clientY;
                 }}
+                style={{ background: SettingsManager.userBackgroundColor }}
                 onClick={e => {
                     if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) {
                         LightboxView.SetLightboxDoc(undefined);
@@ -242,6 +245,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         width: this.lightboxWidth(),
                         height: this.lightboxHeight(),
                         clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
+                        background: SettingsManager.userBackgroundColor,
+                        color: SettingsManager.userColor,
                     }}>
                     {/* <CollectionMenu /> TODO:glr This is where it would go*/}
 
@@ -299,47 +304,68 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     this.future()?.length.toString()
                 )}
                 <LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} />
-                <div
-                    className="lightboxView-navBtn"
-                    title="toggle fit width"
-                    onClick={e => {
-                        e.stopPropagation();
-                        LightboxView.LightboxDoc!._layout_fitWidth = !LightboxView.LightboxDoc!._layout_fitWidth;
-                    }}>
-                    <FontAwesomeIcon icon={LightboxView.LightboxDoc?._layout_fitWidth ? 'arrows-alt-h' : 'arrows-alt-v'} size="2x" />
+                <div className="lightboxView-navBtn">
+                    <Toggle
+                        tooltip="toggle reading view"
+                        color={SettingsManager.userColor}
+                        background={BoolCast(LightboxView.LightboxDoc!._layout_fitWidth) ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
+                        toggleType={ToggleType.BUTTON}
+                        type={Type.TERT}
+                        toggleStatus={BoolCast(LightboxView.LightboxDoc!._layout_fitWidth)}
+                        onClick={e => {
+                            e.stopPropagation();
+                            LightboxView.LightboxDoc!._layout_fitWidth = !LightboxView.LightboxDoc!._layout_fitWidth;
+                        }}
+                        icon={<FontAwesomeIcon icon={LightboxView.LightboxDoc?._layout_fitWidth ? 'book-open' : 'book'} size="sm" />}
+                    />
                 </div>
-                <div
-                    className="lightboxView-tabBtn"
-                    title="open in tab"
-                    onClick={e => {
-                        const lightdoc = LightboxView._docTarget || LightboxView._doc!;
-                        e.stopPropagation();
-                        Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightdoc);
-                        CollectionDockingView.AddSplit(lightdoc, OpenWhereMod.none);
-                        SelectionManager.DeselectAll();
-                        LightboxView.SetLightboxDoc(undefined);
-                    }}>
-                    <FontAwesomeIcon icon={'file-download'} size="2x" />
+                <div className="lightboxView-tabBtn">
+                    <Toggle
+                        tooltip="open document in a tab"
+                        color={SettingsManager.userColor}
+                        background={SettingsManager.userBackgroundColor}
+                        toggleType={ToggleType.BUTTON}
+                        type={Type.TERT}
+                        icon={<FontAwesomeIcon icon="file-download" size="sm" />}
+                        onClick={e => {
+                            const lightdoc = LightboxView._docTarget || LightboxView._doc!;
+                            e.stopPropagation();
+                            Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightdoc);
+                            CollectionDockingView.AddSplit(lightdoc, OpenWhereMod.none);
+                            SelectionManager.DeselectAll();
+                            LightboxView.SetLightboxDoc(undefined);
+                        }}
+                    />
                 </div>
-                <div
-                    className="lightboxView-penBtn"
-                    title="toggle pen annotation"
-                    style={{ background: Doc.ActiveTool === InkTool.Pen ? 'white' : undefined }}
-                    onClick={e => {
-                        e.stopPropagation();
-                        Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
-                    }}>
-                    <FontAwesomeIcon color={Doc.ActiveTool === InkTool.Pen ? 'black' : 'white'} icon={'pen'} size="2x" />
+                <div className="lightboxView-penBtn">
+                    <Toggle
+                        tooltip="toggle pen annotation"
+                        color={SettingsManager.userColor}
+                        background={Doc.ActiveTool === InkTool.Pen ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
+                        toggleType={ToggleType.BUTTON}
+                        toggleStatus={Doc.ActiveTool === InkTool.Pen}
+                        type={Type.TERT}
+                        icon={<FontAwesomeIcon icon="pen" size="sm" />}
+                        onClick={e => {
+                            e.stopPropagation();
+                            Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
+                        }}
+                    />
                 </div>
-                <div
-                    className="lightboxView-exploreBtn"
-                    title="toggle explore mode to navigate among documents only"
-                    style={{ background: DocumentView.ExploreMode ? 'white' : undefined }}
-                    onClick={action(e => {
-                        e.stopPropagation();
-                        DocumentView.ExploreMode = !DocumentView.ExploreMode;
-                    })}>
-                    <FontAwesomeIcon color={DocumentView.ExploreMode ? 'black' : 'white'} icon={'globe-americas'} size="2x" />
+                <div className="lightboxView-exploreBtn">
+                    <Toggle
+                        tooltip="toggle explore mode to navigate among documents only"
+                        color={SettingsManager.userColor}
+                        background={DocumentView.ExploreMode ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
+                        toggleType={ToggleType.BUTTON}
+                        type={Type.TERT}
+                        toggleStatus={DocumentView.ExploreMode}
+                        icon={<FontAwesomeIcon icon="globe-americas" size="sm" />}
+                        onClick={action(e => {
+                            e.stopPropagation();
+                            DocumentView.ExploreMode = !DocumentView.ExploreMode;
+                        })}
+                    />
                 </div>
             </div>
         );
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 40d42a4de..d1561fd67 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -57,6 +57,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return !targetDoc ? null : (
             <Toggle
                 toggleStatus={BoolCast(targetDoc[property])}
+                tooltip={tooltip(BoolCast(targetDoc[property]))}
                 text={label(targetDoc?.[property])}
                 color={SettingsManager.userColor}
                 icon={icon(targetDoc?.[property] as any)}
@@ -165,9 +166,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @computed get forceActiveButton() {
         //select text
         return this.propertyToggleBtn(
-            on => (on ? 'INACTIVE INTERACTION' : 'ACTIVE INTERACTION'),
+            on => (on ? 'SELECT TO INTERACT' : 'ALWAYS INTERACTIVE'),
             '_forceActive',
-            on => `${on ? 'Select to activate' : 'Contents always active'} `,
+            on => `${on ? 'Document must be selected to interact with its contents' : 'Contents always active (respond to click/drag events)'} `,
             on => <MdTouchApp /> // 'eye'
         );
     }
@@ -210,9 +211,12 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     @computed get layout_fitWidthButton() {
         return this.propertyToggleBtn(
-            on => (on ? 'RESTRICT WIDTH' : 'FIT WIDTH'), //'Fit\xA0Width',
+            on => (on ? 'SCALED VIEW' : 'READING VIEW'), //'Fit\xA0Width',
             '_layout_fitWidth',
-            on => `${on ? "Don't" : 'Do'} fit content to width of container`,
+            on =>
+                on
+                    ? "Scale document so it's width and height fit container (no effect when document is viewed on freeform canvas)"
+                    : "Scale document so it's width fits container and its height expands/contracts to fit available space  (no effect when document is viewed on freeform canvas)",
             on => (on ? <AiOutlineColumnWidth /> : <RxWidth />) // 'arrows-alt-h'
         );
     }
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index d94e552b4..cd59a91a1 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -2,14 +2,15 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
+import { Doc, DocListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { returnFalse, returnZero, Utils } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss';
-import { DocumentView } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
 import { StyleProp } from '../StyleProvider';
 import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
@@ -46,6 +47,15 @@ export class CollectionCarousel3DView extends CollectionSubView() {
             .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this.props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
             .scale(1 / this.centerScale);
 
+    focus = (anchor: Doc, options: DocFocusOptions) => {
+        const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
+        if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
+        options.didMove = true;
+        const target = DocCast(anchor.annotationOn) ?? anchor;
+        const index = docs.indexOf(target);
+        index !== -1 && (this.layoutDoc._carousel_index = index);
+        return undefined;
+    };
     @computed get content() {
         const currentIndex = NumCast(this.layoutDoc._carousel_index);
         const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
@@ -61,6 +71,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
                     LayoutTemplateString={this.props.childLayoutString}
                     Document={childPair.layout}
                     DataDoc={childPair.data}
+                    focus={this.focus}
                     ScreenToLocalTransform={this.childScreenToLocal}
                     isContentActive={this.isChildContentActive}
                     isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 54a60271a..26272d2ee 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -210,11 +210,12 @@ export function CollectionSubView<X>(moreProps?: X) {
                 const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]);
                 const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
                 if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop));
-                if ((!dropAction || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) {
+                if ((!dropAction || dropAction === 'inSame' || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) {
                     const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
                     const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
                     if (movedDocs.length) {
-                        const canAdd = de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc);
+                        const canAdd =
+                            (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.rootDoc));
                         const moved = docDragData.moveDocument(movedDocs, this.rootDoc, canAdd ? this.addDocument : returnFalse);
                         added = canAdd || moved ? moved : undefined;
                     } else {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index eed04b3ee..9e5ac77d9 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -149,7 +149,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             if (isAlreadyInTree() !== sameTree) {
                 console.log('WHAAAT');
             }
-            dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree ? 'same' : dragData.dropAction;
+            dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree && dragData.dropAction !== 'inSame' ? 'same' : dragData.dropAction;
             e.stopPropagation();
         }
     };
@@ -438,7 +438,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     render() {
         TraceMobx();
 
-        const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1) || 1;
+        const scale = this.props.NativeDimScaling?.() || 1;
         return (
             <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
                 {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeView_HasOverlay ? (
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index cd526c77e..f89aa065b 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -433,9 +433,9 @@ export class TreeView extends React.Component<TreeViewProps> {
             return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
         };
         const addDoc = inside ? localAdd : parentAddDoc;
-        const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument;
+        const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument;
         const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd;
-        if (canAdd) {
+        if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this.props.parentTreeView?.doc))) {
             this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
             const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false);
             this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = false);
@@ -598,7 +598,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 }
                 const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
                 const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
-                !dataIsComputed && added && Doc.SetContainer(doc, DocCast(this.doc.embedContainer));
+                !dataIsComputed && added && Doc.SetContainer(doc, this.doc);
 
                 return added;
             };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8a812c671..9df96fabc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -33,7 +33,6 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss';
 import { Timeline } from '../../animationtimeline/Timeline';
 import { ContextMenu } from '../../ContextMenu';
-import { DocumentDecorations } from '../../DocumentDecorations';
 import { GestureOverlay } from '../../GestureOverlay';
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
@@ -187,7 +186,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     }
     @computed get cachedGetTransform(): Transform {
-        return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
+        return this.getContainerTransform()
+            .scale(this.props.isAnnotationOverlay ? 1 : 1 / this.nativeDim())
+            .translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY)
+            .transform(this.cachedGetLocalTransform);
     }
 
     public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
@@ -862,7 +864,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 // create a new curve by appending all curves of the current segment together in order to render a single new stroke.
                 if (!e.shiftKey) {
                     this._eraserLock++;
-                    this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
+                    const segments = this.segmentInkStroke(intersect.inkView, intersect.t);
+                    segments.forEach(segment =>
                         this.forceStrokeGesture(
                             e,
                             GestureUtils.Gestures.Stroke,
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 394108be4..14a3d16ef 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -226,7 +226,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             } else {
                 return <Button text="None Selected" type={Type.TERT} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} fillWidth inactive />;
             }
-            noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
+            noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Carousel3D, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
         } else {
             text = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result;
             // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
-- 
cgit v1.2.3-70-g09d2

