From fb9ec75c46bc237bc6c8df24ee998e6de90168a1 Mon Sep 17 00:00:00 2001
From: mehekj <mehek.jethani@gmail.com>
Date: Wed, 12 Apr 2023 20:55:14 -0400
Subject: readonly fields and schema key info display

---
 src/client/views/collections/collectionSchema/SchemaRowBox.tsx | 1 +
 1 file changed, 1 insertion(+)

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

diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 2defaae00..aa8a61205 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -123,6 +123,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <SchemaTableCell
                             key={key}
                             Document={this.rootDoc}
+                            schemaView={this.schemaView}
                             fieldKey={key}
                             columnWidth={this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth}
                             isRowActive={this.props.isContentActive}
-- 
cgit v1.2.3-70-g09d2


From 99326044a2d81a9f0d0312b4d209946e0f119b78 Mon Sep 17 00:00:00 2001
From: mehekj <mehek.jethani@gmail.com>
Date: Mon, 24 Apr 2023 17:47:54 -0400
Subject: crappy version of schema image cell preview on hover

---
 .../collections/collectionSchema/SchemaRowBox.tsx  |  2 +-
 .../collectionSchema/SchemaTableCell.tsx           | 65 ++++++++++++++--------
 2 files changed, 43 insertions(+), 24 deletions(-)

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

diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 59b571b58..79808d8f8 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -88,7 +88,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div
                 className="schema-row"
