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/SchemaTableCell.tsx | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 13e45963e..42c9f9d48 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -9,9 +9,11 @@ import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { DefaultStyleProvider } from '../../StyleProvider';
 import { CollectionSchemaView } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
+import { computed } from 'mobx';
 
 export interface SchemaTableCellProps {
     Document: Doc;
+    schemaView: CollectionSchemaView | undefined;
     fieldKey: string;
     columnWidth: number;
     isRowActive: () => boolean | undefined;
@@ -20,6 +22,10 @@ export interface SchemaTableCellProps {
 
 @observer
 export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
+    get readOnly() {
+        return this.props.schemaView?.keyInfos[this.props.fieldKey].readOnly;
+    }
+
     render() {
         const props: FieldViewProps = {
             Document: this.props.Document,
@@ -50,7 +56,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
 
         return (
             <div className="schema-table-cell" style={{ width: this.props.columnWidth }}>
-                <div className="schemacell-edit-wrapper" style={this.props.isRowActive() ? { cursor: 'text', pointerEvents: 'auto' } : { cursor: 'default', pointerEvents: 'none' }}>
+                <div className="schemacell-edit-wrapper" style={this.props.isRowActive() && !this.readOnly ? { cursor: 'text', pointerEvents: 'auto' } : { cursor: 'default', pointerEvents: 'none' }}>
                     <EditableView
                         contents={<FieldView {...props} />}
                         GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
-- 
cgit v1.2.3-70-g09d2


From 79791c294e948bc5e9f5799b12dec138c7d8b371 Mon Sep 17 00:00:00 2001
From: mehekj <mehek.jethani@gmail.com>
Date: Thu, 20 Apr 2023 01:13:07 -0400
Subject: added schema cell types for images and dates

---
 package-lock.json                                  | 201 ++++++++++++---------
 package.json                                       |   4 +-
 src/client/documents/Documents.ts                  |   4 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  44 +++--
 .../collectionSchema/SchemaTableCell.tsx           | 158 ++++++++++++++--
 5 files changed, 289 insertions(+), 122 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 5f81e1f0f..7d2ef9509 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5580,6 +5580,16 @@
             "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
             "dev": true
         },
+        "d": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+            "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+            "dev": true,
+            "requires": {
+                "es5-ext": "^0.10.50",
+                "type": "^1.0.1"
+            }
+        },
         "d3": {
             "version": "7.8.4",
             "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz",
@@ -6960,6 +6970,28 @@
                 "is-symbol": "^1.0.2"
             }
         },
+        "es5-ext": {
+            "version": "0.10.62",
+            "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
+            "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
+            "dev": true,
+            "requires": {
+                "es6-iterator": "^2.0.3",
+                "es6-symbol": "^3.1.3",
+                "next-tick": "^1.1.0"
+            }
+        },
+        "es6-iterator": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+            "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
+            "dev": true,
+            "requires": {
+                "d": "1",
+                "es5-ext": "^0.10.35",
+                "es6-symbol": "^3.1.1"
+            }
+        },
         "es6-promise": {
             "version": "3.2.1",
             "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
@@ -6971,6 +7003,7 @@
             "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
             "dev": true,
             "requires": {
+                "d": "^1.0.1",
                 "ext": "^1.1.2"
             }
         },
@@ -14015,7 +14048,7 @@
             "dependencies": {
                 "@iarna/cli": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/@iarna/cli/-/cli-2.1.0.tgz",
                     "integrity": "sha512-rvVVqDa2g860niRbqs3D5RhL4la3dc1vwk+NlpKPZxKaMSHtE2se6C2x8NeveN+rcjp3/686X+u+09CZ+7lmAQ==",
                     "requires": {
                         "glob": "^7.1.2",
@@ -14118,7 +14151,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -14133,7 +14166,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -14142,12 +14175,12 @@
                 },
                 "asap": {
                     "version": "2.0.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
                     "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
                 },
                 "asn1": {
                     "version": "0.2.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
                     "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
                     "requires": {
                         "safer-buffer": "~2.1.0"
@@ -14155,7 +14188,7 @@
                 },
                 "assert-plus": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
                     "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
                 },
                 "asynckit": {
@@ -14165,22 +14198,22 @@
                 },
                 "aws-sign2": {
                     "version": "0.7.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
                     "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="
                 },
                 "aws4": {
                     "version": "1.11.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
                     "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
                 },
                 "balanced-match": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
                     "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
                 },
                 "bcrypt-pbkdf": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
                     "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
                     "requires": {
                         "tweetnacl": "^0.14.3"
@@ -14201,7 +14234,7 @@
                 },
                 "bluebird": {
                     "version": "3.7.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
                     "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
                 },
                 "boxen": {
@@ -14249,7 +14282,7 @@
                 },
                 "cacache": {
                     "version": "12.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
                     "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
                     "requires": {
                         "bluebird": "^3.5.5",
@@ -14286,7 +14319,7 @@
                 },
                 "caseless": {
                     "version": "0.12.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
                     "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
                 },
                 "chalk": {
@@ -14430,7 +14463,7 @@
                 },
                 "combined-stream": {
                     "version": "1.0.8",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
                     "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
                     "requires": {
                         "delayed-stream": "~1.0.0"
@@ -14438,7 +14471,7 @@
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
                     "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
                 },
                 "concat-stream": {
@@ -14468,7 +14501,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -14483,7 +14516,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -14492,7 +14525,7 @@
                 },
                 "config-chain": {
                     "version": "1.1.13",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
                     "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
                     "requires": {
                         "ini": "^1.3.4",
@@ -14593,7 +14626,7 @@
                 },
                 "dashdash": {
                     "version": "1.14.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
                     "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
                     "requires": {
                         "assert-plus": "^1.0.0"
@@ -14626,7 +14659,7 @@
                 },
                 "decode-uri-component": {
                     "version": "0.2.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
                     "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
                 },
                 "deep-extend": {
@@ -14672,7 +14705,7 @@
                 },
                 "dezalgo": {
                     "version": "1.0.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
                     "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
                     "requires": {
                         "asap": "^2.0.0",
@@ -14724,7 +14757,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -14739,7 +14772,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -14748,7 +14781,7 @@
                 },
                 "ecc-jsbn": {
                     "version": "0.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
                     "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
                     "requires": {
                         "jsbn": "~0.1.0",
@@ -14783,7 +14816,7 @@
                 },
                 "env-paths": {
                     "version": "2.2.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
                     "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="
                 },
                 "err-code": {
@@ -14867,7 +14900,7 @@
                 },
                 "extsprintf": {
                     "version": "1.3.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
                     "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="
                 },
                 "fast-json-stable-stringify": {
@@ -14877,12 +14910,12 @@
                 },
                 "figgy-pudding": {
                     "version": "3.5.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
                     "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw=="
                 },
                 "filter-obj": {
                     "version": "1.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
                     "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
                 },
                 "find-npm-prefix": {
@@ -14915,7 +14948,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -14930,7 +14963,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -14939,12 +14972,12 @@
                 },
                 "forever-agent": {
                     "version": "0.6.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
                     "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
                 },
                 "form-data": {
                     "version": "2.3.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
                     "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
                     "requires": {
                         "asynckit": "^0.4.0",
@@ -14977,7 +15010,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -14992,7 +15025,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -15060,7 +15093,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -15075,7 +15108,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -15084,7 +15117,7 @@
                 },
                 "fs.realpath": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
                     "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
                 },
                 "function-bind": {
@@ -15174,7 +15207,7 @@
                 },
                 "getpass": {
                     "version": "0.1.7",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
                     "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
                     "requires": {
                         "assert-plus": "^1.0.0"
@@ -15182,7 +15215,7 @@
                 },
                 "glob": {
                     "version": "7.2.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
                     "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
                     "requires": {
                         "fs.realpath": "^1.0.0",
@@ -15195,7 +15228,7 @@
                     "dependencies": {
                         "minimatch": {
                             "version": "3.1.2",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
                             "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
                             "requires": {
                                 "brace-expansion": "^1.1.7"
@@ -15238,12 +15271,12 @@
                 },
                 "graceful-fs": {
                     "version": "4.2.10",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
                     "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
                 },
                 "har-schema": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
                     "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q=="
                 },
                 "har-validator": {
@@ -15322,7 +15355,7 @@
                 },
                 "http-signature": {
                     "version": "1.2.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
                     "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
                     "requires": {
                         "assert-plus": "^1.0.0",
@@ -15449,7 +15482,7 @@
                 },
                 "is-cidr": {
                     "version": "3.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-3.1.1.tgz",
                     "integrity": "sha512-Gx+oErgq1j2jAKCR2Kbq0b3wbH0vQKqZ0wOlHxm0o56nq51Cs/DZA8oz9dMDhbHyHEGgJ86eTeVudtgMMOx3Mw==",
                     "requires": {
                         "cidr-regex": "^2.0.10"
@@ -15528,7 +15561,7 @@
                 },
                 "is-typedarray": {
                     "version": "1.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
                     "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
                 },
                 "isarray": {
@@ -15543,12 +15576,12 @@
                 },
                 "isstream": {
                     "version": "0.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
                     "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
                 },
                 "jsbn": {
                     "version": "0.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
                     "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
                 },
                 "json-parse-better-errors": {
@@ -15558,7 +15591,7 @@
                 },
                 "json-parse-even-better-errors": {
                     "version": "2.3.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
                     "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
                 },
                 "json-schema": {
@@ -15568,7 +15601,7 @@
                 },
                 "json-stringify-safe": {
                     "version": "5.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
                     "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
                 },
                 "jsonparse": {
@@ -15786,7 +15819,7 @@
                 },
                 "lock-verify": {
                     "version": "2.2.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.2.2.tgz",
                     "integrity": "sha512-2CUNtr1ZSVKJHcYP8uEzafmmuyauCB5zZimj8TvQd/Lflt9kXVZs+8S+EbAzZLaVUDn8CYGmeC3DFGdYfnCzeQ==",
                     "requires": {
                         "@iarna/cli": "^2.1.0",
@@ -15915,7 +15948,7 @@
                 },
                 "meant": {
                     "version": "1.0.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.3.tgz",
                     "integrity": "sha512-88ZRGcNxAq4EH38cQ4D85PM57pikCwS8Z99EWHODxN7KBY+UuPiqzRTtZzS8KTXO/ywSWbdjjJST2Hly/EQxLw=="
                 },
                 "mime-db": {
@@ -15933,7 +15966,7 @@
                 },
                 "minimatch": {
                     "version": "3.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
                     "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
                     "requires": {
                         "brace-expansion": "^1.1.7"
@@ -15982,7 +16015,7 @@
                 },
                 "mkdirp": {
                     "version": "0.5.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
                     "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
                     "requires": {
                         "minimist": "^1.2.6"
@@ -16030,7 +16063,7 @@
                 },
                 "node-gyp": {
                     "version": "5.1.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.1.tgz",
                     "integrity": "sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw==",
                     "requires": {
                         "env-paths": "^2.2.0",
@@ -16368,7 +16401,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -16383,7 +16416,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -16397,7 +16430,7 @@
                 },
                 "path-is-absolute": {
                     "version": "1.0.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
                     "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
                 },
                 "path-is-inside": {
@@ -16417,7 +16450,7 @@
                 },
                 "performance-now": {
                     "version": "2.1.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
                     "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
                 },
                 "pify": {
@@ -16466,7 +16499,7 @@
                 },
                 "proto-list": {
                     "version": "1.2.4",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
                     "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="
                 },
                 "protoduck": {
@@ -16489,7 +16522,7 @@
                 },
                 "psl": {
                     "version": "1.9.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
                     "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
                 },
                 "pump": {
@@ -16529,12 +16562,12 @@
                 },
                 "qs": {
                     "version": "6.5.3",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
                     "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
                 },
                 "query-string": {
                     "version": "6.14.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz",
                     "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==",
                     "requires": {
                         "decode-uri-component": "^0.2.0",
@@ -16545,7 +16578,7 @@
                 },
                 "qw": {
                     "version": "1.0.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/qw/-/qw-1.0.2.tgz",
                     "integrity": "sha512-1PhZ/iLKwlVNq45dnerTMKFjMof49uqli7/0QsvPNbX5OJ3IZ8msa9lUpvPheVdP+IYYPrf6cOaVil7S35joVA=="
                 },
                 "rc": {
@@ -16591,7 +16624,7 @@
                 },
                 "read-package-json": {
                     "version": "2.1.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz",
                     "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==",
                     "requires": {
                         "glob": "^7.1.1",
@@ -16650,7 +16683,7 @@
                 },
                 "request": {
                     "version": "2.88.2",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
                     "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
                     "requires": {
                         "aws-sign2": "~0.7.0",
@@ -16720,7 +16753,7 @@
                 },
                 "safe-buffer": {
                     "version": "5.2.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
                     "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
                 },
                 "safer-buffer": {
@@ -16891,7 +16924,7 @@
                 },
                 "sshpk": {
                     "version": "1.17.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
                     "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
                     "requires": {
                         "asn1": "~0.2.3",
@@ -16947,7 +16980,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -16962,7 +16995,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -16976,7 +17009,7 @@
                 },
                 "strict-uri-encode": {
                     "version": "2.0.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
                     "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
                 },
                 "string-width": {
@@ -17132,7 +17165,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -17147,7 +17180,7 @@
                             "dependencies": {
                                 "safe-buffer": {
                                     "version": "5.1.2",
-                                    "resolved": false,
+                                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
                                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                                 }
                             }
@@ -17166,7 +17199,7 @@
                 },
                 "tough-cookie": {
                     "version": "2.5.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
                     "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
                     "requires": {
                         "psl": "^1.1.28",
@@ -17175,7 +17208,7 @@
                     "dependencies": {
                         "punycode": {
                             "version": "2.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
                             "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
                         }
                     }
@@ -17190,7 +17223,7 @@
                 },
                 "tweetnacl": {
                     "version": "0.14.5",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
                     "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
                 },
                 "typedarray": {
@@ -17261,7 +17294,7 @@
                 },
                 "uri-js": {
                     "version": "4.4.1",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
                     "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
                     "requires": {
                         "punycode": "^2.1.0"
@@ -17302,7 +17335,7 @@
                 },
                 "uuid": {
                     "version": "3.4.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
                     "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
                 },
                 "validate-npm-package-license": {
@@ -17324,7 +17357,7 @@
                 },
                 "verror": {
                     "version": "1.10.0",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
                     "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
                     "requires": {
                         "assert-plus": "^1.0.0",
@@ -17832,9 +17865,9 @@
             }
         },
         "openai": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/openai/-/openai-3.1.0.tgz",
-            "integrity": "sha512-v5kKFH5o+8ld+t0arudj833Mgm3GcgBnbyN9946bj6u7bvel4Yg6YFz2A4HLIYDzmMjIo0s6vSG9x73kOwvdCg==",
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz",
+            "integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==",
             "requires": {
                 "axios": "^0.26.0",
                 "form-data": "^4.0.0"
@@ -22643,6 +22676,12 @@
             "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
             "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
         },
+        "type": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+            "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
+            "dev": true
+        },
         "type-check": {
             "version": "0.4.0",
             "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index c1ff9fe92..70dcc1845 100644
--- a/package.json
+++ b/package.json
@@ -157,6 +157,7 @@
         "@types/three": "^0.126.2",
         "@types/web": "0.0.53",
         "@webscopeio/react-textarea-autocomplete": "^4.9.1",
+        "D": "^1.0.0",
         "adm-zip": "^0.4.16",
         "archiver": "^3.1.1",
         "array-batcher": "^1.2.3",
@@ -189,7 +190,6 @@
         "csv-parser": "^3.0.0",
         "csv-stringify": "^6.3.0",
         "d3": "^7.6.1",
-        "D": "^1.0.0",
         "depcheck": "^0.9.2",
         "equation-editor-react": "github:bobzel/equation-editor-react#useLocally",
         "exif": "^0.6.0",
@@ -246,7 +246,7 @@
         "nodemon": "^1.19.4",
         "normalize.css": "^8.0.1",
         "npm": "^6.14.18",
-        "openai": "^3.1.0",
+        "openai": "^3.2.1",
         "p-limit": "^2.2.0",
         "passport": "^0.4.0",
         "passport-google-oauth20": "^2.0.0",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index bd878ca8a..c5b6546d7 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -68,10 +68,10 @@ class EmptyBox {
         return '';
     }
 }
-export abstract class FInfo {
+export class FInfo {
     description: string = '';
     readOnly: boolean = false;
-    fieldType?: string;
+    fieldType?: string = '';
     values?: Field[];
     // format?: string; // format to display values (e.g, decimal places, $, etc)
     // parse?: ScriptField; // parse a value from a string
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 39e223c66..1f76c8099 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -31,8 +31,18 @@ export enum ColumnType {
     String,
     Boolean,
     Date,
+    Image,
+    Any,
 }
 
+export const FInfotoColType: { [key: string]: ColumnType } = {
+    string: ColumnType.String,
+    number: ColumnType.Number,
+    boolean: ColumnType.Boolean,
+    date: ColumnType.Date,
+    image: ColumnType.Image,
+};
+
 const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate', 'text'];
 
 @observer
@@ -42,7 +52,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     private _makeNewColumn: boolean = false;
     private _documentOptions: DocumentOptions = new DocumentOptions();
 
-    public static _rowHeight: number = 40;
+    public static _rowHeight: number = 50;
     public static _minColWidth: number = 25;
     public static _rowMenuWidth: number = 60;
     public static _previewDividerWidth: number = 4;
@@ -55,7 +65,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @observable _colEles: HTMLDivElement[] = [];
     @observable _displayColumnWidths: number[] | undefined;
     @observable _columnMenuIndex: number | undefined;
-    @observable _menuOptions: [string, { description: string; type: string; readOnly: boolean }][] = [];
+    @observable _fieldInfos: [string, FInfo][] = [];
     @observable _newFieldWarning: string = '';
     @observable _makeNewField: boolean = false;
     @observable _newFieldDefault: any = 0;
@@ -64,7 +74,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @observable _filterColumnIndex: number | undefined;
     @observable _filterSearchValue: string = '';
 
-    get keyInfos() {
+    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.
@@ -75,23 +85,23 @@ export class CollectionSchemaView extends CollectionSubView() {
         //TODO Types
         untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)))));
 
-        // this.columns.forEach(key => (keys[key.heading] = true));
+        let computedKeys: { [key: string]: FInfo } = {};
 
-        let computedKeys: { [key: string]: { description: string; type: string; readOnly: boolean } } = {};
-        Object.keys(keys).forEach((key: string) => {
-            computedKeys[key] = { description: '', type: '', readOnly: false };
+        Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => {
+            computedKeys[pair[0]] = pair[1];
         });
 
-        Object.entries(this._documentOptions).forEach((pair: [string, any]) => {
-            const info: FInfo = pair[1];
-            computedKeys[pair[0]] = { description: info.description, type: info.fieldType ?? '', readOnly: info.readOnly };
+        Object.keys(keys).forEach((key: string) => {
+            if (!(key in computedKeys)) {
+                computedKeys[key] = new FInfo('');
+            }
         });
 
         return computedKeys;
     }
 
     get documentKeys() {
-        return Object.keys(this.keyInfos);
+        return Object.keys(this.fieldInfos);
     }
 
     @computed get previewWidth() {
@@ -497,7 +507,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     onSearchKeyDown = (e: React.KeyboardEvent) => {
         switch (e.key) {
             case 'Enter':
-                const menuKeys = Object.keys(this._menuOptions);
+                const menuKeys = Object.keys(this._fieldInfos);
                 menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(menuKeys[0]) : action(() => (this._makeNewField = true))();
                 break;
             case 'Escape':
@@ -527,7 +537,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         this._makeNewColumn = false;
         this._columnMenuIndex = index;
         this._menuValue = '';
-        this._menuOptions = Object.entries(this.keyInfos);
+        this._fieldInfos = Object.entries(this.fieldInfos);
         this._makeNewField = false;
         this._newFieldWarning = '';
         this._makeNewField = false;
@@ -573,7 +583,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action
     updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => {
         this._menuValue = e.target.value;
-        this._menuOptions = Object.entries(this.keyInfos).filter(value => value[0].toLowerCase().includes(this._menuValue.toLowerCase()));
+        this._fieldInfos = Object.entries(this.fieldInfos).filter(value => value[0].toLowerCase().includes(this._menuValue.toLowerCase()));
     };
 
     getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
@@ -675,7 +685,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                             { passive: false }
                         )
                     }>
-                    {this._menuOptions.map(([key, info]) => (
+                    {this._fieldInfos.map(([key, info]) => (
                         <div
                             className="schema-search-result"
                             onPointerDown={e => {
@@ -685,10 +695,10 @@ export class CollectionSchemaView extends CollectionSubView() {
                             <p>
                                 <span className="schema-search-result-key">
                                     {key}
-                                    {info.type ? ', ' : ''}
+                                    {info.fieldType ? ', ' : ''}
                                 </span>
                                 <span className="schema-search-result-type" style={{ color: info.readOnly ? 'red' : 'inherit' }}>
-                                    {info.type}
+                                    {info.fieldType}
                                 </span>
                             </p>
                             <p className="schema-search-result-desc">{info.description}</p>
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index f9319050e..4e31b1e1e 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,15 +1,20 @@
 import React = require('react');
 import { observer } from 'mobx-react';
-import { Doc, Field } from '../../../../fields/Doc';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
+import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
 import { Transform } from '../../../util/Transform';
 import { EditableView } from '../../EditableView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { DefaultStyleProvider } from '../../StyleProvider';
-import { CollectionSchemaView } from './CollectionSchemaView';
+import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
-import { computed } from 'mobx';
+import { action, computed, observable } from 'mobx';
+import { extname } from 'path';
+import { Cast, DateCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { DateField } from '../../../../fields/DateField';
+import DatePicker from 'react-datepicker';
 
 export interface SchemaTableCellProps {
     Document: Doc;
@@ -23,10 +28,10 @@ export interface SchemaTableCellProps {
 @observer
 export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
     get readOnly() {
-        return this.props.schemaView?.keyInfos[this.props.fieldKey].readOnly;
+        return this.props.schemaView?.fieldInfos[this.props.fieldKey]?.readOnly ?? false;
     }
 
-    render() {
+    get defaultCellContent() {
         const props: FieldViewProps = {
             Document: this.props.Document,
             docFilters: returnEmptyFilter,
@@ -53,21 +58,134 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         };
 
         return (
-            <div className="schema-table-cell" style={{ width: this.props.columnWidth }}>
-                <div className="schemacell-edit-wrapper" style={this.props.isRowActive() && !this.readOnly ? { cursor: 'text', pointerEvents: 'auto' } : { cursor: 'default', pointerEvents: 'none' }}>
-                    <EditableView
-                        contents={<FieldView {...props} />}
-                        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);
-                            }
-                            return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
-                        }}
-                        editing={this.props.isRowActive() ? undefined : false}
-                    />
-                </div>
+            <div className="schemacell-edit-wrapper">
+                <EditableView
+                    contents={<FieldView {...props} />}
+                    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);
+                        }
+                        return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
+                    }}
+                    editing={this.props.isRowActive() ? undefined : false}
+                />
+            </div>
+        );
+    }
+
+    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' }}>
+                {content}
             </div>
         );
     }
+
+    get getCellType() {
+        const columnTypeStr = this.props.schemaView?.fieldInfos[this.props.fieldKey]?.fieldType;
+        if (columnTypeStr) {
+            if (columnTypeStr in FInfotoColType) {
+                return FInfotoColType[columnTypeStr];
+            }
+
+            return ColumnType.Any;
+        }
+
+        const cellValue = this.props.Document[this.props.fieldKey];
+        if (cellValue instanceof ImageField) return ColumnType.Image;
+        if (cellValue instanceof DateField) return ColumnType.Date;
+
+        return ColumnType.Any;
+    }
+
+    render() {
+        const cellType: ColumnType = this.getCellType;
+        switch (cellType) {
+            case ColumnType.Image:
+                return <SchemaImageCell {...this.props} />;
+            case ColumnType.Date:
+                return <SchemaDateCell {...this.props} />;
+            default:
+                return this.getCellWithContent(this.defaultCellContent);
+        }
+    }
+}
+
+// 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 {
+    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);
+    }
+
+    get content() {
+        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
+            .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
+            .filter(url => url)
+            .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+        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')];
+
+        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
+
+        return <img src={url[0]} width={paths.length ? width : '20px'} height={paths.length ? height : '20px'} />;
+    }
+
+    render() {
+        return this.getCellWithContent(this.content);
+    }
+}
+
+@observer
+export class SchemaDateCell extends SchemaTableCell {
+    @observable _pickingDate: boolean = false;
+
+    @computed get date(): DateField {
+        // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
+        return DateCast(this.props.Document[this.props.fieldKey]);
+    }
+
+    @action
+    handleChange = (date: any) => {
+        // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
+        // if (script.compiled) {
+        //     this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
+        // } else {
+        // ^ DateCast is always undefined for some reason, but that is what the field should be set to
+        this.props.Document[this.props.fieldKey] = new DateField(date as Date);
+        //}
+    };
+
+    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)}
+            />
+        );
+    }
+
+    render() {
+        return this.getCellWithContent(this.content);
+    }
 }
-- 
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/SchemaTableCell.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/SchemaTableCell.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/SchemaTableCell.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/SchemaTableCell.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 08d94147eb855bbb3d7eb964ffa6a7a3248001a2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Apr 2023 20:41:14 -0400
Subject: forced 'hidden' documents to appear in schema.  added
 colorizing/underlining to schema cells to indicate if value is on layout,
 data, or proto

---
 src/client/views/StyleProvider.tsx                   |  3 ++-
 .../collections/collectionSchema/SchemaTableCell.tsx | 20 +++++++++++++++++++-
 2 files changed, 21 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 5f16e0ebd..c810cb155 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -24,6 +24,7 @@ import { KeyValueBox } from './nodes/KeyValueBox';
 import { SliderBox } from './nodes/SliderBox';
 import './StyleProvider.scss';
 import React = require('react');
+import { SchemaRowBox } from './collections/collectionSchema/SchemaRowBox';
 
 export enum StyleProp {
     TreeViewIcon = 'treeViewIcon',
@@ -173,7 +174,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             if (!backColor) return undefined;
             return lightOrDark(backColor);
         case StyleProp.Hidden:
-            return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? false : BoolCast(doc?.hidden);
+            return props?.LayoutTemplateString?.includes(KeyValueBox.name) || props?.LayoutTemplateString?.includes(SchemaRowBox.name) ? false : BoolCast(doc?.hidden);
         case StyleProp.BorderRounding:
             return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.borderRounding, doc?._viewType === CollectionViewType.Pile ? '50%' : ''));
         case StyleProp.TitleHeight:
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 686b21283..bbf8e2bbf 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -90,9 +90,27 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             addDocTab: returnFalse,
             pinToPres: returnZero,
         };
+        let protoCount = 0;
+        let doc: Doc | undefined = this.props.Document;
+        while (doc) {
+            if (Object.keys(doc).includes(this.props.fieldKey)) {
+                break;
+            }
+            protoCount++;
+            doc = doc.proto;
+        }
+        const parenCount = Math.max(0, protoCount - 1);
+        const color = protoCount === 0 ? 'black' : 'blue';
 
         return (
-            <div className="schemacell-edit-wrapper" style={{ cursor: !this.readOnly ? 'text' : 'default', pointerEvents: !this.readOnly && this.props.isRowActive() ? 'all' : 'none' }}>
+            <div
+                className="schemacell-edit-wrapper"
+                style={{
+                    color,
+                    textDecoration: parenCount ? 'underline' : undefined,
+                    cursor: !this.readOnly ? 'text' : 'default',
+                    pointerEvents: !this.readOnly && this.props.isRowActive() ? 'all' : 'none',
+                }}>
                 <EditableView
                     ref={ref => (this._editorRef = ref)}
                     contents={<FieldView {...props} />}
-- 
cgit v1.2.3-70-g09d2


From cc62b9949854f27bc19eb8d4224456094f0dbe13 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Apr 2023 20:48:27 -0400
Subject: from last

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

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

diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index bbf8e2bbf..4f4986b90 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -93,7 +93,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         let protoCount = 0;
         let doc: Doc | undefined = this.props.Document;
         while (doc) {
-            if (Object.keys(doc).includes(this.props.fieldKey)) {
+            if (Object.keys(doc).includes(this.props.fieldKey.replace(/^_/, ''))) {
                 break;
             }
             protoCount++;
-- 
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/SchemaTableCell.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


From 773164e71dd6420d5ee669b138f2b6ba05164874 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Apr 2023 22:38:59 -0400
Subject: from last - fix for fill down to write to layout /data doc properly.

---
 src/client/views/collections/collectionSchema/SchemaTableCell.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 1fa4312e1..003831094 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -103,9 +103,9 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
                     GetValue={() => 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);
+                            this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
                         }
-                        return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
+                        return KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
                     })}
                 />
             </div>
@@ -255,7 +255,7 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
                     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());
+                            this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
                         }
                         KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
                     })}
@@ -266,7 +266,7 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
                     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);
+                            this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
                         }
                         return KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
                     })}
-- 
cgit v1.2.3-70-g09d2


From d3dc9938b38e89b2215d13fbc5bc92d33502e818 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Apr 2023 22:53:45 -0400
Subject: one more fix to setting on data/layout for undefined values on '_'
 fields.

---
 src/client/views/collections/collectionSchema/SchemaTableCell.tsx | 2 +-
 src/fields/Doc.ts                                                 | 8 ++++++--
 2 files changed, 7 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 003831094..f17f4a73c 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -263,7 +263,7 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
                 <EditableView
                     contents={<FieldView {...fieldProps} />}
                     editing={this.selected ? undefined : false}
-                    GetValue={() => (color === 'black' ? '=' : '') + Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+                    GetValue={() => 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.replace(/^_/, ''), value);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index b0033b977..22d0664ce 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -29,9 +29,13 @@ import JSZip = require('jszip');
 import * as JSZipUtils from '../JSZipUtils';
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
-        const onDelegate = Object.keys(doc).includes(key);
+        const onDelegate = Object.keys(doc).includes(key.replace(/^_/, ''));
         const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-        return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field));
+        return !Field.IsField(field)
+            ? key.startsWith('_')
+                ? '='
+                : ''
+            : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field));
     }
     export function toScriptString(field: Field): string {
         switch (typeof field) {
-- 
cgit v1.2.3-70-g09d2


From becc884883df07635f4619f908747598b3eb86ee Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 28 Apr 2023 09:57:53 -0400
Subject: added RTF cell type for schema.  fixed formatting DateFields so that
 they can be set from kvp/schema.  prevented on infinite loop possibility when
 setting proto to itself.

---
 src/Utils.ts                                       |  3 ++
 .../collectionSchema/CollectionSchemaView.tsx      | 15 ++++----
 .../collectionSchema/SchemaTableCell.tsx           | 40 ++++++++++++++++------
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/fields/DateField.ts                            | 14 ++++----
 5 files changed, 48 insertions(+), 26 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 0c7deaf5d..73de6d754 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -513,6 +513,9 @@ export function returnTrue() {
     return true;
 }
 
+export function returnIgnore(): 'ignore' {
+    return 'ignore';
+}
 export function returnAlways(): 'always' {
     return 'always';
 }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 3f7e037d4..a59d7e5a3 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -8,7 +8,7 @@ import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
+import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
 import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
@@ -32,6 +32,7 @@ export enum ColumnType {
     Boolean,
     Date,
     Image,
+    RTF,
     Any,
 }
 
@@ -41,6 +42,7 @@ export const FInfotoColType: { [key: string]: ColumnType } = {
     boolean: ColumnType.Boolean,
     date: ColumnType.Date,
     image: ColumnType.Image,
+    rtf: ColumnType.RTF,
 };
 
 const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate', 'text'];
@@ -410,14 +412,10 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    selectCell = (doc: Doc, index: number) => {
-        this._selectedCell = [doc, index];
-    };
+    selectCell = (doc: Doc, index: number) => (this._selectedCell = [doc, index]);
 
     @action
-    deselectCell = () => {
-        this._selectedCell = undefined;
-    };
+    deselectCell = () => (this._selectedCell = undefined);
 
     sortedSelectedDocs = () => this.sortedDocs.docs.filter(doc => this._selectedDocs.includes(doc));
 
@@ -891,6 +889,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 dontCenter={'y'}
                                 onClickScriptDisable="always"
                                 focus={emptyFunction}
+                                defaultDoubleClick={returnIgnore}
                                 renderDepth={this.props.renderDepth + 1}
                                 rootSelected={this.rootSelected}
                                 PanelWidth={this.previewWidthFunc}
@@ -949,7 +948,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                                 PanelHeight={this.rowHeightFunc}
                                 styleProvider={DefaultStyleProvider}
                                 waitForDoubleClickToClick={returnNever}
-                                defaultDoubleClick={returnDefault}
+                                defaultDoubleClick={returnIgnore}
                                 enableDragWhenActive={true}
                                 onClickScriptDisable="always"
                                 focus={this.props.schema.focusDocument}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index f17f4a73c..712bd4491 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -5,7 +5,7 @@ import { extname } from 'path';
 import DatePicker from 'react-datepicker';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field } from '../../../../fields/Doc';
-import { BoolCast, Cast, DateCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DateCast, FieldValue } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils';
 import { FInfo } from '../../../documents/Documents';
@@ -19,6 +19,8 @@ import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { DefaultStyleProvider } from '../../StyleProvider';
 import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
+import { RichTextField } from '../../../../fields/RichTextField';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 
 export interface SchemaTableCellProps {
     Document: Doc;
@@ -113,18 +115,18 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
     }
 
     get getCellType() {
-        const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
-        if (columnTypeStr) {
-            if (columnTypeStr in FInfotoColType) {
-                return FInfotoColType[columnTypeStr];
-            }
-
-            return ColumnType.Any;
-        }
-
         const cellValue = this.props.Document[this.props.fieldKey];
         if (cellValue instanceof ImageField) return ColumnType.Image;
         if (cellValue instanceof DateField) return ColumnType.Date;
+        if (cellValue instanceof RichTextField) return ColumnType.RTF;
+        if (typeof cellValue === 'number') return ColumnType.Any;
+        if (typeof cellValue === 'string') return ColumnType.Any;
+        if (typeof cellValue === 'boolean') return ColumnType.Any;
+
+        const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
+        if (columnTypeStr && columnTypeStr in FInfotoColType) {
+            return FInfotoColType[columnTypeStr];
+        }
 
         return ColumnType.Any;
     }
@@ -135,6 +137,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         switch (cellType) {
             case ColumnType.Image: return <SchemaImageCell {...this.props} />;
             case ColumnType.Boolean: return <SchemaBoolCell {...this.props} />;
+            case ColumnType.RTF: return <SchemaRTFCell {...this.props} />;
             case ColumnType.Date: // return <SchemaDateCell {...this.props} />;
             default:  return this.defaultCellContent;
         }
@@ -240,6 +243,23 @@ export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
     }
 }
 @observer
+export class SchemaRTFCell 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;
+    }
+    selectedFunc = () => this.selected;
+    render() {
+        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
+        fieldProps.isContentActive = this.selectedFunc;
+        return (
+            <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
+                {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+            </div>
+        );
+    }
+}
+@observer
 export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
     @computed get selected() {
         const selected: [Doc, number] | undefined = this.props.selectedCell();
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index b54364332..e317de11e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -96,7 +96,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
             }
             field = res.result;
         }
