From af2a2c83868c87812e9ae54c8e3cced81374619a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 27 Feb 2023 15:08:45 -0500
Subject: restructured getAnchor()/scrollFocus to be more consistent.  added
 setterscript for computedFields. restructed getFieldsImpl to avoid making
 multiple requests for the same document due to timing issues by 'locking' a
 document cache with a promise before sending the server request.  added
 rotation and fill color as animatable fields.  fixed image cropping for

---
 .../collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx  | 2 ++
 1 file changed, 2 insertions(+)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 7dd9cdb8b..fa0695fb2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -14,6 +14,7 @@ export interface ViewDefBounds {
     x: number;
     y: number;
     z?: number;
+    rotation?: number;
     text?: string;
     zIndex?: number;
     width?: number;
@@ -31,6 +32,7 @@ export interface PoolData {
     x: number;
     y: number;
     z?: number;
+    rotation?: number;
     zIndex?: number;
     width?: number;
     height?: number;
-- 
cgit v1.2.3-70-g09d2


From 3a3e496dda5f9a1f5286a2c9f62524de59794ade Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 24 Mar 2023 03:37:41 -0400
Subject: overhaul of selection api so that schema and other views behave like
 freeform and use document views onClick for selection

---
 src/client/util/SelectionManager.ts                |  45 +--
 .../views/collections/CollectionCarousel3DView.tsx | 149 +++++-----
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   7 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   9 +-
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |   2 -
 .../CollectionMulticolumnView.tsx                  |   3 +-
 .../CollectionMultirowView.tsx                     |   3 +-
 .../collectionSchema/CollectionSchemaView.tsx      | 318 +++++++++------------
 .../collections/collectionSchema/SchemaRowBox.tsx  |  31 +-
 .../nodes/CollectionFreeFormDocumentView.scss      |   4 +-
 src/client/views/nodes/DocumentView.tsx            |  18 +-
 12 files changed, 282 insertions(+), 309 deletions(-)

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

diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 0f4f77588..313c255a0 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,3 +1,4 @@
+import { ModalManager } from '@material-ui/core';
 import { action, observable, ObservableMap } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { Doc, Opt } from '../../fields/Doc';
@@ -10,7 +11,8 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 export namespace SelectionManager {
     class Manager {
         @observable IsDragging: boolean = false;
-        SelectedViews: ObservableMap<DocumentView, Doc> = new ObservableMap();
+        SelectedViewsMap: ObservableMap<DocumentView, Doc> = new ObservableMap();
+        @observable SelectedViews: DocumentView[] = [];
         @observable SelectedSchemaDocument: Doc | undefined;
 
         @action
@@ -20,7 +22,7 @@ export namespace SelectionManager {
         @action
         SelectView(docView: DocumentView, ctrlPressed: boolean): void {
             // if doc is not in SelectedDocuments, add it
-            if (!manager.SelectedViews.get(docView) && docView.props.Document.type !== DocumentType.MARKER) {
+            if (!manager.SelectedViewsMap.get(docView) && docView.props.Document.type !== DocumentType.MARKER) {
                 if (!ctrlPressed) {
                     if (LinkManager.currentLink && !LinkManager.Links(docView.rootDoc).includes(LinkManager.currentLink) && docView.rootDoc !== LinkManager.currentLink) {
                         LinkManager.currentLink = undefined;
@@ -28,33 +30,38 @@ export namespace SelectionManager {
                     this.DeselectAll();
                 }
 
-                manager.SelectedViews.set(docView, docView.rootDoc);
+                manager.SelectedViews.push(docView);
+                manager.SelectedViewsMap.set(docView, docView.rootDoc);
                 docView.props.whenChildContentsActiveChanged(true);
-            } else if (!ctrlPressed && (Array.from(manager.SelectedViews.entries()).length > 1 || manager.SelectedSchemaDocument)) {
-                Array.from(manager.SelectedViews.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false));
+            } else if (!ctrlPressed && (Array.from(manager.SelectedViewsMap.entries()).length > 1 || manager.SelectedSchemaDocument)) {
+                Array.from(manager.SelectedViewsMap.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false));
                 manager.SelectedSchemaDocument = undefined;
-                manager.SelectedViews.clear();
-                manager.SelectedViews.set(docView, docView.rootDoc);
+                manager.SelectedViews.length = 0;
+                manager.SelectedViewsMap.clear();
+                manager.SelectedViews.push(docView);
+                manager.SelectedViewsMap.set(docView, docView.rootDoc);
             }
         }
         @action
-        DeselectView(docView: DocumentView): void {
-            if (manager.SelectedViews.get(docView)) {
-                manager.SelectedViews.delete(docView);
+        DeselectView(docView?: DocumentView): void {
+            if (docView && manager.SelectedViewsMap.get(docView)) {
+                manager.SelectedViewsMap.delete(docView);
+                manager.SelectedViews.splice(manager.SelectedViews.indexOf(docView), 1);
                 docView.props.whenChildContentsActiveChanged(false);
             }
         }
         @action
         DeselectAll(): void {
             manager.SelectedSchemaDocument = undefined;
-            Array.from(manager.SelectedViews.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false));
-            manager.SelectedViews.clear();
+            Array.from(manager.SelectedViewsMap.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false));
+            manager.SelectedViewsMap.clear();
+            manager.SelectedViews.length = 0;
         }
     }
 
     const manager = new Manager();
 
-    export function DeselectView(docView: DocumentView): void {
+    export function DeselectView(docView?: DocumentView): void {
         manager.DeselectView(docView);
     }
     export function SelectView(docView: DocumentView, ctrlPressed: boolean): void {
@@ -67,7 +74,7 @@ export namespace SelectionManager {
 
     const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) {
         // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
-        return manager.SelectedViews.get(doc) ? true : false;
+        return manager.SelectedViewsMap.get(doc) ? true : false;
     });
     // computed functions, such as used in IsSelected generate errors if they're called outside of a
     // reaction context.  Specifying the context with 'outsideReaction' allows an efficiency feature
@@ -76,7 +83,7 @@ export namespace SelectionManager {
         return !doc
             ? false
             : outsideReaction
-            ? manager.SelectedViews.get(doc)
+            ? manager.SelectedViewsMap.get(doc)
                 ? true
                 : false // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get()
             : IsSelectedCache(doc);
@@ -85,7 +92,7 @@ export namespace SelectionManager {
     export function DeselectAll(except?: Doc): void {
         let found: DocumentView | undefined = undefined;
         if (except) {
-            for (const view of Array.from(manager.SelectedViews.keys())) {
+            for (const view of Array.from(manager.SelectedViewsMap.keys())) {
                 if (view.props.Document === except) found = view;
             }
         }
@@ -95,13 +102,15 @@ export namespace SelectionManager {
     }
 
     export function Views(): Array<DocumentView> {
-        return Array.from(manager.SelectedViews.keys()); //.filter(dv => manager.SelectedViews.get(dv)?._viewType !== CollectionViewType.Docking);
+        return manager.SelectedViews;
+        // Array.from(manager.SelectedViewsMap.keys()); //.filter(dv => manager.SelectedViews.get(dv)?._viewType !== CollectionViewType.Docking);
     }
     export function SelectedSchemaDoc(): Doc | undefined {
         return manager.SelectedSchemaDocument;
     }
     export function Docs(): Doc[] {
-        return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
+        return manager.SelectedViews.map(dv => dv.rootDoc).filter(doc => doc?._viewType !== CollectionViewType.Docking);
+        // Array.from(manager.SelectedViewsMap.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
     }
 }
 ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 01f41869e..57ff1b292 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -9,7 +9,7 @@ import { OmitKeys, returnFalse, Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { DocumentView } from '../nodes/DocumentView';
 import { StyleProp } from '../StyleProvider';
-import "./CollectionCarousel3DView.scss";
+import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
 
 @observer
@@ -20,134 +20,143 @@ export class CollectionCarousel3DView extends CollectionSubView() {
 
     private _dropDisposer?: DragManager.DragDropDisposer;
 
-    componentWillUnmount() { this._dropDisposer?.(); }
+    componentWillUnmount() {
+        this._dropDisposer?.();
+    }
 
-    protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view
+    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+        //used for stacking and masonry view
         this._dropDisposer?.();
         if (ele) {
             this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
         }
-    }
+    };
 
     panelWidth = () => this.props.PanelWidth() / 3;
     panelHeight = () => this.props.PanelHeight() * 0.6;
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
+    isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
+    isChildContentActive = () => (this.isContentActive() ? true : false);
+
     @computed get content() {
         const currentIndex = NumCast(this.layoutDoc._itemIndex);
-        const displayDoc = (childPair: { layout: Doc, data: Doc }) => {
-            return <DocumentView  {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "childLayoutTemplate", "childLayoutString"]).omit}
-                onDoubleClick={this.onChildDoubleClick}
-                renderDepth={this.props.renderDepth + 1}
-                LayoutTemplate={this.props.childLayoutTemplate}
-                LayoutTemplateString={this.props.childLayoutString}
-                Document={childPair.layout}
-                DataDoc={childPair.data}
-                PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight}
-                bringToFront={returnFalse}
-            />;
+        const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
+            return (
+                <DocumentView
+                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'childLayoutTemplate', 'childLayoutString']).omit}
+                    onDoubleClick={this.onChildDoubleClick}
+                    renderDepth={this.props.renderDepth + 1}
+                    LayoutTemplate={this.props.childLayoutTemplate}
+                    LayoutTemplateString={this.props.childLayoutString}
+                    Document={childPair.layout}
+                    DataDoc={childPair.data}
+                    isContentActive={this.isChildContentActive}
+                    isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
+                    PanelWidth={this.panelWidth}
+                    PanelHeight={this.panelHeight}
+                    bringToFront={returnFalse}
+                />
+            );
         };
 
-        return (this.childLayoutPairs.map((childPair, index) => {
+        return this.childLayoutPairs.map((childPair, index) => {
             return (
-                <div key={childPair.layout[Id]}
-                    className={`collectionCarousel3DView-item${index === currentIndex ? "-active" : ""} ${index}`}
-                    style={index === currentIndex ?
-                        { opacity: '1', transform: 'scale(1.3)', width: this.panelWidth() } :
-                        { opacity: '0.5', transform: 'scale(0.6)', userSelect: 'none', width: this.panelWidth() }}>
+                <div
+                    key={childPair.layout[Id]}
+                    className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`}
+                    style={index === currentIndex ? { opacity: '1', transform: 'scale(1.3)', width: this.panelWidth() } : { opacity: '0.5', transform: 'scale(0.6)', userSelect: 'none', width: this.panelWidth() }}>
                     {displayDoc(childPair)}
-                </div>);
-        }));
+                </div>
+            );
+        });
     }
 
     changeSlide = (direction: number) => {
         this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length;
-    }
+    };
 
     onArrowClick = (e: React.MouseEvent, direction: number) => {
         e.stopPropagation();
         this.changeSlide(direction);
-        !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = (direction === 1) ? "fwd" : "back"); // while autoscroll is on, keep the other autoscroll button hidden
+        !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = direction === 1 ? 'fwd' : 'back'); // while autoscroll is on, keep the other autoscroll button hidden
         !this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on
-    }
+    };
 
     interval?: number;
     startAutoScroll = (direction: number) => {
         this.interval = window.setInterval(() => {
             this.changeSlide(direction);
         }, this.scrollSpeed);
-    }
+    };
 
     stopAutoScroll = () => {
         window.clearInterval(this.interval);
         this.interval = undefined;
         this.fadeScrollButton();
-    }
+    };
 
     toggleAutoScroll = (direction: number) => {
         this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true;
         this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll();
-    }
+    };
 
     fadeScrollButton = () => {
         window.setTimeout(() => {
-            !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = "none"); //fade away after 1.5s if it's not clicked.
+            !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); //fade away after 1.5s if it's not clicked.
         }, 1500);
-    }
+    };
 
     @computed get buttons() {
         if (!this.props.isContentActive()) return null;
-        return <div className="arrow-buttons" >
-            <div key="back" className="carousel3DView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
-                onClick={(e) => this.onArrowClick(e, -1)}
-            >
-                <FontAwesomeIcon icon={"angle-left"} size={"2x"} />
-            </div>
-            <div key="fwd" className="carousel3DView-fwd" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
-                onClick={(e) => this.onArrowClick(e, 1)}
-            >
-                <FontAwesomeIcon icon={"angle-right"} size={"2x"} />
+        return (
+            <div className="arrow-buttons">
+                <div key="back" className="carousel3DView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={e => this.onArrowClick(e, -1)}>
+                    <FontAwesomeIcon icon={'angle-left'} size={'2x'} />
+                </div>
+                <div key="fwd" className="carousel3DView-fwd" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={e => this.onArrowClick(e, 1)}>
+                    <FontAwesomeIcon icon={'angle-right'} size={'2x'} />
+                </div>
+                {this.autoScrollButton}
             </div>
-            {this.autoScrollButton}
-        </div>;
+        );
     }
 
     @computed get autoScrollButton() {
         const whichButton = this.layoutDoc.showScrollButton;
-        return <>
-            <div className={`carousel3DView-back-scroll${whichButton === "back" ? "" : "-hidden"}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
-                onClick={() => this.toggleAutoScroll(-1)}>
-                {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={"pause"} size={"1x"} /> : <FontAwesomeIcon icon={"angle-double-left"} size={"1x"} />}
-            </div>
-            <div className={`carousel3DView-fwd-scroll${whichButton === "fwd" ? "" : "-hidden"}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
-                onClick={() => this.toggleAutoScroll(1)}>
-                {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={"pause"} size={"1x"} /> : <FontAwesomeIcon icon={"angle-double-right"} size={"1x"} />}
-            </div>
-        </>;
+        return (
+            <>
+                <div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}>
+                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-left'} size={'1x'} />}
+                </div>
+                <div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}>
+                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-right'} size={'1x'} />}
+                </div>
+            </>
+        );
     }
 
     @computed get dots() {
-        return (this.childLayoutPairs.map((_child, index) =>
-            <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._itemIndex) ? "-active" : ""}`}
-                onClick={() => this.layoutDoc._itemIndex = index} />));
+        return this.childLayoutPairs.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._itemIndex) ? '-active' : ''}`} onClick={() => (this.layoutDoc._itemIndex = index)} />);
     }
 
     render() {
         const index = NumCast(this.layoutDoc._itemIndex);
         const translateX = this.panelWidth() * (1 - index);
 
-        return <div className="collectionCarousel3DView-outer" ref={this.createDashEventsTarget}
-            style={{
-                background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
-                color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
-            }}  >
-            <div className="carousel-wrapper" style={{ transform: `translateX(${translateX}px)` }}>
-                {this.content}
-            </div>
-            {this.props.Document._chromeHidden ? (null) : this.buttons}
-            <div className="dot-bar">
-                {this.dots}
+        return (
+            <div
+                className="collectionCarousel3DView-outer"
+                ref={this.createDashEventsTarget}
+                style={{
+                    background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+                    color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
+                }}>
+                <div className="carousel-wrapper" style={{ transform: `translateX(${translateX}px)` }}>
+                    {this.content}
+                </div>
+                {this.props.Document._chromeHidden ? null : this.buttons}
+                <div className="dot-bar">{this.dots}</div>
             </div>
-        </div>;
+        );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index eafa50d27..51624689e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -257,7 +257,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         return StrCast(this.rootDoc.childLayoutString, this.props.childLayoutString);
     }
 
-    isContentActive = (outsideReaction?: boolean) => this.props.isContentActive();
+    isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() || this.isAnyChildContentActive();
 
     render() {
         TraceMobx();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index fa0695fb2..81b0c4d8a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -116,6 +116,8 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
             zIndex: NumCast(layout.zIndex),
             pair: { layout, data },
             replica: '',
+            color: 'white',
+            backgroundColor: 'white',
         });
     });
     const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
@@ -408,7 +410,7 @@ function normalizeResults(
         .map(ele => {
             const newPosRaw = ele[1];
             if (newPosRaw) {
-                const newPos = {
+                const newPos: PoolData = {
                     x: newPosRaw.x * scale,
                     y: newPosRaw.y * scale,
                     z: newPosRaw.z,
@@ -417,6 +419,9 @@ function normalizeResults(
                     zIndex: newPosRaw.zIndex,
                     width: (newPosRaw.width || 0) * scale,
                     height: newPosRaw.height! * scale,
+                    backgroundColor: newPosRaw.backgroundColor,
+                    opacity: newPosRaw.opacity,
+                    color: newPosRaw.color,
                     pair: ele[1].pair,
                 };
                 poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3a8edb1a5..4f81af95d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1291,10 +1291,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     pointerEvents = () => {
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
-        const pointerEvents =
-            this.props.isContentActive() === false || DocumentDecorations.Instance.Interacting
-                ? 'none'
-                : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
+        const pointerEvents = DocumentDecorations.Instance.Interacting
+            ? 'none'
+            : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
         return pointerEvents;
     };
     getChildDocView(entry: PoolData) {
@@ -1995,7 +1994,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         TraceMobx();
         return (
             <div
-                className={'collectionfreeformview-container'}
+                className="collectionfreeformview-container"
                 ref={this.createDashEventsTarget}
                 onWheel={this.onPointerWheel}
                 onClick={this.onClick}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 9581563ce..c9168d40a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -3,8 +3,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { observer } from 'mobx-react';
 import { unimplementedFunction } from '../../../../Utils';
-import { DocumentType } from '../../../documents/DocumentTypes';
-import { SelectionManager } from '../../../util/SelectionManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 
 @observer
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 88d045fa7..b73b1d779 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -2,14 +2,13 @@ import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
-import { List } from '../../../../fields/List';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { returnFalse } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
-import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMulticolumnView.scss';
 import ResizeBar from './MulticolumnResizer';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index f18917bef..0cca83803 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -2,14 +2,13 @@ import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
-import { List } from '../../../../fields/List';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { returnFalse } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
-import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMultirowView.scss';
 import HeightLabel from './MultirowHeightLabel';
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 151e6cbef..ad31113a2 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,15 +1,17 @@
 import React = require('react');
-import { action, computed, observable, ObservableMap, ObservableSet, untracked } from 'mobx';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, ObservableMap, trace, untracked } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, StrListCast } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTransparent, returnTrue, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
+import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { Transform } from '../../../util/Transform';
@@ -17,18 +19,14 @@ import { undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
 import { EditableView } from '../../EditableView';
-import { DocComponentView, DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { KeyValueBox } from '../../nodes/KeyValueBox';
+import { DefaultStyleProvider } from '../../StyleProvider';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
-import { DefaultStyleProvider } from '../../StyleProvider';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { ScriptField } from '../../../../fields/ScriptField';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { IconButton } from 'browndash-components';
-import { KeyValueBox } from '../../nodes/KeyValueBox';
 
 export enum ColumnType {
     Number,
@@ -43,7 +41,6 @@ const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate',
 @observer
 export class CollectionSchemaView extends CollectionSubView() {
     private _ref: HTMLDivElement | null = null;
-    private _selectedDocSortedArray: Doc[] = [];
     private _closestDropIndex: number = 0;
     private _previewRef: HTMLDivElement | null = null;
     private _makeNewColumn: boolean = false;
@@ -53,13 +50,11 @@ export class CollectionSchemaView extends CollectionSubView() {
     public static _rowMenuWidth: number = 100;
     public static _previewDividerWidth: number = 4;
 
-    @observable _lastSelectedRow: number | undefined;
     @computed get _selectedDocs() {
-        return new Set(SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.props.Document)));
+        return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.props.Document));
     }
     @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
     @observable _colEles: HTMLDivElement[] = [];
-    @observable _isDragging: boolean = false;
     @observable _displayColumnWidths: number[] | undefined;
     @observable _columnMenuIndex: number | undefined;
     @observable _menuOptions: string[] = [];
@@ -133,7 +128,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     componentDidMount() {
-        this.props.setContentView?.(this as DocComponentView);
+        this.props.setContentView?.(this);
         document.addEventListener('keydown', this.onKeyDown);
     }
 
@@ -143,24 +138,36 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     @action
     onKeyDown = (e: KeyboardEvent) => {
-        if (this._selectedDocs.size > 0) {
-            if (e.key == 'ArrowDown') {
-                const lastDoc = Array.from(this._selectedDocs.values()).lastElement();
-                const lastIndex = this.rowIndex(lastDoc);
-                if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) {
-                    !e.shiftKey && this.clearSelection();
-                    const newDoc = this.childDocs[lastIndex + 1];
-                    this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
-                }
-            }
-            if (e.key == 'ArrowUp') {
-                const firstDoc = Array.from(this._selectedDocs.values())[0];
-                const firstIndex = this.rowIndex(firstDoc);
-                if (firstIndex > 0 && firstIndex < this.childDocs.length) {
-                    !e.shiftKey && this.clearSelection();
-                    const newDoc = this.childDocs[firstIndex - 1];
-                    this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
-                }
+        if (this._selectedDocs.length > 0) {
+            switch (e.key) {
+                case 'ArrowDown':
+                    {
+                        const lastDoc = this._selectedDocs.lastElement();
+                        const lastIndex = this.rowIndex(lastDoc);
+                        const curDoc = this.childDocs[lastIndex];
+                        if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) {
+                            !e.shiftKey && this.clearSelection();
+                            const newDoc = this.childDocs[lastIndex + 1];
+                            if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+                            else this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
+                        }
+                        e.stopPropagation();
+                    }
+                    break;
+                case 'ArrowUp':
+                    {
+                        const firstDoc = this._selectedDocs.lastElement();
+                        const firstIndex = this.rowIndex(firstDoc);
+                        const curDoc = this.childDocs[firstIndex];
+                        if (firstIndex > 0 && firstIndex < this.childDocs.length) {
+                            !e.shiftKey && this.clearSelection();
+                            const newDoc = this.childDocs[firstIndex - 1];
+                            if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+                            else this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
+                        }
+                        e.stopPropagation();
+                    }
+                    break;
             }
         }
     };
@@ -326,60 +333,25 @@ export class CollectionSchemaView extends CollectionSubView() {
     addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => {
         const rowDocView = DocumentManager.Instance.getDocumentView(doc);
         if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection);
-        this._lastSelectedRow = index;
-    };
-
-    @action
-    removeDocFromSelection = (doc: Doc) => {
-        const rowDocView = DocumentManager.Instance.getDocumentView(doc);
-        if (rowDocView) SelectionManager.DeselectView(rowDocView);
-        if (this._selectedDocs.size === 0) {
-            this._lastSelectedRow = undefined;
-        }
     };
 
     @action
-    clearSelection = () => {
-        SelectionManager.DeselectAll();
-        this._lastSelectedRow = undefined;
-    };
-
-    rowOnClickScript = ScriptField.MakeFunction('scriptContext.selectRow(self, shiftKey, ctrlKey || metaKey)', { scriptContext: 'any', shiftKey: 'boolean', ctrlKey: 'boolean', metaKey: 'boolean' })!;
-
-    @action
-    selectRow = (doc: Doc, shift: boolean, ctrl: boolean) => {
-        const index = this.childDocs.indexOf(doc);
-        if (index < 0) return;
-        if (shift && this._lastSelectedRow !== undefined) {
-            const startRow = Math.min(this._lastSelectedRow, index);
-            const endRow = Math.max(this._lastSelectedRow, index);
-            for (let i = startRow; i <= endRow; i++) {
-                const currDoc = this.childDocs[i];
-                if (!this._selectedDocs.has(currDoc)) this.addDocToSelection(currDoc, true, i);
-            }
-            this._lastSelectedRow = index;
-        } else if (ctrl) {
-            if (!this._selectedDocs.has(doc)) {
-                this.addDocToSelection(doc, true, index);
-            } else {
-                this.removeDocFromSelection(doc);
-            }
-        } else {
-            if (!this._selectedDocs.has(doc)) {
-                this.clearSelection();
-                this.addDocToSelection(doc, false, index);
-            }
+    clearSelection = () => SelectionManager.DeselectAll();
+
+    selectRows = (rootDoc: Doc, lastSelected: Doc) => {
+        const index = this.childDocs.indexOf(rootDoc);
+        const lastSelectedRow = this.childDocs.indexOf(lastSelected);
+        const startRow = Math.min(lastSelectedRow, index);
+        const endRow = Math.max(lastSelectedRow, index);
+        for (let i = startRow; i <= endRow; i++) {
+            const currDoc = this.childDocs[i];
+            if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true, i);
         }
     };
 
-    @action
-    sortedSelectedDocs = (): Doc[] => {
-        return this.childDocs.filter(doc => this._selectedDocs.has(doc));
-    };
+    sortedSelectedDocs = () => this.childDocs.filter(doc => this._selectedDocs.includes(doc));
 
-    setDropIndex = (index: number) => {
-        this._closestDropIndex = index;
-    };
+    setDropIndex = (index: number) => (this._closestDropIndex = index);
 
     @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
@@ -394,17 +366,20 @@ export class CollectionSchemaView extends CollectionSubView() {
                 return total + curr;
             }, CollectionSchemaView._rowMenuWidth);
             this.swapColumns(de.complete.columnDragData.colIndex, i);
+            e.stopPropagation();
             return true;
         }
-        if (super.onInternalDrop(e, de)) {
-            this._isDragging = false;
-            const pushedDocs: Doc[] = this.childDocs.filter((doc: Doc, index: number) => index >= this._closestDropIndex && !this._selectedDocs.has(doc));
+        const draggedDocs = de.complete.docDragData?.draggedDocuments;
+        if (draggedDocs && super.onInternalDrop(e, de)) {
+            const pushedDocs = this.childDocs.filter((doc, index) => index >= this._closestDropIndex && !draggedDocs.includes(doc));
             this.props.removeDocument?.(pushedDocs);
-            this.props.removeDocument?.(this._selectedDocSortedArray);
-            this.addDocument(this._selectedDocSortedArray);
+            this.props.removeDocument?.(draggedDocs);
+            this.addDocument(draggedDocs);
             this.addDocument(pushedDocs);
             this.setSort(undefined);
-            this.clearSelection();
+            SelectionManager.DeselectAll();
+            setTimeout(() => draggedDocs.forEach(doc => DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true))), 100);
+            e.stopPropagation();
             return true;
         }
         return false;
@@ -412,46 +387,11 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     @action
     onExternalDrop = async (e: React.DragEvent): Promise<void> => {
-        super.onExternalDrop(
-            e,
-            {},
-            undoBatch(
-                action(docus => {
-                    this._isDragging = false;
-                    docus.map((doc: Doc) => {
-                        this.addDocument(doc);
-                    });
-                })
-            )
-        );
+        super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc)))));
         this.setSort(undefined);
     };
 
-    @action
-    startDrag = (e: PointerEvent, doc: Doc, index: number) => {
-        if (!this._selectedDocs.has(doc)) {
-            this.clearSelection();
-            this.addDocToSelection(doc, false, index);
-        }
-        this._isDragging = true;
-        this._selectedDocSortedArray = this.sortedSelectedDocs();
-        const dragData = new DragManager.DocumentDragData(this._selectedDocSortedArray, 'move');
-        dragData.moveDocument = this.props.moveDocument;
-        const dragItem: HTMLElement[] = Array.from(this._selectedDocs.values()).map((doc: Doc) => this._rowEles.get(doc));
-
-        DragManager.StartDocumentDrag(
-            dragItem.map(ele => ele),
-            dragData,
-            e.clientX,
-            e.clientY,
-            undefined
-        );
-        return true;
-    };
-
-    onDividerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
-    };
+    onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
 
     @action
     onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
@@ -557,9 +497,6 @@ export class CollectionSchemaView extends CollectionSubView() {
         return undefined;
     };
 
-    isChildContentActive = () =>
-        this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined;
-
     @computed get fieldDefaultInput() {
         switch (this._newFieldType) {
             case ColumnType.Number:
@@ -644,23 +581,17 @@ export class CollectionSchemaView extends CollectionSubView() {
         ContextMenu.Instance.clearItems();
         ContextMenu.Instance.addItem({
             description: 'Change field',
-            event: () => {
-                this.openColumnMenu(index, false);
-            },
+            event: () => this.openColumnMenu(index, false),
             icon: 'pencil-alt',
         });
         ContextMenu.Instance.addItem({
             description: 'Filter field',
-            event: () => {
-                this.openFilterMenu(index);
-            },
+            event: () => this.openFilterMenu(index),
             icon: 'filter',
         });
         ContextMenu.Instance.addItem({
             description: 'Delete column',
-            event: () => {
-                this.removeColumn(index);
-            },
+            event: () => this.removeColumn(index),
             icon: 'trash',
         });
         ContextMenu.Instance.displayMenu(x, y, undefined, false);
@@ -672,9 +603,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         this._menuOptions = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
     };
 
-    getFieldFilters = (field: string) => {
-        return StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
-    };
+    getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
 
     removeFieldFilters = (field: string) => {
         this.getFieldFilters(field).forEach(filter => {
@@ -694,9 +623,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this._filterValue = e.target.value;
-    };
+    updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterValue = e.target.value);
 
     @computed get newFieldMenu() {
         return (
@@ -705,7 +632,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                     <input
                         type="radio"
                         name="newFieldType"
-                        id=""
                         checked={this._newFieldType == ColumnType.Number}
                         onChange={action(() => {
                             this._newFieldType = ColumnType.Number;
@@ -718,7 +644,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                     <input
                         type="radio"
                         name="newFieldType"
-                        id=""
                         checked={this._newFieldType == ColumnType.Boolean}
                         onChange={action(() => {
                             this._newFieldType = ColumnType.Boolean;
@@ -731,7 +656,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                     <input
                         type="radio"
                         name="newFieldType"
-                        id=""
                         checked={this._newFieldType == ColumnType.String}
                         onChange={action(() => {
                             this._newFieldType = ColumnType.String;
@@ -880,13 +804,11 @@ export class CollectionSchemaView extends CollectionSubView() {
         );
     }
 
-    tableWidthFunc = () => this.tableWidth;
-    rowHeightFunc = () => CollectionSchemaView._rowHeight;
-    rowClickScriptFunc = () => this.rowOnClickScript;
     isContentActive = () => this.props.isSelected() || this.props.isContentActive();
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(-this.tableWidth, 0);
     previewWidthFunc = () => this.previewWidth;
     render() {
+        trace();
         return (
             <div
                 className="collectionSchemaView"
@@ -894,6 +816,12 @@ export class CollectionSchemaView extends CollectionSubView() {
                     this._ref = ele;
                     this.createDashEventsTarget(ele);
                 }}
+                onPointerDown={e => {
+                    // this is analogous to the panning code for a freeform view.
+                    // however, schema views don't pan so it does nothing.  but it does eat the pointerDown event
+                    // if the content is active to prevent the schema from being dragged
+                    this.isContentActive() && setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false);
+                }}
                 onDrop={this.onExternalDrop.bind(this)}>
                 <div className="schema-table">
                     <div className="schema-header-row" style={{ height: CollectionSchemaView._rowHeight }}>
@@ -927,53 +855,16 @@ export class CollectionSchemaView extends CollectionSubView() {
                     </div>
                     {this._columnMenuIndex !== undefined && this.renderColumnMenu}
                     {this._filterColumnIndex !== undefined && this.renderFilterMenu}
-                    <div className="schema-table-content">
-                        {this.childDocs.map((doc: Doc, index: number) => {
-                            const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
-                            let dref: Opt<DocumentView>;
-                            return (
-                                <div className="schema-row-wrapper" style={{ maxHeight: CollectionSchemaView._rowHeight }}>
-                                    <DocumentView
-                                        {...this.props}
-                                        ref={r => (dref = r || undefined)}
-                                        LayoutTemplate={this.props.childLayoutTemplate}
-                                        LayoutTemplateString={SchemaRowBox.LayoutString(this.props.fieldKey)}
-                                        Document={doc}
-                                        DataDoc={dataDoc}
-                                        ContainingCollectionView={this.props.CollectionView}
-                                        ContainingCollectionDoc={this.Document}
-                                        PanelWidth={this.tableWidthFunc}
-                                        PanelHeight={this.rowHeightFunc}
-                                        styleProvider={DefaultStyleProvider}
-                                        focus={this.focusDocument}
-                                        docFilters={this.childDocFilters}
-                                        docRangeFilters={this.childDocRangeFilters}
-                                        searchFilterDocs={this.searchFilterDocs}
-                                        rootSelected={this.rootSelected}
-                                        ScreenToLocalTransform={Transform.Identity}
-                                        bringToFront={emptyFunction}
-                                        isDocumentActive={this.isContentActive}
-                                        isContentActive={emptyFunction}
-                                        hideDecorations={true}
-                                        hideTitle={true}
-                                        hideDocumentButtonBar={true}
-                                        hideLinkAnchors={true}
-                                        fitWidth={returnTrue}
-                                        onClick={this.rowClickScriptFunc}
-                                        scriptContext={this}
-                                    />
-                                </div>
-                            );
-                        })}
-                    </div>
+                    <CollectionSchemaViewDocs schema={this} />
+
                     <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
                 </div>
                 {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
                 {this.previewWidth > 0 && (
                     <div style={{ width: `${this.previewWidth}px` }} ref={ref => (this._previewRef = ref)}>
-                        {this._lastSelectedRow !== undefined && (
+                        {Array.from(this._selectedDocs).lastElement() && (
                             <DocumentView
-                                Document={this.childDocs[this._lastSelectedRow]}
+                                Document={Array.from(this._selectedDocs).lastElement()}
                                 DataDoc={undefined}
                                 fitContentsToBox={returnTrue}
                                 dontCenter={'y'}
@@ -1007,3 +898,54 @@ export class CollectionSchemaView extends CollectionSubView() {
         );
     }
 }
+
+interface CollectionSchemaViewDocsProps {
+    schema: CollectionSchemaView;
+}
+
+@observer
+class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsProps> {
+    tableWidthFunc = () => this.props.schema.tableWidth;
+    rowHeightFunc = () => CollectionSchemaView._rowHeight;
+    render() {
+        return (
+            <div className="schema-table-content">
+                {this.props.schema.childDocs.map((doc: Doc, index: number) => {
+                    const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.schema.props.DataDoc;
+                    return (
+                        <div className="schema-row-wrapper" style={{ maxHeight: CollectionSchemaView._rowHeight }}>
+                            <DocumentView
+                                {...this.props.schema.props}
+                                LayoutTemplate={this.props.schema.props.childLayoutTemplate}
+                                LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey)}
+                                Document={doc}
+                                DataDoc={dataDoc}
+                                ContainingCollectionView={this.props.schema.props.CollectionView}
+                                ContainingCollectionDoc={this.props.schema.Document}
+                                PanelWidth={this.tableWidthFunc}
+                                PanelHeight={this.rowHeightFunc}
+                                styleProvider={DefaultStyleProvider}
+                                focus={this.props.schema.focusDocument}
+                                docFilters={this.props.schema.childDocFilters}
+                                docRangeFilters={this.props.schema.childDocRangeFilters}
+                                searchFilterDocs={this.props.schema.searchFilterDocs}
+                                rootSelected={this.props.schema.rootSelected}
+                                ScreenToLocalTransform={Transform.Identity}
+                                bringToFront={emptyFunction}
+                                isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive}
+                                isContentActive={emptyFunction}
+                                whenChildContentsActiveChanged={active => this.props.schema.props.whenChildContentsActiveChanged(active)}
+                                hideDecorations={true}
+                                hideTitle={true}
+                                hideDocumentButtonBar={true}
+                                hideLinkAnchors={true}
+                                fitWidth={returnTrue}
+                                scriptContext={this}
+                            />
+                        </div>
+                    );
+                })}
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 87284be70..0c8c0ee59 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -1,9 +1,9 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed } from 'mobx';
+import { computed } from 'mobx';
 import { observer } from 'mobx-react';
-import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { DragManager } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
 import { undoBatch } from '../../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
@@ -36,24 +36,30 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return this.schemaView?.rowIndex(this.rootDoc) ?? -1;
     }
 
-    @action
-    onRowPointerDown = (e: React.PointerEvent) => {
-        if (!this.isContentActive()) return;
-        setupMoveUpEvents(this, e, e => this.schemaView?.startDrag(e, this.rootDoc, this.rowIndex) ?? true, emptyFunction, emptyFunction);
+    componentDidMount(): void {
+        this.props.setContentView?.(this);
+    }
+
+    select = (ctrlKey: boolean, shiftKey: boolean) => {
+        if (!this.schemaView) return;
+        const lastSelected = Array.from(this.schemaView._selectedDocs).lastElement();
+        if (shiftKey && lastSelected) this.schemaView.selectRows(this.rootDoc, lastSelected);
+        else {
+            this.props.select?.(ctrlKey);
+        }
     };
 
     onPointerEnter = (e: any) => {
-        if (!this.schemaView?._isDragging) return;
+        //if (!this.schemaView?._isDragging) return;
+        if (!SnappingManager.GetIsDragging()) return;
         document.removeEventListener('pointermove', this.onPointerMove);
         document.addEventListener('pointermove', this.onPointerMove);
     };
 
     onPointerMove = (e: any) => {
-        if (!this.schemaView?._isDragging) return;
-        let dragIsRow: boolean = true;
-        DragManager.docsBeingDragged.forEach(doc => {
-            dragIsRow = this.schemaView?._selectedDocs.has(doc) ?? false;
-        });
+        if (!SnappingManager.GetIsDragging()) return;
+        const dragIsRow = DragManager.docsBeingDragged.some(doc => doc.context === this.schemaDoc); //  this.schemaView?._selectedDocs.has(doc) ?? false;
+
         if (this._ref && dragIsRow) {
             const rect = this._ref.getBoundingClientRect();
             const y = e.clientY - rect.top; //y position within the element.
@@ -88,7 +94,6 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         ? { height: CollectionSchemaView._rowHeight, backgroundColor: Colors.LIGHT_BLUE, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined /*, opacity: this.props.dragging ? 0.5 : 1 */ }
                         : { height: CollectionSchemaView._rowHeight, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined }
                 }
-                onPointerDown={this.onRowPointerDown}
                 onPointerEnter={this.onPointerEnter}
                 onPointerLeave={this.onPointerLeave}
                 ref={(row: HTMLDivElement | null) => {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
index 724394025..f99011b8f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
@@ -5,5 +5,5 @@
     touch-action: manipulation;
     top: 0;
     left: 0;
-    pointer-events: none;
-}
\ No newline at end of file
+    //pointer-events: none;
+}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c13934945..1f717932e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -121,6 +121,7 @@ export interface DocComponentView {
     addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
     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;
     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
     getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
@@ -236,7 +237,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
     NativeHeight: () => number;
     isSelected: (outsideReaction?: boolean) => boolean;
     isHovering: () => boolean;
-    select: (ctrlPressed: boolean) => void;
+    select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
     DocumentView: () => DocumentView;
     viewPath: () => DocumentView[];
 }
@@ -538,7 +539,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
         if (this._mainCont.current) {
-            const dragData = new DragManager.DocumentDragData([this.props.Document]);
+            const views = SelectionManager.Views().filter(dv => dv.docView?._mainCont.current);
+            const selected = views.some(dv => dv.rootDoc === this.Document) ? views : [this.props.DocumentView()];
+            const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.rootDoc));
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
             dragData.offset = this.props
                 .ScreenToLocalTransform()
@@ -551,8 +554,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             dragData.canEmbed = this.props.canEmbedOnDrag;
             const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
             ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
-            DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) }, () =>
-                setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))
+            DragManager.StartDocumentDrag(
+                selected.map(dv => dv.docView!._mainCont.current!),
+                dragData,
+                x,
+                y,
+                { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) },
+                () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))
             ); // this needs to happen after the drop event is processed.
             ffview?.setupDragLines(false);
         }
@@ -659,7 +667,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         }),
                         350
                     );
-                    this.props.select(e.ctrlKey || e.shiftKey);
+                    (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey);
                 }
                 preventDefault = false;
             }
-- 
cgit v1.2.3-70-g09d2