-                style={{ height: CollectionSchemaView._rowHeight, backgroundColor: this.props.isSelected() ? Colors.LIGHT_BLUE : undefined, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined }}
+                style={{ height: CollectionSchemaView._rowHeight, backgroundColor: this.props.isSelected() ? Colors.LIGHT_BLUE : undefined }}
                 onPointerEnter={this.onPointerEnter}
                 onPointerLeave={this.onPointerLeave}
                 ref={(row: HTMLDivElement | null) => {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 4e31b1e1e..2325339fc 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -58,7 +58,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         };
 
         return (
-            <div className="schemacell-edit-wrapper">
+            <div className="schemacell-edit-wrapper" style={{ cursor: !this.readOnly ? 'text' : 'default', pointerEvents: !this.readOnly && this.props.isRowActive() ? 'all' : 'none' }}>
                 <EditableView
                     contents={<FieldView {...props} />}
                     GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
@@ -76,9 +76,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
 
     getCellWithContent(content: any) {
         return (
-            <div
-                className="schema-table-cell"
-                style={this.props.isRowActive() && !this.readOnly ? { width: this.props.columnWidth, cursor: 'text', pointerEvents: 'auto' } : { width: this.props.columnWidth, cursor: 'default', pointerEvents: 'none' }}>
+            <div className="schema-table-cell" style={this.props.isRowActive() && !this.readOnly ? { width: this.props.columnWidth } : { width: this.props.columnWidth }}>
                 {content}
             </div>
         );
@@ -107,7 +105,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             case ColumnType.Image:
                 return <SchemaImageCell {...this.props} />;
             case ColumnType.Date:
-                return <SchemaDateCell {...this.props} />;
+            // return <SchemaDateCell {...this.props} />;
             default:
                 return this.getCellWithContent(this.defaultCellContent);
         }
@@ -117,16 +115,18 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
 // mj: most of this is adapted from old schema code so I'm not sure what it does tbh
 @observer
 export class SchemaImageCell extends SchemaTableCell {
+    @observable _previewRef: HTMLImageElement | undefined;
+
     choosePath(url: URL) {
         if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
         if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
         if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question
 
         const ext = extname(url.href);
-        return url.href.replace(ext, '_o' + ext);
+        return url.href.replace(ext, '_s' + ext);
     }
 
-    get content() {
+    get url() {
         const field = Cast(this.props.Document[this.props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
         const alts = DocListCast(this.props.Document[this.props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
         const altpaths = alts
@@ -136,13 +136,42 @@ export class SchemaImageCell extends SchemaTableCell {
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
         // If there is a path, follow it; otherwise, follow a link to a default image icon
         const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
+        return url[0];
+    }
+
+    @action
+    showHoverPreview = (e: React.PointerEvent) => {
+        this._previewRef = document.createElement('img');
+        document.body.appendChild(this._previewRef);
+        const ext = extname(this.url);
+        this._previewRef.src = this.url.replace('_s' + ext, '_m' + ext);
+        this._previewRef.style.position = 'absolute';
+        this._previewRef.style.left = e.clientX + 10 + 'px';
+        this._previewRef.style.top = e.clientY + 10 + 'px';
+        this._previewRef.style.zIndex = '1000';
+    };
+
+    @action
+    moveHoverPreview = (e: React.PointerEvent) => {
+        if (!this._previewRef) return;
+        this._previewRef.style.left = e.clientX + 10 + 'px';
+        this._previewRef.style.top = e.clientY + 10 + 'px';
+    };
 
+    @action
+    removeHoverPreview = (e: React.PointerEvent) => {
+        if (!this._previewRef) return;
+        document.body.removeChild(this._previewRef);
+    };
+
+    get content() {
         const aspect = Doc.NativeAspect(this.props.Document); // aspect ratio
-        let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px
-        const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
-        width = height * aspect; // increase the width of the image if necessary to maintain proportionality
+        // let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px
+        // const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
+        const height = CollectionSchemaView._rowHeight - 10;
+        const width = height * aspect; // increase the width of the image if necessary to maintain proportionality
 
-        return <img src={url[0]} width={paths.length ? width : '20px'} height={paths.length ? height : '20px'} />;
+        return <img src={this.url} width={width} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
     }
 
     render() {
@@ -171,18 +200,8 @@ export class SchemaDateCell extends SchemaTableCell {
     };
 
     get content() {
-        return !this._pickingDate ? (
-            <div onPointerDown={action(() => (this._pickingDate = true))}>{this.defaultCellContent}</div>
-        ) : (
-            <DatePicker
-                selected={this.date.date}
-                onSelect={(date: any) => {
-                    this.handleChange(date);
-                    this._pickingDate = false;
-                }}
-                onChange={(date: any) => this.handleChange(date)}
-            />
-        );
+        return <DatePicker dateFormat={'Pp'} selected={this.date.date} onChange={(date: any) => this.handleChange(date)} />;
+        // return !this._pickingDate ? <div onPointerDown={action(() => (this._pickingDate = true))}>{this.defaultCellContent}</div> : <DatePicker dateFormat={'Pp'} selected={this.date.date} onChange={(date: any) => this.handleChange(date)} />;
     }
 
     render() {
-- 
cgit v1.2.3-70-g09d2


From bc550e43b6042176cf59f04cbff51e1b21adaa8a Mon Sep 17 00:00:00 2001
From: mehekj <mehek.jethani@gmail.com>
Date: Thu, 27 Apr 2023 00:41:43 -0400
Subject: trying to get schema keyboard controls working

---
 .../collectionSchema/CollectionSchemaView.tsx      | 43 ++++++++++++--
 .../collections/collectionSchema/SchemaRowBox.tsx  |  1 +
 .../collectionSchema/SchemaTableCell.tsx           | 65 +++++++++++++---------
 3 files changed, 78 insertions(+), 31 deletions(-)

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

diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index f6dc2a3d7..92a04f5ec 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -74,6 +74,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @observable _menuValue: string = '';
     @observable _filterColumnIndex: number | undefined;
     @observable _filterSearchValue: string = '';
+    @observable _selectedCell: [Doc, number] | undefined;
 
     get fieldInfos() {
         const docs = this.childDocs;
@@ -174,8 +175,11 @@ export class CollectionSchemaView extends CollectionSubView() {
                             !e.shiftKey && this.clearSelection();
                             const newDoc = this.sortedDocs.docs[lastIndex + 1];
                             if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
-                            else this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
-                            this.scrollToDoc(newDoc, {});
+                            else {
+                                this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
+                                this._selectedCell && (this._selectedCell[0] = newDoc);
+                                this.scrollToDoc(newDoc, {});
+                            }
                         }
                         e.stopPropagation();
                         e.preventDefault();
@@ -191,13 +195,37 @@ export class CollectionSchemaView extends CollectionSubView() {
                             !e.shiftKey && this.clearSelection();
                             const newDoc = this.sortedDocs.docs[firstIndex - 1];
                             if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
-                            else this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
-                            this.scrollToDoc(newDoc, {});
+                            else {
+                                this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
+                                this._selectedCell && (this._selectedCell[0] = newDoc);
+                                this.scrollToDoc(newDoc, {});
+                            }
                         }
                         e.stopPropagation();
                         e.preventDefault();
                     }
                     break;
+                case 'ArrowRight':
+                    if (this._selectedCell) {
+                        this._selectedCell[1] = Math.min(this._selectedCell[1] + 1, this.columnKeys.length - 1);
+                    } else if (this._selectedDocs.length > 0) {
+                        this.selectCell(this._selectedDocs[0], 0);
+                    }
+                    break;
+                case 'ArrowLeft':
+                    if (this._selectedCell) {
+                        this._selectedCell[1] = Math.max(this._selectedCell[1] - 1, 0);
+                    } else if (this._selectedDocs.length > 0) {
+                        this.selectCell(this._selectedDocs[0], 0);
+                    }
+                    break;
+                case 'Backspace': {
+                    this.removeDocument(this._selectedDocs);
+                    break;
+                }
+                case 'Escape': {
+                    this._selectedCell = undefined;
+                }
             }
         }
     };
@@ -392,6 +420,11 @@ export class CollectionSchemaView extends CollectionSubView() {
         }
     };
 
+    @action
+    selectCell = (doc: Doc, index: number) => {
+        this._selectedCell = [doc, index];
+    };
+
     sortedSelectedDocs = () => this.sortedDocs.docs.filter(doc => this._selectedDocs.includes(doc));
 
     setDropIndex = (index: number) => (this._closestDropIndex = index);
@@ -428,7 +461,7 @@ export class CollectionSchemaView extends CollectionSubView() {
             const pushedAndDraggedDocs = [...pushedDocs, ...draggedDocs];
             const removed = this.childDocs.slice().filter(doc => !pushedAndDraggedDocs.includes(doc));
             this.dataDoc[this.fieldKey ?? 'data'] = new List<Doc>([...removed, ...draggedDocs, ...pushedDocs]);
-            SelectionManager.DeselectAll();
+            this.clearSelection();
             draggedDocs.forEach(doc => {
                 const draggedView = DocumentManager.Instance.getFirstDocumentView(doc);
                 if (draggedView) DocumentManager.Instance.RemoveView(draggedView);
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 79808d8f8..f5a16cec0 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -123,6 +123,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <SchemaTableCell
                             key={key}
                             Document={this.rootDoc}
+                            col={index}
                             schemaView={this.schemaView}
                             fieldKey={key}
                             columnWidth={this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 2325339fc..2b61ea261 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -11,13 +11,16 @@ import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSc
 import './CollectionSchemaView.scss';
 import { action, computed, observable } from 'mobx';
 import { extname } from 'path';
-import { Cast, DateCast } from '../../../../fields/Types';
+import { Cast, DateCast, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { DateField } from '../../../../fields/DateField';
 import DatePicker from 'react-datepicker';
+import { Colors } from '../../global/globalEnums';
+import { SelectionManager } from '../../../util/SelectionManager';
 
 export interface SchemaTableCellProps {
     Document: Doc;
+    col: number;
     schemaView: CollectionSchemaView | undefined;
     fieldKey: string;
     columnWidth: number;
@@ -27,10 +30,23 @@ export interface SchemaTableCellProps {
 
 @observer
 export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
-    get readOnly() {
+    private _editorRef: EditableView | null = null;
+
+    @computed get readOnly() {
         return this.props.schemaView?.fieldInfos[this.props.fieldKey]?.readOnly ?? false;
     }
 
+    @computed get selected() {
+        const selected: [Doc, number] | undefined = this.props.schemaView?._selectedCell;
+        return this.props.isRowActive() && selected && selected[0] == this.props.Document && selected[1] == this.props.col;
+    }
+
+    componentDidUpdate() {
+        if (!this.selected) {
+            this._editorRef?.setIsFocused(false);
+        }
+    }
+
     get defaultCellContent() {
         const props: FieldViewProps = {
             Document: this.props.Document,
@@ -60,6 +76,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         return (
             <div className="schemacell-edit-wrapper" style={{ cursor: !this.readOnly ? 'text' : 'default', pointerEvents: !this.readOnly && this.props.isRowActive() ? 'all' : 'none' }}>
                 <EditableView
+                    ref={ref => (this._editorRef = ref)}
                     contents={<FieldView {...props} />}
                     GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
                     SetValue={(value: string, shiftDown?: boolean, enterKey?: boolean) => {
@@ -68,20 +85,12 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
                         }
                         return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
                     }}
-                    editing={this.props.isRowActive() ? undefined : false}
+                    editing={this.selected ? undefined : false}
                 />
             </div>
         );
     }
 
-    getCellWithContent(content: any) {
-        return (
-            <div className="schema-table-cell" style={this.props.isRowActive() && !this.readOnly ? { width: this.props.columnWidth } : { width: this.props.columnWidth }}>
-                {content}
-            </div>
-        );
-    }
-
     get getCellType() {
         const columnTypeStr = this.props.schemaView?.fieldInfos[this.props.fieldKey]?.fieldType;
         if (columnTypeStr) {
@@ -99,7 +108,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         return ColumnType.Any;
     }
 
-    render() {
+    get content() {
         const cellType: ColumnType = this.getCellType;
         switch (cellType) {
             case ColumnType.Image:
@@ -107,14 +116,27 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             case ColumnType.Date:
             // return <SchemaDateCell {...this.props} />;
             default:
-                return this.getCellWithContent(this.defaultCellContent);
+                return this.defaultCellContent;
         }
     }
+
+    render() {
+        return (
+            <div
+                className="schema-table-cell"
+                onPointerDown={action(e => {
+                    if (!this.selected) this.props.schemaView?.selectCell(this.props.Document, this.props.col);
+                })}
+                style={{ width: this.props.columnWidth, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
+                {this.content}
+            </div>
+        );
+    }
 }
 
 // mj: most of this is adapted from old schema code so I'm not sure what it does tbh
 @observer
-export class SchemaImageCell extends SchemaTableCell {
+export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
     @observable _previewRef: HTMLImageElement | undefined;
 
     choosePath(url: URL) {
@@ -164,7 +186,7 @@ export class SchemaImageCell extends SchemaTableCell {
         document.body.removeChild(this._previewRef);
     };
 
-    get content() {
+    render() {
         const aspect = Doc.NativeAspect(this.props.Document); // aspect ratio
         // let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px
         // const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
@@ -173,14 +195,10 @@ export class SchemaImageCell extends SchemaTableCell {
 
         return <img src={this.url} width={width} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
     }
-
-    render() {
-        return this.getCellWithContent(this.content);
-    }
 }
 
 @observer
-export class SchemaDateCell extends SchemaTableCell {
+export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
     @observable _pickingDate: boolean = false;
 
     @computed get date(): DateField {
@@ -199,12 +217,7 @@ export class SchemaDateCell extends SchemaTableCell {
         //}
     };
 
-    get content() {
-        return <DatePicker dateFormat={'Pp'} selected={this.date.date} onChange={(date: any) => this.handleChange(date)} />;
-        // return !this._pickingDate ? <div onPointerDown={action(() => (this._pickingDate = true))}>{this.defaultCellContent}</div> : <DatePicker dateFormat={'Pp'} selected={this.date.date} onChange={(date: any) => this.handleChange(date)} />;
-    }
-
     render() {
-        return this.getCellWithContent(this.content);
+        return <DatePicker dateFormat={'Pp'} selected={this.date.date} onChange={(date: any) => this.handleChange(date)} />;
     }
 }
-- 
cgit v1.2.3-70-g09d2


From b82a909a63a6de414d075735453240ebc02f5aa3 Mon Sep 17 00:00:00 2001
From: mehekj <mehek.jethani@gmail.com>
Date: Thu, 27 Apr 2023 13:20:52 -0400
Subject: fixed editing schema cells with keyboard only

---
 src/client/views/EditableView.tsx                  |  6 +++++
 .../collectionSchema/CollectionSchemaView.tsx      | 19 +++++++++------
 .../collections/collectionSchema/SchemaRowBox.tsx  |  6 +++--
 .../collectionSchema/SchemaTableCell.tsx           | 28 ++++++++++++++++------
 4 files changed, 43 insertions(+), 16 deletions(-)

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

diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 164b6c57a..d1311a60a 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -106,6 +106,12 @@ export class EditableView extends React.Component<EditableProps> {
             case ':':
                 this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
                 break;
+            case 'ArrowUp':
+            case 'ArrowDown':
+            case 'ArrowLeft':
+            case 'ArrowRight':
+                e.stopPropagation();
+                break;
             case 'Shift':
             case 'Alt':
             case 'Meta':
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 92a04f5ec..8cd307adf 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,6 +1,6 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, ObservableMap, untracked } from 'mobx';
+import { action, computed, observable, ObservableMap, trace, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Doc, DocListCast, Field, StrListCast } from '../../../../fields/Doc';
@@ -170,12 +170,12 @@ export class CollectionSchemaView extends CollectionSubView() {
                         const lastDoc = this._selectedDocs.lastElement();
                         const lastIndex = this.rowIndex(lastDoc);
                         const curDoc = this.sortedDocs.docs[lastIndex];
-                        // const curDoc = this.childDocs[lastIndex];
                         if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) {
                             !e.shiftKey && this.clearSelection();
                             const newDoc = this.sortedDocs.docs[lastIndex + 1];
-                            if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
-                            else {
+                            if (this._selectedDocs.includes(newDoc)) {
+                                SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+                            } else {
                                 this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
                                 this._selectedCell && (this._selectedCell[0] = newDoc);
                                 this.scrollToDoc(newDoc, {});
@@ -189,7 +189,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                     {
                         const firstDoc = this._selectedDocs.lastElement();
                         const firstIndex = this.rowIndex(firstDoc);
-                        // const curDoc = this.childDocs[firstIndex];
                         const curDoc = this.sortedDocs.docs[firstIndex];
                         if (firstIndex > 0 && firstIndex < this.childDocs.length) {
                             !e.shiftKey && this.clearSelection();
@@ -224,7 +223,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                     break;
                 }
                 case 'Escape': {
-                    this._selectedCell = undefined;
+                    this.deselectCell();
                 }
             }
         }
@@ -425,6 +424,11 @@ export class CollectionSchemaView extends CollectionSubView() {
         this._selectedCell = [doc, index];
     };
 
+    @action
+    deselectCell = () => {
+        this._selectedCell = undefined;
+    };
+
     sortedSelectedDocs = () => this.sortedDocs.docs.filter(doc => this._selectedDocs.includes(doc));
 
     setDropIndex = (index: number) => (this._closestDropIndex = index);
@@ -456,7 +460,7 @@ export class CollectionSchemaView extends CollectionSubView() {
             return true;
         }
         const draggedDocs = de.complete.docDragData?.draggedDocuments;
-        if (draggedDocs && super.onInternalDrop(e, de)) {
+        if (draggedDocs && super.onInternalDrop(e, de) && !this.sortField) {
             const pushedDocs = this.childDocs.filter((doc, index) => index >= this._closestDropIndex && !draggedDocs.includes(doc));
             const pushedAndDraggedDocs = [...pushedDocs, ...draggedDocs];
             const removed = this.childDocs.slice().filter(doc => !pushedAndDraggedDocs.includes(doc));
@@ -829,6 +833,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     @computed get sortedDocs() {
+        trace();
         const field = StrCast(this.layoutDoc.sortField);
         const desc = BoolCast(this.layoutDoc.sortDesc);
         const docs = !field
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index f5a16cec0..9864820a3 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -28,6 +28,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as CollectionSchemaView) : undefined;
     }
 
+    schemaViewFunc = () => this.schemaView;
+
     @computed get schemaDoc() {
         return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
     }
@@ -124,11 +126,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             key={key}
                             Document={this.rootDoc}
                             col={index}
-                            schemaView={this.schemaView}
+                            schemaView={this.schemaViewFunc}
                             fieldKey={key}
                             columnWidth={this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth}
                             isRowActive={this.props.isContentActive}
-                            setColumnValues={(field, value) => this.schemaView?.setColumnValues(field, value) ?? false}
+                            // setColumnValues={(field, value) => this.schemaView?.setColumnValues(field, value) ?? false}
                         />
                     ))}
                 </div>
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 2b61ea261..374b92d72 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -21,11 +21,11 @@ import { SelectionManager } from '../../../util/SelectionManager';
 export interface SchemaTableCellProps {
     Document: Doc;
     col: number;
-    schemaView: CollectionSchemaView | undefined;
+    schemaView: () => CollectionSchemaView | undefined;
     fieldKey: string;
     columnWidth: number;
     isRowActive: () => boolean | undefined;
-    setColumnValues: (field: string, value: string) => boolean;
+    // setColumnValues: (field: string, value: string) => boolean;
 }
 
 @observer
@@ -33,20 +33,34 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
     private _editorRef: EditableView | null = null;
 
     @computed get readOnly() {
-        return this.props.schemaView?.fieldInfos[this.props.fieldKey]?.readOnly ?? false;
+        return this.props.schemaView()?.fieldInfos[this.props.fieldKey]?.readOnly ?? false;
     }
 
     @computed get selected() {
-        const selected: [Doc, number] | undefined = this.props.schemaView?._selectedCell;
+        const selected: [Doc, number] | undefined = this.props.schemaView()?._selectedCell;
         return this.props.isRowActive() && selected && selected[0] == this.props.Document && selected[1] == this.props.col;
     }
 
     componentDidUpdate() {
         if (!this.selected) {
             this._editorRef?.setIsFocused(false);
+            document.removeEventListener('keydown', this.onKeyDown);
+        } else if (!this.readOnly) {
+            document.addEventListener('keydown', this.onKeyDown);
         }
     }
 
+    @action
+    onKeyDown = (e: KeyboardEvent) => {
+        e.stopPropagation();
+        if (e.key == 'Enter') {
+            this._editorRef?.setIsFocused(true);
+        }
+        if (e.key == 'Escape') {
+            this.props.schemaView()?.deselectCell();
+        }
+    };
+
     get defaultCellContent() {
         const props: FieldViewProps = {
             Document: this.props.Document,
@@ -81,7 +95,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
                     GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
                     SetValue={(value: string, shiftDown?: boolean, enterKey?: boolean) => {
                         if (shiftDown && enterKey) {
-                            this.props.setColumnValues(this.props.fieldKey, value);
+                            // this.props.setColumnValues(this.props.fieldKey, value);
                         }
                         return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
                     }}
@@ -92,7 +106,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
     }
 
     get getCellType() {
-        const columnTypeStr = this.props.schemaView?.fieldInfos[this.props.fieldKey]?.fieldType;
+        const columnTypeStr = this.props.schemaView()?.fieldInfos[this.props.fieldKey]?.fieldType;
         if (columnTypeStr) {
             if (columnTypeStr in FInfotoColType) {
                 return FInfotoColType[columnTypeStr];
@@ -125,7 +139,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             <div
                 className="schema-table-cell"
                 onPointerDown={action(e => {
-                    if (!this.selected) this.props.schemaView?.selectCell(this.props.Document, this.props.col);
+                    if (!this.selected) this.props.schemaView()?.selectCell(this.props.Document, this.props.col);
                 })}
                 style={{ width: this.props.columnWidth, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
                 {this.content}
-- 
cgit v1.2.3-70-g09d2


From ba5b687011526188bb024ddf37c254aaf285c06d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Apr 2023 16:45:59 -0400
Subject: performance tuning for schema views to avoid re-rendering each table
 cell when child docs change.

---
 .../collectionLinear/CollectionLinearView.tsx      |  20 ++--
 .../collectionSchema/CollectionSchemaView.tsx      | 105 +++++++++------------
 .../collections/collectionSchema/SchemaRowBox.tsx  |  19 ++--
 .../collectionSchema/SchemaTableCell.tsx           |  48 +++++-----
 4 files changed, 94 insertions(+), 98 deletions(-)

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

diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 97eed7752..efd73a927 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -255,15 +255,17 @@ export class CollectionLinearView extends CollectionSubView() {
                         })}
                     />
 
-                    <div
-                        className="collectionLinearView-content"
-                        style={{
-                            height: this.dimension(),
-                            flexDirection: flexDir as any,
-                            gap: flexGap,
-                        }}>
-                        {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
-                    </div>
+                    {!this.layoutDoc.linearViewIsExpanded ? null : (
+                        <div
+                            className="collectionLinearView-content"
+                            style={{
+                                height: this.dimension(),
+                                flexDirection: flexDir as any,
+                                gap: flexGap,
+                            }}>
+                            {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+                        </div>
+                    )}
                 </div>
             </div>
         );
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 8cd307adf..50a91a60a 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,9 +1,9 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, ObservableMap, trace, untracked } from 'mobx';
+import { action, computed, observable, ObservableMap, observe, reaction, trace, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, Field, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, NumListCast, StrListCast } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
@@ -47,6 +47,7 @@ const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate',
 
 @observer
 export class CollectionSchemaView extends CollectionSubView() {
+    private _keysDisposer: any;
     private _closestDropIndex: number = 0;
     private _previewRef: HTMLDivElement | null = null;
     private _makeNewColumn: boolean = false;
@@ -58,15 +59,13 @@ export class CollectionSchemaView extends CollectionSubView() {
     public static _rowMenuWidth: number = 60;
     public static _previewDividerWidth: number = 4;
     public static _newNodeInputHeight: number = 30;
+    public fieldInfos = new ObservableMap<string, FInfo>();
 
-    @computed get _selectedDocs() {
-        return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.props.Document));
-    }
+    @observable _menuKeys: string[] = [];
     @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
     @observable _colEles: HTMLDivElement[] = [];
     @observable _displayColumnWidths: number[] | undefined;
     @observable _columnMenuIndex: number | undefined;
-    @observable _fieldInfos: [string, FInfo][] = [];
     @observable _newFieldWarning: string = '';
     @observable _makeNewField: boolean = false;
     @observable _newFieldDefault: any = 0;
@@ -76,34 +75,12 @@ export class CollectionSchemaView extends CollectionSubView() {
     @observable _filterSearchValue: string = '';
     @observable _selectedCell: [Doc, number] | undefined;
 
-    get fieldInfos() {
-        const docs = this.childDocs;
-        const keys: { [key: string]: boolean } = {};
-        // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.
-        //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be
-        //  invalidated and re-rendered.   This workaround will inquire all of the document fields before the options button is clicked.
-        //  then by the time the options button is clicked, all of the fields should be in place.  If a new field is added while this menu
-        //  is displayed (unlikely) it won't show up until something else changes.
-        //TODO Types
-        untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)))));
-
-        let computedKeys: { [key: string]: FInfo } = {};
-
-        Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => {
-            computedKeys[pair[0]] = pair[1];
-        });
-
-        Object.keys(keys).forEach((key: string) => {
-            if (!(key in computedKeys)) {
-                computedKeys[key] = new FInfo('');
-            }
-        });
-
-        return computedKeys;
+    @computed get _selectedDocs() {
+        return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.rootDoc));
     }
 
-    get documentKeys() {
-        return Object.keys(this.fieldInfos);
+    @computed get documentKeys() {
+        return Array.from(this.fieldInfos.keys());
     }
 
     @computed get previewWidth() {
@@ -119,20 +96,15 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     @computed get storedColumnWidths() {
-        let widths = Cast(
+        const widths = NumListCast(
             this.layoutDoc.columnWidths,
-            listSpec('number'),
             this.columnKeys.map(() => (this.tableWidth - CollectionSchemaView._rowMenuWidth) / this.columnKeys.length)
         );
 
         const totalWidth = widths.reduce((sum, width) => sum + width, 0);
         if (totalWidth !== this.tableWidth - CollectionSchemaView._rowMenuWidth) {
-            widths = widths.map(w => {
-                const proportion = w / totalWidth;
-                return proportion * (this.tableWidth - CollectionSchemaView._rowMenuWidth);
-            });
+            return widths.map(w => (w / totalWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth));
         }
-
         return widths;
     }
 
@@ -148,19 +120,37 @@ export class CollectionSchemaView extends CollectionSubView() {
         return BoolCast(this.layoutDoc.sortDesc);
     }
 
-    rowIndex(doc: Doc) {
-        return this.sortedDocs.docs.indexOf(doc);
-    }
-
+    @action
     componentDidMount() {
         this.props.setContentView?.(this);
         document.addEventListener('keydown', this.onKeyDown);
+
+        Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
+        this._keysDisposer = observe(
+            this.rootDoc[this.fieldKey ?? 'data'] as List<Doc>,
+            change => {
+                switch (change.type as any) {
+                    case 'splice':
+                        // prettier-ignore
+                        (change as any).added.forEach((doc: Doc) => // for each document added 
+                            Doc.GetAllPrototypes(doc).forEach(proto => // for all of its prototypes (and itself)
+                                Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
+                                    !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(''))))));
+                        break;
+                    case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+                }
+            },
+            true
+        );
     }
 
     componentWillUnmount() {
+        this._keysDisposer?.();
         document.removeEventListener('keydown', this.onKeyDown);
     }
 
+    rowIndex = (doc: Doc) => this.sortedDocs.docs.indexOf(doc);
+
     @action
     onKeyDown = (e: KeyboardEvent) => {
         if (this._selectedDocs.length > 0) {
@@ -554,8 +544,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     onSearchKeyDown = (e: React.KeyboardEvent) => {
         switch (e.key) {
             case 'Enter':
-                const menuKeys = Object.keys(this._fieldInfos);
-                menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(menuKeys[0]) : action(() => (this._makeNewField = true))();
+                this._menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuKeys[0]) : action(() => (this._makeNewField = true))();
                 break;
             case 'Escape':
                 this.closeColumnMenu();
@@ -584,7 +573,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         this._makeNewColumn = false;
         this._columnMenuIndex = index;
         this._menuValue = '';
-        this._fieldInfos = Object.entries(this.fieldInfos);
+        this._menuKeys = this.documentKeys;
         this._makeNewField = false;
         this._newFieldWarning = '';
         this._makeNewField = false;
@@ -630,7 +619,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action
     updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => {
         this._menuValue = e.target.value;
-        this._fieldInfos = Object.entries(this.fieldInfos).filter(value => value[0].toLowerCase().includes(this._menuValue.toLowerCase()));
+        this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
     };
 
     getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
@@ -732,7 +721,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                             { passive: false }
                         )
                     }>
-                    {this._fieldInfos.map(([key, info]) => (
+                    {this._menuKeys.map(key => (
                         <div
                             className="schema-search-result"
                             onPointerDown={e => {
@@ -742,13 +731,13 @@ export class CollectionSchemaView extends CollectionSubView() {
                             <p>
                                 <span className="schema-search-result-key">
                                     {key}
-                                    {info.fieldType ? ', ' : ''}
+                                    {this.fieldInfos.get(key)!.fieldType ? ', ' : ''}
                                 </span>
-                                <span className="schema-search-result-type" style={{ color: info.readOnly ? 'red' : 'inherit' }}>
-                                    {info.fieldType}
+                                <span className="schema-search-result-type" style={{ color: this.fieldInfos.get(key)!.readOnly ? 'red' : 'inherit' }}>
+                                    {this.fieldInfos.get(key)!.fieldType}
                                 </span>
                             </p>
-                            <p className="schema-search-result-desc">{info.description}</p>
+                            <p className="schema-search-result-desc">{this.fieldInfos.get(key)!.description}</p>
                         </div>
                     ))}
                 </div>
@@ -888,13 +877,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                     </div>
                     {this._columnMenuIndex !== undefined && this.renderColumnMenu}
                     {this._filterColumnIndex !== undefined && this.renderFilterMenu}
-                    <CollectionSchemaViewDocs
-                        schema={this}
-                        childDocs={this.sortedDocsFunc}
-                        setRef={(ref: HTMLDivElement | null) => {
-                            this._tableContentRef = ref;
-                        }}
-                    />
+                    <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
                     <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} height={CollectionSchemaView._newNodeInputHeight} />
                 </div>
                 {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
@@ -923,7 +906,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 moveDocument={this.props.moveDocument}
                                 addDocument={this.addRow}
                                 removeDocument={this.props.removeDocument}
-                                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+                                whenChildContentsActiveChanged={returnFalse}
                                 addDocTab={this.props.addDocTab}
                                 pinToPres={this.props.pinToPres}
                                 bringToFront={returnFalse}
@@ -978,7 +961,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                                 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)}
+                                whenChildContentsActiveChanged={this.props.schema.props.whenChildContentsActiveChanged}
                                 hideDecorations={true}
                                 hideTitle={true}
                                 hideDocumentButtonBar={true}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 9864820a3..9772ce118 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -12,6 +12,8 @@ import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { CollectionSchemaView } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 import { SchemaTableCell } from './SchemaTableCell';
+import { computedFn } from 'mobx-utils';
+import { Doc } from '../../../../fields/Doc';
 
 @observer
 export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -24,12 +26,9 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
     bounds = () => this._ref?.getBoundingClientRect();
 
     @computed get schemaView() {
-        const vpath = this.props.docViewPath();
-        return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as CollectionSchemaView) : undefined;
+        return this.props.DocumentView?.().props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
     }
 
-    schemaViewFunc = () => this.schemaView;
-
     @computed get schemaDoc() {
         return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
     }
@@ -86,6 +85,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
         document.removeEventListener('pointermove', this.onPointerMove);
     };
 
+    getFinfo = computedFn((fieldKey: string) => this.schemaView?.fieldInfos.get(fieldKey));
+    selectCell = (doc: Doc, col: number) => this.schemaView?.selectCell(doc, col);
+    deselectCell = () => this.schemaView?.deselectCell();
+    selectedCell = () => this.schemaView?._selectedCell;
+    setColumnValues = (field: any, value: any) => this.schemaView?.setColumnValues(field, value) ?? false;
     render() {
         return (
             <div
@@ -126,11 +130,14 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             key={key}
                             Document={this.rootDoc}
                             col={index}
-                            schemaView={this.schemaViewFunc}
                             fieldKey={key}
                             columnWidth={this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth}
                             isRowActive={this.props.isContentActive}
-                            // setColumnValues={(field, value) => this.schemaView?.setColumnValues(field, value) ?? false}
+                            getFinfo={this.getFinfo}
+                            selectCell={this.selectCell}
+                            deselectCell={this.deselectCell}
+                            selectedCell={this.selectedCell}
+                            setColumnValues={this.setColumnValues}
                         />
                     ))}
                 </div>
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 374b92d72..686b21283 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,31 +1,34 @@
 import React = require('react');
+import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
+import { extname } from 'path';
+import DatePicker from 'react-datepicker';
+import { DateField } from '../../../../fields/DateField';
+import { Doc, DocListCast, Field } from '../../../../fields/Doc';
+import { Cast, DateCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils';
 import { Transform } from '../../../util/Transform';
 import { EditableView } from '../../EditableView';
+import { Colors } from '../../global/globalEnums';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { DefaultStyleProvider } from '../../StyleProvider';
 import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
-import { action, computed, observable } from 'mobx';
-import { extname } from 'path';
-import { Cast, DateCast, StrCast } from '../../../../fields/Types';
-import { ImageField } from '../../../../fields/URLField';
-import { DateField } from '../../../../fields/DateField';
-import DatePicker from 'react-datepicker';
-import { Colors } from '../../global/globalEnums';
-import { SelectionManager } from '../../../util/SelectionManager';
+import { FInfo } from '../../../documents/Documents';
 
 export interface SchemaTableCellProps {
     Document: Doc;
     col: number;
-    schemaView: () => CollectionSchemaView | undefined;
+    deselectCell: () => void;
+    selectCell: (doc: Doc, col: number) => void;
+    selectedCell: () => [Doc, number] | undefined;
     fieldKey: string;
     columnWidth: number;
     isRowActive: () => boolean | undefined;
-    // setColumnValues: (field: string, value: string) => boolean;
+    getFinfo: (fieldKey: string) => FInfo | undefined;
+    setColumnValues: (field: string, value: string) => boolean;
 }
 
 @observer
@@ -33,11 +36,11 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
     private _editorRef: EditableView | null = null;
 
     @computed get readOnly() {
-        return this.props.schemaView()?.fieldInfos[this.props.fieldKey]?.readOnly ?? false;
+        return this.props.getFinfo(this.props.fieldKey)?.readOnly ?? false;
     }
 
     @computed get selected() {
-        const selected: [Doc, number] | undefined = this.props.schemaView()?._selectedCell;
+        const selected: [Doc, number] | undefined = this.props.selectedCell();
         return this.props.isRowActive() && selected && selected[0] == this.props.Document && selected[1] == this.props.col;
     }
 
@@ -57,11 +60,12 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             this._editorRef?.setIsFocused(true);
         }
         if (e.key == 'Escape') {
-            this.props.schemaView()?.deselectCell();
+            this.props.deselectCell();
         }
     };
-
-    get defaultCellContent() {
+    colWidthFunc = () => this.props.columnWidth;
+    colRowHeightFunc = () => CollectionSchemaView._rowHeight;
+    @computed get defaultCellContent() {
         const props: FieldViewProps = {
             Document: this.props.Document,
             docFilters: returnEmptyFilter,
@@ -81,8 +85,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             whenChildContentsActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
-            PanelWidth: () => this.props.columnWidth,
-            PanelHeight: () => CollectionSchemaView._rowHeight,
+            PanelWidth: this.colWidthFunc,
+            PanelHeight: this.colRowHeightFunc,
             addDocTab: returnFalse,
             pinToPres: returnZero,
         };
@@ -95,7 +99,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
                     GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
                     SetValue={(value: string, shiftDown?: boolean, enterKey?: boolean) => {
                         if (shiftDown && enterKey) {
-                            // this.props.setColumnValues(this.props.fieldKey, value);
+                            this.props.setColumnValues(this.props.fieldKey, value);
                         }
                         return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
                     }}
@@ -106,7 +110,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
     }
 
     get getCellType() {
-        const columnTypeStr = this.props.schemaView()?.fieldInfos[this.props.fieldKey]?.fieldType;
+        const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
         if (columnTypeStr) {
             if (columnTypeStr in FInfotoColType) {
                 return FInfotoColType[columnTypeStr];
@@ -139,7 +143,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             <div
                 className="schema-table-cell"
                 onPointerDown={action(e => {
-                    if (!this.selected) this.props.schemaView()?.selectCell(this.props.Document, this.props.col);
+                    if (!this.selected) this.props.selectCell(this.props.Document, this.props.col);
                 })}
                 style={{ width: this.props.columnWidth, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
                 {this.content}
-- 
cgit v1.2.3-70-g09d2


From 223dde9f35408c229a4da583083d10cbf81fc264 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Apr 2023 22:23:42 -0400
Subject: fixed some undo issues with schema fill down.  added bool cell view. 
 added UI for showing/setting values on layout vs data docs.

---
 .../collections/collectionSchema/SchemaRowBox.tsx  |   3 +-
 .../collectionSchema/SchemaTableCell.tsx           | 156 ++++++++++++---------
 src/client/views/nodes/KeyValueBox.tsx             |   2 -
 3 files changed, 89 insertions(+), 72 deletions(-)

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

diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 9772ce118..ca9e0bda0 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -90,6 +90,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
     deselectCell = () => this.schemaView?.deselectCell();
     selectedCell = () => this.schemaView?._selectedCell;
     setColumnValues = (field: any, value: any) => this.schemaView?.setColumnValues(field, value) ?? false;
+    columnWidth = computedFn((index: number) => () => this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth);
     render() {
         return (
             <div
@@ -131,7 +132,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             Document={this.rootDoc}
                             col={index}
                             fieldKey={key}
-                            columnWidth={this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth}
+                            columnWidth={this.columnWidth(index)}
                             isRowActive={this.props.isContentActive}
                             getFinfo={this.getFinfo}
                             selectCell={this.selectCell}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 4f4986b90..1fa4312e1 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -5,10 +5,13 @@ import { extname } from 'path';
 import DatePicker from 'react-datepicker';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field } from '../../../../fields/Doc';
-import { Cast, DateCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DateCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils';
+import { FInfo } from '../../../documents/Documents';
+import { dropActionType } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
@@ -16,7 +19,6 @@ import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { DefaultStyleProvider } from '../../StyleProvider';
 import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
-import { FInfo } from '../../../documents/Documents';
 
 export interface SchemaTableCellProps {
     Document: Doc;
@@ -25,7 +27,7 @@ export interface SchemaTableCellProps {
     selectCell: (doc: Doc, col: number) => void;
     selectedCell: () => [Doc, number] | undefined;
     fieldKey: string;
-    columnWidth: number;
+    columnWidth: () => number;
     isRowActive: () => boolean | undefined;
     getFinfo: (fieldKey: string) => FInfo | undefined;
     setColumnValues: (field: string, value: string) => boolean;
@@ -33,95 +35,78 @@ export interface SchemaTableCellProps {
 
 @observer
 export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
-    private _editorRef: EditableView | null = null;
-
-    @computed get readOnly() {
-        return this.props.getFinfo(this.props.fieldKey)?.readOnly ?? false;
-    }
-
-    @computed get selected() {
-        const selected: [Doc, number] | undefined = this.props.selectedCell();
-        return this.props.isRowActive() && selected && selected[0] == this.props.Document && selected[1] == this.props.col;
+    public static colRowHeightFunc() {
+        return CollectionSchemaView._rowHeight;
     }
-
-    componentDidUpdate() {
-        if (!this.selected) {
-            this._editorRef?.setIsFocused(false);
-            document.removeEventListener('keydown', this.onKeyDown);
-        } else if (!this.readOnly) {
-            document.addEventListener('keydown', this.onKeyDown);
-        }
-    }
-
-    @action
-    onKeyDown = (e: KeyboardEvent) => {
-        e.stopPropagation();
-        if (e.key == 'Enter') {
-            this._editorRef?.setIsFocused(true);
-        }
-        if (e.key == 'Escape') {
-            this.props.deselectCell();
+    public static renderProps(props: SchemaTableCellProps) {
+        const { Document, fieldKey, getFinfo, columnWidth, isRowActive } = props;
+        let protoCount = 0;
+        let doc: Doc | undefined = Document;
+        while (doc) {
+            if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) {
+                break;
+            }
+            protoCount++;
+            doc = doc.proto;
         }
-    };
-    colWidthFunc = () => this.props.columnWidth;
-    colRowHeightFunc = () => CollectionSchemaView._rowHeight;
-    @computed get defaultCellContent() {
-        const props: FieldViewProps = {
-            Document: this.props.Document,
+        const parenCount = Math.max(0, protoCount - 1);
+        const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue';
+        const textDecoration = color !== 'black' && parenCount ? 'underline' : '';
+        const fieldProps: FieldViewProps = {
             docFilters: returnEmptyFilter,
             docRangeFilters: returnEmptyFilter,
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             docViewPath: returnEmptyDoclist,
-            fieldKey: this.props.fieldKey,
             rootSelected: returnFalse,
             isSelected: returnFalse,
             setHeight: returnFalse,
             select: emptyFunction,
-            dropAction: 'alias',
+            dropAction: 'alias' as dropActionType,
             bringToFront: emptyFunction,
             renderDepth: 1,
             isContentActive: returnFalse,
             whenChildContentsActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
-            PanelWidth: this.colWidthFunc,
-            PanelHeight: this.colRowHeightFunc,
             addDocTab: returnFalse,
             pinToPres: returnZero,
+            Document,
+            fieldKey,
+            PanelWidth: columnWidth,
+            PanelHeight: SchemaTableCell.colRowHeightFunc,
         };
-        let protoCount = 0;
-        let doc: Doc | undefined = this.props.Document;
-        while (doc) {
-            if (Object.keys(doc).includes(this.props.fieldKey.replace(/^_/, ''))) {
-                break;
-            }
-            protoCount++;
-            doc = doc.proto;
-        }
-        const parenCount = Math.max(0, protoCount - 1);
-        const color = protoCount === 0 ? 'black' : 'blue';
+        const readOnly = getFinfo(fieldKey)?.readOnly ?? false;
+        const cursor = !readOnly ? 'text' : 'default';
+        const pointerEvents: 'all' | 'none' = !readOnly && isRowActive() ? 'all' : 'none';
+        return { color, textDecoration, fieldProps, cursor, pointerEvents };
+    }
+
+    @computed get selected() {
+        const selected: [Doc, number] | undefined = this.props.selectedCell();
+        return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+    }
+
+    @computed get defaultCellContent() {
+        const { color, textDecoration, fieldProps } = SchemaTableCell.renderProps(this.props);
 
         return (
             <div
                 className="schemacell-edit-wrapper"
                 style={{
                     color,
-                    textDecoration: parenCount ? 'underline' : undefined,
-                    cursor: !this.readOnly ? 'text' : 'default',
-                    pointerEvents: !this.readOnly && this.props.isRowActive() ? 'all' : 'none',
+                    textDecoration,
                 }}>
                 <EditableView
-                    ref={ref => (this._editorRef = ref)}
-                    contents={<FieldView {...props} />}
+                    contents={<FieldView {...fieldProps} />}
+                    editing={this.selected ? undefined : false}
                     GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
-                    SetValue={(value: string, shiftDown?: boolean, enterKey?: boolean) => {
+                    SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => {
                         if (shiftDown && enterKey) {
                             this.props.setColumnValues(this.props.fieldKey, value);
                         }
                         return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
-                    }}
-                    editing={this.selected ? undefined : false}
+                    })}
                 />
             </div>
         );
@@ -146,13 +131,12 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
 
     get content() {
         const cellType: ColumnType = this.getCellType;
+        // prettier-ignore
         switch (cellType) {
-            case ColumnType.Image:
-                return <SchemaImageCell {...this.props} />;
-            case ColumnType.Date:
-            // return <SchemaDateCell {...this.props} />;
-            default:
-                return this.defaultCellContent;
+            case ColumnType.Image: return <SchemaImageCell {...this.props} />;
+            case ColumnType.Boolean: return <SchemaBoolCell {...this.props} />;
+            case ColumnType.Date: // return <SchemaDateCell {...this.props} />;
+            default:  return this.defaultCellContent;
         }
     }
 
@@ -160,10 +144,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         return (
             <div
                 className="schema-table-cell"
-                onPointerDown={action(e => {
-                    if (!this.selected) this.props.selectCell(this.props.Document, this.props.col);
-                })}
-                style={{ width: this.props.columnWidth, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
+                onPointerDown={action(e => !this.selected && this.props.selectCell(this.props.Document, this.props.col))}
+                style={{ width: this.props.columnWidth(), border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
                 {this.content}
             </div>
         );
@@ -257,3 +239,39 @@ export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
         return <DatePicker dateFormat={'Pp'} selected={this.date.date} onChange={(date: any) => this.handleChange(date)} />;
     }
 }
+@observer
+export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
+    @computed get selected() {
+        const selected: [Doc, number] | undefined = this.props.selectedCell();
+        return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+    }
+    render() {
+        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
+        return (
+            <div className="schemaBoolCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}>
+                <input
+                    style={{ marginRight: 4 }}
+                    type="checkbox"
+                    checked={BoolCast(this.props.Document[this.props.fieldKey])}
+                    onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
+                        if ((value?.nativeEvent as any).shiftKey) {
+                            this.props.setColumnValues(this.props.fieldKey, (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+                        }
+                        KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+                    })}
+                />
+                <EditableView
+                    contents={<FieldView {...fieldProps} />}
+                    editing={this.selected ? undefined : false}
+                    GetValue={() => (color === 'black' ? '=' : '') + Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+                    SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => {
+                        if (shiftDown && enterKey) {
+                            this.props.setColumnValues(this.props.fieldKey, value);
+                        }
+                        return KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
+                    })}
+                />
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 57018fb93..11220c300 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -20,14 +20,12 @@ import { ImageBox } from './ImageBox';
 import './KeyValueBox.scss';
 import { KeyValuePair } from './KeyValuePair';
 import React = require('react');
-import e = require('express');
 
 export type KVPScript = {
     script: CompiledScript;
     type: 'computed' | 'script' | false;
     onDelegate: boolean;
 };
-
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
     public static LayoutString() {
-- 
cgit v1.2.3-70-g09d2