-        if (Field.IsField(field, true)) {
+        if (Field.IsField(field, true) && (key !== 'proto' || field !== target)) {
             target[key] = field;
             return true;
         }
diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts
index 26f51b2d3..2ea619bd9 100644
--- a/src/fields/DateField.ts
+++ b/src/fields/DateField.ts
@@ -1,11 +1,11 @@
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, date } from "serializr";
-import { ObjectField } from "./ObjectField";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { scriptingGlobal, ScriptingGlobals } from "../client/util/ScriptingGlobals";
+import { Deserializable } from '../client/util/SerializationHelper';
+import { serializable, date } from 'serializr';
+import { ObjectField } from './ObjectField';
+import { Copy, ToScriptString, ToString } from './FieldSymbols';
+import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
 
 @scriptingGlobal
-@Deserializable("date")
+@Deserializable('date')
 export class DateField extends ObjectField {
     @serializable(date())
     readonly date: Date;
@@ -24,7 +24,7 @@ export class DateField extends ObjectField {
     }
 
     [ToScriptString]() {
-        return `new DateField(new Date(${this.date.toISOString()}))`;
+        return `new DateField(new Date("${this.date.toISOString()}"))`;
     }
     [ToString]() {
         return this.date.toLocaleString();
-- 
cgit v1.2.3-70-g09d2

