aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/settings.json5
-rw-r--r--package-lock.json46
-rw-r--r--src/client/documents/Documents.ts4
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx683
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx513
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx138
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx152
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss719
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx1289
-rw-r--r--src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx76
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx129
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTable.tsx694
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx61
-rw-r--r--src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx683
-rw-r--r--src/client/views/collections/old_collectionSchema/OldCollectionSchemaHeaders.tsx510
-rw-r--r--src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx138
-rw-r--r--src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx152
-rw-r--r--src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.scss599
-rw-r--r--src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.tsx649
-rw-r--r--src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx694
-rw-r--r--src/client/views/nodes/AudioBox.tsx1
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx3
23 files changed, 4679 insertions, 3261 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index f0cebd6fd..8849f30dd 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -10,5 +10,8 @@
"editor.detectIndentation": false,
"search.usePCRE2": true,
"typescript.tsdk": "node_modules/typescript/lib",
- "editor.defaultFormatter": "esbenp.prettier-vscode"
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ }
}
diff --git a/package-lock.json b/package-lock.json
index 4695adf40..a30e9e6e3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2232,7 +2232,7 @@
"@types/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=",
+ "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==",
"dev": true
},
"@types/strip-json-comments": {
@@ -2583,7 +2583,7 @@
"textarea-caret": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.0.2.tgz",
- "integrity": "sha512-gRzeti2YS4did7UJnPQ47wrjD+vp+CJIe9zbsu0bJ987d8QVLvLNG9757rqiQTIy4hGIeFauTTJt5Xkn51UkXg=="
+ "integrity": "sha1-82DEhpmqGr9xhoCkOjGoUGZcLK8="
}
}
},
@@ -2682,7 +2682,7 @@
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
- "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA=="
+ "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
},
"agent-base": {
"version": "6.0.2",
@@ -3579,7 +3579,7 @@
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
- "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA=="
+ "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"bail": {
"version": "2.0.2",
@@ -3649,7 +3649,7 @@
"base64-arraybuffer": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
- "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg=="
+ "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
},
"base64-js": {
"version": "1.5.1",
@@ -4617,7 +4617,7 @@
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
- "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw=="
+ "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
},
"component-emitter": {
"version": "1.3.0",
@@ -4627,7 +4627,7 @@
"component-inherit": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
- "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA=="
+ "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
},
"compress-commons": {
"version": "2.1.1",
@@ -5329,7 +5329,7 @@
"custom-event": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
- "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg=="
+ "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU="
},
"cyclist": {
"version": "1.0.1",
@@ -6255,7 +6255,7 @@
"dynamic-dedupe": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
- "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=",
+ "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==",
"dev": true,
"requires": {
"xtend": "^4.0.0"
@@ -6385,7 +6385,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"ws": {
"version": "7.4.6",
@@ -8918,7 +8918,7 @@
"fs-extra": {
"version": "0.26.7",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz",
- "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=",
+ "integrity": "sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^2.1.0",
@@ -9578,14 +9578,14 @@
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
- "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
+ "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
}
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
- "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA=="
+ "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
},
"has-flag": {
"version": "3.0.0",
@@ -10161,7 +10161,7 @@
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
- "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg=="
+ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
},
"inflight": {
"version": "1.0.6",
@@ -11272,7 +11272,7 @@
"jsonfile": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
- "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
+ "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==",
"requires": {
"graceful-fs": "^4.1.6"
}
@@ -11695,7 +11695,7 @@
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
- "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"lodash.isplainobject": {
"version": "4.0.6",
@@ -11956,7 +11956,7 @@
"mathquill": {
"version": "0.10.1-a",
"resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz",
- "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==",
+ "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=",
"requires": {
"jquery": "^1.12.3"
},
@@ -11964,7 +11964,7 @@
"jquery": {
"version": "1.12.4",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz",
- "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg=="
+ "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw="
}
}
},
@@ -21650,7 +21650,7 @@
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
- "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A=="
+ "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
},
"to-fast-properties": {
"version": "2.0.0",
@@ -22022,7 +22022,7 @@
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
"dev": true
}
}
@@ -23243,7 +23243,7 @@
"resolve-cwd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
- "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+ "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==",
"dev": true,
"requires": {
"resolve-from": "^3.0.0"
@@ -23252,7 +23252,7 @@
"resolve-from": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
- "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+ "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==",
"dev": true
},
"semver": {
@@ -23804,7 +23804,7 @@
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
- "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg=="
+ "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
},
"yn": {
"version": "3.1.1",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 251e432ef..422ee7711 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1134,8 +1134,8 @@ export namespace Docs {
return Prototypes.get(DocumentType.PRESELEMENT);
}
- export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) {
- return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }, undefined, undefined, undefined, overwriteDoc);
+ export function DataVizDocument(url: string, options?: DocumentOptions) {
+ return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options });
}
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
deleted file mode 100644
index 18ddd881b..000000000
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ /dev/null
@@ -1,683 +0,0 @@
-import React = require('react');
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import { extname } from 'path';
-import DatePicker from 'react-datepicker';
-import { CellInfo } from 'react-table';
-import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
-import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { ComputedField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DateCast, FieldValue, StrCast } from '../../../../fields/Types';
-import { ImageField } from '../../../../fields/URLField';
-import { emptyFunction, Utils } from '../../../../Utils';
-import { Docs } from '../../../documents/Documents';
-import { DocumentType } from '../../../documents/DocumentTypes';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
-import { KeyCodes } from '../../../util/KeyCodes';
-import { CompileScript } from '../../../util/Scripting';
-import { SearchUtil } from '../../../util/SearchUtil';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { undoBatch } from '../../../util/UndoManager';
-import '../../../views/DocumentDecorations.scss';
-import { EditableView } from '../../EditableView';
-import { MAX_ROW_HEIGHT } from '../../global/globalCssVariables.scss';
-import { DocumentIconContainer } from '../../nodes/DocumentIcon';
-import { OverlayView } from '../../OverlayView';
-import { CollectionView } from '../CollectionView';
-import './CollectionSchemaView.scss';
-import { OpenWhere } from '../../nodes/DocumentView';
-import { PinProps } from '../../nodes/trails';
-
-// intialize cell properties
-export interface CellProps {
- row: number;
- col: number;
- rowProps: CellInfo;
- // currently unused
- CollectionView: Opt<CollectionView>;
- // currently unused
- ContainingCollection: Opt<CollectionView>;
- Document: Doc;
- // column name
- fieldKey: string;
- // currently unused
- renderDepth: number;
- // called when a button is pressed on the node itself
- addDocTab: (document: Doc, where: OpenWhere) => boolean;
- pinToPres: (document: Doc, pinProps: PinProps) => void;
- moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
- isFocused: boolean;
- changeFocusedCellByIndex: (row: number, col: number) => void;
- // set whether the cell is in the isEditing mode
- setIsEditing: (isEditing: boolean) => void;
- isEditable: boolean;
- setPreviewDoc: (doc: Doc) => void;
- setComputed: (script: string, doc: Doc, field: string, row: number, col: number) => boolean;
- getField: (row: number, col?: number) => void;
- // currnetly unused
- showDoc: (doc: Doc | undefined, dataDoc?: any, screenX?: number, screenY?: number) => void;
-}
-
-@observer
-export class CollectionSchemaCell extends React.Component<CellProps> {
- // return a field key that is corrected for whether it COMMENT
- public static resolvedFieldKey(column: string, rowDoc: Doc) {
- const fieldKey = column;
- if (fieldKey.startsWith('*')) {
- const rootKey = fieldKey.substring(1);
- const allKeys = [...Array.from(Object.keys(rowDoc)), ...Array.from(Object.keys(Doc.GetProto(rowDoc)))];
- const matchedKeys = allKeys.filter(key => key.includes(rootKey));
- if (matchedKeys.length) return matchedKeys[0];
- }
- return fieldKey;
- }
- @observable protected _isEditing: boolean = false;
- protected _focusRef = React.createRef<HTMLDivElement>();
- protected _rowDoc = this.props.rowProps.original;
- // Gets the serialized data in proto form of the base proto that this document's proto inherits from
- protected _rowDataDoc = Doc.GetProto(this.props.rowProps.original);
- // methods for dragging and dropping
- protected _dropDisposer?: DragManager.DragDropDisposer;
- @observable contents: string = '';
-
- componentDidMount() {
- document.addEventListener('keydown', this.onKeyDown);
- }
- componentWillUnmount() {
- document.removeEventListener('keydown', this.onKeyDown);
- }
-
- @action
- onKeyDown = (e: KeyboardEvent): void => {
- // If a cell is editable and clicked, hitting enter shoudl allow the user to edit it
- if (this.props.isFocused && this.props.isEditable && e.keyCode === KeyCodes.ENTER) {
- document.removeEventListener('keydown', this.onKeyDown);
- this._isEditing = true;
- this.props.setIsEditing(true);
- }
- };
-
- @action
- isEditingCallback = (isEditing: boolean): void => {
- // a general method that takes a boolean that determines whether the cell should be in
- // is-editing mode
- // remove the event listener if it's there
- document.removeEventListener('keydown', this.onKeyDown);
- // it's not already in is-editing mode, re-add the event listener
- isEditing && document.addEventListener('keydown', this.onKeyDown);
- this._isEditing = isEditing;
- this.props.setIsEditing(isEditing);
- this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
- };
-
- @action
- onPointerDown = async (e: React.PointerEvent): Promise<void> => {
- // pan to the cell
- this.onItemDown(e);
- // focus on it
- this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
- this.props.setPreviewDoc(this.props.rowProps.original);
-
- let url: string;
- if ((url = StrCast(this.props.rowProps.row.href))) {
- // opens up the the doc in a new window, blurring the old one
- try {
- new URL(url);
- const temp = window.open(url)!;
- temp.blur();
- window.focus();
- } catch {}
- }
-
- const doc = Cast(this._rowDoc[this.renderFieldKey], Doc, null);
- doc && this.props.setPreviewDoc(doc);
- };
-
- @undoBatch
- applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => {
- // apply a specified change to the cell
- const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) });
- if (!res.success) return false;
- // change what is rendered to this new changed cell content
- doc[this.renderFieldKey] = res.result;
- return true;
- // return whether the change was successful
- };
-
- private drop = (e: Event, de: DragManager.DropEvent) => {
- // if the drag has data at its completion
- if (de.complete.docDragData) {
- // if only one doc was dragged
- if (de.complete.docDragData.draggedDocuments.length === 1) {
- // update the renderFieldKey
- this._rowDataDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0];
- } else {
- // create schema document reflecting the new column arrangement
- const coll = Docs.Create.SchemaDocument([new SchemaHeaderField('title', '#f1efeb')], de.complete.docDragData.draggedDocuments, {});
- this._rowDataDoc[this.renderFieldKey] = coll;
- }
- e.stopPropagation();
- }
- };
-
- protected dropRef = (ele: HTMLElement | null) => {
- // if the drop disposer is not undefined, run its function
- this._dropDisposer?.();
- // if ele is not null, give ele a non-undefined drop disposer
- ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
- };
-
- returnHighlights(contents: string, positions?: number[]) {
- if (positions) {
- const results = [];
- StrCast(this.props.Document._searchString);
- const length = StrCast(this.props.Document._searchString).length;
- const color = contents ? 'black' : 'grey';
-
- results.push(
- <span key="-1" style={{ color }}>
- {contents?.slice(0, positions[0])}
- </span>
- );
- positions.forEach((num, cur) => {
- results.push(
- <span key={'start' + cur} style={{ backgroundColor: '#FFFF00', color }}>
- {contents?.slice(num, num + length)}
- </span>
- );
- let end = 0;
- cur === positions.length - 1 ? (end = contents.length) : (end = positions[cur + 1]);
- results.push(
- <span key={'end' + cur} style={{ color }}>
- {contents?.slice(num + length, end)}
- </span>
- );
- });
- return results;
- }
- return <span style={{ color: contents ? 'black' : 'grey' }}>{contents ? contents?.valueOf() : 'undefined'}</span>;
- }
-
- @computed get renderFieldKey() {
- // gets the resolved field key of this cell
- return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original);
- }
-
- onItemDown = async (e: React.PointerEvent) => {
- // if the document is a document used to change UI for search results in schema view
- if (this.props.Document._searchDoc) {
- const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc);
- const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null);
- // Jump to the this document
- DocumentManager.Instance.showDocument(this._rowDoc, { willPan: true }, () => this.props.setPreviewDoc(this._rowDoc));
- }
- };
-
- renderCellWithType(type: string | undefined) {
- const dragRef: React.RefObject<HTMLDivElement> = React.createRef();
-
- // the column
- const fieldKey = this.renderFieldKey;
- // the exact cell
- const field = this._rowDoc[fieldKey];
-
- const onPointerEnter = (e: React.PointerEvent): void => {
- // e.buttons === 1 means the left moue pointer is down
- if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === 'document' || type === undefined)) {
- dragRef.current!.className = 'collectionSchemaView-cellContainer doc-drag-over';
- }
- };
- const onPointerLeave = (e: React.PointerEvent): void => {
- // change the class name to indicate that the cell is no longer being dragged
- dragRef.current!.className = 'collectionSchemaView-cellContainer';
- };
-
- let contents = Field.toString(field as Field);
- // display 2 hyphens instead of a blank box for empty cells
- contents = contents === '' ? '--' : contents;
-
- // classname reflects the tatus of the cell
- let className = 'collectionSchemaView-cellWrapper';
- if (this._isEditing) className += ' editing';
- if (this.props.isFocused && this.props.isEditable) className += ' focused';
- if (this.props.isFocused && !this.props.isEditable) className += ' inactive';
-
- const positions = [];
- if (StrCast(this.props.Document._searchString).toLowerCase() !== '') {
- // term is ...promise pending... if the field is a Promise, otherwise it is the cell's contents
- let term = field instanceof Promise ? '...promise pending...' : contents.toLowerCase();
- const search = StrCast(this.props.Document._searchString).toLowerCase();
- let start = term.indexOf(search);
- let tally = 0;
- // if search is found in term
- if (start !== -1) {
- positions.push(start);
- }
- // if search is found in term, continue finding all instances of search in term
- while (start < contents?.length && start !== -1) {
- term = term.slice(start + search.length + 1);
- tally += start + search.length + 1;
- start = term.indexOf(search);
- positions.push(tally + start);
- }
- // remove the last position
- if (positions.length > 1) {
- positions.pop();
- }
- }
- const placeholder = type === 'number' ? '0' : contents === '' ? '--' : 'undefined';
- return (
- <div
- className="collectionSchemaView-cellContainer"
- style={{ cursor: field instanceof Doc ? 'grab' : 'auto' }}
- ref={dragRef}
- onPointerDown={this.onPointerDown}
- onClick={action(e => (this._isEditing = true))}
- onPointerEnter={onPointerEnter}
- onPointerLeave={onPointerLeave}>
- <div className={className} ref={this._focusRef} tabIndex={-1}>
- <div className="collectionSchemaView-cellContents" ref={type === undefined || type === 'document' ? this.dropRef : null}>
- {!this.props.Document._searchDoc ? (
- <EditableView
- editing={this._isEditing}
- isEditingCallback={this.isEditingCallback}
- display={'inline'}
- contents={contents}
- height={'auto'}
- maxHeight={Number(MAX_ROW_HEIGHT)}
- placeholder={placeholder}
- GetValue={() => {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(field));
- const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
- const cfinalScript = cscript?.split('return')[cscript.split('return').length - 1];
- return cscript ? (cfinalScript?.endsWith(';') ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : Field.IsField(cfield) ? Field.toScriptString(cfield) : '';
- }}
- SetValue={action((value: string) => {
- // sets what is displayed after the user makes an input
- let retVal = false;
- if (value.startsWith(':=') || value.startsWith('=:=')) {
- // decides how to compute a value when given either of the above strings
- const script = value.substring(value.startsWith('=:=') ? 3 : 2);
- retVal = this.props.setComputed(script, value.startsWith(':=') ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col);
- } else {
- // check if the input is a number
- let inputIsNum = true;
- for (const s of value) {
- if (isNaN(parseInt(s)) && !(s === '.') && !(s === ',')) {
- inputIsNum = false;
- }
- }
- // check if the input is a boolean
- const inputIsBool: boolean = value === 'false' || value === 'true';
- // what to do in the case
- if (!inputIsNum && !inputIsBool && !value.startsWith('=')) {
- // if it's not a number, it's a string, and should be processed as such
- // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically
- // after each edit
- let valueSansQuotes = value;
- if (this._isEditing) {
- const vsqLength = valueSansQuotes.length;
- // get rid of outer quotes
- valueSansQuotes = valueSansQuotes.substring(value.startsWith('"') ? 1 : 0, valueSansQuotes.charAt(vsqLength - 1) === '"' ? vsqLength - 1 : vsqLength);
- }
- let inputAsString = '"';
- // escape any quotes in the string
- for (const i of valueSansQuotes) {
- if (i === '"') {
- inputAsString += '\\"';
- } else {
- inputAsString += i;
- }
- }
- // add a closing quote
- inputAsString += '"';
- //two options here: we can strip off outer quotes or we can figure out what's going on with the script
- const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
- const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length;
- // change it if a change is made, otherwise, just compile using the old cell conetnts
- script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
- // handle numbers and expressions
- } else if (inputIsNum || value.startsWith('=')) {
- //TODO: make accept numbers
- const inputscript = value.substring(value.startsWith('=') ? 1 : 0);
- // if commas are not stripped, the parser only considers the numbers after the last comma
- let inputSansCommas = '';
- for (const s of inputscript) {
- if (!(s === ',')) {
- inputSansCommas += s;
- }
- }
- const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
- const changeMade = value.length - 2 !== value.length;
- script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
- // handle booleans
- } else if (inputIsBool) {
- const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
- const changeMade = value.length - 2 !== value.length;
- script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
- }
- }
- if (retVal) {
- this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing'
- this.props.setIsEditing(false);
- }
- return retVal;
- })}
- OnFillDown={async (value: string) => {
- // computes all of the value preceded by :=
- const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
- script.compiled &&
- DocListCast(this.props.Document[this.props.fieldKey]).forEach((doc, i) =>
- value.startsWith(':=') ? this.props.setComputed(value.substring(2), Doc.GetProto(doc), this.renderFieldKey, i, this.props.col) : this.applyToDoc(Doc.GetProto(doc), i, this.props.col, script.run)
- );
- }}
- />
- ) : (
- this.returnHighlights(contents, positions)
- )}
- </div>
- </div>
- </div>
- );
- }
-
- render() {
- return this.renderCellWithType(undefined);
- }
-}
-
-@observer
-export class CollectionSchemaNumberCell extends CollectionSchemaCell {
- render() {
- return this.renderCellWithType('number');
- }
-}
-
-@observer
-export class CollectionSchemaBooleanCell extends CollectionSchemaCell {
- render() {
- return this.renderCellWithType('boolean');
- }
-}
-
-@observer
-export class CollectionSchemaStringCell extends CollectionSchemaCell {
- render() {
- return this.renderCellWithType('string');
- }
-}
-
-@observer
-export class CollectionSchemaDateCell extends CollectionSchemaCell {
- @computed get _date(): Opt<DateField> {
- // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
- return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined;
- }
-
- @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._rowDoc[this.renderFieldKey] = new DateField(date as Date);
- //}
- };
-
- render() {
- return !this.props.isFocused ? (
- <span onPointerDown={this.onPointerDown}>{this._date ? Field.toString(this._date as Field) : '--'}</span>
- ) : (
- <DatePicker selected={this._date?.date || new Date()} onSelect={date => this.handleChange(date)} onChange={date => this.handleChange(date)} />
- );
- }
-}
-
-@observer
-export class CollectionSchemaDocCell extends CollectionSchemaCell {
- _overlayDisposer?: () => void;
-
- @computed get _doc() {
- return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc));
- }
-
- @action
- onSetValue = (value: string) => {
- this._doc && (Doc.GetProto(this._doc).title = value);
-
- const script = CompileScript(value, {
- addReturn: true,
- typecheck: true,
- transformer: DocumentIconContainer.getTransformer(),
- });
- // compile the script
- const results = script.compiled && script.run();
- // if the script was compiled and run
- if (results && results.success) {
- this._rowDoc[this.renderFieldKey] = results.result;
- return true;
- }
- return false;
- };
-
- componentWillUnmount() {
- this.onBlur();
- }
-
- onBlur = () => {
- this._overlayDisposer?.();
- };
- onFocus = () => {
- this.onBlur();
- this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
- };
-
- @action
- isEditingCallback = (isEditing: boolean): void => {
- // the isEditingCallback from a general CollectionSchemaCell
- document.removeEventListener('keydown', this.onKeyDown);
- isEditing && document.addEventListener('keydown', this.onKeyDown);
- this._isEditing = isEditing;
- this.props.setIsEditing(isEditing);
- this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
- };
-
- render() {
- // if there's a doc, render it
- return !this._doc ? (
- this.renderCellWithType('document')
- ) : (
- <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <div className="collectionSchemaView-cellContents-document" style={{ padding: '5.9px' }} ref={this.dropRef} onFocus={this.onFocus} onBlur={this.onBlur}>
- <EditableView
- editing={this._isEditing}
- isEditingCallback={this.isEditingCallback}
- display={'inline'}
- contents={this._doc.title || '--'}
- height={'auto'}
- maxHeight={Number(MAX_ROW_HEIGHT)}
- GetValue={() => StrCast(this._doc?.title)}
- SetValue={action((value: string) => {
- this.onSetValue(value);
- return true;
- })}
- />
- </div>
- <div onClick={() => this._doc && this.props.addDocTab(this._doc, OpenWhere.addRight)} className="collectionSchemaView-cellContents-docButton">
- <FontAwesomeIcon icon="external-link-alt" size="lg" />
- </div>
- </div>
- );
- }
-}
-
-@observer
-export class CollectionSchemaImageCell extends CollectionSchemaCell {
- 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);
- }
-
- render() {
- const field = Cast(this._rowDoc[this.renderFieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = DocListCast(this._rowDoc[this.renderFieldKey + '-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._rowDoc); // aspect ratio
- let width = Math.min(75, this.props.rowProps.width); // get a with that is no smaller than 75px
- const height = Math.min(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
-
- const reference = React.createRef<HTMLDivElement>();
- return (
- <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
- <img src={url[0]} width={paths.length ? width : '20px'} height={paths.length ? height : '20px'} />
- </div>
- </div>
- );
- }
-}
-
-@observer
-export class CollectionSchemaListCell extends CollectionSchemaCell {
- _overlayDisposer?: () => void;
-
- @computed get _field() {
- return this._rowDoc[this.renderFieldKey];
- }
- @computed get _optionsList() {
- return this._field as List<any>;
- }
- @observable private _opened = false; // whether the list is opened
- @observable private _text = 'select an item';
- @observable private _selectedNum = 0; // the index of the list item selected
-
- @action
- onSetValue = (value: string) => {
- // change if it's a document
- this._optionsList[this._selectedNum] = this._text = value;
-
- (this._field as List<any>).splice(this._selectedNum, 1, value);
- };
-
- @action
- onSelected = (element: string, index: number) => {
- // if an item is selected, the private variables should update to reflect this
- this._text = element;
- this._selectedNum = index;
- };
-
- onFocus = () => {
- this._overlayDisposer?.();
- this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
- };
-
- render() {
- const link = false;
- const reference = React.createRef<HTMLDivElement>();
-
- // if the list is not opened, don't display it; otherwise, do.
- if (this._optionsList?.length) {
- const options = !this._opened ? null : (
- <div>
- {this._optionsList.map((element, index) => {
- const val = Field.toString(element);
- return (
- <div className="collectionSchemaView-dropdownOption" key={index} style={{ padding: '6px' }} onPointerDown={e => this.onSelected(StrCast(element), index)}>
- {val}
- </div>
- );
- })}
- </div>
- );
-
- const plainText = <div style={{ padding: '5.9px' }}>{this._text}</div>;
- const textarea = (
- <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} style={{ padding: '5.9px' }} ref={this.dropRef}>
- <EditableView
- editing={this._isEditing}
- isEditingCallback={this.isEditingCallback}
- display={'inline'}
- contents={this._text}
- height={'auto'}
- maxHeight={Number(MAX_ROW_HEIGHT)}
- GetValue={() => this._text}
- SetValue={action((value: string) => {
- // add special for params
- this.onSetValue(value);
- return true;
- })}
- />
- </div>
- );
-
- //☰
- return (
- <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
- <div className="collectionSchemaView-dropDownWrapper">
- <button type="button" className="collectionSchemaView-dropdownButton" style={{ right: 'length', position: 'relative' }} onClick={action(e => (this._opened = !this._opened))}>
- <FontAwesomeIcon icon={this._opened ? 'caret-up' : 'caret-down'} size="sm" />
- </button>
- <div className="collectionSchemaView-dropdownText"> {link ? plainText : textarea} </div>
- </div>
- {options}
- </div>
- </div>
- );
- }
- return this.renderCellWithType('list');
- }
-}
-
-@observer
-export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
- @computed get _isChecked() {
- return BoolCast(this._rowDoc[this.renderFieldKey]);
- }
-
- render() {
- const reference = React.createRef<HTMLDivElement>();
- return (
- <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
- <input type="checkbox" checked={this._isChecked} onChange={e => (this._rowDoc[this.renderFieldKey] = e.target.checked)} />
- </div>
- );
- }
-}
-
-@observer
-export class CollectionSchemaButtons extends CollectionSchemaCell {
- // the navigation buttons for schema view when it is used for search.
- render() {
- return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? (
- <></>
- ) : (
- <div style={{ paddingTop: 8, paddingLeft: 3 }}>
- <button style={{ padding: 2, left: 77 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, true)}>
- <FontAwesomeIcon icon="arrow-up" size="sm" />
- </button>
- <button style={{ padding: 2 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, false)}>
- <FontAwesomeIcon icon="arrow-down" size="sm" />
- </button>
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
deleted file mode 100644
index 9653f2808..000000000
--- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
+++ /dev/null
@@ -1,513 +0,0 @@
-import React = require("react");
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction, trace } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt, StrListCast } from "../../../../fields/Doc";
-import { listSpec } from "../../../../fields/Schema";
-import { PastelSchemaPalette, SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { ScriptField } from "../../../../fields/ScriptField";
-import { Cast, StrCast } from "../../../../fields/Types";
-import { undoBatch } from "../../../util/UndoManager";
-import { CollectionView } from "../CollectionView";
-import { ColumnType } from "./CollectionSchemaView";
-import "./CollectionSchemaView.scss";
-
-const higflyout = require("@hig/flyout");
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-
-export interface AddColumnHeaderProps {
- createColumn: () => void;
-}
-
-@observer
-export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHeaderProps> {
- // the button that allows the user to add a column
- render() {
- return <button className="add-column" onClick={() => this.props.createColumn()}>
- <FontAwesomeIcon icon="plus" size="sm" />
- </button>;
- }
-}
-
-export interface ColumnMenuProps {
- columnField: SchemaHeaderField;
- // keyValue: string;
- possibleKeys: string[];
- existingKeys: string[];
- // keyType: ColumnType;
- typeConst: boolean;
- menuButtonContent: JSX.Element;
- addNew: boolean;
- onSelect: (oldKey: string, newKey: string, addnew: boolean) => void;
- setIsEditing: (isEditing: boolean) => void;
- deleteColumn: (column: string) => void;
- onlyShowOptions: boolean;
- setColumnType: (column: SchemaHeaderField, type: ColumnType) => void;
- setColumnSort: (column: SchemaHeaderField, desc: boolean | undefined) => void;
- anchorPoint?: any;
- setColumnColor: (column: SchemaHeaderField, color: string) => void;
-}
-@observer
-export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> {
- @observable private _isOpen: boolean = false;
- @observable private _node: HTMLDivElement | null = null;
-
- componentDidMount() { document.addEventListener("pointerdown", this.detectClick); }
-
- componentWillUnmount() { document.removeEventListener("pointerdown", this.detectClick); }
-
- @action
- detectClick = (e: PointerEvent) => {
- !this._node?.contains(e.target as Node) && this.props.setIsEditing(this._isOpen = false);
- }
-
- @action
- toggleIsOpen = (): void => {
- this.props.setIsEditing(this._isOpen = !this._isOpen);
- }
-
- changeColumnType = (type: ColumnType) => {
- this.props.setColumnType(this.props.columnField, type);
- }
-
- changeColumnSort = (desc: boolean | undefined) => {
- this.props.setColumnSort(this.props.columnField, desc);
- }
-
- changeColumnColor = (color: string) => {
- this.props.setColumnColor(this.props.columnField, color);
- }
-
- @action
- setNode = (node: HTMLDivElement): void => {
- if (node) {
- this._node = node;
- }
- }
-
- renderTypes = () => {
- if (this.props.typeConst) return (null);
-
- const type = this.props.columnField.type;
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Column type:</label>
- <div className="columnMenu-types">
- <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Any)}>
- <FontAwesomeIcon icon={"align-justify"} size="sm" />
- Any
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Number)}>
- <FontAwesomeIcon icon={"hashtag"} size="sm" />
- Number
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.String)}>
- <FontAwesomeIcon icon={"font"} size="sm" />
- Text
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Boolean)}>
- <FontAwesomeIcon icon={"check-square"} size="sm" />
- Checkbox
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.List)}>
- <FontAwesomeIcon icon={"list-ul"} size="sm" />
- List
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Doc)}>
- <FontAwesomeIcon icon={"file"} size="sm" />
- Document
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Image)}>
- <FontAwesomeIcon icon={"image"} size="sm" />
- Image
- </div>
- <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Date)}>
- <FontAwesomeIcon icon={"calendar"} size="sm" />
- Date
- </div>
- </div>
- </div >
- );
- }
-
- renderSorting = () => {
- const sort = this.props.columnField.desc;
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Sort by:</label>
- <div className="columnMenu-sort">
- <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.changeColumnSort(true)}>
- <FontAwesomeIcon icon="sort-amount-down" size="sm" />
- Sort descending
- </div>
- <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.changeColumnSort(false)}>
- <FontAwesomeIcon icon="sort-amount-up" size="sm" />
- Sort ascending
- </div>
- <div className="columnMenu-option" onClick={() => this.changeColumnSort(undefined)}>
- <FontAwesomeIcon icon="times" size="sm" />
- Clear sorting
- </div>
- </div>
- </div>
- );
- }
-
- renderColors = () => {
- const selected = this.props.columnField.color;
-
- const pink = PastelSchemaPalette.get("pink2");
- const purple = PastelSchemaPalette.get("purple2");
- const blue = PastelSchemaPalette.get("bluegreen1");
- const yellow = PastelSchemaPalette.get("yellow4");
- const red = PastelSchemaPalette.get("red2");
- const gray = "#f1efeb";
-
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Color:</label>
- <div className="columnMenu-colors">
- <div className={"columnMenu-colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div>
- </div>
- </div>
- );
- }
-
- renderContent = () => {
- return (
- <div className="collectionSchema-header-menuOptions">
- {this.props.onlyShowOptions ? <></> :
- <>
- {this.renderTypes()}
- {this.renderSorting()}
- {this.renderColors()}
- <div className="collectionSchema-headerMenu-group">
- <button onClick={() => this.props.deleteColumn(this.props.columnField.heading)}>Hide Column</button>
- </div>
- </>
- }
- </div>
- );
- }
-
- render() {
- return (
- <div className="collectionSchema-header-menu" ref={this.setNode}>
- <Flyout anchorPoint={this.props.anchorPoint ? this.props.anchorPoint : anchorPoints.TOP_CENTER} content={this.renderContent()}>
- <div className="collectionSchema-header-toggler" onClick={() => this.toggleIsOpen()}>{this.props.menuButtonContent}</div>
- </ Flyout >
- </div>
- );
- }
-}
-
-
-export interface KeysDropdownProps {
- keyValue: string;
- possibleKeys: string[];
- existingKeys: string[];
- canAddNew: boolean;
- addNew: boolean;
- onSelect: (oldKey: string, newKey: string, addnew: boolean, filter?: string) => void;
- setIsEditing: (isEditing: boolean) => void;
- width?: string;
- docs?: Doc[];
- Document: Doc;
- dataDoc: Doc | undefined;
- fieldKey: string;
- ContainingCollectionDoc: Doc | undefined;
- ContainingCollectionView: Opt<CollectionView>;
- active?: (outsideReaction?: boolean) => boolean | undefined;
- openHeader: (column: any, screenx: number, screeny: number) => void;
- col: SchemaHeaderField;
- icon: IconProp;
-}
-@observer
-export class KeysDropdown extends React.Component<KeysDropdownProps> {
- @observable private _key: string = this.props.keyValue;
- @observable private _searchTerm: string = this.props.keyValue + ":";
- @observable private _isOpen: boolean = false;
- @observable private _node: HTMLDivElement | null = null;
- @observable private _inputRef: React.RefObject<HTMLInputElement> = React.createRef();
-
- @action setSearchTerm = (value: string): void => { this._searchTerm = value; };
- @action setKey = (key: string): void => { this._key = key; };
- @action setIsOpen = (isOpen: boolean): void => { this._isOpen = isOpen; };
-
- @action
- onSelect = (key: string): void => {
- this.props.onSelect(this._key, key, this.props.addNew);
- this.setKey(key);
- this._isOpen = false;
- this.props.setIsEditing(false);
- }
-
- @action
- setNode = (node: HTMLDivElement): void => {
- if (node) {
- this._node = node;
- }
- }
-
- componentDidMount() {
- document.addEventListener("pointerdown", this.detectClick);
- const filters = Cast(this.props.Document._docFilters, listSpec("string"));
- if (filters?.some(filter => filter.split(":")[0] === this._key)) {
- runInAction(() => this.closeResultsVisibility = "contents");
- }
- }
-
- @action
- detectClick = (e: PointerEvent): void => {
- if (this._node && this._node.contains(e.target as Node)) {
- } else {
- this._isOpen = false;
- this.props.setIsEditing(false);
- }
- }
-
- private tempfilter: string = "";
- @undoBatch
- onKeyDown = (e: React.KeyboardEvent): void => {
- if (e.key === "Enter") {
- e.stopPropagation();
- if (this._searchTerm.includes(":")) {
- const colpos = this._searchTerm.indexOf(":");
- const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
- if (temp === "") {
- Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove");
- this.updateFilter();
- }
- else {
- Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove");
- this.tempfilter = temp;
- Doc.setDocFilter(this.props.Document, this._key, temp, "check");
- this.props.col.setColor("green");
- this.closeResultsVisibility = "contents";
- }
- }
- else {
- Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove");
- this.updateFilter();
- if (this.showKeys.length) {
- this.onSelect(this.showKeys[0]);
- } else if (this._searchTerm !== "" && this.props.canAddNew) {
- this.setSearchTerm(this._searchTerm || this._key);
- this.onSelect(this._searchTerm);
- }
- }
- }
- }
-
- onChange = (val: string): void => {
- this.setSearchTerm(val);
- }
-
- @action
- onFocus = (e: React.FocusEvent): void => {
- this._isOpen = true;
- this.props.setIsEditing(true);
- }
-
- @computed get showKeys() {
- const whitelistKeys = ["context", "author", "*lastModified", "text", "data", "tags", "creationDate"];
- const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
- const showKeys = new Set<string>();
- [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.noviceMode ||
- whitelistKeys.includes(key)
- || ((!key.startsWith("_") && key[0] === key[0].toUpperCase()) || key[0] === "#")) ? showKeys.add(key) : null);
- return Array.from(showKeys.keys()).filter(key => !this._searchTerm || key.includes(this._searchTerm));
- }
-
- @computed get renderOptions() {
- if (!this._isOpen) {
- this.defaultMenuHeight = 0;
- return (null);
- }
- const options = this.showKeys.map(key => {
- return <div key={key} className="key-option" style={{
- border: "1px solid lightgray",
- width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white",
- }}
- onPointerDown={e => {
- e.stopPropagation();
- }}
- onClick={() => {
- this.onSelect(key);
- this.setSearchTerm("");
- }}>{key}</div>;
- });
-
- // if search term does not already exist as a group type, give option to create new group type
-
- if (this._key !== this._searchTerm.slice(0, this._key.length)) {
- if (this._searchTerm !== "" && this.props.canAddNew) {
- options.push(<div key={""} className="key-option" style={{
- border: "1px solid lightgray", width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white",
- }}
- onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}>
- Create "{this._searchTerm}" key</div>);
- }
- }
-
- if (options.length === 0) {
- this.defaultMenuHeight = 0;
- }
- else {
- if (this.props.docs) {
- const panesize = this.props.docs.length * 30;
- options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8;
- }
- else {
- options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8;
- }
- }
- return options;
- }
-
- @computed get docSafe() { return DocListCast(this.props.dataDoc?.[this.props.fieldKey]); }
-
- @computed get renderFilterOptions() {
- if (!this._isOpen || !this.props.dataDoc) {
- this.defaultMenuHeight = 0;
- return (null);
- }
- const keyOptions: string[] = [];
- const colpos = this._searchTerm.indexOf(":");
- const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
- this.docSafe.forEach(doc => {
- const key = StrCast(doc[this._key]);
- if (keyOptions.includes(key) === false && key.includes(temp) && key !== "") {
- keyOptions.push(key);
- }
- });
-
- const filters = StrListCast(this.props.Document._docFilters);
- if (filters.some(filter => filter.split(":")[0] === this._key) === false) {
- this.props.col.setColor("rgb(241, 239, 235)");
- this.closeResultsVisibility = "none";
- }
- for (let i = 0; i < (filters?.length ?? 0) - 1; i++) {
- if (filters[i] === this.props.col.heading && keyOptions.includes(filters[i].split(":")[1]) === false) {
- keyOptions.push(filters[i + 1]);
- }
- }
- const options = keyOptions.map(key => {
- let bool = false;
- if (filters !== undefined) {
- const ind = filters.findIndex(filter => filter.split(":")[1] === key);
- const fields = ind === -1 ? undefined : filters[ind].split(":");
- bool = fields ? fields[2] === "check" : false;
- }
- return <div key={key} className="key-option" style={{
- paddingLeft: 5, textAlign: "left",
- width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", backgroundColor: "white",
- }}
- >
- <input type="checkbox"
- onPointerDown={e => e.stopPropagation()}
- onClick={e => e.stopPropagation()}
- onChange={action(e => {
- if (e.target.checked) {
- Doc.setDocFilter(this.props.Document, this._key, key, "check");
- this.closeResultsVisibility = "contents";
- this.props.col.setColor("green");
- } else {
- Doc.setDocFilter(this.props.Document, this._key, key, "remove");
- this.updateFilter();
- }
- })}
- checked={bool}
- />
- <span style={{ paddingLeft: 4 }}>
- {key}
- </span>
-
- </div>;
- });
- if (options.length === 0) {
- this.defaultMenuHeight = 0;
- }
- else {
- if (this.props.docs) {
- const panesize = this.props.docs.length * 30;
- options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8;
- }
- else {
- options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8;
- }
-
- }
- return options;
- }
-
- @observable defaultMenuHeight = 0;
-
-
- updateFilter() {
- const filters = Cast(this.props.Document._docFilters, listSpec("string"));
- if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) {
- this.props.col.setColor("rgb(241, 239, 235)");
- this.closeResultsVisibility = "none";
- }
- }
-
- @computed get scriptField() {
- const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
- const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
- return script ? () => script : undefined;
- }
- filterBackground = () => "rgba(105, 105, 105, 0.432)";
- @observable filterOpen: boolean | undefined = undefined;
- closeResultsVisibility: string = "none";
-
- removeFilters = (e: React.PointerEvent): void => {
- const keyOptions: string[] = [];
- this.docSafe.forEach(doc => {
- const key = StrCast(doc[this._key]);
- if (keyOptions.includes(key) === false) {
- keyOptions.push(key);
- }
- });
-
- Doc.setDocFilter(this.props.Document, this._key, "", "remove");
- this.props.col.setColor("rgb(241, 239, 235)");
- this.closeResultsVisibility = "none";
- }
- render() {
- return (
- <div style={{ display: "flex", width: '100%', alignContent: 'center', alignItems: 'center' }} ref={this.setNode}>
- <div className="schema-icon" onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); e.stopPropagation(); }}>
- <FontAwesomeIcon icon={this.props.icon} size="lg" style={{ display: "inline" }} />
- </div>
-
- <div className="keys-dropdown" style={{ zIndex: 1, width: this.props.width, maxWidth: this.props.width }}>
- <input className="keys-search" style={{ width: "100%" }}
- ref={this._inputRef} type="text"
- value={this._searchTerm} placeholder="Column key"
- onKeyDown={this.onKeyDown}
- onChange={e => this.onChange(e.target.value)}
- onClick={(e) => { e.stopPropagation(); this._inputRef.current?.focus(); }}
- onFocus={this.onFocus} ></input>
- <div style={{ display: this.closeResultsVisibility }}>
- <FontAwesomeIcon onPointerDown={this.removeFilters} icon={"times-circle"} size="lg"
- style={{ cursor: "hand", color: "grey", padding: 2, left: -20, top: -1, height: 15, position: "relative" }} />
- </div>
- {!this._isOpen ? (null) : <div className="keys-options-wrapper" style={{
- width: this.props.width, maxWidth: this.props.width, height: "auto",
- }}>
- {this._searchTerm.includes(":") ? this.renderFilterOptions : this.renderOptions}
- </div>}
- </div >
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
deleted file mode 100644
index 28d2e6ab1..000000000
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import React = require('react');
-import { action } from 'mobx';
-import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { DragManager } from '../../../util/DragManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { Transform } from '../../../util/Transform';
-import './CollectionSchemaView.scss';
-
-export interface MovableColumnProps {
- columnRenderer: React.ReactNode;
- columnValue: SchemaHeaderField;
- allColumns: SchemaHeaderField[];
- reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columns: SchemaHeaderField[]) => void;
- ScreenToLocalTransform: () => Transform;
-}
-export class MovableColumn extends React.Component<MovableColumnProps> {
- // The header of the column
- private _header?: React.RefObject<HTMLDivElement> = React.createRef();
- // The container of the function that is responsible for moving the column over to a new plac
- private _colDropDisposer?: DragManager.DragDropDisposer;
- // initial column position
- private _startDragPosition: { x: number; y: number } = { x: 0, y: 0 };
- // sensitivity to being dragged, in pixels
- private _sensitivity: number = 16;
- // Column reference ID
- private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
-
- onPointerEnter = (e: React.PointerEvent): void => {
- // if the column is left-clicked and it is being dragged
- if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
- this._header!.current!.className = 'collectionSchema-col-wrapper';
- document.addEventListener('pointermove', this.onDragMove, true);
- }
- };
-
- onPointerLeave = (e: React.PointerEvent): void => {
- this._header!.current!.className = 'collectionSchema-col-wrapper';
- document.removeEventListener('pointermove', this.onDragMove, true);
- !e.buttons && document.removeEventListener('pointermove', this.onPointerMove);
- };
-
- onDragMove = (e: PointerEvent): void => {
- // only take into account the horizonal direction when a column is dragged
- const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
- const rect = this._header!.current!.getBoundingClientRect();
- // Now store the point at the top center of the column when it was in its original position
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top);
- // to be compared with its new horizontal position
- const before = x[0] < bounds[0];
- this._header!.current!.className = 'collectionSchema-col-wrapper';
- if (before) this._header!.current!.className += ' col-before';
- if (!before) this._header!.current!.className += ' col-after';
- e.stopPropagation();
- };
-
- createColDropTarget = (ele: HTMLDivElement) => {
- this._colDropDisposer?.();
- if (ele) {
- this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
- }
- };
-
- colDrop = (e: Event, de: DragManager.DropEvent) => {
- document.removeEventListener('pointermove', this.onDragMove, true);
- // we only care about whether the column is shifted to the side
- const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
- // get the dimensions of the smallest rectangle that bounds the header
- const rect = this._header!.current!.getBoundingClientRect();
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top);
- // get whether the column was dragged before or after where it is now
- const before = x[0] < bounds[0];
- const colDragData = de.complete.columnDragData;
- // if there is colDragData, which happen when the drag is complete, reorder the columns according to the established variables
- if (colDragData) {
- e.stopPropagation();
- this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns);
- return true;
- }
- return false;
- };
-
- onPointerMove = (e: PointerEvent) => {
- const onRowMove = (e: PointerEvent) => {
- e.stopPropagation();
- e.preventDefault();
-
- document.removeEventListener('pointermove', onRowMove);
- document.removeEventListener('pointerup', onRowUp);
- const dragData = new DragManager.ColumnDragData(this.props.columnValue);
- DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y);
- };
- const onRowUp = (): void => {
- document.removeEventListener('pointermove', onRowMove);
- document.removeEventListener('pointerup', onRowUp);
- };
- // if the left mouse button is the one being held
- if (e.buttons === 1) {
- const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
- // If the movemnt of the drag exceeds the sensitivity value
- if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
- document.removeEventListener('pointermove', this.onPointerMove);
- e.stopPropagation();
-
- document.addEventListener('pointermove', onRowMove);
- document.addEventListener('pointerup', onRowUp);
- }
- }
- };
-
- onPointerUp = (e: React.PointerEvent) => {
- document.removeEventListener('pointermove', this.onPointerMove);
- };
-
- @action
- onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => {
- this._dragRef = ref;
- const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY);
- // If the cell thing dragged is not being edited
- if (!(e.target as any)?.tagName.includes('INPUT')) {
- this._startDragPosition = { x: dx, y: dy };
- document.addEventListener('pointermove', this.onPointerMove);
- }
- };
-
- render() {
- const reference = React.createRef<HTMLDivElement>();
-
- return (
- <div className="collectionSchema-col" ref={this.createColDropTarget}>
- <div className="collectionSchema-col-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- <div className="col-dragger" ref={reference} onPointerDown={e => this.onPointerDown(e, reference)} onPointerUp={this.onPointerUp}>
- {this.props.columnRenderer}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
deleted file mode 100644
index 3cb2df7d3..000000000
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
+++ /dev/null
@@ -1,152 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action } from 'mobx';
-import * as React from 'react';
-import { ReactTableDefaults, RowInfo } from 'react-table';
-import { Doc } from '../../../../fields/Doc';
-import { Cast, FieldValue, StrCast } from '../../../../fields/Types';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType, SetupDrag } from '../../../util/DragManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { Transform } from '../../../util/Transform';
-import { undoBatch } from '../../../util/UndoManager';
-import { ContextMenu } from '../../ContextMenu';
-import { OpenWhere } from '../../nodes/DocumentView';
-import './CollectionSchemaView.scss';
-
-export interface MovableRowProps {
- rowInfo: RowInfo;
- ScreenToLocalTransform: () => Transform;
- addDoc: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
- removeDoc: (doc: Doc | Doc[]) => boolean;
- rowFocused: boolean;
- textWrapRow: (doc: Doc) => void;
- rowWrapped: boolean;
- dropAction: string;
- addDocTab: any;
-}
-
-export class MovableRow extends React.Component<React.PropsWithChildren<MovableRowProps>> {
- private _header?: React.RefObject<HTMLDivElement> = React.createRef();
- private _rowDropDisposer?: DragManager.DragDropDisposer;
-
- // Event listeners are only necessary when the user is hovering over the table
- // Create one when the mouse starts hovering...
- onPointerEnter = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
- this._header!.current!.className = 'collectionSchema-row-wrapper';
- document.addEventListener('pointermove', this.onDragMove, true);
- }
- };
- // ... and delete it when the mouse leaves
- onPointerLeave = (e: React.PointerEvent): void => {
- this._header!.current!.className = 'collectionSchema-row-wrapper';
- document.removeEventListener('pointermove', this.onDragMove, true);
- };
- // The method for the event listener, reorders columns when dragged to their new locations.
- onDragMove = (e: PointerEvent): void => {
- const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
- const rect = this._header!.current!.getBoundingClientRect();
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
- const before = x[1] < bounds[1];
- this._header!.current!.className = 'collectionSchema-row-wrapper';
- if (before) this._header!.current!.className += ' row-above';
- if (!before) this._header!.current!.className += ' row-below';
- e.stopPropagation();
- };
- componentWillUnmount() {
- this._rowDropDisposer?.();
- }
- //
- createRowDropTarget = (ele: HTMLDivElement) => {
- this._rowDropDisposer?.();
- if (ele) {
- this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
- }
- };
- // Controls what hppens when a row is dragged and dropped
- rowDrop = (e: Event, de: DragManager.DropEvent) => {
- this.onPointerLeave(e as any);
- const rowDoc = FieldValue(Cast(this.props.rowInfo.original, Doc));
- if (!rowDoc) return false;
-
- const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
- const rect = this._header!.current!.getBoundingClientRect();
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
- const before = x[1] < bounds[1];
-
- const docDragData = de.complete.docDragData;
- if (docDragData) {
- e.stopPropagation();
- if (docDragData.draggedDocuments[0] === rowDoc) return true;
- const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before);
- const movedDocs = docDragData.draggedDocuments;
- return docDragData.dropAction || docDragData.userDropAction
- ? docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
- : docDragData.moveDocument
- ? movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false)
- : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
- }
- return false;
- };
-
- onRowContextMenu = (e: React.MouseEvent): void => {
- const description = this.props.rowWrapped ? 'Unwrap text on row' : 'Text wrap row';
- ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: 'file-pdf' });
- };
-
- @undoBatch
- @action
- move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => {
- const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
- return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
- };
-
- @action
- onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
- console.log('yes');
- if (e.key === 'Backspace' || e.key === 'Delete') {
- undoBatch(() => this.props.removeDoc(this.props.rowInfo.original));
- }
- };
-
- render() {
- const { children = null, rowInfo } = this.props;
-
- if (!rowInfo) {
- return <ReactTableDefaults.TrComponent>{children}</ReactTableDefaults.TrComponent>;
- }
-
- const { original } = rowInfo;
- const doc = FieldValue(Cast(original, Doc));
-
- if (!doc) return null;
-
- const reference = React.createRef<HTMLDivElement>();
- const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType);
-
- let className = 'collectionSchema-row';
- if (this.props.rowFocused) className += ' row-focused';
- if (this.props.rowWrapped) className += ' row-wrapped';
-
- return (
- <div className={className} onKeyPress={this.onKeyDown} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}>
- <div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- <ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown}>
- <div className="row-dragger">
- <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}>
- <FontAwesomeIcon icon="trash" size="sm" />
- </div>
- <div className="row-option" style={{ cursor: 'grab' }} ref={reference} onPointerDown={onItemDown}>
- <FontAwesomeIcon icon="grip-vertical" size="sm" />
- </div>
- <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, OpenWhere.addRight)}>
- <FontAwesomeIcon icon="external-link-alt" size="sm" />
- </div>
- </div>
- {children}
- </ReactTableDefaults.TrComponent>
- </div>
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 19401c7f0..5eb5cc86d 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -1,599 +1,218 @@
@import '../../global/globalCssVariables.scss';
-@import '../../../../../node_modules/react-table/react-table.css';
-.collectionSchemaView-container {
- border-width: $COLLECTION_BORDER_WIDTH;
- border-color: $medium-gray;
- border-style: solid;
- border-radius: $border-radius;
- box-sizing: border-box;
- position: relative;
- top: 0;
- width: 100%;
- height: 100%;
- margin-top: 0;
- transition: top 0.5s;
- display: flex;
- justify-content: space-between;
- flex-wrap: nowrap;
- touch-action: none;
- div {
- touch-action: none;
- }
- .collectionSchemaView-tableContainer {
- width: 100%;
- height: 100%;
- }
- .collectionSchemaView-dividerDragger {
- position: relative;
- height: 100%;
- width: $SCHEMA_DIVIDER_WIDTH;
- z-index: 20;
- right: 0;
- top: 0;
- background: gray;
- cursor: col-resize;
- }
- // .documentView-node:first-child {
- // background: $white;
- // }
-}
-.collectionSchemaView-searchContainer {
- border-width: $COLLECTION_BORDER_WIDTH;
- border-color: $medium-gray;
- border-style: solid;
- border-radius: $border-radius;
- box-sizing: border-box;
- position: relative;
- top: 0;
- width: 100%;
+.collectionSchemaView {
+ cursor: default;
height: 100%;
- margin-top: 0;
- transition: top 0.5s;
display: flex;
- justify-content: space-between;
- flex-wrap: nowrap;
- touch-action: none;
- padding: 2px;
- div {
- touch-action: none;
- }
- .collectionSchemaView-tableContainer {
- width: 100%;
- height: 100%;
- }
- .collectionSchemaView-dividerDragger {
- position: relative;
- height: 100%;
- width: 20px;
- z-index: 20;
- right: 0;
- top: 0;
- background: gray;
- cursor: col-resize;
- }
- // .documentView-node:first-child {
- // background: $white;
- // }
-}
+ flex-direction: row;
-.ReactTable {
- width: 100%;
- background: white;
- box-sizing: border-box;
- border: none !important;
- float: none !important;
- .rt-table {
- height: 100%;
- display: -webkit-inline-box;
- direction: ltr;
- overflow: visible;
- }
- .rt-noData {
- display: none;
- }
- .rt-thead {
- width: 100%;
- z-index: 100;
- overflow-y: visible;
- &.-header {
- font-size: 12px;
- height: 30px;
- box-shadow: none;
- z-index: 100;
- overflow-y: visible;
- }
- .rt-resizable-header-content {
- height: 100%;
- overflow: visible;
- }
- .rt-th {
- padding: 0;
- border-left: solid 1px $light-gray;
- }
- }
- .rt-th {
- font-size: 13px;
- text-align: center;
- &:last-child {
- overflow: visible;
- }
- }
- .rt-tbody {
- width: 100%;
- direction: rtl;
- overflow: visible;
- .rt-td {
- border-right: 1px solid rgba(0, 0, 0, 0.2);
- }
- }
- .rt-tr-group {
- direction: ltr;
- flex: 0 1 auto;
- min-height: 30px;
- border: 0 !important;
- }
- .rt-tr-group:nth-of-type(even) {
- direction: ltr;
- flex: 0 1 auto;
- min-height: 30px;
- border: 0 !important;
- background-color: red;
- }
- .rt-tr {
- width: 100%;
- min-height: 30px;
- }
- .rt-td {
- padding: 0;
- font-size: 13px;
- text-align: center;
- white-space: nowrap;
- display: flex;
- align-items: center;
- .imageBox-cont {
- position: relative;
- max-height: 100%;
- }
- .imageBox-cont img {
- object-fit: contain;
- max-width: 100%;
- height: 100%;
- }
- .videoBox-cont {
- object-fit: contain;
- width: auto;
- height: 100%;
- }
- }
- .rt-td.rt-expandable {
- display: flex;
- align-items: center;
- height: inherit;
- }
- .rt-resizer {
- width: 8px;
- right: -4px;
- }
- .rt-resizable-header {
- padding: 0;
- height: 30px;
- }
- .rt-resizable-header:last-child {
- overflow: visible;
- .rt-resizer {
- width: 5px !important;
- }
- }
-}
+ .schema-table {
+ background-color: $white;
-.documentView-node-topmost {
- text-align: left;
- transform-origin: center top;
- display: inline-block;
-}
+ .schema-column-menu,
+ .schema-filter-menu {
+ background: $light-gray;
+ position: absolute;
+ min-width: 200px;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ z-index: 1;
-.collectionSchema-col {
- height: 100%;
-}
+ .schema-key-search-input {
+ width: calc(100% - 20px);
+ margin: 10px;
+ }
-.collectionSchema-header-menu {
- height: auto;
- z-index: 100;
- position: absolute;
- background: white;
- padding: 5px;
- position: fixed;
- background: white;
- border: black 1px solid;
- .collectionSchema-header-toggler {
- z-index: 100;
- width: 100%;
- height: 100%;
- padding: 4px;
- letter-spacing: 2px;
- text-transform: uppercase;
- svg {
- margin-right: 4px;
- }
- }
-}
+ .schema-key-search-result {
+ cursor: pointer;
+ padding: 2px 10px;
+ width: 100%;
-.collectionSchemaView-header {
- height: 100%;
- color: gray;
- z-index: 100;
- overflow-y: visible;
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
-}
+ &:hover {
+ background-color: $medium-gray;
+ }
+ }
-button.add-column {
- width: 28px;
-}
+ .schema-key-search,
+ .schema-new-key-options {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ }
-.collectionSchemaView-menuOptions-wrapper {
- background: rgb(241, 239, 235);
- display: flex;
- cursor: default;
- height: 100%;
- align-content: center;
- align-items: center;
-}
+ .schema-new-key-options {
+ margin: 10px;
+ .schema-key-warning {
+ color: red;
+ font-weight: normal;
+ align-self: center;
+ }
+ }
-.collectionSchema-header-menuOptions {
- color: black;
- width: 180px;
- text-align: left;
- .collectionSchema-headerMenu-group {
- padding: 7px 0;
- border-bottom: 1px solid lightgray;
- cursor: pointer;
- &:first-child {
- padding-top: 0;
- }
- &:last-child {
- border: none;
- text-align: center;
- padding: 12px 0 0 0;
- }
- }
- label {
- color: $medium-gray;
- font-weight: normal;
- letter-spacing: 2px;
- text-transform: uppercase;
- }
- input {
- color: black;
- width: 100%;
- }
- .columnMenu-option {
- cursor: pointer;
- padding: 3px;
- background-color: white;
- transition: background-color 0.2s;
- &:hover {
- background-color: $light-gray;
- }
- &.active {
- font-weight: bold;
- border: 2px solid $light-gray;
- }
- svg {
- color: gray;
- margin-right: 5px;
- width: 10px;
- }
- }
+ .schema-key-list {
+ width: 100%;
+ max-height: 300px;
+ overflow-y: auto;
+ }
+
+ .schema-key-type-option {
+ margin: 2px 0px;
- .keys-dropdown {
- position: relative;
- //width: 100%;
- background-color: white;
- input {
- border: 2px solid $light-gray;
- padding: 3px;
- height: 28px;
- font-weight: bold;
- letter-spacing: '2px';
- text-transform: 'uppercase';
- &:focus {
- font-weight: normal;
+ input {
+ margin-right: 5px;
+ }
}
- }
- }
- .columnMenu-colors {
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
- .columnMenu-colorPicker {
- cursor: pointer;
- width: 20px;
- height: 20px;
- border-radius: 10px;
- &.active {
- border: 2px solid white;
- box-shadow: 0 0 0 2px lightgray;
+
+ .schema-key-default-val {
+ margin: 5px 0;
+ }
+
+ .schema-column-menu-button {
+ cursor: pointer;
+ padding: 2px 5px;
+ background: $medium-blue;
+ border-radius: 9999px;
+ color: $white;
+ width: fit-content;
+ margin: 5px;
+ align-self: center;
}
}
- }
-}
-.schema-icon {
- cursor: pointer;
- width: 25px;
- height: 25px;
- display: flex;
- align-items: center;
- justify-content: center;
- align-content: center;
- background-color: $medium-blue;
- color: white;
- margin-right: 5px;
- font-size: 10px;
- border-radius: 3px;
-}
+ .schema-header-row {
+ justify-content: flex-end;
-.keys-options-wrapper {
- position: absolute;
- text-align: left;
- height: fit-content;
- top: 100%;
- z-index: 21;
- background-color: #ffffff;
- box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
- padding: 1px;
- .key-option {
- cursor: pointer;
- color: #000000;
- width: 100%;
- height: 25px;
- font-weight: 400;
- display: flex;
- justify-content: left;
- align-items: center;
- padding-left: 5px;
- &:hover {
- background-color: $light-gray;
- }
- }
-}
+ .row-menu {
+ display: flex;
+ justify-content: flex-end;
+ }
-.collectionSchema-row {
- height: 100%;
- background-color: white;
- &.row-focused .rt-td {
- background-color: $light-blue; //$light-gray;
- overflow: visible;
- }
- &.row-wrapped {
- .rt-td {
- white-space: normal;
+ .schema-column-header {
+ font-weight: bold;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0;
+ z-index: 1;
+
+ .schema-column-title {
+ flex-grow: 2;
+ margin: 5px;
+ }
+
+ .schema-header-menu {
+ margin: 5px;
+ }
+
+ .schema-column-resizer {
+ height: 100%;
+ width: 3px;
+ cursor: ew-resize;
+
+ &:hover {
+ background-color: $light-blue;
+ }
+ }
+
+ .schema-column-resizer.right {
+ margin-left: 5px;
+ align-self: flex-end;
+ }
+
+ .schema-column-resizer.left {
+ margin-right: 5px;
+ align-self: flex-start;
+ }
+ }
}
- }
- .row-dragger {
- display: flex;
- justify-content: space-evenly;
- width: 58px;
- position: absolute;
- /* max-width: 50px; */
- min-height: 30px;
- align-items: center;
- color: lightgray;
- background-color: white;
- transition: color 0.1s ease;
- .row-option {
- color: black;
- cursor: pointer;
- position: relative;
- transition: color 0.1s ease;
+
+ .schema-header-menu {
display: flex;
- flex-direction: column;
- justify-content: center;
- z-index: 2;
- border-radius: 3px;
- padding: 3px;
- &:hover {
- background-color: $light-gray;
- }
+ flex-direction: row;
}
}
- .collectionSchema-row-wrapper {
- &.row-above {
- border-top: 1px solid $medium-blue;
- }
- &.row-below {
- border-bottom: 1px solid $medium-blue;
- }
- &.row-inside {
- border: 2px dashed $medium-blue;
- }
- .row-dragging {
- background-color: blue;
- }
+
+ .schema-preview-divider {
+ height: 100%;
+ background: black;
+ cursor: ew-resize;
}
}
-.collectionSchemaView-cellContainer {
- width: 100%;
- height: unset;
+.schema-row-wrapper {
+ // max-height: 70px;
+ overflow: hidden;
}
-.collectionSchemaView-cellContents {
- width: 100%;
+.schema-header-row {
+ background-color: $light-gray;
}
-.collectionSchemaView-cellWrapper {
+.schema-header-row,
+.schema-row {
display: flex;
+ flex-direction: row;
height: 100%;
- text-align: left;
- padding-left: 19px;
- position: relative;
- align-items: center;
- align-content: center;
- &:focus {
- outline: none;
- }
- &.editing {
- padding: 0;
- box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
- transform: scale(1.1);
- z-index: 40;
- input {
- outline: 0;
- border: none;
- background-color: $white;
- width: 100%;
- height: fit-content;
- min-height: 26px;
- }
- }
- &.focused {
+ // max-height: 70px;
+ overflow: auto;
+
+ .schema-column-header,
+ .schema-table-cell,
+ .row-menu {
+ border: 1px solid $medium-gray;
+ padding: 5px;
overflow: hidden;
- &.inactive {
- border: none;
- }
- }
- p {
- width: 100%;
- height: 100%;
- }
- &:hover .collectionSchemaView-cellContents-docExpander {
- display: block;
- }
- .collectionSchemaView-cellContents-document {
- display: inline-block;
}
- .collectionSchemaView-cellContents-docButton {
- float: right;
- width: '15px';
- height: '15px';
- }
- .collectionSchemaView-dropdownWrapper {
- border: grey;
- border-style: solid;
- border-width: 1px;
- height: 30px;
- .collectionSchemaView-dropdownButton {
- //display: inline-block;
- float: left;
- height: 100%;
- }
- .collectionSchemaView-dropdownText {
- display: inline-block;
- //float: right;
- height: 100%;
- display: 'flex';
- font-size: 13;
- justify-content: 'center';
- align-items: 'center';
- }
- }
- .collectionSchemaView-dropdownContainer {
- position: absolute;
- border: 1px solid rgba(0, 0, 0, 0.04);
- box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14);
- .collectionSchemaView-dropdownOption:hover {
- background-color: rgba(0, 0, 0, 0.14);
- cursor: pointer;
- }
- }
-}
-
-.collectionSchemaView-cellContents-docExpander {
- height: 30px;
- width: 30px;
- display: none;
- position: absolute;
- top: 0;
- right: 0;
- background-color: lightgray;
-}
-
-.doc-drag-over {
- background-color: red;
-}
-
-.collectionSchemaView-toolbar {
- z-index: 100;
}
-.collectionSchemaView-toolbar {
- height: 30px;
- display: flex;
+.schema-row {
justify-content: flex-end;
- padding: 0 10px;
- border-bottom: 2px solid gray;
- .collectionSchemaView-toolbar-item {
+ background: white;
+
+ .row-menu {
display: flex;
- flex-direction: column;
- justify-content: center;
+ flex-direction: row;
+ min-width: 50px;
+ justify-content: flex-end;
}
-}
-
-#preview-schema-checkbox-div {
- margin-left: 20px;
- font-size: 12px;
-}
-
-.collectionSchemaView-table {
- width: 100%;
- height: 100%;
- overflow: auto;
- padding: 3px;
-}
-.rt-td.rt-expandable {
- overflow: visible;
- position: relative;
- height: 100%;
- z-index: 1;
-}
-
-.reactTable-sub {
- background-color: rgb(252, 252, 252);
- width: 100%;
- .rt-thead {
- display: none;
- }
- .row-dragger {
- background-color: rgb(252, 252, 252);
- }
- .rt-table {
- background-color: rgb(252, 252, 252);
- }
- .collectionSchemaView-table {
- width: 100%;
- border: solid 1px;
- overflow: visible;
- padding: 0px;
+ .row-cells {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
}
}
-.collectionSchemaView-expander {
- height: 100%;
- min-height: 30px;
- position: absolute;
- color: gray;
- width: 20;
- height: auto;
- left: 55;
+.schema-row-button,
+.schema-header-button {
+ color: $dark-gray;
+ margin: 3px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
svg {
- position: absolute;
- top: 50%;
- left: 10;
- transform: translate(-50%, -50%);
+ width: 15px;
}
}
-.collectionSchemaView-addRow {
- color: gray;
- letter-spacing: 2px;
- text-transform: uppercase;
+.schema-sort-button {
+ width: 17px;
+ height: 17px;
+ border-radius: 30%;
+ background-color: $dark-gray;
+ color: white;
+ margin: 3px;
cursor: pointer;
- font-size: 10.5px;
- margin-left: 50px;
- margin-top: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ svg {
+ width: 12px;
+ }
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index c4ee1805f..e1c2d989f 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,108 +1,70 @@
import React = require('react');
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked } from 'mobx';
+import { action, computed, observable, ObservableMap, ObservableSet, untracked } from 'mobx';
import { observer } from 'mobx-react';
-import Measure from 'react-measure';
-import { Resize } from 'react-table';
-import { Doc, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
+import { RichTextField } from '../../../../fields/RichTextField';
import { listSpec } from '../../../../fields/Schema';
-import { PastelSchemaPalette, SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast, NumCast } from '../../../../fields/Types';
-import { TraceMobx } from '../../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
-import { DocUtils } from '../../../documents/Documents';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTransparent, returnTrue, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { DragManager } from '../../../util/DragManager';
import { SelectionManager } from '../../../util/SelectionManager';
-import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
-import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
-import { DocumentView } from '../../nodes/DocumentView';
-import { DefaultStyleProvider } from '../../StyleProvider';
+import { EditableView } from '../../EditableView';
+import { DocComponentView, DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
-import { SchemaTable } from './SchemaTable';
-// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
+import { SchemaColumnHeader } from './SchemaColumnHeader';
+import { SchemaRowBox } from './SchemaRowBox';
+import { DefaultStyleProvider } from '../../StyleProvider';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { ScriptField } from '../../../../fields/ScriptField';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
export enum ColumnType {
- Any,
Number,
String,
Boolean,
Doc,
Image,
- List,
- Date,
}
-// this map should be used for keys that should have a const type of value
-const columnTypes: Map<string, ColumnType> = new Map([
- ['title', ColumnType.String],
- ['x', ColumnType.Number],
- ['y', ColumnType.Number],
- ['_width', ColumnType.Number],
- ['_height', ColumnType.Number],
- ['_nativeWidth', ColumnType.Number],
- ['_nativeHeight', ColumnType.Number],
- ['isPrototype', ColumnType.Boolean],
- ['_curPage', ColumnType.Number],
- ['_currentTimecode', ColumnType.Number],
- ['zIndex', ColumnType.Number],
-]);
+
+const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate', 'links'];
@observer
export class CollectionSchemaView extends CollectionSubView() {
- private _previewCont?: HTMLDivElement;
-
- @observable _previewDoc: Doc | undefined = undefined;
- @observable _focusedTable: Doc = this.props.Document;
- @observable _col: any = '';
- @observable _menuWidth = 0;
- @observable _headerOpen = false;
- @observable _headerIsEditing = false;
- @observable _menuHeight = 0;
- @observable _pointerX = 0;
- @observable _pointerY = 0;
- @observable _openTypes: boolean = false;
-
- @computed get previewWidth() {
- return () => NumCast(this.props.Document.schemaPreviewWidth);
- }
- @computed get previewHeight() {
- return () => this.props.PanelHeight() - 2 * this.borderWidth;
- }
- @computed get tableWidth() {
- return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
- }
- @computed get borderWidth() {
- return Number(COLLECTION_BORDER_WIDTH);
- }
- @computed get scale() {
- return this.props.ScreenToLocalTransform().Scale;
- }
- @computed get columns() {
- return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
- }
- set columns(columns: SchemaHeaderField[]) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns);
- }
-
- @computed get menuCoordinates() {
- let searchx = 0;
- let searchy = 0;
- if (this.props.Document._searchDoc) {
- const el = document.getElementsByClassName('collectionSchemaView-searchContainer')[0];
- if (el !== undefined) {
- const rect = el.getBoundingClientRect();
- searchx = rect.x;
- searchy = rect.y;
- }
- }
- const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx;
- const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy;
- return this.props.ScreenToLocalTransform().transformPoint(x, y);
- }
+ private _ref: HTMLDivElement | null = null;
+ private _selectedDocSortedArray: Doc[] = [];
+ private _closestDropIndex: number = 0;
+ private _previewRef: HTMLDivElement | null = null;
+ private _makeNewColumn: boolean = false;
+
+ public static _rowHeight: number = 40;
+ public static _minColWidth: number = 150;
+ public static _rowMenuWidth: number = 100;
+ public static _previewDividerWidth: number = 4;
+
+ @observable _lastSelectedRow: number | undefined;
+ @observable _selectedDocs: ObservableSet = new ObservableSet<Doc>();
+ @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
+ @observable _isDragging: boolean = false;
+ @observable _displayColumnWidths: number[] | undefined;
+ @observable _columnMenuIndex: number | undefined;
+ @observable _menuOptions: string[] = [];
+ @observable _newFieldWarning: string = '';
+ @observable _makeNewField: boolean = false;
+ @observable _newFieldDefault: any = 0;
+ @observable _newFieldType: ColumnType = ColumnType.Number;
+ @observable _menuValue: string = '';
+ @observable _filterColumnIndex: number | undefined;
+ @observable _filterValue: string = '';
get documentKeys() {
const docs = this.childDocs;
@@ -115,532 +77,851 @@ 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));
+ // this.columns.forEach(key => (keys[key.heading] = true));
return Array.from(Object.keys(keys));
}
- @action setHeaderIsEditing = (isEditing: boolean) => (this._headerIsEditing = isEditing);
+ @computed get previewWidth() {
+ return NumCast(this.layoutDoc.schemaPreviewWidth);
+ }
- @undoBatch
- setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => {
- this._openTypes = false;
- if (columnTypes.get(columnField.heading)) return;
-
- const columns = this.columns;
- const index = columns.indexOf(columnField);
- if (index > -1) {
- columnField.setType(NumCast(type));
- columns[index] = columnField;
- this.columns = columns;
+ @computed get tableWidth() {
+ return this.props.PanelWidth() - this.previewWidth - (this.previewWidth === 0 ? 0 : CollectionSchemaView._previewDividerWidth);
+ }
+
+ @computed get columnKeys() {
+ return Cast(this.layoutDoc.columnKeys, listSpec('string'), defaultColumnKeys);
+ }
+
+ @computed get storedColumnWidths() {
+ let widths = Cast(
+ 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);
+ });
}
- });
- @undoBatch
- setColumnColor = (columnField: SchemaHeaderField, color: string): void => {
- const columns = this.columns;
- const index = columns.indexOf(columnField);
- if (index > -1) {
- columnField.setColor(color);
- columns[index] = columnField;
- this.columns = columns; // need to set the columns to trigger rerender
+ return widths;
+ }
+
+ @computed get displayColumnWidths() {
+ return this._displayColumnWidths ?? this.storedColumnWidths;
+ }
+
+ @computed get sortField() {
+ return StrCast(this.layoutDoc.sortField);
+ }
+
+ @computed get sortDesc() {
+ return BoolCast(this.layoutDoc.sortDesc);
+ }
+
+ rowIndex(doc: Doc) {
+ return this.childDocs.indexOf(doc);
+ }
+
+ componentDidMount() {
+ this.props.setContentView?.(this as DocComponentView);
+ document.addEventListener('keydown', this.onKeyDown);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('keydown', this.onKeyDown);
+ }
+
+ @action
+ onKeyDown = (e: KeyboardEvent) => {
+ if (this._selectedDocs.size > 0) {
+ if (e.key == 'ArrowDown') {
+ const lastDoc = Array.from(this._selectedDocs.values()).lastElement();
+ const lastIndex = this.rowIndex(lastDoc);
+ if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) {
+ !e.shiftKey && this.clearSelection();
+ const newDoc = this.childDocs[lastIndex + 1];
+ this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
+ }
+ }
+ if (e.key == 'ArrowUp') {
+ const firstDoc = Array.from(this._selectedDocs.values())[0];
+ const firstIndex = this.rowIndex(firstDoc);
+ if (firstIndex > 0 && firstIndex < this.childDocs.length) {
+ !e.shiftKey && this.clearSelection();
+ const newDoc = this.childDocs[firstIndex - 1];
+ this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
+ }
+ }
}
};
@undoBatch
@action
- setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => {
- const columns = this.columns;
- columns.forEach(col => col.setDesc(undefined));
+ setSort = (field: string | undefined, desc: boolean = false) => {
+ this.layoutDoc.sortField = field;
+ this.layoutDoc.sortDesc = desc;
+
+ if (field === undefined) return;
+
+ this.childDocs.sort((docA, docB) => {
+ const aStr = Field.toString(docA[field] as Field);
+ const bStr = Field.toString(docB[field] as Field);
+ var out = 0;
+ if (aStr < bStr) out = -1;
+ if (aStr > bStr) out = 1;
+ if (desc) out *= -1;
+ return out;
+ });
+ };
- const index = columns.findIndex(c => c.heading === columnField.heading);
- const column = columns[index];
- column.setDesc(descending);
- columns[index] = column;
- this.columns = columns;
+ addRow = (doc: Doc | Doc[]) => {
+ const result: boolean = this.addDocument(doc);
+ this.setSort(this.sortField, this.sortDesc);
+ return result;
};
- renderTypes = (col: any) => {
- if (columnTypes.get(col.heading)) return null;
+ @undoBatch
+ @action
+ changeColumnKey = (index: number, newKey: string, defaultVal?: any) => {
+ if (!this.documentKeys.includes(newKey)) {
+ this.addNewKey(newKey, defaultVal);
+ }
- const type = col.type;
+ let currKeys = [...this.columnKeys];
+ currKeys[index] = newKey;
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+ };
- const anyType = (
- <div className={'columnMenu-option' + (type === ColumnType.Any ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Any)}>
- <FontAwesomeIcon icon={'align-justify'} size="sm" />
- Any
- </div>
- );
+ @undoBatch
+ @action
+ addColumn = (key: string, defaultVal?: any) => {
+ if (!this.documentKeys.includes(key)) {
+ this.addNewKey(key, defaultVal);
+ }
- const numType = (
- <div className={'columnMenu-option' + (type === ColumnType.Number ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Number)}>
- <FontAwesomeIcon icon={'hashtag'} size="sm" />
- Number
- </div>
- );
+ let currWidths = [...this.storedColumnWidths];
+ const newColWidth = this.tableWidth / (currWidths.length + 1);
+ currWidths = currWidths.map(w => {
+ const proportion = w / (this.tableWidth - CollectionSchemaView._rowMenuWidth);
+ return proportion * (this.tableWidth - CollectionSchemaView._rowMenuWidth - newColWidth);
+ });
+ currWidths.splice(0, 0, newColWidth);
+ this.layoutDoc.columnWidths = new List<number>(currWidths);
- const textType = (
- <div className={'columnMenu-option' + (type === ColumnType.String ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.String)}>
- <FontAwesomeIcon icon={'font'} size="sm" />
- Text
- </div>
- );
+ let currKeys = [...this.columnKeys];
+ currKeys.splice(0, 0, key);
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+ };
- const boolType = (
- <div className={'columnMenu-option' + (type === ColumnType.Boolean ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Boolean)}>
- <FontAwesomeIcon icon={'check-square'} size="sm" />
- Checkbox
- </div>
- );
+ @action
+ addNewKey = (key: string, defaultVal: any) => {
+ this.childDocs.forEach(doc => (doc[key] = defaultVal));
+ };
- const listType = (
- <div className={'columnMenu-option' + (type === ColumnType.List ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.List)}>
- <FontAwesomeIcon icon={'list-ul'} size="sm" />
- List
- </div>
- );
+ @undoBatch
+ @action
+ removeColumn = (index: number) => {
+ if (this.columnKeys.length === 1) return;
+ let currWidths = [...this.storedColumnWidths];
+ const removedColWidth = currWidths[index];
+ currWidths = currWidths.map(w => {
+ const proportion = w / (this.tableWidth - CollectionSchemaView._rowMenuWidth - removedColWidth);
+ return proportion * (this.tableWidth - CollectionSchemaView._rowMenuWidth);
+ });
+ currWidths.splice(index, 1);
+ this.layoutDoc.columnWidths = new List<number>(currWidths);
- const docType = (
- <div className={'columnMenu-option' + (type === ColumnType.Doc ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Doc)}>
- <FontAwesomeIcon icon={'file'} size="sm" />
- Document
- </div>
- );
+ let currKeys = [...this.columnKeys];
+ currKeys.splice(index, 1);
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+ };
- const imageType = (
- <div className={'columnMenu-option' + (type === ColumnType.Image ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Image)}>
- <FontAwesomeIcon icon={'image'} size="sm" />
- Image
- </div>
- );
+ @action
+ startResize = (e: any, index: number, left: boolean) => {
+ this._displayColumnWidths = this.storedColumnWidths;
+ setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index, left), this.finishResize, emptyFunction);
+ };
- const dateType = (
- <div className={'columnMenu-option' + (type === ColumnType.Date ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Date)}>
- <FontAwesomeIcon icon={'calendar'} size="sm" />
- Date
- </div>
- );
+ @action
+ resizeColumn = (e: PointerEvent, index: number, left: boolean) => {
+ if (this._displayColumnWidths) {
+ let shrinking;
+ let growing;
+
+ let change = e.movementX;
+
+ if (left && index !== 0) {
+ growing = change < 0 ? index : index - 1;
+ shrinking = change < 0 ? index - 1 : index;
+ } else if (!left && index !== this.columnKeys.length - 1) {
+ growing = change > 0 ? index : index + 1;
+ shrinking = change > 0 ? index + 1 : index;
+ }
- const allColumnTypes = (
- <div className="columnMenu-types">
- {anyType}
- {numType}
- {textType}
- {boolType}
- {listType}
- {docType}
- {imageType}
- {dateType}
- </div>
- );
+ if (shrinking === undefined || growing === undefined) return true;
- const justColType =
- type === ColumnType.Any
- ? anyType
- : type === ColumnType.Number
- ? numType
- : type === ColumnType.String
- ? textType
- : type === ColumnType.Boolean
- ? boolType
- : type === ColumnType.List
- ? listType
- : type === ColumnType.Doc
- ? docType
- : type === ColumnType.Date
- ? dateType
- : imageType;
+ change = Math.abs(change);
+ if (this._displayColumnWidths[shrinking] - change < CollectionSchemaView._minColWidth) {
+ change = this._displayColumnWidths[shrinking] - CollectionSchemaView._minColWidth;
+ }
- return (
- <div className="collectionSchema-headerMenu-group" onClick={action(() => (this._openTypes = !this._openTypes))}>
- <div>
- <label style={{ cursor: 'pointer' }}>Column type:</label>
- <FontAwesomeIcon icon={'caret-down'} size="lg" style={{ float: 'right', transform: `rotate(${this._openTypes ? '180deg' : 0})`, transition: '0.2s all ease' }} />
- </div>
- {this._openTypes ? allColumnTypes : justColType}
- </div>
- );
+ this._displayColumnWidths[shrinking] -= change;
+ this._displayColumnWidths[growing] += change;
+
+ return false;
+ }
+ return true;
};
- renderSorting = (col: any) => {
- const sort = col.desc;
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Sort by:</label>
- <div className="columnMenu-sort">
- <div className={'columnMenu-option' + (sort === true ? ' active' : '')} onClick={() => this.setColumnSort(col, true)}>
- <FontAwesomeIcon icon="sort-amount-down" size="sm" />
- Sort descending
- </div>
- <div className={'columnMenu-option' + (sort === false ? ' active' : '')} onClick={() => this.setColumnSort(col, false)}>
- <FontAwesomeIcon icon="sort-amount-up" size="sm" />
- Sort ascending
- </div>
- <div className="columnMenu-option" onClick={() => this.setColumnSort(col, undefined)}>
- <FontAwesomeIcon icon="times" size="sm" />
- Clear sorting
- </div>
- </div>
- </div>
- );
+ @action
+ finishResize = () => {
+ this.layoutDoc.columnWidths = new List<number>(this._displayColumnWidths);
+ this._displayColumnWidths = undefined;
};
- renderColors = (col: any) => {
- const selected = col.color;
+ @undoBatch
+ @action
+ swapColumns = (index1: number, index2: number) => {
+ const tempKey = this.columnKeys[index1];
+ const tempWidth = this.storedColumnWidths[index1];
+
+ let currKeys = this.columnKeys;
+ currKeys[index1] = currKeys[index2];
+ currKeys[index2] = tempKey;
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+
+ let currWidths = this.storedColumnWidths;
+ currWidths[index1] = currWidths[index2];
+ currWidths[index2] = tempWidth;
+ this.layoutDoc.columnWidths = new List<number>(currWidths);
+ };
- const pink = PastelSchemaPalette.get('pink2');
- const purple = PastelSchemaPalette.get('purple2');
- const blue = PastelSchemaPalette.get('bluegreen1');
- const yellow = PastelSchemaPalette.get('yellow4');
- const red = PastelSchemaPalette.get('red2');
- const gray = '#f1efeb';
+ // @action
+ // dragColumn = (e: any, index: number) => {
+ // e.stopPropagation();
+ // e.preventDefault();
+ // const rect = e.target.getBoundingClientRect();
+ // if (e.clientX < rect.x) {
+ // if (index < 1) return true;
+ // this.swapColumns(index - 1, index);
+ // return true;
+ // }
+ // if (e.clientX > rect.x + rect.width) {
+ // if (index === this.columnKeys.length) return true;
+ // this.swapColumns(index, index + 1);
+ // return true;
+ // }
+ // return false;
+ // };
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Color:</label>
- <div className="columnMenu-colors">
- <div className={'columnMenu-colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.setColumnColor(col, pink!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.setColumnColor(col, purple!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.setColumnColor(col, blue!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.setColumnColor(col, yellow!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.setColumnColor(col, red!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.setColumnColor(col, gray)}></div>
- </div>
- </div>
- );
+ @action
+ addRowRef = (doc: Doc, ref: HTMLDivElement) => {
+ this._rowEles.set(doc, ref);
};
- @undoBatch
@action
- changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, 'f1efeb')]);
- } else {
- if (addNew) {
- columns.push(new SchemaHeaderField(newKey, 'f1efeb'));
- this.columns = columns;
- } else {
- const index = columns.map(c => c.heading).indexOf(oldKey);
- if (index > -1) {
- const column = columns[index];
- column.setHeading(newKey);
- columns[index] = column;
- this.columns = columns;
- if (filter) {
- Doc.setDocFilter(this.props.Document, newKey, filter, 'match');
- } else {
- this.props.Document._docFilters = undefined;
- }
- }
- }
- }
+ addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => {
+ this._selectedDocs.add(doc);
+ const rowDocView = DocumentManager.Instance.getDocumentView(doc);
+ if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection);
+ this._lastSelectedRow = index;
};
@action
- openHeader = (col: any, screenx: number, screeny: number) => {
- this._col = col;
- this._headerOpen = true;
- this._pointerX = screenx;
- this._pointerY = screeny;
+ removeDocFromSelection = (doc: Doc) => {
+ if (this._selectedDocs.has(doc)) this._selectedDocs.delete(doc);
+ const rowDocView = DocumentManager.Instance.getDocumentView(doc);
+ if (rowDocView) SelectionManager.DeselectView(rowDocView);
+ if (this._selectedDocs.size === 0) {
+ this._lastSelectedRow = undefined;
+ }
};
@action
- closeHeader = () => {
- this._headerOpen = false;
+ clearSelection = () => {
+ this._selectedDocs.clear();
+ SelectionManager.DeselectAll();
+ this._lastSelectedRow = undefined;
};
- @undoBatch
+ rowOnClickScript = ScriptField.MakeFunction('scriptContext.selectRow(self, shiftKey, ctrlKey || metaKey)', { scriptContext: 'any', shiftKey: 'boolean', ctrlKey: 'boolean', metaKey: 'boolean' })!;
+
@action
- deleteColumn = (key: string) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([]);
+ selectRow = (doc: Doc, shift: boolean, ctrl: boolean) => {
+ const index = this.childDocs.indexOf(doc);
+ if (index < 0) return;
+ if (shift && this._lastSelectedRow !== undefined) {
+ const startRow = Math.min(this._lastSelectedRow, index);
+ const endRow = Math.max(this._lastSelectedRow, index);
+ for (let i = startRow; i <= endRow; i++) {
+ const currDoc = this.childDocs[i];
+ if (!this._selectedDocs.has(currDoc)) this.addDocToSelection(currDoc, true, i);
+ }
+ this._lastSelectedRow = index;
+ } else if (ctrl) {
+ if (!this._selectedDocs.has(doc)) {
+ this.addDocToSelection(doc, true, index);
+ } else {
+ this.removeDocFromSelection(doc);
+ }
} else {
- const index = columns.map(c => c.heading).indexOf(key);
- if (index > -1) {
- columns.splice(index, 1);
- this.columns = columns;
+ if (!this._selectedDocs.has(doc)) {
+ this.clearSelection();
+ this.addDocToSelection(doc, false, index);
}
}
- this.closeHeader();
};
- getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(-this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, -this.borderWidth);
+ @action
+ sortedSelectedDocs = (): Doc[] => {
+ return this.childDocs.filter(doc => this._selectedDocs.has(doc));
};
- @action
- onHeaderClick = (e: React.PointerEvent) => {
- e.stopPropagation();
+ setDropIndex = (index: number) => {
+ this._closestDropIndex = index;
};
@action
- onWheel(e: React.WheelEvent) {
- const scale = this.props.ScreenToLocalTransform().Scale;
- this.props.isContentActive(true) && e.stopPropagation();
- }
-
- @computed get renderMenuContent() {
- TraceMobx();
- return (
- <div className="collectionSchema-header-menuOptions">
- {this.renderTypes(this._col)}
- {this.renderColors(this._col)}
- <div className="collectionSchema-headerMenu-group">
- <button
- onClick={() => {
- this.deleteColumn(this._col.heading);
- }}>
- Hide Column
- </button>
- </div>
- </div>
- );
- }
-
- private createTarget = (ele: HTMLDivElement) => {
- this._previewCont = ele;
- super.CreateDropTarget(ele);
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+ if (super.onInternalDrop(e, de)) {
+ this._isDragging = false;
+ const pushedDocs: Doc[] = this.childDocs.filter((doc: Doc, index: number) => index >= this._closestDropIndex && !this._selectedDocs.has(doc));
+ this.props.removeDocument?.(pushedDocs);
+ this.props.removeDocument?.(this._selectedDocSortedArray);
+ this.addDocument(this._selectedDocSortedArray);
+ this.addDocument(pushedDocs);
+ this.setSort(undefined);
+ this.clearSelection();
+ return true;
+ }
+ return false;
};
- isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable;
-
- @action setFocused = (doc: Doc) => (this._focusedTable = doc);
-
- @action setPreviewDoc = (doc: Opt<Doc>) => {
- SelectionManager.SelectSchemaViewDoc(doc);
- this._previewDoc = doc;
+ @action
+ onExternalDrop = async (e: React.DragEvent): Promise<void> => {
+ super.onExternalDrop(
+ e,
+ {},
+ undoBatch(
+ action(docus => {
+ this._isDragging = false;
+ docus.map((doc: Doc) => {
+ this.addDocument(doc);
+ });
+ })
+ )
+ );
+ this.setSort(undefined);
};
- //toggles preview side-panel of schema
@action
- toggleExpander = () => {
- this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0;
+ startDrag = (e: PointerEvent, doc: Doc, index: number) => {
+ if (!this._selectedDocs.has(doc)) {
+ this.clearSelection();
+ this.addDocToSelection(doc, false, index);
+ }
+ this._isDragging = true;
+ this._selectedDocSortedArray = this.sortedSelectedDocs();
+ const dragData = new DragManager.DocumentDragData(this._selectedDocSortedArray, 'move');
+ dragData.moveDocument = this.props.moveDocument;
+ const dragItem: HTMLElement[] = Array.from(this._selectedDocs.values()).map((doc: Doc) => this._rowEles.get(doc));
+
+ DragManager.StartDocumentDrag(
+ dragItem.map(ele => ele),
+ dragData,
+ e.clientX,
+ e.clientY,
+ undefined
+ );
+ return true;
};
onDividerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander);
+ setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
};
+
@action
onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
- const nativeWidth = this._previewCont!.getBoundingClientRect();
+ const nativeWidth = this._previewRef!.getBoundingClientRect();
const minWidth = 40;
const maxWidth = 1000;
const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
- this.props.Document.schemaPreviewWidth = width;
+ this.layoutDoc.schemaPreviewWidth = width;
return false;
};
- onPointerDown = (e: React.PointerEvent): void => {
- if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (this.props.isSelected(true)) e.stopPropagation();
- else this.props.select(false);
- }
+ @action
+ addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
+ if (!value && !forceEmptyNote) return false;
+ const newDoc = Docs.Create.TextDocument(value, { title: value, _autoHeight: true });
+ FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
+ return this.addRow(newDoc) || false;
};
- @computed
- get previewDocument(): Doc | undefined {
- return this._previewDoc;
- }
-
- @computed
- get dividerDragger() {
- return this.previewWidth() === 0 ? null : (
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown}>
- <div className="collectionSchemaView-dividerDragger" />
- </div>
+ menuCallback = (x: number, y: number) => {
+ ContextMenu.Instance.clearItems();
+ const layoutItems: ContextMenuProps[] = [];
+ const docItems: ContextMenuProps[] = [];
+ const dataDoc = this.props.DataDoc || this.props.Document;
+
+ DocUtils.addDocumentCreatorMenuItems(
+ doc => {
+ FormattedTextBox.SelectOnLoad = StrCast(doc[Id]);
+ return this.addRow(doc);
+ },
+ this.addRow,
+ x,
+ y,
+ true
);
- }
- @computed
- get previewPanel() {
- return (
- <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
- {!this.previewDocument ? null : (
- <DocumentView
- Document={this.previewDocument}
- DataDoc={undefined}
- fitContentsToBox={returnTrue}
- dontCenter={'y'}
- focus={DocUtils.DefaultFocus}
- renderDepth={this.props.renderDepth}
- rootSelected={this.rootSelected}
- PanelWidth={this.previewWidth}
- PanelHeight={this.previewHeight}
- isContentActive={returnTrue}
- isDocumentActive={returnFalse}
- ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={this.childDocFilters}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- styleProvider={DefaultStyleProvider}
- docViewPath={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}
- />
- )}
- </div>
- );
- }
+ Array.from(Object.keys(Doc.GetProto(dataDoc)))
+ .filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string')
+ .map(fieldKey =>
+ docItems.push({
+ description: ':' + fieldKey,
+ event: () => {
+ const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
+ if (created) {
+ if (this.props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ }
+ return this.addRow(created);
+ }
+ },
+ icon: 'compress-arrows-alt',
+ })
+ );
+ Array.from(Object.keys(Doc.GetProto(dataDoc)))
+ .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length)
+ .map(fieldKey =>
+ docItems.push({
+ description: ':' + fieldKey,
+ event: () => {
+ const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
+ if (created) {
+ const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
+ if (container.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, container);
+ return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
+ }
+ return this.addRow(created) || false;
+ }
+ },
+ icon: 'compress-arrows-alt',
+ })
+ );
+ !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
+ !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
+ ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
+ Doc.GetProto(this.props.Document)[name] = '';
+ const created = Docs.Create.TextDocument('', { title: name, _autoHeight: true });
+ if (created) {
+ if (this.props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ }
+ this.addRow(created);
+ }
+ });
+ ContextMenu.Instance.displayMenu(x, y, undefined, true);
+ };
- @computed
- get schemaTable() {
- return (
- <SchemaTable
- Document={this.props.Document}
- PanelHeight={this.props.PanelHeight}
- PanelWidth={this.props.PanelWidth}
- childDocs={this.childDocs}
- CollectionView={this.props.CollectionView}
- ContainingCollectionView={this.props.ContainingCollectionView}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- fieldKey={this.props.fieldKey}
- renderDepth={this.props.renderDepth}
- moveDocument={this.props.moveDocument}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- active={this.props.isContentActive}
- onDrop={this.onExternalDrop}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- isSelected={this.props.isSelected}
- isFocused={this.isFocused}
- setFocused={this.setFocused}
- setPreviewDoc={this.setPreviewDoc}
- deleteDocument={this.props.removeDocument}
- addDocument={this.props.addDocument}
- dataDoc={this.props.DataDoc}
- columns={this.columns}
- documentKeys={this.documentKeys}
- headerIsEditing={this._headerIsEditing}
- openHeader={this.openHeader}
- onClick={this.onTableClick}
- onPointerDown={emptyFunction}
- onResizedChange={this.onResizedChange}
- setColumns={this.setColumns}
- reorderColumns={this.reorderColumns}
- changeColumns={this.changeColumns}
- setHeaderIsEditing={this.setHeaderIsEditing}
- changeColumnSort={this.setColumnSort}
- />
- );
- }
+ focusDocument = (doc: Doc, options: DocFocusOptions) => {
+ Doc.BrushDoc(doc);
+
+ const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
+ if (found) {
+ const top = found.getBoundingClientRect().top;
+ const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
+ if (Math.floor(localTop[1]) !== 0) {
+ let focusSpeed = options.zoomTime ?? 500;
+ smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
+ return focusSpeed;
+ }
+ }
+ return undefined;
+ };
- @computed
- public get schemaToolbar() {
- return (
- <div className="collectionSchemaView-toolbar">
- <div className="collectionSchemaView-toolbar-item">
- <div id="preview-schema-checkbox-div">
- <input type="checkbox" key={'Show Preview'} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} />
- Show Preview
- </div>
- </div>
- </div>
- );
+ isChildContentActive = () =>
+ this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined;
+
+ @computed get fieldDefaultInput() {
+ switch (this._newFieldType) {
+ case ColumnType.Number:
+ return <input type="number" name="" id="" value={this._newFieldDefault ?? 0} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+ case ColumnType.Boolean:
+ return (
+ <>
+ <input type="checkbox" name="" id="" value={this._newFieldDefault} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.checked))} />
+ {this._newFieldDefault ? 'true' : 'false'}
+ </>
+ );
+ case ColumnType.String:
+ return <input type="text" name="" id="" value={this._newFieldDefault ?? ''} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+ }
}
- onSpecificMenu = (e: React.MouseEvent) => {
- if ((e.target as any)?.className?.includes?.('collectionSchemaView-cell') || e.target instanceof HTMLSpanElement) {
- const cm = ContextMenu.Instance;
- const options = cm.findByDescription('Options...');
- const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: 'remove', event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: 'trash' });
- !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
- cm.displayMenu(e.clientX, e.clientY);
- (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this.
- e.stopPropagation();
+ onSearchKeyDown = (e: React.KeyboardEvent) => {
+ switch (e.key) {
+ case 'Enter':
+ this._menuOptions.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuOptions[0]) : action(() => (this._makeNewField = true))();
+ break;
+ case 'Escape':
+ this.closeColumnMenu();
+ break;
}
};
@action
- onTableClick = (e: React.MouseEvent): void => {
- if (!(e.target as any)?.className?.includes?.('collectionSchemaView-cell') && !(e.target instanceof HTMLSpanElement)) {
- this.setPreviewDoc(undefined);
+ setKey = (key: string, defaultVal?: any) => {
+ if (this._makeNewColumn) {
+ this.addColumn(key, defaultVal);
} else {
- e.stopPropagation();
+ this.changeColumnKey(this._columnMenuIndex!, key, defaultVal);
}
- this.setFocused(this.props.Document);
- this.closeHeader();
+ this.closeColumnMenu();
+ };
+
+ @action
+ openColumnMenu = (index: number, newCol: boolean) => {
+ this._makeNewColumn = false;
+ this._columnMenuIndex = index;
+ this._menuValue = '';
+ this._menuOptions = this.documentKeys;
+ this._makeNewField = false;
+ this._newFieldWarning = '';
+ this._makeNewField = false;
+ this._makeNewColumn = newCol;
+ };
+
+ @action
+ closeColumnMenu = () => {
+ this._columnMenuIndex = undefined;
+ };
+
+ @action
+ openFilterMenu = (index: number) => {
+ this._filterColumnIndex = index;
+ this._filterValue = this.getFieldFilters(this.columnKeys[this._filterColumnIndex!]).map(filter => filter.split(':')[1])[0];
+ };
+
+ @action
+ closeFilterMenu = () => {
+ this._filterColumnIndex = undefined;
};
- onResizedChange = (newResized: Resize[], event: any) => {
- const columns = this.columns;
- newResized.forEach(resized => {
- const index = columns.findIndex(c => c.heading === resized.id);
- const column = columns[index];
- column.setWidth(resized.value);
- columns[index] = column;
+ openContextMenu = (x: number, y: number, index: number) => {
+ this.closeColumnMenu();
+ this.closeFilterMenu();
+ ContextMenu.Instance.clearItems();
+ ContextMenu.Instance.addItem({
+ description: 'Change field',
+ event: () => {
+ this.openColumnMenu(index, false);
+ },
+ icon: 'pencil-alt',
+ });
+ ContextMenu.Instance.addItem({
+ description: 'Filter field',
+ event: () => {
+ this.openFilterMenu(index);
+ },
+ icon: 'filter',
});
- this.columns = columns;
+ ContextMenu.Instance.addItem({
+ description: 'Delete column',
+ event: () => {
+ this.removeColumn(index);
+ },
+ icon: 'trash',
+ });
+ ContextMenu.Instance.displayMenu(x, y, undefined, false);
};
@action
- setColumns = (columns: SchemaHeaderField[]) => (this.columns = columns);
+ updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._menuValue = e.target.value;
+ this._menuOptions = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
+ };
- @undoBatch
- reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => {
- const columns = [...columnsValues];
- const oldIndex = columns.indexOf(toMove);
- const relIndex = columns.indexOf(relativeTo);
- const newIndex = oldIndex > relIndex && !before ? relIndex + 1 : oldIndex < relIndex && before ? relIndex - 1 : relIndex;
+ getFieldFilters = (field: string) => {
+ return StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
+ };
- if (oldIndex === newIndex) return;
+ removeFieldFilters = (field: string) => {
+ this.getFieldFilters(field).forEach(filter => {
+ Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove');
+ });
+ };
- columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
- this.columns = columns;
+ onFilterKeyDown = (e: React.KeyboardEvent) => {
+ switch (e.key) {
+ case 'Enter':
+ if (this._filterValue !== '') {
+ Doc.setDocFilter(this.Document, this.columnKeys[this._filterColumnIndex!], this._filterValue, 'check', false, undefined, false);
+ } else {
+ this.removeFieldFilters(this.columnKeys[this._filterColumnIndex!]);
+ }
+ this.closeFilterMenu();
+ break;
+ case 'Escape':
+ this.closeFilterMenu();
+ break;
+ }
};
- onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation();
+ @action
+ updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._filterValue = e.target.value;
+ };
- render() {
- TraceMobx();
- if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0);
- const menuContent = this.renderMenuContent;
- const menu = (
- <div className="collectionSchema-header-menu" onWheel={e => this.onZoomMenu(e)} onPointerDown={e => this.onHeaderClick(e)} style={{ transform: `translate(${this.menuCoordinates[0]}px, ${this.menuCoordinates[1]}px)` }}>
- <Measure
- offset
- onResize={action((r: any) => {
- const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height);
- this._menuWidth = dim[0];
- this._menuHeight = dim[1];
+ @computed get newFieldMenu() {
+ return (
+ <div className="schema-new-key-options">
+ <div className="schema-key-type-option">
+ <input
+ type="radio"
+ name="newFieldType"
+ id=""
+ checked={this._newFieldType == ColumnType.Number}
+ onChange={action(() => {
+ this._newFieldType = ColumnType.Number;
+ this._newFieldDefault = 0;
+ })}
+ />
+ number
+ </div>
+ <div className="schema-key-type-option">
+ <input
+ type="radio"
+ name="newFieldType"
+ id=""
+ checked={this._newFieldType == ColumnType.Boolean}
+ onChange={action(() => {
+ this._newFieldType = ColumnType.Boolean;
+ this._newFieldDefault = false;
+ })}
+ />
+ boolean
+ </div>
+ <div className="schema-key-type-option">
+ <input
+ type="radio"
+ name="newFieldType"
+ id=""
+ checked={this._newFieldType == ColumnType.String}
+ onChange={action(() => {
+ this._newFieldType = ColumnType.String;
+ this._newFieldDefault = '';
+ })}
+ />
+ string
+ </div>
+ <div className="schema-key-default-val">value: {this.fieldDefaultInput}</div>
+ <div className="schema-key-warning">{this._newFieldWarning}</div>
+ <div
+ className="schema-column-menu-button"
+ onPointerDown={action(e => {
+ if (this.documentKeys.includes(this._menuValue)) {
+ this._newFieldWarning = 'Field already exists';
+ } else if (this._menuValue.length === 0) {
+ this._newFieldWarning = 'Field cannot be an empty string';
+ } else {
+ this.setKey(this._menuValue, this._newFieldDefault);
+ }
})}>
- {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>}
- </Measure>
+ done
+ </div>
</div>
);
+ }
+
+ @computed get keysDropdown() {
+ return (
+ <div className="schema-key-search">
+ <div
+ className="schema-column-menu-button"
+ onPointerDown={action(e => {
+ e.stopPropagation();
+ this._makeNewField = true;
+ })}>
+ + new field
+ </div>
+ <div className="schema-key-list">
+ {this._menuOptions.map(key => (
+ <div
+ className="schema-key-search-result"
+ onPointerDown={e => {
+ e.stopPropagation();
+ this.setKey(key);
+ }}>
+ {key}
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+ }
+
+ @computed get renderColumnMenu() {
+ const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
+ return (
+ <div className="schema-column-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}>
+ <input className="schema-key-search-input" type="text" value={this._menuValue} onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
+ {this._makeNewField ? this.newFieldMenu : this.keysDropdown}
+ <div
+ className="schema-column-menu-button"
+ onPointerDown={action(e => {
+ e.stopPropagation();
+ this.closeColumnMenu();
+ })}>
+ cancel
+ </div>
+ </div>
+ );
+ }
+
+ @computed get renderFilterMenu() {
+ const x = this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._filterColumnIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
+ return (
+ <div className="schema-filter-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}>
+ <input className="schema-filter-input" type="text" value={this._filterValue} onKeyDown={this.onFilterKeyDown} onChange={this.updateFilterSearch} onPointerDown={e => e.stopPropagation()} />
+ <div
+ className="schema-column-menu-button"
+ onPointerDown={action(e => {
+ e.stopPropagation();
+ this.closeFilterMenu();
+ })}>
+ cancel
+ </div>
+ </div>
+ );
+ }
+
+ render() {
return (
<div
- className={'collectionSchemaView' + (this.props.Document._searchDoc ? '-searchContainer' : '-container')}
- style={{
- overflow: this.props.scrollOverflow === true ? 'scroll' : undefined,
- backgroundColor: 'white',
- pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined,
- width: this.props.PanelWidth() || '100%',
- height: this.props.PanelHeight() || '100%',
- position: 'relative',
- }}>
+ className="collectionSchemaView"
+ ref={(ele: HTMLDivElement | null) => {
+ this._ref = ele;
+ this.createDashEventsTarget(ele);
+ }}
+ onDrop={this.onExternalDrop.bind(this)}>
<div
- className="collectionSchemaView-tableContainer"
- style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
- onContextMenu={this.onSpecificMenu}
- onPointerDown={this.onPointerDown}
- onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}
- onDrop={e => this.onExternalDrop(e, {})}
- ref={this.createTarget}>
- {this.schemaTable}
+ className="schema-table"
+ onPointerDown={action(() => {
+ this.clearSelection();
+ })}>
+ <div className="schema-header-row" style={{ height: CollectionSchemaView._rowHeight }}>
+ <div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth }}>
+ <div
+ className="schema-header-button"
+ onPointerDown={e => {
+ this.openColumnMenu(-1, true);
+ }}>
+ <FontAwesomeIcon icon="plus" />
+ </div>
+ </div>
+ {this.columnKeys.map((key, index) => {
+ return (
+ <SchemaColumnHeader
+ key={index}
+ columnIndex={index}
+ columnKeys={this.columnKeys}
+ columnWidths={this.displayColumnWidths}
+ sortField={this.sortField}
+ sortDesc={this.sortDesc}
+ setSort={this.setSort}
+ removeColumn={this.removeColumn}
+ resizeColumn={this.startResize}
+ openContextMenu={this.openContextMenu}
+ />
+ );
+ })}
+ </div>
+ {this._columnMenuIndex !== undefined && this.renderColumnMenu}
+ {this._filterColumnIndex !== undefined && this.renderFilterMenu}
+ <div className="schema-table-content" onPointerDown={e => e.stopPropagation()}>
+ {this.childDocs.map((doc: Doc, index: number) => {
+ const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
+ let dref: Opt<DocumentView>;
+ return (
+ <div className="schema-row-wrapper" style={{ maxHeight: CollectionSchemaView._rowHeight }}>
+ <DocumentView
+ {...this.props}
+ ref={r => (dref = r || undefined)}
+ LayoutTemplate={this.props.childLayoutTemplate}
+ LayoutTemplateString={SchemaRowBox.LayoutString(this.props.fieldKey)}
+ Document={doc}
+ DataDoc={dataDoc}
+ ContainingCollectionView={this.props.CollectionView}
+ ContainingCollectionDoc={this.Document}
+ PanelWidth={() => this.tableWidth}
+ PanelHeight={() => CollectionSchemaView._rowHeight}
+ styleProvider={DefaultStyleProvider}
+ focus={this.focusDocument}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ rootSelected={this.rootSelected}
+ ScreenToLocalTransform={Transform.Identity}
+ bringToFront={emptyFunction}
+ isContentActive={this.isChildContentActive}
+ hideDecorations={true}
+ hideTitle={true}
+ hideDocumentButtonBar={true}
+ fitWidth={returnTrue}
+ onClick={() => this.rowOnClickScript}
+ scriptContext={this}
+ />
+ </div>
+ );
+ })}
+ </div>
+ <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
</div>
- {this.dividerDragger}
- {!this.previewWidth() ? null : this.previewPanel}
- {this._headerOpen && this.props.isContentActive() ? menu : null}
+ {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
+ {this.previewWidth > 0 && (
+ <div style={{ width: `${this.previewWidth}px` }} ref={ref => (this._previewRef = ref)}>
+ {this._lastSelectedRow !== undefined && (
+ <DocumentView
+ Document={this.childDocs[this._lastSelectedRow]}
+ DataDoc={undefined}
+ fitContentsToBox={returnTrue}
+ dontCenter={'y'}
+ focus={DocUtils.DefaultFocus}
+ renderDepth={this.props.renderDepth}
+ rootSelected={this.rootSelected}
+ PanelWidth={() => this.previewWidth}
+ PanelHeight={this.props.PanelHeight}
+ isContentActive={returnTrue}
+ isDocumentActive={returnFalse}
+ ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-this.tableWidth, 0)}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ styleProvider={DefaultStyleProvider}
+ docViewPath={returnEmptyDoclist}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ moveDocument={this.props.moveDocument}
+ addDocument={this.addRow}
+ removeDocument={this.props.removeDocument}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ />
+ )}
+ </div>
+ )}
</div>
);
}
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
new file mode 100644
index 000000000..42626697a
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -0,0 +1,76 @@
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import './CollectionSchemaView.scss';
+import { ColumnType } from './CollectionSchemaView';
+import { IconButton } from 'browndash-components';
+import { Colors } from '../../global/globalEnums';
+import { ContextMenu } from '../../ContextMenu';
+import { Doc, DocListCast } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { RichTextField } from '../../../../fields/RichTextField';
+import { StrCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { DocUtils, Docs } from '../../../documents/Documents';
+import { ContextMenuProps } from '../../ContextMenuItem';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+
+export interface SchemaColumnHeaderProps {
+ columnKeys: string[];
+ columnWidths: number[];
+ columnIndex: number;
+ sortField: string;
+ sortDesc: boolean;
+ setSort: (field: string, desc: boolean) => void;
+ removeColumn: (index: number) => void;
+ resizeColumn: (e: any, index: number, left: boolean) => void;
+ // dragColumn: (e: any, index: number) => boolean;
+ openContextMenu: (x: number, y: number, index: number) => void;
+}
+
+@observer
+export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> {
+ @computed get fieldKey() {
+ return this.props.columnKeys[this.props.columnIndex];
+ }
+
+ @action
+ sortClicked = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ if (this.props.sortField == this.fieldKey) {
+ this.props.setSort(this.fieldKey, !this.props.sortDesc);
+ } else {
+ this.props.setSort(this.fieldKey, false);
+ }
+ };
+
+ // @action
+ // onPointerDown = (e: React.PointerEvent) => {
+ // e.stopPropagation();
+
+ // setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction);
+ // };
+
+ render() {
+ return (
+ <div className="schema-column-header" style={{ width: this.props.columnWidths[this.props.columnIndex] }}>
+ <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex, true)}></div>
+ <div className="schema-column-title">{this.fieldKey}</div>
+
+ <div className="schema-header-menu">
+ <div className="schema-header-button" onPointerDown={e => this.props.openContextMenu(e.clientX, e.clientY, this.props.columnIndex)}>
+ <FontAwesomeIcon icon="ellipsis-h" />
+ </div>
+ <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField == this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}>
+ <FontAwesomeIcon icon="caret-right" style={this.props.sortField == this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} />
+ </div>
+ </div>
+
+ <div className="schema-column-resizer right" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex, false)}></div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
new file mode 100644
index 000000000..05197d05f
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -0,0 +1,129 @@
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, ObservableSet } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../../fields/Doc';
+import { undoBatch } from '../../../util/UndoManager';
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { OpenWhere } from '../../nodes/DocumentView';
+import { FieldView, FieldViewProps } from '../../nodes/FieldView';
+import { CollectionSchemaView } from './CollectionSchemaView';
+import './CollectionSchemaView.scss';
+import { SchemaTableCell } from './SchemaTableCell';
+import { Colors } from '../../global/globalEnums';
+import { DocCast, StrCast } from '../../../../fields/Types';
+import { setupMoveUpEvents, emptyFunction } from '../../../../Utils';
+import { DragManager } from '../../../util/DragManager';
+
+@observer
+export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(SchemaRowBox, fieldKey);
+ }
+
+ private _ref: HTMLDivElement | null = null;
+
+ bounds = () => this._ref?.getBoundingClientRect();
+
+ @computed get schemaView() {
+ const vpath = this.props.docViewPath();
+ return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as CollectionSchemaView) : undefined;
+ }
+
+ @computed get schemaDoc() {
+ return this.props.ContainingCollectionDoc!;
+ }
+
+ @computed get rowIndex() {
+ return this.schemaView?.rowIndex(this.rootDoc) ?? -1;
+ }
+
+ @action
+ onRowPointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, e => this.schemaView?.startDrag(e, this.rootDoc, this.rowIndex) ?? true, emptyFunction, emptyFunction, false);
+ };
+
+ onPointerEnter = (e: any) => {
+ if (!this.schemaView?._isDragging) return;
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.addEventListener('pointermove', this.onPointerMove);
+ };
+
+ onPointerMove = (e: any) => {
+ if (!this.schemaView?._isDragging) return;
+ let dragIsRow: boolean = true;
+ DragManager.docsBeingDragged.forEach(doc => {
+ dragIsRow = this.schemaView?._selectedDocs.has(doc) ?? false;
+ });
+ if (this._ref && dragIsRow) {
+ const rect = this._ref.getBoundingClientRect();
+ const y = e.clientY - rect.top; //y position within the element.
+ const height = this._ref.clientHeight;
+ const halfLine = height / 2;
+ if (y <= halfLine) {
+ this._ref.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ this._ref.style.borderBottom = '0px';
+ this.schemaView?.setDropIndex(this.rowIndex);
+ } else if (y > halfLine) {
+ this._ref.style.borderTop = '0px';
+ this._ref.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ this.schemaView?.setDropIndex(this.rowIndex + 1);
+ }
+ }
+ };
+
+ onPointerLeave = (e: any) => {
+ if (this._ref) {
+ this._ref.style.borderTop = '0px';
+ this._ref.style.borderBottom = '0px';
+ }
+ document.removeEventListener('pointermove', this.onPointerMove);
+ };
+
+ render() {
+ return (
+ <div
+ className="schema-row"
+ style={
+ this.props.isSelected()
+ ? { height: CollectionSchemaView._rowHeight, backgroundColor: Colors.LIGHT_BLUE, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined /*, opacity: this.props.dragging ? 0.5 : 1 */ }
+ : { height: CollectionSchemaView._rowHeight, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined }
+ }
+ onPointerDown={this.onRowPointerDown}
+ onPointerEnter={this.onPointerEnter}
+ onPointerLeave={this.onPointerLeave}
+ ref={(row: HTMLDivElement | null) => {
+ row && this.schemaView?.addRowRef?.(this.rootDoc, row);
+ this._ref = row;
+ }}>
+ <div
+ className="row-menu"
+ style={{
+ width: CollectionSchemaView._rowMenuWidth,
+ }}>
+ <div
+ className="schema-row-button"
+ onPointerDown={undoBatch(e => {
+ e.stopPropagation();
+ this.props.removeDocument?.(this.rootDoc);
+ })}>
+ <FontAwesomeIcon icon="times" />
+ </div>
+ <div
+ className="schema-row-button"
+ onPointerDown={e => {
+ e.stopPropagation();
+ this.props.addDocTab(this.rootDoc, OpenWhere.addRight);
+ }}>
+ <FontAwesomeIcon icon="external-link-alt" />
+ </div>
+ </div>
+ <div className="row-cells">
+ {this.schemaView?.columnKeys?.map((key, index) => (
+ <SchemaTableCell key={key} Document={this.rootDoc} fieldKey={key} columnWidth={this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth} isRowActive={this.props.isContentActive} />
+ ))}
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx
deleted file mode 100644
index 16910cc83..000000000
--- a/src/client/views/collections/collectionSchema/SchemaTable.tsx
+++ /dev/null
@@ -1,694 +0,0 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from 'react-table';
-import { DateField } from '../../../../fields/DateField';
-import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
-import { listSpec } from '../../../../fields/Schema';
-import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { ComputedField } from '../../../../fields/ScriptField';
-import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
-import { ImageField } from '../../../../fields/URLField';
-import { GetEffectiveAcl } from '../../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../../Utils';
-import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents';
-import { DocumentType } from '../../../documents/DocumentTypes';
-import { CompileScript, Transformer, ts } from '../../../util/Scripting';
-import { Transform } from '../../../util/Transform';
-import { undoBatch } from '../../../util/UndoManager';
-import '../../../views/DocumentDecorations.scss';
-import { ContextMenu } from '../../ContextMenu';
-import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
-import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
-import { PinProps } from '../../nodes/trails';
-import { DefaultStyleProvider } from '../../StyleProvider';
-import { CollectionView } from '../CollectionView';
-import {
- CellProps,
- CollectionSchemaButtons,
- CollectionSchemaCell,
- CollectionSchemaCheckboxCell,
- CollectionSchemaDateCell,
- CollectionSchemaDocCell,
- CollectionSchemaImageCell,
- CollectionSchemaListCell,
- CollectionSchemaNumberCell,
- CollectionSchemaStringCell,
-} from './CollectionSchemaCells';
-import { CollectionSchemaAddColumnHeader, KeysDropdown } from './CollectionSchemaHeaders';
-import { MovableColumn } from './CollectionSchemaMovableColumn';
-import { MovableRow } from './CollectionSchemaMovableRow';
-import './CollectionSchemaView.scss';
-
-enum ColumnType {
- Any,
- Number,
- String,
- Boolean,
- Doc,
- Image,
- List,
- Date,
-}
-
-// this map should be used for keys that should have a const type of value
-const columnTypes: Map<string, ColumnType> = new Map([
- ['title', ColumnType.String],
- ['x', ColumnType.Number],
- ['y', ColumnType.Number],
- ['_width', ColumnType.Number],
- ['_height', ColumnType.Number],
- ['_nativeWidth', ColumnType.Number],
- ['_nativeHeight', ColumnType.Number],
- ['isPrototype', ColumnType.Boolean],
- ['_curPage', ColumnType.Number],
- ['_currentTimecode', ColumnType.Number],
- ['zIndex', ColumnType.Number],
-]);
-
-export interface SchemaTableProps {
- Document: Doc; // child doc
- dataDoc?: Doc;
- PanelHeight: () => number;
- PanelWidth: () => number;
- childDocs?: Doc[];
- CollectionView: Opt<CollectionView>;
- ContainingCollectionView: Opt<CollectionView>;
- ContainingCollectionDoc: Opt<Doc>;
- fieldKey: string;
- renderDepth: number;
- deleteDocument?: (document: Doc | Doc[]) => boolean;
- addDocument?: (document: Doc | Doc[]) => boolean;
- moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
- ScreenToLocalTransform: () => Transform;
- active: (outsideReaction: boolean | undefined) => boolean | undefined;
- onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
- addDocTab: (document: Doc, where: OpenWhere) => boolean;
- pinToPres: (document: Doc, pinProps: PinProps) => void;
- isSelected: (outsideReaction?: boolean) => boolean;
- isFocused: (document: Doc, outsideReaction: boolean) => boolean;
- setFocused: (document: Doc) => void;
- setPreviewDoc: (document: Opt<Doc>) => void;
- columns: SchemaHeaderField[];
- documentKeys: any[];
- headerIsEditing: boolean;
- openHeader: (column: any, screenx: number, screeny: number) => void;
- onClick: (e: React.MouseEvent) => void;
- onPointerDown: (e: React.PointerEvent) => void;
- onResizedChange: (newResized: Resize[], event: any) => void;
- setColumns: (columns: SchemaHeaderField[]) => void;
- reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => void;
- changeColumns: (oldKey: string, newKey: string, addNew: boolean) => void;
- setHeaderIsEditing: (isEditing: boolean) => void;
- changeColumnSort: (columnField: SchemaHeaderField, descending: boolean | undefined) => void;
-}
-
-@observer
-export class SchemaTable extends React.Component<SchemaTableProps> {
- @observable _cellIsEditing: boolean = false;
- @observable _focusedCell: { row: number; col: number } = { row: 0, col: 0 };
- @observable _openCollections: Set<number> = new Set();
-
- @observable _showDoc: Doc | undefined;
- @observable _showDataDoc: any = '';
- @observable _showDocPos: number[] = [];
-
- @observable _showTitleDropdown: boolean = false;
-
- @computed get previewWidth() {
- return () => NumCast(this.props.Document.schemaPreviewWidth);
- }
- @computed get previewHeight() {
- return () => this.props.PanelHeight() - 2 * this.borderWidth;
- }
- @computed get tableWidth() {
- return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
- }
-
- @computed get childDocs() {
- if (this.props.childDocs) return this.props.childDocs;
-
- const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- return DocListCast(doc[this.props.fieldKey]);
- }
- set childDocs(docs: Doc[]) {
- const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- doc[this.props.fieldKey] = new List<Doc>(docs);
- }
-
- @computed get textWrappedRows() {
- return Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []);
- }
- set textWrappedRows(textWrappedRows: string[]) {
- this.props.Document.textwrappedSchemaRows = new List<string>(textWrappedRows);
- }
-
- @computed get resized(): { id: string; value: number }[] {
- return this.props.columns.reduce((resized, shf) => {
- shf.width > -1 && resized.push({ id: shf.heading, value: shf.width });
- return resized;
- }, [] as { id: string; value: number }[]);
- }
- @computed get sorted(): SortingRule[] {
- return this.props.columns.reduce((sorted, shf) => {
- shf.desc !== undefined && sorted.push({ id: shf.heading, desc: shf.desc });
- return sorted;
- }, [] as SortingRule[]);
- }
-
- @action
- changeSorting = (col: any) => {
- this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true);
- };
-
- @action
- changeTitleMode = () => (this._showTitleDropdown = !this._showTitleDropdown);
-
- @computed get borderWidth() {
- return Number(COLLECTION_BORDER_WIDTH);
- }
- @computed get tableColumns(): Column<Doc>[] {
- const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1);
- const columns: Column<Doc>[] = [];
- const tableIsFocused = this.props.isFocused(this.props.Document, false);
- const focusedRow = this._focusedCell.row;
- const focusedCol = this._focusedCell.col;
- const isEditable = !this.props.headerIsEditing;
-
- columns.push({
- expander: true,
- Header: '',
- width: 58,
- Expander: rowInfo => {
- return rowInfo.original.type !== DocumentType.COL ? null : (
- <div className="collectionSchemaView-expander" onClick={action(() => this._openCollections[rowInfo.isExpanded ? 'delete' : 'add'](rowInfo.viewIndex))}>
- <FontAwesomeIcon icon={rowInfo.isExpanded ? 'caret-down' : 'caret-right'} size="lg" />
- </div>
- );
- },
- });
- columns.push(
- ...this.props.columns.map(col => {
- const icon: IconProp =
- this.getColumnType(col) === ColumnType.Number
- ? 'hashtag'
- : this.getColumnType(col) === ColumnType.String
- ? 'font'
- : this.getColumnType(col) === ColumnType.Boolean
- ? 'check-square'
- : this.getColumnType(col) === ColumnType.Doc
- ? 'file'
- : this.getColumnType(col) === ColumnType.Image
- ? 'image'
- : this.getColumnType(col) === ColumnType.List
- ? 'list-ul'
- : this.getColumnType(col) === ColumnType.Date
- ? 'calendar'
- : 'align-justify';
-
- const keysDropdown = (
- <KeysDropdown
- keyValue={col.heading}
- possibleKeys={possibleKeys}
- existingKeys={this.props.columns.map(c => c.heading)}
- canAddNew={true}
- addNew={false}
- onSelect={this.props.changeColumns}
- setIsEditing={this.props.setHeaderIsEditing}
- docs={this.props.childDocs}
- Document={this.props.Document}
- dataDoc={this.props.dataDoc}
- fieldKey={this.props.fieldKey}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- ContainingCollectionView={this.props.ContainingCollectionView}
- active={this.props.active}
- openHeader={this.props.openHeader}
- icon={icon}
- col={col}
- // try commenting this out
- width={'100%'}
- />
- );
-
- const sortIcon = col.desc === undefined ? 'caret-right' : col.desc === true ? 'caret-down' : 'caret-up';
- const header = (
- <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: '2px', display: 'flex', cursor: 'default', height: '100%' }}>
- {keysDropdown}
- <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: 'inline', zIndex: 1, background: 'inherit', cursor: 'pointer' }}>
- <FontAwesomeIcon icon={sortIcon} size="lg" />
- </div>
- {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>} */}
- </div>
- );
-
- return {
- Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.props.columns} reorderColumns={this.props.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />,
- accessor: (doc: Doc) => (doc ? Field.toString(doc[col.heading] as Field) : 0),
- id: col.heading,
- Cell: (rowProps: CellInfo) => {
- const rowIndex = rowProps.index;
- const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
- const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
-
- const props: CellProps = {
- row: rowIndex,
- col: columnIndex,
- rowProps: rowProps,
- isFocused: isFocused,
- changeFocusedCellByIndex: this.changeFocusedCellByIndex,
- CollectionView: this.props.CollectionView,
- ContainingCollection: this.props.ContainingCollectionView,
- Document: this.props.Document,
- fieldKey: this.props.fieldKey,
- renderDepth: this.props.renderDepth,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- moveDocument: this.props.moveDocument,
- setIsEditing: this.setCellIsEditing,
- isEditable: isEditable,
- setPreviewDoc: this.props.setPreviewDoc,
- setComputed: this.setComputed,
- getField: this.getField,
- showDoc: this.showDoc,
- };
-
- switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) {
- case ColumnType.Number:
- return <CollectionSchemaNumberCell {...props} />;
- case ColumnType.String:
- return <CollectionSchemaStringCell {...props} />;
- case ColumnType.Boolean:
- return <CollectionSchemaCheckboxCell {...props} />;
- case ColumnType.Doc:
- return <CollectionSchemaDocCell {...props} />;
- case ColumnType.Image:
- return <CollectionSchemaImageCell {...props} />;
- case ColumnType.List:
- return <CollectionSchemaListCell {...props} />;
- case ColumnType.Date:
- return <CollectionSchemaDateCell {...props} />;
- default:
- return <CollectionSchemaCell {...props} />;
- }
- },
- minWidth: 200,
- };
- })
- );
- columns.push({
- Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />,
- accessor: (doc: Doc) => 0,
- id: 'add',
- Cell: (rowProps: CellInfo) => {
- const rowIndex = rowProps.index;
- const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
- const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
- return (
- <CollectionSchemaButtons
- {...{
- row: rowProps.index,
- col: columnIndex,
- rowProps: rowProps,
- isFocused: isFocused,
- changeFocusedCellByIndex: this.changeFocusedCellByIndex,
- CollectionView: this.props.CollectionView,
- ContainingCollection: this.props.ContainingCollectionView,
- Document: this.props.Document,
- fieldKey: this.props.fieldKey,
- renderDepth: this.props.renderDepth,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- moveDocument: this.props.moveDocument,
- setIsEditing: this.setCellIsEditing,
- isEditable: isEditable,
- setPreviewDoc: this.props.setPreviewDoc,
- setComputed: this.setComputed,
- getField: this.getField,
- showDoc: this.showDoc,
- }}
- />
- );
- },
- width: 28,
- resizable: false,
- });
- return columns;
- }
-
- constructor(props: SchemaTableProps) {
- super(props);
- if (this.props.Document._schemaHeaders === undefined) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>([
- new SchemaHeaderField('title', '#f1efeb'),
- new SchemaHeaderField('author', '#f1efeb'),
- new SchemaHeaderField('*lastModified', '#f1efeb', ColumnType.Date),
- new SchemaHeaderField('text', '#f1efeb', ColumnType.String),
- new SchemaHeaderField('type', '#f1efeb'),
- new SchemaHeaderField('context', '#f1efeb', ColumnType.Doc),
- ]);
- }
- }
-
- componentDidMount() {
- document.addEventListener('keydown', this.onKeyDown);
- }
-
- componentWillUnmount() {
- document.removeEventListener('keydown', this.onKeyDown);
- }
-
- tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
- const tableDoc = this.props.Document[DataSym];
- const effectiveAcl = GetEffectiveAcl(tableDoc);
-
- if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) {
- doc.context = this.props.Document;
- tableDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()));
- return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
- }
- return false;
- };
-
- private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
- return !rowInfo
- ? {}
- : {
- ScreenToLocalTransform: this.props.ScreenToLocalTransform,
- addDoc: this.tableAddDoc,
- removeDoc: this.props.deleteDocument,
- rowInfo,
- rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true),
- textWrapRow: this.toggleTextWrapRow,
- rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1,
- dropAction: StrCast(this.props.Document.childDropAction),
- addDocTab: this.props.addDocTab,
- };
- };
-
- private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => {
- if (!rowInfo || column) return {};
-
- const row = rowInfo.index;
- //@ts-ignore
- const col = this.columns.map(c => c.heading).indexOf(column!.id);
- const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true);
- // TODO: editing border doesn't work :(
- return {
- style: { border: !this.props.headerIsEditing && isFocused ? '2px solid rgb(255, 160, 160)' : '1px solid #f1efeb' },
- };
- };
-
- @action setCellIsEditing = (isEditing: boolean) => (this._cellIsEditing = isEditing);
-
- @action
- onKeyDown = (e: KeyboardEvent): void => {
- if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {
- // && this.props.isSelected(true)) {
- const direction = e.key === 'Tab' ? 'tab' : e.which === 39 ? 'right' : e.which === 37 ? 'left' : e.which === 38 ? 'up' : e.which === 40 ? 'down' : '';
- this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col);
-
- if (direction) {
- const pdoc = FieldValue(this.childDocs[this._focusedCell.row]);
- pdoc && this.props.setPreviewDoc(pdoc);
- e.stopPropagation();
- }
- } else if (e.keyCode === 27) {
- this.props.setPreviewDoc(undefined);
- e.stopPropagation(); // stopPropagation for left/right arrows
- }
- };
-
- changeFocusedCellByDirection = (direction: string, curRow: number, curCol: number) => {
- switch (direction) {
- case 'tab':
- return { row: curRow + 1 === this.childDocs.length ? 0 : curRow + 1, col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 };
- case 'right':
- return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 };
- case 'left':
- return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 };
- case 'up':
- return { row: curRow === 0 ? curRow : curRow - 1, col: curCol };
- case 'down':
- return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol };
- }
- return this._focusedCell;
- };
-
- @action
- changeFocusedCellByIndex = (row: number, col: number): void => {
- if (this._focusedCell.row !== row || this._focusedCell.col !== col) {
- this._focusedCell = { row: row, col: col };
- }
- this.props.setFocused(this.props.Document);
- };
-
- @undoBatch
- createRow = action(() => {
- this.props.addDocument?.(Docs.Create.TextDocument('', { title: '', _width: 100, _height: 30 }));
- this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col };
- });
-
- @undoBatch
- @action
- createColumn = () => {
- const newFieldName = (index: number) => `New field${index ? ` (${index})` : ''}`;
- for (let index = 0; index < 100; index++) {
- if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) {
- this.props.columns.push(new SchemaHeaderField(newFieldName(index), '#f1efeb'));
- break;
- }
- }
- };
-
- @action
- getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => {
- if (doc && field && column.type === ColumnType.Any) {
- const val = doc[CollectionSchemaCell.resolvedFieldKey(field, doc)];
- if (val instanceof ImageField) return ColumnType.Image;
- if (val instanceof Doc) return ColumnType.Doc;
- if (val instanceof DateField) return ColumnType.Date;
- if (val instanceof List) return ColumnType.List;
- }
- if (column.type && column.type !== 0) {
- return column.type;
- }
- if (columnTypes.get(column.heading)) {
- return (column.type = columnTypes.get(column.heading)!);
- }
- return (column.type = ColumnType.Any);
- };
-
- @undoBatch
- @action
- toggleTextwrap = async () => {
- const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []);
- if (textwrappedRows.length) {
- this.props.Document.textwrappedSchemaRows = new List<string>([]);
- } else {
- const docs = DocListCast(this.props.Document[this.props.fieldKey]);
- const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
- this.props.Document.textwrappedSchemaRows = new List<string>(allRows);
- }
- };
-
- @action
- toggleTextWrapRow = (doc: Doc): void => {
- const textWrapped = this.textWrappedRows;
- const index = textWrapped.findIndex(id => doc[Id] === id);
-
- index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]);
-
- this.textWrappedRows = textWrapped;
- };
-
- @computed
- get reactTable() {
- const children = this.childDocs;
- const hasCollectionChild = children.reduce((found, doc) => found || doc.type === DocumentType.COL, false);
- const expanded: { [name: string]: any } = {};
- Array.from(this._openCollections.keys()).map(col => (expanded[col.toString()] = true));
- const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :((((
-
- return (
- <ReactTable
- style={{ position: 'relative' }}
- data={children}
- page={0}
- pageSize={children.length}
- showPagination={false}
- columns={this.tableColumns}
- getTrProps={this.getTrProps}
- getTdProps={this.getTdProps}
- sortable={false}
- TrComponent={MovableRow}
- sorted={this.sorted}
- expanded={expanded}
- resized={this.resized}
- onResizedChange={this.props.onResizedChange}
- // if it has a child, render another table with the children
- SubComponent={
- !hasCollectionChild
- ? undefined
- : row =>
- row.original.type !== DocumentType.COL ? null : (
- <div style={{ paddingLeft: 57 + 'px' }} className="reactTable-sub">
- <SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} />
- </div>
- )
- }
- />
- );
- }
-
- onContextMenu = (e: React.MouseEvent): void => {
- ContextMenu.Instance.addItem({ description: 'Toggle text wrapping', event: this.toggleTextwrap, icon: 'table' });
- };
-
- getField = (row: number, col?: number) => {
- const docs = this.childDocs;
-
- row = row % docs.length;
- while (row < 0) row += docs.length;
- const columns = this.props.columns;
- const doc = docs[row];
- if (col === undefined) {
- return doc;
- }
- if (col >= 0 && col < columns.length) {
- const column = this.props.columns[col].heading;
- return doc[column];
- }
- return undefined;
- };
-
- createTransformer = (row: number, col: number): Transformer => {
- const self = this;
- const captures: { [name: string]: Field } = {};
-
- const transformer: ts.TransformerFactory<ts.SourceFile> = context => {
- return root => {
- function visit(node: ts.Node) {
- node = ts.visitEachChild(node, visit, context);
- if (ts.isIdentifier(node)) {
- const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
- const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
- if (isntPropAccess && isntPropAssign) {
- if (node.text === '$r') {
- return ts.createNumericLiteral(row.toString());
- } else if (node.text === '$c') {
- return ts.createNumericLiteral(col.toString());
- } else if (node.text === '$') {
- if (ts.isCallExpression(node.parent)) {
- // captures.doc = self.props.Document;
- // captures.key = self.props.fieldKey;
- }
- }
- }
- }
-
- return node;
- }
- return ts.visitNode(root, visit);
- };
- };
-
- // const getVars = () => {
- // return { capturedVariables: captures };
- // };
-
- return { transformer /*getVars*/ };
- };
-
- setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => {
- script = `const $ = (row:number, col?:number) => {
- const rval = (doc as any)[key][row + ${row}];
- return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading];
- }
- return ${script}`;
- const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) });
- if (compiled.compiled) {
- doc[field] = new ComputedField(compiled);
- return true;
- }
- return false;
- };
-
- @action
- showDoc = (doc: Doc | undefined, dataDoc?: Doc, screenX?: number, screenY?: number) => {
- this._showDoc = doc;
- if (dataDoc && screenX && screenY) {
- this._showDocPos = this.props.ScreenToLocalTransform().transformPoint(screenX, screenY);
- }
- };
-
- onOpenClick = () => {
- this._showDoc && this.props.addDocTab(this._showDoc, OpenWhere.addRight);
- };
-
- getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(-this.borderWidth - 4 - this.tableWidth, -this.borderWidth);
- };
-
- render() {
- const preview = '';
- return (
- <div
- className="collectionSchemaView-table"
- onPointerDown={this.props.onPointerDown}
- onClick={this.props.onClick}
- onWheel={e => this.props.active(true) && e.stopPropagation()}
- onDrop={e => this.props.onDrop(e, {})}
- onContextMenu={this.onContextMenu}>
- {this.reactTable}
- {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : (
- <div className="collectionSchemaView-addRow" onClick={this.createRow}>
- + new
- </div>
- )}
- {!this._showDoc ? null : (
- <div
- className="collectionSchemaView-documentPreview"
- ref="overlay"
- style={{
- position: 'absolute',
- width: 150,
- height: 150,
- background: 'dimgray',
- display: 'block',
- top: 0,
- left: 0,
- transform: `translate(${this._showDocPos[0]}px, ${this._showDocPos[1] - 180}px)`,
- }}>
- <DocumentView
- Document={this._showDoc}
- DataDoc={this._showDataDoc}
- styleProvider={DefaultStyleProvider}
- docViewPath={returnEmptyDoclist}
- focus={DocUtils.DefaultFocus}
- renderDepth={this.props.renderDepth}
- rootSelected={returnFalse}
- isContentActive={returnTrue}
- isDocumentActive={returnFalse}
- PanelWidth={() => 150}
- PanelHeight={() => 150}
- ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- whenChildContentsActiveChanged={emptyFunction}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}></DocumentView>
- </div>
- )}
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
new file mode 100644
index 000000000..43b7da544
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -0,0 +1,61 @@
+import React = require('react');
+import { observer } from 'mobx-react';
+import { Doc, Field } from '../../../../fields/Doc';
+import { 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.scss';
+
+export interface SchemaTableCellProps {
+ Document: Doc;
+ fieldKey: string;
+ columnWidth: number;
+ isRowActive: () => boolean | undefined;
+}
+
+@observer
+export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
+ render() {
+ const props: FieldViewProps = {
+ Document: this.props.Document,
+ docFilters: returnEmptyFilter,
+ docRangeFilters: returnEmptyFilter,
+ searchFilterDocs: returnEmptyDoclist,
+ styleProvider: DefaultStyleProvider,
+ docViewPath: returnEmptyDoclist,
+ ContainingCollectionView: undefined,
+ ContainingCollectionDoc: undefined,
+ fieldKey: this.props.fieldKey,
+ rootSelected: returnFalse,
+ isSelected: returnFalse,
+ setHeight: returnFalse,
+ select: emptyFunction,
+ dropAction: 'alias',
+ bringToFront: emptyFunction,
+ renderDepth: 1,
+ isContentActive: returnFalse,
+ whenChildContentsActiveChanged: emptyFunction,
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyFunction,
+ PanelWidth: () => this.props.columnWidth,
+ PanelHeight: () => CollectionSchemaView._rowHeight,
+ addDocTab: returnFalse,
+ pinToPres: returnZero,
+ };
+
+ return (
+ <div className="schema-table-cell" style={{ width: this.props.columnWidth, pointerEvents: this.props.isRowActive() ? 'all' : 'none' }}>
+ <EditableView
+ contents={<FieldView {...props} />}
+ height={'auto'}
+ GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+ SetValue={(value: string) => KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value)}
+ />
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx
new file mode 100644
index 000000000..5953f85ad
--- /dev/null
+++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx
@@ -0,0 +1,683 @@
+// import React = require('react');
+// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+// import { action, computed, observable } from 'mobx';
+// import { observer } from 'mobx-react';
+// import { extname } from 'path';
+// import DatePicker from 'react-datepicker';
+// import { CellInfo } from 'react-table';
+// import { DateField } from '../../../../fields/DateField';
+// import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+// import { Id } from '../../../../fields/FieldSymbols';
+// import { List } from '../../../../fields/List';
+// import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+// import { ComputedField } from '../../../../fields/ScriptField';
+// import { BoolCast, Cast, DateCast, FieldValue, StrCast } from '../../../../fields/Types';
+// import { ImageField } from '../../../../fields/URLField';
+// import { emptyFunction, Utils } from '../../../../Utils';
+// import { Docs } from '../../../documents/Documents';
+// import { DocumentType } from '../../../documents/DocumentTypes';
+// import { DocumentManager } from '../../../util/DocumentManager';
+// import { DragManager } from '../../../util/DragManager';
+// import { KeyCodes } from '../../../util/KeyCodes';
+// import { CompileScript } from '../../../util/Scripting';
+// import { SearchUtil } from '../../../util/SearchUtil';
+// import { SnappingManager } from '../../../util/SnappingManager';
+// import { undoBatch } from '../../../util/UndoManager';
+// import '../../../views/DocumentDecorations.scss';
+// import { EditableView } from '../../EditableView';
+// import { MAX_ROW_HEIGHT } from '../../global/globalCssVariables.scss';
+// import { DocumentIconContainer } from '../../nodes/DocumentIcon';
+// import { OverlayView } from '../../OverlayView';
+// import { CollectionView } from '../CollectionView';
+// import './CollectionSchemaView.scss';
+// import { OpenWhere } from '../../nodes/DocumentView';
+// import { PinProps } from '../../nodes/trails';
+
+// // intialize cell properties
+// export interface CellProps {
+// row: number;
+// col: number;
+// rowProps: CellInfo;
+// // currently unused
+// CollectionView: Opt<CollectionView>;
+// // currently unused
+// ContainingCollection: Opt<CollectionView>;
+// Document: Doc;
+// // column name
+// fieldKey: string;
+// // currently unused
+// renderDepth: number;
+// // called when a button is pressed on the node itself
+// addDocTab: (document: Doc, where: OpenWhere) => boolean;
+// pinToPres: (document: Doc, pinProps: PinProps) => void;
+// moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+// isFocused: boolean;
+// changeFocusedCellByIndex: (row: number, col: number) => void;
+// // set whether the cell is in the isEditing mode
+// setIsEditing: (isEditing: boolean) => void;
+// isEditable: boolean;
+// setPreviewDoc: (doc: Doc) => void;
+// setComputed: (script: string, doc: Doc, field: string, row: number, col: number) => boolean;
+// getField: (row: number, col?: number) => void;
+// // currnetly unused
+// showDoc: (doc: Doc | undefined, dataDoc?: any, screenX?: number, screenY?: number) => void;
+// }
+
+// @observer
+// export class CollectionSchemaCell extends React.Component<CellProps> {
+// // return a field key that is corrected for whether it COMMENT
+// public static resolvedFieldKey(column: string, rowDoc: Doc) {
+// const fieldKey = column;
+// if (fieldKey.startsWith('*')) {
+// const rootKey = fieldKey.substring(1);
+// const allKeys = [...Array.from(Object.keys(rowDoc)), ...Array.from(Object.keys(Doc.GetProto(rowDoc)))];
+// const matchedKeys = allKeys.filter(key => key.includes(rootKey));
+// if (matchedKeys.length) return matchedKeys[0];
+// }
+// return fieldKey;
+// }
+// @observable protected _isEditing: boolean = false;
+// protected _focusRef = React.createRef<HTMLDivElement>();
+// protected _rowDoc = this.props.rowProps.original;
+// // Gets the serialized data in proto form of the base proto that this document's proto inherits from
+// protected _rowDataDoc = Doc.GetProto(this.props.rowProps.original);
+// // methods for dragging and dropping
+// protected _dropDisposer?: DragManager.DragDropDisposer;
+// @observable contents: string = '';
+
+// componentDidMount() {
+// document.addEventListener('keydown', this.onKeyDown);
+// }
+// componentWillUnmount() {
+// document.removeEventListener('keydown', this.onKeyDown);
+// }
+
+// @action
+// onKeyDown = (e: KeyboardEvent): void => {
+// // If a cell is editable and clicked, hitting enter shoudl allow the user to edit it
+// if (this.props.isFocused && this.props.isEditable && e.keyCode === KeyCodes.ENTER) {
+// document.removeEventListener('keydown', this.onKeyDown);
+// this._isEditing = true;
+// this.props.setIsEditing(true);
+// }
+// };
+
+// @action
+// isEditingCallback = (isEditing: boolean): void => {
+// // a general method that takes a boolean that determines whether the cell should be in
+// // is-editing mode
+// // remove the event listener if it's there
+// document.removeEventListener('keydown', this.onKeyDown);
+// // it's not already in is-editing mode, re-add the event listener
+// isEditing && document.addEventListener('keydown', this.onKeyDown);
+// this._isEditing = isEditing;
+// this.props.setIsEditing(isEditing);
+// this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
+// };
+
+// @action
+// onPointerDown = async (e: React.PointerEvent): Promise<void> => {
+// // pan to the cell
+// this.onItemDown(e);
+// // focus on it
+// this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
+// this.props.setPreviewDoc(this.props.rowProps.original);
+
+// let url: string;
+// if ((url = StrCast(this.props.rowProps.row.href))) {
+// // opens up the the doc in a new window, blurring the old one
+// try {
+// new URL(url);
+// const temp = window.open(url)!;
+// temp.blur();
+// window.focus();
+// } catch {}
+// }
+
+// const doc = Cast(this._rowDoc[this.renderFieldKey], Doc, null);
+// doc && this.props.setPreviewDoc(doc);
+// };
+
+// @undoBatch
+// applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => {
+// // apply a specified change to the cell
+// const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) });
+// if (!res.success) return false;
+// // change what is rendered to this new changed cell content
+// doc[this.renderFieldKey] = res.result;
+// return true;
+// // return whether the change was successful
+// };
+
+// private drop = (e: Event, de: DragManager.DropEvent) => {
+// // if the drag has data at its completion
+// if (de.complete.docDragData) {
+// // if only one doc was dragged
+// if (de.complete.docDragData.draggedDocuments.length === 1) {
+// // update the renderFieldKey
+// this._rowDataDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0];
+// } else {
+// // create schema document reflecting the new column arrangement
+// const coll = Docs.Create.SchemaDocument([new SchemaHeaderField('title', '#f1efeb')], de.complete.docDragData.draggedDocuments, {});
+// this._rowDataDoc[this.renderFieldKey] = coll;
+// }
+// e.stopPropagation();
+// }
+// };
+
+// protected dropRef = (ele: HTMLElement | null) => {
+// // if the drop disposer is not undefined, run its function
+// this._dropDisposer?.();
+// // if ele is not null, give ele a non-undefined drop disposer
+// ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
+// };
+
+// returnHighlights(contents: string, positions?: number[]) {
+// if (positions) {
+// const results = [];
+// StrCast(this.props.Document._searchString);
+// const length = StrCast(this.props.Document._searchString).length;
+// const color = contents ? 'black' : 'grey';
+
+// results.push(
+// <span key="-1" style={{ color }}>
+// {contents?.slice(0, positions[0])}
+// </span>
+// );
+// positions.forEach((num, cur) => {
+// results.push(
+// <span key={'start' + cur} style={{ backgroundColor: '#FFFF00', color }}>
+// {contents?.slice(num, num + length)}
+// </span>
+// );
+// let end = 0;
+// cur === positions.length - 1 ? (end = contents.length) : (end = positions[cur + 1]);
+// results.push(
+// <span key={'end' + cur} style={{ color }}>
+// {contents?.slice(num + length, end)}
+// </span>
+// );
+// });
+// return results;
+// }
+// return <span style={{ color: contents ? 'black' : 'grey' }}>{contents ? contents?.valueOf() : 'undefined'}</span>;
+// }
+
+// @computed get renderFieldKey() {
+// // gets the resolved field key of this cell
+// return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original);
+// }
+
+// onItemDown = async (e: React.PointerEvent) => {
+// // if the document is a document used to change UI for search results in schema view
+// if (this.props.Document._searchDoc) {
+// const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc);
+// const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null);
+// // Jump to the this document
+// DocumentManager.Instance.jumpToDocument(this._rowDoc, { willPan: true }, emptyFunction, targetContext ? [targetContext] : [], () => this.props.setPreviewDoc(this._rowDoc));
+// }
+// };
+
+// renderCellWithType(type: string | undefined) {
+// const dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+
+// // the column
+// const fieldKey = this.renderFieldKey;
+// // the exact cell
+// const field = this._rowDoc[fieldKey];
+
+// const onPointerEnter = (e: React.PointerEvent): void => {
+// // e.buttons === 1 means the left moue pointer is down
+// if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === 'document' || type === undefined)) {
+// dragRef.current!.className = 'collectionSchemaView-cellContainer doc-drag-over';
+// }
+// };
+// const onPointerLeave = (e: React.PointerEvent): void => {
+// // change the class name to indicate that the cell is no longer being dragged
+// dragRef.current!.className = 'collectionSchemaView-cellContainer';
+// };
+
+// let contents = Field.toString(field as Field);
+// // display 2 hyphens instead of a blank box for empty cells
+// contents = contents === '' ? '--' : contents;
+
+// // classname reflects the tatus of the cell
+// let className = 'collectionSchemaView-cellWrapper';
+// if (this._isEditing) className += ' editing';
+// if (this.props.isFocused && this.props.isEditable) className += ' focused';
+// if (this.props.isFocused && !this.props.isEditable) className += ' inactive';
+
+// const positions = [];
+// if (StrCast(this.props.Document._searchString).toLowerCase() !== '') {
+// // term is ...promise pending... if the field is a Promise, otherwise it is the cell's contents
+// let term = field instanceof Promise ? '...promise pending...' : contents.toLowerCase();
+// const search = StrCast(this.props.Document._searchString).toLowerCase();
+// let start = term.indexOf(search);
+// let tally = 0;
+// // if search is found in term
+// if (start !== -1) {
+// positions.push(start);
+// }
+// // if search is found in term, continue finding all instances of search in term
+// while (start < contents?.length && start !== -1) {
+// term = term.slice(start + search.length + 1);
+// tally += start + search.length + 1;
+// start = term.indexOf(search);
+// positions.push(tally + start);
+// }
+// // remove the last position
+// if (positions.length > 1) {
+// positions.pop();
+// }
+// }
+// const placeholder = type === 'number' ? '0' : contents === '' ? '--' : 'undefined';
+// return (
+// <div
+// className="collectionSchemaView-cellContainer"
+// style={{ cursor: field instanceof Doc ? 'grab' : 'auto' }}
+// ref={dragRef}
+// onPointerDown={this.onPointerDown}
+// onClick={action(e => (this._isEditing = true))}
+// onPointerEnter={onPointerEnter}
+// onPointerLeave={onPointerLeave}>
+// <div className={className} ref={this._focusRef} tabIndex={-1}>
+// <div className="collectionSchemaView-cellContents" ref={type === undefined || type === 'document' ? this.dropRef : null}>
+// {!this.props.Document._searchDoc ? (
+// <EditableView
+// editing={this._isEditing}
+// isEditingCallback={this.isEditingCallback}
+// display={'inline'}
+// contents={contents}
+// height={'auto'}
+// maxHeight={Number(MAX_ROW_HEIGHT)}
+// placeholder={placeholder}
+// GetValue={() => {
+// const cfield = ComputedField.WithoutComputed(() => FieldValue(field));
+// const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
+// const cfinalScript = cscript?.split('return')[cscript.split('return').length - 1];
+// return cscript ? (cfinalScript?.endsWith(';') ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : Field.IsField(cfield) ? Field.toScriptString(cfield) : '';
+// }}
+// SetValue={action((value: string) => {
+// // sets what is displayed after the user makes an input
+// let retVal = false;
+// if (value.startsWith(':=') || value.startsWith('=:=')) {
+// // decides how to compute a value when given either of the above strings
+// const script = value.substring(value.startsWith('=:=') ? 3 : 2);
+// retVal = this.props.setComputed(script, value.startsWith(':=') ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col);
+// } else {
+// // check if the input is a number
+// let inputIsNum = true;
+// for (const s of value) {
+// if (isNaN(parseInt(s)) && !(s === '.') && !(s === ',')) {
+// inputIsNum = false;
+// }
+// }
+// // check if the input is a boolean
+// const inputIsBool: boolean = value === 'false' || value === 'true';
+// // what to do in the case
+// if (!inputIsNum && !inputIsBool && !value.startsWith('=')) {
+// // if it's not a number, it's a string, and should be processed as such
+// // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically
+// // after each edit
+// let valueSansQuotes = value;
+// if (this._isEditing) {
+// const vsqLength = valueSansQuotes.length;
+// // get rid of outer quotes
+// valueSansQuotes = valueSansQuotes.substring(value.startsWith('"') ? 1 : 0, valueSansQuotes.charAt(vsqLength - 1) === '"' ? vsqLength - 1 : vsqLength);
+// }
+// let inputAsString = '"';
+// // escape any quotes in the string
+// for (const i of valueSansQuotes) {
+// if (i === '"') {
+// inputAsString += '\\"';
+// } else {
+// inputAsString += i;
+// }
+// }
+// // add a closing quote
+// inputAsString += '"';
+// //two options here: we can strip off outer quotes or we can figure out what's going on with the script
+// const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
+// const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length;
+// // change it if a change is made, otherwise, just compile using the old cell conetnts
+// script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
+// // handle numbers and expressions
+// } else if (inputIsNum || value.startsWith('=')) {
+// //TODO: make accept numbers
+// const inputscript = value.substring(value.startsWith('=') ? 1 : 0);
+// // if commas are not stripped, the parser only considers the numbers after the last comma
+// let inputSansCommas = '';
+// for (const s of inputscript) {
+// if (!(s === ',')) {
+// inputSansCommas += s;
+// }
+// }
+// const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
+// const changeMade = value.length - 2 !== value.length;
+// script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
+// // handle booleans
+// } else if (inputIsBool) {
+// const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
+// const changeMade = value.length - 2 !== value.length;
+// script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
+// }
+// }
+// if (retVal) {
+// this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing'
+// this.props.setIsEditing(false);
+// }
+// return retVal;
+// })}
+// OnFillDown={async (value: string) => {
+// // computes all of the value preceded by :=
+// const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
+// script.compiled &&
+// DocListCast(this.props.Document[this.props.fieldKey]).forEach((doc, i) =>
+// value.startsWith(':=') ? this.props.setComputed(value.substring(2), Doc.GetProto(doc), this.renderFieldKey, i, this.props.col) : this.applyToDoc(Doc.GetProto(doc), i, this.props.col, script.run)
+// );
+// }}
+// />
+// ) : (
+// this.returnHighlights(contents, positions)
+// )}
+// </div>
+// </div>
+// </div>
+// );
+// }
+
+// render() {
+// return this.renderCellWithType(undefined);
+// }
+// }
+
+// @observer
+// export class CollectionSchemaNumberCell extends CollectionSchemaCell {
+// render() {
+// return this.renderCellWithType('number');
+// }
+// }
+
+// @observer
+// export class CollectionSchemaBooleanCell extends CollectionSchemaCell {
+// render() {
+// return this.renderCellWithType('boolean');
+// }
+// }
+
+// @observer
+// export class CollectionSchemaStringCell extends CollectionSchemaCell {
+// render() {
+// return this.renderCellWithType('string');
+// }
+// }
+
+// @observer
+// export class CollectionSchemaDateCell extends CollectionSchemaCell {
+// @computed get _date(): Opt<DateField> {
+// // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
+// return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined;
+// }
+
+// @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._rowDoc[this.renderFieldKey] = new DateField(date as Date);
+// //}
+// };
+
+// render() {
+// return !this.props.isFocused ? (
+// <span onPointerDown={this.onPointerDown}>{this._date ? Field.toString(this._date as Field) : '--'}</span>
+// ) : (
+// <DatePicker selected={this._date?.date || new Date()} onSelect={date => this.handleChange(date)} onChange={date => this.handleChange(date)} />
+// );
+// }
+// }
+
+// @observer
+// export class CollectionSchemaDocCell extends CollectionSchemaCell {
+// _overlayDisposer?: () => void;
+
+// @computed get _doc() {
+// return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc));
+// }
+
+// @action
+// onSetValue = (value: string) => {
+// this._doc && (Doc.GetProto(this._doc).title = value);
+
+// const script = CompileScript(value, {
+// addReturn: true,
+// typecheck: true,
+// transformer: DocumentIconContainer.getTransformer(),
+// });
+// // compile the script
+// const results = script.compiled && script.run();
+// // if the script was compiled and run
+// if (results && results.success) {
+// this._rowDoc[this.renderFieldKey] = results.result;
+// return true;
+// }
+// return false;
+// };
+
+// componentWillUnmount() {
+// this.onBlur();
+// }
+
+// onBlur = () => {
+// this._overlayDisposer?.();
+// };
+// onFocus = () => {
+// this.onBlur();
+// this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
+// };
+
+// @action
+// isEditingCallback = (isEditing: boolean): void => {
+// // the isEditingCallback from a general CollectionSchemaCell
+// document.removeEventListener('keydown', this.onKeyDown);
+// isEditing && document.addEventListener('keydown', this.onKeyDown);
+// this._isEditing = isEditing;
+// this.props.setIsEditing(isEditing);
+// this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
+// };
+
+// render() {
+// // if there's a doc, render it
+// return !this._doc ? (
+// this.renderCellWithType('document')
+// ) : (
+// <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
+// <div className="collectionSchemaView-cellContents-document" style={{ padding: '5.9px' }} ref={this.dropRef} onFocus={this.onFocus} onBlur={this.onBlur}>
+// <EditableView
+// editing={this._isEditing}
+// isEditingCallback={this.isEditingCallback}
+// display={'inline'}
+// contents={this._doc.title || '--'}
+// height={'auto'}
+// maxHeight={Number(MAX_ROW_HEIGHT)}
+// GetValue={() => StrCast(this._doc?.title)}
+// SetValue={action((value: string) => {
+// this.onSetValue(value);
+// return true;
+// })}
+// />
+// </div>
+// <div onClick={() => this._doc && this.props.addDocTab(this._doc, OpenWhere.addRight)} className="collectionSchemaView-cellContents-docButton">
+// <FontAwesomeIcon icon="external-link-alt" size="lg" />
+// </div>
+// </div>
+// );
+// }
+// }
+
+// @observer
+// export class CollectionSchemaImageCell extends CollectionSchemaCell {
+// 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);
+// }
+
+// render() {
+// const field = Cast(this._rowDoc[this.renderFieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+// const alts = DocListCast(this._rowDoc[this.renderFieldKey + '-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._rowDoc); // aspect ratio
+// let width = Math.min(75, this.props.rowProps.width); // get a with that is no smaller than 75px
+// const height = Math.min(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
+
+// const reference = React.createRef<HTMLDivElement>();
+// return (
+// <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
+// <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
+// <img src={url[0]} width={paths.length ? width : '20px'} height={paths.length ? height : '20px'} />
+// </div>
+// </div>
+// );
+// }
+// }
+
+// @observer
+// export class CollectionSchemaListCell extends CollectionSchemaCell {
+// _overlayDisposer?: () => void;
+
+// @computed get _field() {
+// return this._rowDoc[this.renderFieldKey];
+// }
+// @computed get _optionsList() {
+// return this._field as List<any>;
+// }
+// @observable private _opened = false; // whether the list is opened
+// @observable private _text = 'select an item';
+// @observable private _selectedNum = 0; // the index of the list item selected
+
+// @action
+// onSetValue = (value: string) => {
+// // change if it's a document
+// this._optionsList[this._selectedNum] = this._text = value;
+
+// (this._field as List<any>).splice(this._selectedNum, 1, value);
+// };
+
+// @action
+// onSelected = (element: string, index: number) => {
+// // if an item is selected, the private variables should update to reflect this
+// this._text = element;
+// this._selectedNum = index;
+// };
+
+// onFocus = () => {
+// this._overlayDisposer?.();
+// this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
+// };
+
+// render() {
+// const link = false;
+// const reference = React.createRef<HTMLDivElement>();
+
+// // if the list is not opened, don't display it; otherwise, do.
+// if (this._optionsList?.length) {
+// const options = !this._opened ? null : (
+// <div>
+// {this._optionsList.map((element, index) => {
+// const val = Field.toString(element);
+// return (
+// <div className="collectionSchemaView-dropdownOption" key={index} style={{ padding: '6px' }} onPointerDown={e => this.onSelected(StrCast(element), index)}>
+// {val}
+// </div>
+// );
+// })}
+// </div>
+// );
+
+// const plainText = <div style={{ padding: '5.9px' }}>{this._text}</div>;
+// const textarea = (
+// <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} style={{ padding: '5.9px' }} ref={this.dropRef}>
+// <EditableView
+// editing={this._isEditing}
+// isEditingCallback={this.isEditingCallback}
+// display={'inline'}
+// contents={this._text}
+// height={'auto'}
+// maxHeight={Number(MAX_ROW_HEIGHT)}
+// GetValue={() => this._text}
+// SetValue={action((value: string) => {
+// // add special for params
+// this.onSetValue(value);
+// return true;
+// })}
+// />
+// </div>
+// );
+
+// //☰
+// return (
+// <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
+// <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
+// <div className="collectionSchemaView-dropDownWrapper">
+// <button type="button" className="collectionSchemaView-dropdownButton" style={{ right: 'length', position: 'relative' }} onClick={action(e => (this._opened = !this._opened))}>
+// <FontAwesomeIcon icon={this._opened ? 'caret-up' : 'caret-down'} size="sm" />
+// </button>
+// <div className="collectionSchemaView-dropdownText"> {link ? plainText : textarea} </div>
+// </div>
+// {options}
+// </div>
+// </div>
+// );
+// }
+// return this.renderCellWithType('list');
+// }
+// }
+
+// @observer
+// export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
+// @computed get _isChecked() {
+// return BoolCast(this._rowDoc[this.renderFieldKey]);
+// }
+
+// render() {
+// const reference = React.createRef<HTMLDivElement>();
+// return (
+// <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
+// <input type="checkbox" checked={this._isChecked} onChange={e => (this._rowDoc[this.renderFieldKey] = e.target.checked)} />
+// </div>
+// );
+// }
+// }
+
+// @observer
+// export class CollectionSchemaButtons extends CollectionSchemaCell {
+// // the navigation buttons for schema view when it is used for search.
+// render() {
+// return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? (
+// <></>
+// ) : (
+// <div style={{ paddingTop: 8, paddingLeft: 3 }}>
+// <button style={{ padding: 2, left: 77 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, true)}>
+// <FontAwesomeIcon icon="arrow-up" size="sm" />
+// </button>
+// <button style={{ padding: 2 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, false)}>
+// <FontAwesomeIcon icon="arrow-down" size="sm" />
+// </button>
+// </div>
+// );
+// }
+// }
diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaHeaders.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaHeaders.tsx
new file mode 100644
index 000000000..32283d76c
--- /dev/null
+++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaHeaders.tsx
@@ -0,0 +1,510 @@
+// import React = require("react");
+// import { IconProp } from "@fortawesome/fontawesome-svg-core";
+// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+// import { action, computed, observable, runInAction, trace } from "mobx";
+// import { observer } from "mobx-react";
+// import { Doc, DocListCast, Opt, StrListCast } from "../../../../fields/Doc";
+// import { listSpec } from "../../../../fields/Schema";
+// import { PastelSchemaPalette, SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+// import { ScriptField } from "../../../../fields/ScriptField";
+// import { Cast, StrCast } from "../../../../fields/Types";
+// import { undoBatch } from "../../../util/UndoManager";
+// import { CollectionView } from "../CollectionView";
+// import { ColumnType } from "./CollectionSchemaView";
+// import "./CollectionSchemaView.scss";
+
+// const higflyout = require("@hig/flyout");
+// export const { anchorPoints } = higflyout;
+// export const Flyout = higflyout.default;
+
+// export interface AddColumnHeaderProps {
+// createColumn: () => void;
+// }
+
+// @observer
+// export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHeaderProps> {
+// // the button that allows the user to add a column
+// render() {
+// return <button className="add-column" onClick={() => this.props.createColumn()}>
+// <FontAwesomeIcon icon="plus" size="sm" />
+// </button>;
+// }
+// }
+
+// export interface ColumnMenuProps {
+// columnField: SchemaHeaderField;
+// // keyValue: string;
+// possibleKeys: string[];
+// existingKeys: string[];
+// // keyType: ColumnType;
+// typeConst: boolean;
+// menuButtonContent: JSX.Element;
+// addNew: boolean;
+// onSelect: (oldKey: string, newKey: string, addnew: boolean) => void;
+// setIsEditing: (isEditing: boolean) => void;
+// deleteColumn: (column: string) => void;
+// onlyShowOptions: boolean;
+// setColumnType: (column: SchemaHeaderField, type: ColumnType) => void;
+// setColumnSort: (column: SchemaHeaderField, desc: boolean | undefined) => void;
+// anchorPoint?: any;
+// setColumnColor: (column: SchemaHeaderField, color: string) => void;
+// }
+// @observer
+// export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> {
+// @observable private _isOpen: boolean = false;
+// @observable private _node: HTMLDivElement | null = null;
+
+// componentDidMount() { document.addEventListener("pointerdown", this.detectClick); }
+
+// componentWillUnmount() { document.removeEventListener("pointerdown", this.detectClick); }
+
+// @action
+// detectClick = (e: PointerEvent) => {
+// !this._node?.contains(e.target as Node) && this.props.setIsEditing(this._isOpen = false);
+// }
+
+// @action
+// toggleIsOpen = (): void => {
+// this.props.setIsEditing(this._isOpen = !this._isOpen);
+// }
+
+// changeColumnType = (type: ColumnType) => {
+// this.props.setColumnType(this.props.columnField, type);
+// }
+
+// changeColumnSort = (desc: boolean | undefined) => {
+// this.props.setColumnSort(this.props.columnField, desc);
+// }
+
+// changeColumnColor = (color: string) => {
+// this.props.setColumnColor(this.props.columnField, color);
+// }
+
+// @action
+// setNode = (node: HTMLDivElement): void => {
+// if (node) {
+// this._node = node;
+// }
+// }
+
+// renderTypes = () => {
+// if (this.props.typeConst) return (null);
+
+// const type = this.props.columnField.type;
+// return (
+// <div className="collectionSchema-headerMenu-group">
+// <label>Column type:</label>
+// <div className="columnMenu-types">
+// <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Any)}>
+// <FontAwesomeIcon icon={"align-justify"} size="sm" />
+// Any
+// </div>
+// <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Number)}>
+// <FontAwesomeIcon icon={"hashtag"} size="sm" />
+// Number
+// </div>
+// <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.String)}>
+// <FontAwesomeIcon icon={"font"} size="sm" />
+// Text
+// </div>
+// <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Boolean)}>
+// <FontAwesomeIcon icon={"check-square"} size="sm" />
+// Checkbox
+// </div>
+// <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.List)}>
+// <FontAwesomeIcon icon={"list-ul"} size="sm" />
+// List
+// </div>
+// <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Doc)}>
+// <FontAwesomeIcon icon={"file"} size="sm" />
+// Document
+// </div>
+// <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Image)}>
+// <FontAwesomeIcon icon={"image"} size="sm" />
+// Image
+// </div>
+// <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Date)}>
+// <FontAwesomeIcon icon={"calendar"} size="sm" />
+// Date
+// </div>
+// </div>
+// </div >
+// );
+// }
+
+// renderSorting = () => {
+// const sort = this.props.columnField.desc;
+// return (
+// <div className="collectionSchema-headerMenu-group">
+// <label>Sort by:</label>
+// <div className="columnMenu-sort">
+// <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.changeColumnSort(true)}>
+// <FontAwesomeIcon icon="sort-amount-down" size="sm" />
+// Sort descending
+// </div>
+// <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.changeColumnSort(false)}>
+// <FontAwesomeIcon icon="sort-amount-up" size="sm" />
+// Sort ascending
+// </div>
+// <div className="columnMenu-option" onClick={() => this.changeColumnSort(undefined)}>
+// <FontAwesomeIcon icon="times" size="sm" />
+// Clear sorting
+// </div>
+// </div>
+// </div>
+// );
+// }
+
+// renderColors = () => {
+// const selected = this.props.columnField.color;
+
+// const pink = PastelSchemaPalette.get("pink2");
+// const purple = PastelSchemaPalette.get("purple2");
+// const blue = PastelSchemaPalette.get("bluegreen1");
+// const yellow = PastelSchemaPalette.get("yellow4");
+// const red = PastelSchemaPalette.get("red2");
+// const gray = "#f1efeb";
+
+// return (
+// <div className="collectionSchema-headerMenu-group">
+// <label>Color:</label>
+// <div className="columnMenu-colors">
+// <div className={"columnMenu-colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div>
+// <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div>
+// <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div>
+// <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div>
+// <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div>
+// <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div>
+// </div>
+// </div>
+// );
+// }
+
+// renderContent = () => {
+// return (
+// <div className="collectionSchema-header-menuOptions">
+// {this.props.onlyShowOptions ? <></> :
+// <>
+// {this.renderTypes()}
+// {this.renderSorting()}
+// {this.renderColors()}
+// <div className="collectionSchema-headerMenu-group">
+// <button onClick={() => this.props.deleteColumn(this.props.columnField.heading)}>Hide Column</button>
+// </div>
+// </>
+// }
+// </div>
+// );
+// }
+
+// render() {
+// return (
+// <div className="collectionSchema-header-menu" ref={this.setNode}>
+// <Flyout anchorPoint={this.props.anchorPoint ? this.props.anchorPoint : anchorPoints.TOP_CENTER} content={this.renderContent()}>
+// <div className="collectionSchema-header-toggler" onClick={() => this.toggleIsOpen()}>{this.props.menuButtonContent}</div>
+// </ Flyout >
+// </div>
+// );
+// }
+// }
+
+// export interface KeysDropdownProps {
+// keyValue: string;
+// possibleKeys: string[];
+// existingKeys: string[];
+// canAddNew: boolean;
+// addNew: boolean;
+// onSelect: (oldKey: string, newKey: string, addnew: boolean, filter?: string) => void;
+// setIsEditing: (isEditing: boolean) => void;
+// width?: string;
+// docs?: Doc[];
+// Document: Doc;
+// dataDoc: Doc | undefined;
+// fieldKey: string;
+// ContainingCollectionDoc: Doc | undefined;
+// ContainingCollectionView: Opt<CollectionView>;
+// active?: (outsideReaction?: boolean) => boolean | undefined;
+// openHeader: (column: any, screenx: number, screeny: number) => void;
+// col: SchemaHeaderField;
+// icon: IconProp;
+// }
+// @observer
+// export class KeysDropdown extends React.Component<KeysDropdownProps> {
+// @observable private _key: string = this.props.keyValue;
+// @observable private _searchTerm: string = this.props.keyValue + ":";
+// @observable private _isOpen: boolean = false;
+// @observable private _node: HTMLDivElement | null = null;
+// @observable private _inputRef: React.RefObject<HTMLInputElement> = React.createRef();
+
+// @action setSearchTerm = (value: string): void => { this._searchTerm = value; };
+// @action setKey = (key: string): void => { this._key = key; };
+// @action setIsOpen = (isOpen: boolean): void => { this._isOpen = isOpen; };
+
+// @action
+// onSelect = (key: string): void => {
+// this.props.onSelect(this._key, key, this.props.addNew);
+// this.setKey(key);
+// this._isOpen = false;
+// this.props.setIsEditing(false);
+// }
+
+// @action
+// setNode = (node: HTMLDivElement): void => {
+// if (node) {
+// this._node = node;
+// }
+// }
+
+// componentDidMount() {
+// document.addEventListener("pointerdown", this.detectClick);
+// const filters = Cast(this.props.Document._docFilters, listSpec("string"));
+// if (filters?.some(filter => filter.split(":")[0] === this._key)) {
+// runInAction(() => this.closeResultsVisibility = "contents");
+// }
+// }
+
+// @action
+// detectClick = (e: PointerEvent): void => {
+// if (this._node && this._node.contains(e.target as Node)) {
+// } else {
+// this._isOpen = false;
+// this.props.setIsEditing(false);
+// }
+// }
+
+// private tempfilter: string = "";
+// @undoBatch
+// onKeyDown = (e: React.KeyboardEvent): void => {
+// if (e.key === "Enter") {
+// e.stopPropagation();
+// if (this._searchTerm.includes(":")) {
+// const colpos = this._searchTerm.indexOf(":");
+// const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
+// if (temp === "") {
+// Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove");
+// this.updateFilter();
+// }
+// else {
+// Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove");
+// this.tempfilter = temp;
+// Doc.setDocFilter(this.props.Document, this._key, temp, "check");
+// this.props.col.setColor("green");
+// this.closeResultsVisibility = "contents";
+// }
+// }
+// else {
+// Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, "remove");
+// this.updateFilter();
+// if (this.showKeys.length) {
+// this.onSelect(this.showKeys[0]);
+// } else if (this._searchTerm !== "" && this.props.canAddNew) {
+// this.setSearchTerm(this._searchTerm || this._key);
+// this.onSelect(this._searchTerm);
+// }
+// }
+// }
+// }
+
+// onChange = (val: string): void => {
+// this.setSearchTerm(val);
+// }
+
+// @action
+// onFocus = (e: React.FocusEvent): void => {
+// this._isOpen = true;
+// this.props.setIsEditing(true);
+// }
+
+// @computed get showKeys() {
+// const whitelistKeys = ["context", "author", "*lastModified", "text", "data", "tags", "creationDate"];
+// const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
+// const showKeys = new Set<string>();
+// [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.noviceMode ||
+// whitelistKeys.includes(key)
+// || ((!key.startsWith("_") && key[0] === key[0].toUpperCase()) || key[0] === "#")) ? showKeys.add(key) : null);
+// return Array.from(showKeys.keys()).filter(key => !this._searchTerm || key.includes(this._searchTerm));
+// }
+
+// @computed get renderOptions() {
+// if (!this._isOpen) {
+// this.defaultMenuHeight = 0;
+// return (null);
+// }
+// const options = this.showKeys.map(key => {
+// return <div key={key} className="key-option" style={{
+// border: "1px solid lightgray",
+// width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white",
+// }}
+// onPointerDown={e => {
+// e.stopPropagation();
+// }}
+// onClick={() => {
+// this.onSelect(key);
+// this.setSearchTerm("");
+// }}>{key}</div>;
+// });
+
+// // if search term does not already exist as a group type, give option to create new group type
+
+// if (this._key !== this._searchTerm.slice(0, this._key.length)) {
+// if (this._searchTerm !== "" && this.props.canAddNew) {
+// options.push(<div key={""} className="key-option" style={{
+// border: "1px solid lightgray", width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white",
+// }}
+// onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}>
+// Create "{this._searchTerm}" key</div>);
+// }
+// }
+
+// if (options.length === 0) {
+// this.defaultMenuHeight = 0;
+// }
+// else {
+// if (this.props.docs) {
+// const panesize = this.props.docs.length * 30;
+// options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8;
+// }
+// else {
+// options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8;
+// }
+// }
+// return options;
+// }
+
+// @computed get docSafe() { return DocListCast(this.props.dataDoc?.[this.props.fieldKey]); }
+
+// @computed get renderFilterOptions() {
+// if (!this._isOpen || !this.props.dataDoc) {
+// this.defaultMenuHeight = 0;
+// return (null);
+// }
+// const keyOptions: string[] = [];
+// const colpos = this._searchTerm.indexOf(":");
+// const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
+// this.docSafe.forEach(doc => {
+// const key = StrCast(doc[this._key]);
+// if (keyOptions.includes(key) === false && key.includes(temp) && key !== "") {
+// keyOptions.push(key);
+// }
+// });
+
+// const filters = StrListCast(this.props.Document._docFilters);
+// if (filters.some(filter => filter.split(":")[0] === this._key) === false) {
+// this.props.col.setColor("rgb(241, 239, 235)");
+// this.closeResultsVisibility = "none";
+// }
+// for (let i = 0; i < (filters?.length ?? 0) - 1; i++) {
+// if (filters[i] === this.props.col.heading && keyOptions.includes(filters[i].split(":")[1]) === false) {
+// keyOptions.push(filters[i + 1]);
+// }
+// }
+// const options = keyOptions.map(key => {
+// let bool = false;
+// if (filters !== undefined) {
+// const ind = filters.findIndex(filter => filter.split(":")[1] === key);
+// const fields = ind === -1 ? undefined : filters[ind].split(":");
+// bool = fields ? fields[2] === "check" : false;
+// }
+// return <div key={key} className="key-option" style={{
+// paddingLeft: 5, textAlign: "left",
+// width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", backgroundColor: "white",
+// }}
+// >
+// <input type="checkbox"
+// onPointerDown={e => e.stopPropagation()}
+// onClick={e => e.stopPropagation()}
+// onChange={action(e => {
+// if (e.target.checked) {
+// Doc.setDocFilter(this.props.Document, this._key, key, "check");
+// this.closeResultsVisibility = "contents";
+// this.props.col.setColor("green");
+// } else {
+// Doc.setDocFilter(this.props.Document, this._key, key, "remove");
+// this.updateFilter();
+// }
+// })}
+// checked={bool}
+// />
+// <span style={{ paddingLeft: 4 }}>
+// {key}
+// </span>
+
+// </div>;
+// });
+// if (options.length === 0) {
+// this.defaultMenuHeight = 0;
+// }
+// else {
+// if (this.props.docs) {
+// const panesize = this.props.docs.length * 30;
+// options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8;
+// }
+// else {
+// options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8;
+// }
+
+// }
+// return options;
+// }
+
+// @observable defaultMenuHeight = 0;
+
+// updateFilter() {
+// const filters = Cast(this.props.Document._docFilters, listSpec("string"));
+// if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) {
+// this.props.col.setColor("rgb(241, 239, 235)");
+// this.closeResultsVisibility = "none";
+// }
+// }
+
+// @computed get scriptField() {
+// const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
+// const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+// return script ? () => script : undefined;
+// }
+// filterBackground = () => "rgba(105, 105, 105, 0.432)";
+// @observable filterOpen: boolean | undefined = undefined;
+// closeResultsVisibility: string = "none";
+
+// removeFilters = (e: React.PointerEvent): void => {
+// const keyOptions: string[] = [];
+// this.docSafe.forEach(doc => {
+// const key = StrCast(doc[this._key]);
+// if (keyOptions.includes(key) === false) {
+// keyOptions.push(key);
+// }
+// });
+
+// Doc.setDocFilter(this.props.Document, this._key, "", "remove");
+// this.props.col.setColor("rgb(241, 239, 235)");
+// this.closeResultsVisibility = "none";
+// }
+// render() {
+// return (
+// <div style={{ display: "flex", width: '100%', alignContent: 'center', alignItems: 'center' }} ref={this.setNode}>
+// <div className="schema-icon" onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); e.stopPropagation(); }}>
+// <FontAwesomeIcon icon={this.props.icon} size="lg" style={{ display: "inline" }} />
+// </div>
+
+// <div className="keys-dropdown" style={{ zIndex: 1, width: this.props.width, maxWidth: this.props.width }}>
+// <input className="keys-search" style={{ width: "100%" }}
+// ref={this._inputRef} type="text"
+// value={this._searchTerm} placeholder="Column key"
+// onKeyDown={this.onKeyDown}
+// onChange={e => this.onChange(e.target.value)}
+// onClick={(e) => { e.stopPropagation(); this._inputRef.current?.focus(); }}
+// onFocus={this.onFocus} ></input>
+// <div style={{ display: this.closeResultsVisibility }}>
+// <FontAwesomeIcon onPointerDown={this.removeFilters} icon={"times-circle"} size="lg"
+// style={{ cursor: "hand", color: "grey", padding: 2, left: -20, top: -1, height: 15, position: "relative" }} />
+// </div>
+// {!this._isOpen ? (null) : <div className="keys-options-wrapper" style={{
+// width: this.props.width, maxWidth: this.props.width, height: "auto",
+// }}>
+// {this._searchTerm.includes(":") ? this.renderFilterOptions : this.renderOptions}
+// </div>}
+// </div >
+// </div>
+// );
+// }
+// }
diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx
new file mode 100644
index 000000000..c2182ae0c
--- /dev/null
+++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx
@@ -0,0 +1,138 @@
+// import React = require('react');
+// import { action } from 'mobx';
+// import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+// import { DragManager } from '../../../util/DragManager';
+// import { SnappingManager } from '../../../util/SnappingManager';
+// import { Transform } from '../../../util/Transform';
+// import './CollectionSchemaView.scss';
+
+// export interface MovableColumnProps {
+// columnRenderer: React.ReactNode;
+// columnValue: SchemaHeaderField;
+// allColumns: SchemaHeaderField[];
+// reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columns: SchemaHeaderField[]) => void;
+// ScreenToLocalTransform: () => Transform;
+// }
+// export class MovableColumn extends React.Component<MovableColumnProps> {
+// // The header of the column
+// private _header?: React.RefObject<HTMLDivElement> = React.createRef();
+// // The container of the function that is responsible for moving the column over to a new plac
+// private _colDropDisposer?: DragManager.DragDropDisposer;
+// // initial column position
+// private _startDragPosition: { x: number; y: number } = { x: 0, y: 0 };
+// // sensitivity to being dragged, in pixels
+// private _sensitivity: number = 16;
+// // Column reference ID
+// private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+
+// onPointerEnter = (e: React.PointerEvent): void => {
+// // if the column is left-clicked and it is being dragged
+// if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
+// this._header!.current!.className = 'collectionSchema-col-wrapper';
+// document.addEventListener('pointermove', this.onDragMove, true);
+// }
+// };
+
+// onPointerLeave = (e: React.PointerEvent): void => {
+// this._header!.current!.className = 'collectionSchema-col-wrapper';
+// document.removeEventListener('pointermove', this.onDragMove, true);
+// !e.buttons && document.removeEventListener('pointermove', this.onPointerMove);
+// };
+
+// onDragMove = (e: PointerEvent): void => {
+// // only take into account the horizonal direction when a column is dragged
+// const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+// const rect = this._header!.current!.getBoundingClientRect();
+// // Now store the point at the top center of the column when it was in its original position
+// const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top);
+// // to be compared with its new horizontal position
+// const before = x[0] < bounds[0];
+// this._header!.current!.className = 'collectionSchema-col-wrapper';
+// if (before) this._header!.current!.className += ' col-before';
+// if (!before) this._header!.current!.className += ' col-after';
+// e.stopPropagation();
+// };
+
+// createColDropTarget = (ele: HTMLDivElement) => {
+// this._colDropDisposer?.();
+// if (ele) {
+// this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
+// }
+// };
+
+// colDrop = (e: Event, de: DragManager.DropEvent) => {
+// document.removeEventListener('pointermove', this.onDragMove, true);
+// // we only care about whether the column is shifted to the side
+// const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+// // get the dimensions of the smallest rectangle that bounds the header
+// const rect = this._header!.current!.getBoundingClientRect();
+// const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top);
+// // get whether the column was dragged before or after where it is now
+// const before = x[0] < bounds[0];
+// const colDragData = de.complete.columnDragData;
+// // if there is colDragData, which happen when the drag is complete, reorder the columns according to the established variables
+// if (colDragData) {
+// e.stopPropagation();
+// this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns);
+// return true;
+// }
+// return false;
+// };
+
+// onPointerMove = (e: PointerEvent) => {
+// const onRowMove = (e: PointerEvent) => {
+// e.stopPropagation();
+// e.preventDefault();
+
+// document.removeEventListener('pointermove', onRowMove);
+// document.removeEventListener('pointerup', onRowUp);
+// const dragData = new DragManager.ColumnDragData(this.props.columnValue);
+// DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y);
+// };
+// const onRowUp = (): void => {
+// document.removeEventListener('pointermove', onRowMove);
+// document.removeEventListener('pointerup', onRowUp);
+// };
+// // if the left mouse button is the one being held
+// if (e.buttons === 1) {
+// const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
+// // If the movemnt of the drag exceeds the sensitivity value
+// if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
+// document.removeEventListener('pointermove', this.onPointerMove);
+// e.stopPropagation();
+
+// document.addEventListener('pointermove', onRowMove);
+// document.addEventListener('pointerup', onRowUp);
+// }
+// }
+// };
+
+// onPointerUp = (e: React.PointerEvent) => {
+// document.removeEventListener('pointermove', this.onPointerMove);
+// };
+
+// @action
+// onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => {
+// this._dragRef = ref;
+// const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY);
+// // If the cell thing dragged is not being edited
+// if (!(e.target as any)?.tagName.includes('INPUT')) {
+// this._startDragPosition = { x: dx, y: dy };
+// document.addEventListener('pointermove', this.onPointerMove);
+// }
+// };
+
+// render() {
+// const reference = React.createRef<HTMLDivElement>();
+
+// return (
+// <div className="collectionSchema-col" ref={this.createColDropTarget}>
+// <div className="collectionSchema-col-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+// <div className="col-dragger" ref={reference} onPointerDown={e => this.onPointerDown(e, reference)} onPointerUp={this.onPointerUp}>
+// {this.props.columnRenderer}
+// </div>
+// </div>
+// </div>
+// );
+// }
+// }
diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx
new file mode 100644
index 000000000..2b39df201
--- /dev/null
+++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx
@@ -0,0 +1,152 @@
+// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+// import { action } from 'mobx';
+// import * as React from 'react';
+// import { ReactTableDefaults, RowInfo } from 'react-table';
+// import { Doc } from '../../../../fields/Doc';
+// import { Cast, FieldValue, StrCast } from '../../../../fields/Types';
+// import { DocumentManager } from '../../../util/DocumentManager';
+// import { DragManager, dropActionType, SetupDrag } from '../../../util/DragManager';
+// import { SnappingManager } from '../../../util/SnappingManager';
+// import { Transform } from '../../../util/Transform';
+// import { undoBatch } from '../../../util/UndoManager';
+// import { ContextMenu } from '../../ContextMenu';
+// import { OpenWhere } from '../../nodes/DocumentView';
+// import './CollectionSchemaView.scss';
+
+// export interface MovableRowProps {
+// rowInfo: RowInfo;
+// ScreenToLocalTransform: () => Transform;
+// addDoc: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
+// removeDoc: (doc: Doc | Doc[]) => boolean;
+// rowFocused: boolean;
+// textWrapRow: (doc: Doc) => void;
+// rowWrapped: boolean;
+// dropAction: string;
+// addDocTab: any;
+// }
+
+// export class MovableRow extends React.Component<React.PropsWithChildren<MovableRowProps>> {
+// private _header?: React.RefObject<HTMLDivElement> = React.createRef();
+// private _rowDropDisposer?: DragManager.DragDropDisposer;
+
+// // Event listeners are only necessary when the user is hovering over the table
+// // Create one when the mouse starts hovering...
+// onPointerEnter = (e: React.PointerEvent): void => {
+// if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
+// this._header!.current!.className = 'collectionSchema-row-wrapper';
+// document.addEventListener('pointermove', this.onDragMove, true);
+// }
+// };
+// // ... and delete it when the mouse leaves
+// onPointerLeave = (e: React.PointerEvent): void => {
+// this._header!.current!.className = 'collectionSchema-row-wrapper';
+// document.removeEventListener('pointermove', this.onDragMove, true);
+// };
+// // The method for the event listener, reorders columns when dragged to their new locations.
+// onDragMove = (e: PointerEvent): void => {
+// const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+// const rect = this._header!.current!.getBoundingClientRect();
+// const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
+// const before = x[1] < bounds[1];
+// this._header!.current!.className = 'collectionSchema-row-wrapper';
+// if (before) this._header!.current!.className += ' row-above';
+// if (!before) this._header!.current!.className += ' row-below';
+// e.stopPropagation();
+// };
+// componentWillUnmount() {
+// this._rowDropDisposer?.();
+// }
+// //
+// createRowDropTarget = (ele: HTMLDivElement) => {
+// this._rowDropDisposer?.();
+// if (ele) {
+// this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
+// }
+// };
+// // Controls what hppens when a row is dragged and dropped
+// rowDrop = (e: Event, de: DragManager.DropEvent) => {
+// this.onPointerLeave(e as any);
+// const rowDoc = FieldValue(Cast(this.props.rowInfo.original, Doc));
+// if (!rowDoc) return false;
+
+// const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+// const rect = this._header!.current!.getBoundingClientRect();
+// const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
+// const before = x[1] < bounds[1];
+
+// const docDragData = de.complete.docDragData;
+// if (docDragData) {
+// e.stopPropagation();
+// if (docDragData.draggedDocuments[0] === rowDoc) return true;
+// const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before);
+// const movedDocs = docDragData.draggedDocuments;
+// return docDragData.dropAction || docDragData.userDropAction
+// ? docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
+// : docDragData.moveDocument
+// ? movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false)
+// : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
+// }
+// return false;
+// };
+
+// onRowContextMenu = (e: React.MouseEvent): void => {
+// const description = this.props.rowWrapped ? 'Unwrap text on row' : 'Text wrap row';
+// ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: 'file-pdf' });
+// };
+
+// @undoBatch
+// @action
+// move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => {
+// const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
+// return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
+// };
+
+// @action
+// onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+// console.log('yes');
+// if (e.key === 'Backspace' || e.key === 'Delete') {
+// undoBatch(() => this.props.removeDoc(this.props.rowInfo.original));
+// }
+// };
+
+// render() {
+// const { children = null, rowInfo } = this.props;
+
+// if (!rowInfo) {
+// return <ReactTableDefaults.TrComponent>{children}</ReactTableDefaults.TrComponent>;
+// }
+
+// const { original } = rowInfo;
+// const doc = FieldValue(Cast(original, Doc));
+
+// if (!doc) return null;
+
+// const reference = React.createRef<HTMLDivElement>();
+// const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType);
+
+// let className = 'collectionSchema-row';
+// if (this.props.rowFocused) className += ' row-focused';
+// if (this.props.rowWrapped) className += ' row-wrapped';
+
+// return (
+// <div className={className} onKeyPress={this.onKeyDown} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}>
+// <div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+// <ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown}>
+// <div className="row-dragger">
+// <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}>
+// <FontAwesomeIcon icon="trash" size="sm" />
+// </div>
+// <div className="row-option" style={{ cursor: 'grab' }} ref={reference} onPointerDown={onItemDown}>
+// <FontAwesomeIcon icon="grip-vertical" size="sm" />
+// </div>
+// <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, OpenWhere.addRight)}>
+// <FontAwesomeIcon icon="external-link-alt" size="sm" />
+// </div>
+// </div>
+// {children}
+// </ReactTableDefaults.TrComponent>
+// </div>
+// </div>
+// );
+// }
+// }
diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.scss b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.scss
new file mode 100644
index 000000000..22ce8c8f2
--- /dev/null
+++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.scss
@@ -0,0 +1,599 @@
+@import '../../global/globalCssVariables.scss';
+// @import '../../../../../node_modules/react-table/react-table.css';
+.collectionSchemaView-container {
+ border-width: $COLLECTION_BORDER_WIDTH;
+ border-color: $medium-gray;
+ border-style: solid;
+ border-radius: $border-radius;
+ box-sizing: border-box;
+ position: relative;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ margin-top: 0;
+ transition: top 0.5s;
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: nowrap;
+ touch-action: none;
+ div {
+ touch-action: none;
+ }
+ .collectionSchemaView-tableContainer {
+ width: 100%;
+ height: 100%;
+ }
+ .collectionSchemaView-dividerDragger {
+ position: relative;
+ height: 100%;
+ width: $SCHEMA_DIVIDER_WIDTH;
+ z-index: 20;
+ right: 0;
+ top: 0;
+ background: gray;
+ cursor: col-resize;
+ }
+ // .documentView-node:first-child {
+ // background: $white;
+ // }
+}
+
+.collectionSchemaView-searchContainer {
+ border-width: $COLLECTION_BORDER_WIDTH;
+ border-color: $medium-gray;
+ border-style: solid;
+ border-radius: $border-radius;
+ box-sizing: border-box;
+ position: relative;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ margin-top: 0;
+ transition: top 0.5s;
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: nowrap;
+ touch-action: none;
+ padding: 2px;
+ div {
+ touch-action: none;
+ }
+ .collectionSchemaView-tableContainer {
+ width: 100%;
+ height: 100%;
+ }
+ .collectionSchemaView-dividerDragger {
+ position: relative;
+ height: 100%;
+ width: 20px;
+ z-index: 20;
+ right: 0;
+ top: 0;
+ background: gray;
+ cursor: col-resize;
+ }
+ // .documentView-node:first-child {
+ // background: $white;
+ // }
+}
+
+.ReactTable {
+ width: 100%;
+ background: white;
+ box-sizing: border-box;
+ border: none !important;
+ float: none !important;
+ .rt-table {
+ height: 100%;
+ display: -webkit-inline-box;
+ direction: ltr;
+ overflow: visible;
+ }
+ .rt-noData {
+ display: none;
+ }
+ .rt-thead {
+ width: 100%;
+ z-index: 100;
+ overflow-y: visible;
+ &.-header {
+ font-size: 12px;
+ height: 30px;
+ box-shadow: none;
+ z-index: 100;
+ overflow-y: visible;
+ }
+ .rt-resizable-header-content {
+ height: 100%;
+ overflow: visible;
+ }
+ .rt-th {
+ padding: 0;
+ border-left: solid 1px $light-gray;
+ }
+ }
+ .rt-th {
+ font-size: 13px;
+ text-align: center;
+ &:last-child {
+ overflow: visible;
+ }
+ }
+ .rt-tbody {
+ width: 100%;
+ direction: rtl;
+ overflow: visible;
+ .rt-td {
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+ }
+ }
+ .rt-tr-group {
+ direction: ltr;
+ flex: 0 1 auto;
+ min-height: 30px;
+ border: 0 !important;
+ }
+ .rt-tr-group:nth-of-type(even) {
+ direction: ltr;
+ flex: 0 1 auto;
+ min-height: 30px;
+ border: 0 !important;
+ background-color: red;
+ }
+ .rt-tr {
+ width: 100%;
+ min-height: 30px;
+ }
+ .rt-td {
+ padding: 0;
+ font-size: 13px;
+ text-align: center;
+ white-space: nowrap;
+ display: flex;
+ align-items: center;
+ .imageBox-cont {
+ position: relative;
+ max-height: 100%;
+ }
+ .imageBox-cont img {
+ object-fit: contain;
+ max-width: 100%;
+ height: 100%;
+ }
+ .videoBox-cont {
+ object-fit: contain;
+ width: auto;
+ height: 100%;
+ }
+ }
+ .rt-td.rt-expandable {
+ display: flex;
+ align-items: center;
+ height: inherit;
+ }
+ .rt-resizer {
+ width: 8px;
+ right: -4px;
+ }
+ .rt-resizable-header {
+ padding: 0;
+ height: 30px;
+ }
+ .rt-resizable-header:last-child {
+ overflow: visible;
+ .rt-resizer {
+ width: 5px !important;
+ }
+ }
+}
+
+.documentView-node-topmost {
+ text-align: left;
+ transform-origin: center top;
+ display: inline-block;
+}
+
+.collectionSchema-col {
+ height: 100%;
+}
+
+.collectionSchema-header-menu {
+ height: auto;
+ z-index: 100;
+ position: absolute;
+ background: white;
+ padding: 5px;
+ position: fixed;
+ background: white;
+ border: black 1px solid;
+ .collectionSchema-header-toggler {
+ z-index: 100;
+ width: 100%;
+ height: 100%;
+ padding: 4px;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ svg {
+ margin-right: 4px;
+ }
+ }
+}
+
+.collectionSchemaView-header {
+ height: 100%;
+ color: gray;
+ z-index: 100;
+ overflow-y: visible;
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+}
+
+button.add-column {
+ width: 28px;
+}
+
+.collectionSchemaView-menuOptions-wrapper {
+ background: rgb(241, 239, 235);
+ display: flex;
+ cursor: default;
+ height: 100%;
+ align-content: center;
+ align-items: center;
+}
+
+.collectionSchema-header-menuOptions {
+ color: black;
+ width: 180px;
+ text-align: left;
+ .collectionSchema-headerMenu-group {
+ padding: 7px 0;
+ border-bottom: 1px solid lightgray;
+ cursor: pointer;
+ &:first-child {
+ padding-top: 0;
+ }
+ &:last-child {
+ border: none;
+ text-align: center;
+ padding: 12px 0 0 0;
+ }
+ }
+ label {
+ color: $medium-gray;
+ font-weight: normal;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ }
+ input {
+ color: black;
+ width: 100%;
+ }
+ .columnMenu-option {
+ cursor: pointer;
+ padding: 3px;
+ background-color: white;
+ transition: background-color 0.2s;
+ &:hover {
+ background-color: $light-gray;
+ }
+ &.active {
+ font-weight: bold;
+ border: 2px solid $light-gray;
+ }
+ svg {
+ color: gray;
+ margin-right: 5px;
+ width: 10px;
+ }
+ }
+
+ .keys-dropdown {
+ position: relative;
+ //width: 100%;
+ background-color: white;
+ input {
+ border: 2px solid $light-gray;
+ padding: 3px;
+ height: 28px;
+ font-weight: bold;
+ letter-spacing: '2px';
+ text-transform: 'uppercase';
+ &:focus {
+ font-weight: normal;
+ }
+ }
+ }
+ .columnMenu-colors {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ .columnMenu-colorPicker {
+ cursor: pointer;
+ width: 20px;
+ height: 20px;
+ border-radius: 10px;
+ &.active {
+ border: 2px solid white;
+ box-shadow: 0 0 0 2px lightgray;
+ }
+ }
+ }
+}
+
+.schema-icon {
+ cursor: pointer;
+ width: 25px;
+ height: 25px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ align-content: center;
+ background-color: $medium-blue;
+ color: white;
+ margin-right: 5px;
+ font-size: 10px;
+ border-radius: 3px;
+}
+
+.keys-options-wrapper {
+ position: absolute;
+ text-align: left;
+ height: fit-content;
+ top: 100%;
+ z-index: 21;
+ background-color: #ffffff;
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
+ padding: 1px;
+ .key-option {
+ cursor: pointer;
+ color: #000000;
+ width: 100%;
+ height: 25px;
+ font-weight: 400;
+ display: flex;
+ justify-content: left;
+ align-items: center;
+ padding-left: 5px;
+ &:hover {
+ background-color: $light-gray;
+ }
+ }
+}
+
+.collectionSchema-row {
+ height: 100%;
+ background-color: white;
+ &.row-focused .rt-td {
+ background-color: $light-blue; //$light-gray;
+ overflow: visible;
+ }
+ &.row-wrapped {
+ .rt-td {
+ white-space: normal;
+ }
+ }
+ .row-dragger {
+ display: flex;
+ justify-content: space-evenly;
+ width: 58px;
+ position: absolute;
+ /* max-width: 50px; */
+ min-height: 30px;
+ align-items: center;
+ color: lightgray;
+ background-color: white;
+ transition: color 0.1s ease;
+ .row-option {
+ color: black;
+ cursor: pointer;
+ position: relative;
+ transition: color 0.1s ease;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ z-index: 2;
+ border-radius: 3px;
+ padding: 3px;
+ &:hover {
+ background-color: $light-gray;
+ }
+ }
+ }
+ .collectionSchema-row-wrapper {
+ &.row-above {
+ border-top: 1px solid $medium-blue;
+ }
+ &.row-below {
+ border-bottom: 1px solid $medium-blue;
+ }
+ &.row-inside {
+ border: 2px dashed $medium-blue;
+ }
+ .row-dragging {
+ background-color: blue;
+ }
+ }
+}
+
+.collectionSchemaView-cellContainer {
+ width: 100%;
+ height: unset;
+}
+
+.collectionSchemaView-cellContents {
+ width: 100%;
+}
+
+.collectionSchemaView-cellWrapper {
+ display: flex;
+ height: 100%;
+ text-align: left;
+ padding-left: 19px;
+ position: relative;
+ align-items: center;
+ align-content: center;
+ &:focus {
+ outline: none;
+ }
+ &.editing {
+ padding: 0;
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
+ transform: scale(1.1);
+ z-index: 40;
+ input {
+ outline: 0;
+ border: none;
+ background-color: $white;
+ width: 100%;
+ height: fit-content;
+ min-height: 26px;
+ }
+ }
+ &.focused {
+ overflow: hidden;
+ &.inactive {
+ border: none;
+ }
+ }
+ p {
+ width: 100%;
+ height: 100%;
+ }
+ &:hover .collectionSchemaView-cellContents-docExpander {
+ display: block;
+ }
+ .collectionSchemaView-cellContents-document {
+ display: inline-block;
+ }
+ .collectionSchemaView-cellContents-docButton {
+ float: right;
+ width: '15px';
+ height: '15px';
+ }
+ .collectionSchemaView-dropdownWrapper {
+ border: grey;
+ border-style: solid;
+ border-width: 1px;
+ height: 30px;
+ .collectionSchemaView-dropdownButton {
+ //display: inline-block;
+ float: left;
+ height: 100%;
+ }
+ .collectionSchemaView-dropdownText {
+ display: inline-block;
+ //float: right;
+ height: 100%;
+ display: 'flex';
+ font-size: 13;
+ justify-content: 'center';
+ align-items: 'center';
+ }
+ }
+ .collectionSchemaView-dropdownContainer {
+ position: absolute;
+ border: 1px solid rgba(0, 0, 0, 0.04);
+ box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14);
+ .collectionSchemaView-dropdownOption:hover {
+ background-color: rgba(0, 0, 0, 0.14);
+ cursor: pointer;
+ }
+ }
+}
+
+.collectionSchemaView-cellContents-docExpander {
+ height: 30px;
+ width: 30px;
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+ background-color: lightgray;
+}
+
+.doc-drag-over {
+ background-color: red;
+}
+
+.collectionSchemaView-toolbar {
+ z-index: 100;
+}
+
+.collectionSchemaView-toolbar {
+ height: 30px;
+ display: flex;
+ justify-content: flex-end;
+ padding: 0 10px;
+ border-bottom: 2px solid gray;
+ .collectionSchemaView-toolbar-item {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ }
+}
+
+#preview-schema-checkbox-div {
+ margin-left: 20px;
+ font-size: 12px;
+}
+
+.collectionSchemaView-table {
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ padding: 3px;
+}
+
+.rt-td.rt-expandable {
+ overflow: visible;
+ position: relative;
+ height: 100%;
+ z-index: 1;
+}
+
+.reactTable-sub {
+ background-color: rgb(252, 252, 252);
+ width: 100%;
+ .rt-thead {
+ display: none;
+ }
+ .row-dragger {
+ background-color: rgb(252, 252, 252);
+ }
+ .rt-table {
+ background-color: rgb(252, 252, 252);
+ }
+ .collectionSchemaView-table {
+ width: 100%;
+ border: solid 1px;
+ overflow: visible;
+ padding: 0px;
+ }
+}
+
+.collectionSchemaView-expander {
+ height: 100%;
+ min-height: 30px;
+ position: absolute;
+ color: gray;
+ width: 20;
+ height: auto;
+ left: 55;
+ svg {
+ position: absolute;
+ top: 50%;
+ left: 10;
+ transform: translate(-50%, -50%);
+ }
+}
+
+.collectionSchemaView-addRow {
+ color: gray;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ cursor: pointer;
+ font-size: 10.5px;
+ margin-left: 50px;
+ margin-top: 10px;
+}
diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.tsx
new file mode 100644
index 000000000..f3f09cbf0
--- /dev/null
+++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.tsx
@@ -0,0 +1,649 @@
+// import React = require('react');
+// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+// import { action, computed, observable, untracked } from 'mobx';
+// import { observer } from 'mobx-react';
+// import Measure from 'react-measure';
+// // import { Resize } from 'react-table';
+// import { Doc, Opt } from '../../../../fields/Doc';
+// import { List } from '../../../../fields/List';
+// import { listSpec } from '../../../../fields/Schema';
+// import { PastelSchemaPalette, SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+// import { Cast, NumCast } from '../../../../fields/Types';
+// import { TraceMobx } from '../../../../fields/util';
+// import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+// import { DocUtils } from '../../../documents/Documents';
+// import { SelectionManager } from '../../../util/SelectionManager';
+// import { SnappingManager } from '../../../util/SnappingManager';
+// import { Transform } from '../../../util/Transform';
+// import { undoBatch } from '../../../util/UndoManager';
+// import { ContextMenu } from '../../ContextMenu';
+// import { ContextMenuProps } from '../../ContextMenuItem';
+// import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
+// import { DocumentView } from '../../nodes/DocumentView';
+// import { DefaultStyleProvider } from '../../StyleProvider';
+// import { CollectionSubView } from '../CollectionSubView';
+// import './CollectionSchemaView.scss';
+// // import { SchemaTable } from './SchemaTable';
+// // bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
+
+// export enum ColumnType {
+// Any,
+// Number,
+// String,
+// Boolean,
+// Doc,
+// Image,
+// List,
+// Date,
+// }
+// // this map should be used for keys that should have a const type of value
+// const columnTypes: Map<string, ColumnType> = new Map([
+// ['title', ColumnType.String],
+// ['x', ColumnType.Number],
+// ['y', ColumnType.Number],
+// ['_width', ColumnType.Number],
+// ['_height', ColumnType.Number],
+// ['_nativeWidth', ColumnType.Number],
+// ['_nativeHeight', ColumnType.Number],
+// ['isPrototype', ColumnType.Boolean],
+// ['_curPage', ColumnType.Number],
+// ['_currentTimecode', ColumnType.Number],
+// ['zIndex', ColumnType.Number],
+// ]);
+
+// @observer
+// export class CollectionSchemaView extends CollectionSubView() {
+// private _previewCont?: HTMLDivElement;
+
+// @observable _previewDoc: Doc | undefined = undefined;
+// @observable _focusedTable: Doc = this.props.Document;
+// @observable _col: any = '';
+// @observable _menuWidth = 0;
+// @observable _headerOpen = false;
+// @observable _headerIsEditing = false;
+// @observable _menuHeight = 0;
+// @observable _pointerX = 0;
+// @observable _pointerY = 0;
+// @observable _openTypes: boolean = false;
+
+// @computed get previewWidth() {
+// return () => NumCast(this.props.Document.schemaPreviewWidth);
+// }
+// @computed get previewHeight() {
+// return () => this.props.PanelHeight() - 2 * this.borderWidth;
+// }
+// @computed get tableWidth() {
+// return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
+// }
+// @computed get borderWidth() {
+// return Number(COLLECTION_BORDER_WIDTH);
+// }
+// @computed get scale() {
+// return this.props.ScreenToLocalTransform().Scale;
+// }
+// @computed get columns() {
+// return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
+// }
+// set columns(columns: SchemaHeaderField[]) {
+// this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns);
+// }
+
+// @computed get menuCoordinates() {
+// let searchx = 0;
+// let searchy = 0;
+// if (this.props.Document._searchDoc) {
+// const el = document.getElementsByClassName('collectionSchemaView-searchContainer')[0];
+// if (el !== undefined) {
+// const rect = el.getBoundingClientRect();
+// searchx = rect.x;
+// searchy = rect.y;
+// }
+// }
+// const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx;
+// const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy;
+// return this.props.ScreenToLocalTransform().transformPoint(x, y);
+// }
+
+// get documentKeys() {
+// 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)))));
+
+// this.columns.forEach(key => (keys[key.heading] = true));
+// return Array.from(Object.keys(keys));
+// }
+
+// @action setHeaderIsEditing = (isEditing: boolean) => (this._headerIsEditing = isEditing);
+
+// @undoBatch
+// setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => {
+// this._openTypes = false;
+// if (columnTypes.get(columnField.heading)) return;
+
+// const columns = this.columns;
+// const index = columns.indexOf(columnField);
+// if (index > -1) {
+// columnField.setType(NumCast(type));
+// columns[index] = columnField;
+// this.columns = columns;
+// }
+// });
+
+// @undoBatch
+// setColumnColor = (columnField: SchemaHeaderField, color: string): void => {
+// const columns = this.columns;
+// const index = columns.indexOf(columnField);
+// if (index > -1) {
+// columnField.setColor(color);
+// columns[index] = columnField;
+// this.columns = columns; // need to set the columns to trigger rerender
+// }
+// };
+
+// @undoBatch
+// @action
+// setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => {
+// const columns = this.columns;
+// columns.forEach(col => col.setDesc(undefined));
+
+// const index = columns.findIndex(c => c.heading === columnField.heading);
+// const column = columns[index];
+// column.setDesc(descending);
+// columns[index] = column;
+// this.columns = columns;
+// };
+
+// renderTypes = (col: any) => {
+// if (columnTypes.get(col.heading)) return null;
+
+// const type = col.type;
+
+// const anyType = (
+// <div className={'columnMenu-option' + (type === ColumnType.Any ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Any)}>
+// <FontAwesomeIcon icon={'align-justify'} size="sm" />
+// Any
+// </div>
+// );
+
+// const numType = (
+// <div className={'columnMenu-option' + (type === ColumnType.Number ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Number)}>
+// <FontAwesomeIcon icon={'hashtag'} size="sm" />
+// Number
+// </div>
+// );
+
+// const textType = (
+// <div className={'columnMenu-option' + (type === ColumnType.String ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.String)}>
+// <FontAwesomeIcon icon={'font'} size="sm" />
+// Text
+// </div>
+// );
+
+// const boolType = (
+// <div className={'columnMenu-option' + (type === ColumnType.Boolean ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Boolean)}>
+// <FontAwesomeIcon icon={'check-square'} size="sm" />
+// Checkbox
+// </div>
+// );
+
+// const listType = (
+// <div className={'columnMenu-option' + (type === ColumnType.List ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.List)}>
+// <FontAwesomeIcon icon={'list-ul'} size="sm" />
+// List
+// </div>
+// );
+
+// const docType = (
+// <div className={'columnMenu-option' + (type === ColumnType.Doc ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Doc)}>
+// <FontAwesomeIcon icon={'file'} size="sm" />
+// Document
+// </div>
+// );
+
+// const imageType = (
+// <div className={'columnMenu-option' + (type === ColumnType.Image ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Image)}>
+// <FontAwesomeIcon icon={'image'} size="sm" />
+// Image
+// </div>
+// );
+
+// const dateType = (
+// <div className={'columnMenu-option' + (type === ColumnType.Date ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Date)}>
+// <FontAwesomeIcon icon={'calendar'} size="sm" />
+// Date
+// </div>
+// );
+
+// const allColumnTypes = (
+// <div className="columnMenu-types">
+// {anyType}
+// {numType}
+// {textType}
+// {boolType}
+// {listType}
+// {docType}
+// {imageType}
+// {dateType}
+// </div>
+// );
+
+// const justColType =
+// type === ColumnType.Any
+// ? anyType
+// : type === ColumnType.Number
+// ? numType
+// : type === ColumnType.String
+// ? textType
+// : type === ColumnType.Boolean
+// ? boolType
+// : type === ColumnType.List
+// ? listType
+// : type === ColumnType.Doc
+// ? docType
+// : type === ColumnType.Date
+// ? dateType
+// : imageType;
+
+// return (
+// <div className="collectionSchema-headerMenu-group" onClick={action(() => (this._openTypes = !this._openTypes))}>
+// <div>
+// <label style={{ cursor: 'pointer' }}>Column type:</label>
+// <FontAwesomeIcon icon={'caret-down'} size="lg" style={{ float: 'right', transform: `rotate(${this._openTypes ? '180deg' : 0})`, transition: '0.2s all ease' }} />
+// </div>
+// {this._openTypes ? allColumnTypes : justColType}
+// </div>
+// );
+// };
+
+// renderSorting = (col: any) => {
+// const sort = col.desc;
+// return (
+// <div className="collectionSchema-headerMenu-group">
+// <label>Sort by:</label>
+// <div className="columnMenu-sort">
+// <div className={'columnMenu-option' + (sort === true ? ' active' : '')} onClick={() => this.setColumnSort(col, true)}>
+// <FontAwesomeIcon icon="sort-amount-down" size="sm" />
+// Sort descending
+// </div>
+// <div className={'columnMenu-option' + (sort === false ? ' active' : '')} onClick={() => this.setColumnSort(col, false)}>
+// <FontAwesomeIcon icon="sort-amount-up" size="sm" />
+// Sort ascending
+// </div>
+// <div className="columnMenu-option" onClick={() => this.setColumnSort(col, undefined)}>
+// <FontAwesomeIcon icon="times" size="sm" />
+// Clear sorting
+// </div>
+// </div>
+// </div>
+// );
+// };
+
+// renderColors = (col: any) => {
+// const selected = col.color;
+
+// const pink = PastelSchemaPalette.get('pink2');
+// const purple = PastelSchemaPalette.get('purple2');
+// const blue = PastelSchemaPalette.get('bluegreen1');
+// const yellow = PastelSchemaPalette.get('yellow4');
+// const red = PastelSchemaPalette.get('red2');
+// const gray = '#f1efeb';
+
+// return (
+// <div className="collectionSchema-headerMenu-group">
+// <label>Color:</label>
+// <div className="columnMenu-colors">
+// <div className={'columnMenu-colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.setColumnColor(col, pink!)}></div>
+// <div className={'columnMenu-colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.setColumnColor(col, purple!)}></div>
+// <div className={'columnMenu-colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.setColumnColor(col, blue!)}></div>
+// <div className={'columnMenu-colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.setColumnColor(col, yellow!)}></div>
+// <div className={'columnMenu-colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.setColumnColor(col, red!)}></div>
+// <div className={'columnMenu-colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.setColumnColor(col, gray)}></div>
+// </div>
+// </div>
+// );
+// };
+
+// @undoBatch
+// @action
+// changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => {
+// const columns = this.columns;
+// if (columns === undefined) {
+// this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, 'f1efeb')]);
+// } else {
+// if (addNew) {
+// columns.push(new SchemaHeaderField(newKey, 'f1efeb'));
+// this.columns = columns;
+// } else {
+// const index = columns.map(c => c.heading).indexOf(oldKey);
+// if (index > -1) {
+// const column = columns[index];
+// column.setHeading(newKey);
+// columns[index] = column;
+// this.columns = columns;
+// if (filter) {
+// Doc.setDocFilter(this.props.Document, newKey, filter, 'match');
+// } else {
+// this.props.Document._docFilters = undefined;
+// }
+// }
+// }
+// }
+// };
+
+// @action
+// openHeader = (col: any, screenx: number, screeny: number) => {
+// this._col = col;
+// this._headerOpen = true;
+// this._pointerX = screenx;
+// this._pointerY = screeny;
+// };
+
+// @action
+// closeHeader = () => {
+// this._headerOpen = false;
+// };
+
+// @undoBatch
+// @action
+// deleteColumn = (key: string) => {
+// const columns = this.columns;
+// if (columns === undefined) {
+// this.columns = new List<SchemaHeaderField>([]);
+// } else {
+// const index = columns.map(c => c.heading).indexOf(key);
+// if (index > -1) {
+// columns.splice(index, 1);
+// this.columns = columns;
+// }
+// }
+// this.closeHeader();
+// };
+
+// getPreviewTransform = (): Transform => {
+// return this.props.ScreenToLocalTransform().translate(-this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, -this.borderWidth);
+// };
+
+// @action
+// onHeaderClick = (e: React.PointerEvent) => {
+// e.stopPropagation();
+// };
+
+// @action
+// onWheel(e: React.WheelEvent) {
+// const scale = this.props.ScreenToLocalTransform().Scale;
+// this.props.isContentActive(true) && e.stopPropagation();
+// }
+
+// @computed get renderMenuContent() {
+// TraceMobx();
+// return (
+// <div className="collectionSchema-header-menuOptions">
+// {this.renderTypes(this._col)}
+// {this.renderColors(this._col)}
+// <div className="collectionSchema-headerMenu-group">
+// <button
+// onClick={() => {
+// this.deleteColumn(this._col.heading);
+// }}>
+// Hide Column
+// </button>
+// </div>
+// </div>
+// );
+// }
+
+// private createTarget = (ele: HTMLDivElement) => {
+// this._previewCont = ele;
+// super.CreateDropTarget(ele);
+// };
+
+// isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable;
+
+// @action setFocused = (doc: Doc) => (this._focusedTable = doc);
+
+// @action setPreviewDoc = (doc: Opt<Doc>) => {
+// SelectionManager.SelectSchemaViewDoc(doc);
+// this._previewDoc = doc;
+// };
+
+// //toggles preview side-panel of schema
+// @action
+// toggleExpander = () => {
+// this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0;
+// };
+
+// onDividerDown = (e: React.PointerEvent) => {
+// setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander);
+// };
+// @action
+// onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
+// const nativeWidth = this._previewCont!.getBoundingClientRect();
+// const minWidth = 40;
+// const maxWidth = 1000;
+// const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
+// const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
+// this.props.Document.schemaPreviewWidth = width;
+// return false;
+// };
+
+// onPointerDown = (e: React.PointerEvent): void => {
+// if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
+// if (this.props.isSelected(true)) e.stopPropagation();
+// else this.props.select(false);
+// }
+// };
+
+// @computed
+// get previewDocument(): Doc | undefined {
+// return this._previewDoc;
+// }
+
+// @computed
+// get dividerDragger() {
+// return this.previewWidth() === 0 ? null : (
+// <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown}>
+// <div className="collectionSchemaView-dividerDragger" />
+// </div>
+// );
+// }
+
+// @computed
+// get previewPanel() {
+// return (
+// <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
+// {!this.previewDocument ? null : (
+// <DocumentView
+// Document={this.previewDocument}
+// DataDoc={undefined}
+// fitContentsToBox={returnTrue}
+// dontCenter={'y'}
+// focus={DocUtils.DefaultFocus}
+// renderDepth={this.props.renderDepth}
+// rootSelected={this.rootSelected}
+// PanelWidth={this.previewWidth}
+// PanelHeight={this.previewHeight}
+// isContentActive={returnTrue}
+// isDocumentActive={returnFalse}
+// ScreenToLocalTransform={this.getPreviewTransform}
+// docFilters={this.childDocFilters}
+// docRangeFilters={this.childDocRangeFilters}
+// searchFilterDocs={this.searchFilterDocs}
+// styleProvider={DefaultStyleProvider}
+// docViewPath={returnEmptyDoclist}
+// ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+// ContainingCollectionView={this.props.CollectionView}
+// moveDocument={this.props.moveDocument}
+// addDocument={this.props.addDocument}
+// removeDocument={this.props.removeDocument}
+// whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+// addDocTab={this.props.addDocTab}
+// pinToPres={this.props.pinToPres}
+// bringToFront={returnFalse}
+// />
+// )}
+// </div>
+// );
+// }
+
+// @computed
+// get schemaTable() {
+// return (
+// <SchemaTable
+// Document={this.props.Document}
+// PanelHeight={this.props.PanelHeight}
+// PanelWidth={this.props.PanelWidth}
+// childDocs={this.childDocs}
+// CollectionView={this.props.CollectionView}
+// ContainingCollectionView={this.props.ContainingCollectionView}
+// ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+// fieldKey={this.props.fieldKey}
+// renderDepth={this.props.renderDepth}
+// moveDocument={this.props.moveDocument}
+// ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+// active={this.props.isContentActive}
+// onDrop={this.onExternalDrop}
+// addDocTab={this.props.addDocTab}
+// pinToPres={this.props.pinToPres}
+// isSelected={this.props.isSelected}
+// isFocused={this.isFocused}
+// setFocused={this.setFocused}
+// setPreviewDoc={this.setPreviewDoc}
+// deleteDocument={this.props.removeDocument}
+// addDocument={this.props.addDocument}
+// dataDoc={this.props.DataDoc}
+// columns={this.columns}
+// documentKeys={this.documentKeys}
+// headerIsEditing={this._headerIsEditing}
+// openHeader={this.openHeader}
+// onClick={this.onTableClick}
+// onPointerDown={emptyFunction}
+// onResizedChange={this.onResizedChange}
+// setColumns={this.setColumns}
+// reorderColumns={this.reorderColumns}
+// changeColumns={this.changeColumns}
+// setHeaderIsEditing={this.setHeaderIsEditing}
+// changeColumnSort={this.setColumnSort}
+// />
+// );
+// }
+
+// @computed
+// public get schemaToolbar() {
+// return (
+// <div className="collectionSchemaView-toolbar">
+// <div className="collectionSchemaView-toolbar-item">
+// <div id="preview-schema-checkbox-div">
+// <input type="checkbox" key={'Show Preview'} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} />
+// Show Preview
+// </div>
+// </div>
+// </div>
+// );
+// }
+
+// onSpecificMenu = (e: React.MouseEvent) => {
+// if ((e.target as any)?.className?.includes?.('collectionSchemaView-cell') || e.target instanceof HTMLSpanElement) {
+// const cm = ContextMenu.Instance;
+// const options = cm.findByDescription('Options...');
+// const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
+// optionItems.push({ description: 'remove', event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: 'trash' });
+// !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
+// cm.displayMenu(e.clientX, e.clientY);
+// (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this.
+// e.stopPropagation();
+// }
+// };
+
+// @action
+// onTableClick = (e: React.MouseEvent): void => {
+// if (!(e.target as any)?.className?.includes?.('collectionSchemaView-cell') && !(e.target instanceof HTMLSpanElement)) {
+// this.setPreviewDoc(undefined);
+// } else {
+// e.stopPropagation();
+// }
+// this.setFocused(this.props.Document);
+// this.closeHeader();
+// };
+
+// onResizedChange = (newResized: Resize[], event: any) => {
+// const columns = this.columns;
+// newResized.forEach(resized => {
+// const index = columns.findIndex(c => c.heading === resized.id);
+// const column = columns[index];
+// column.setWidth(resized.value);
+// columns[index] = column;
+// });
+// this.columns = columns;
+// };
+
+// @action
+// setColumns = (columns: SchemaHeaderField[]) => (this.columns = columns);
+
+// @undoBatch
+// reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => {
+// const columns = [...columnsValues];
+// const oldIndex = columns.indexOf(toMove);
+// const relIndex = columns.indexOf(relativeTo);
+// const newIndex = oldIndex > relIndex && !before ? relIndex + 1 : oldIndex < relIndex && before ? relIndex - 1 : relIndex;
+
+// if (oldIndex === newIndex) return;
+
+// columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
+// this.columns = columns;
+// };
+
+// onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation();
+
+// render() {
+// TraceMobx();
+// if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0);
+// const menuContent = this.renderMenuContent;
+// const menu = (
+// <div className="collectionSchema-header-menu" onWheel={e => this.onZoomMenu(e)} onPointerDown={e => this.onHeaderClick(e)} style={{ transform: `translate(${this.menuCoordinates[0]}px, ${this.menuCoordinates[1]}px)` }}>
+// <Measure
+// offset
+// onResize={action((r: any) => {
+// const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height);
+// this._menuWidth = dim[0];
+// this._menuHeight = dim[1];
+// })}>
+// {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>}
+// </Measure>
+// </div>
+// );
+// return (
+// <div
+// className={'collectionSchemaView' + (this.props.Document._searchDoc ? '-searchContainer' : '-container')}
+// style={{
+// overflow: this.props.scrollOverflow === true ? 'scroll' : undefined,
+// backgroundColor: 'white',
+// pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined,
+// width: this.props.PanelWidth() || '100%',
+// height: this.props.PanelHeight() || '100%',
+// position: 'relative',
+// }}>
+// <div
+// className="collectionSchemaView-tableContainer"
+// style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
+// onContextMenu={this.onSpecificMenu}
+// onPointerDown={this.onPointerDown}
+// onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}
+// onDrop={e => this.onExternalDrop(e, {})}
+// ref={this.createTarget}>
+// {this.schemaTable}
+// </div>
+// {this.dividerDragger}
+// {!this.previewWidth() ? null : this.previewPanel}
+// {this._headerOpen && this.props.isContentActive() ? menu : null}
+// </div>
+// );
+// TraceMobx();
+// return <div>HELLO</div>;
+// }
+// }
diff --git a/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx b/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx
new file mode 100644
index 000000000..bc33f3be5
--- /dev/null
+++ b/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx
@@ -0,0 +1,694 @@
+// import { IconProp } from '@fortawesome/fontawesome-svg-core';
+// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+// import { action, computed, observable } from 'mobx';
+// import { observer } from 'mobx-react';
+// import * as React from 'react';
+// import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from 'react-table';
+// import { DateField } from '../../../../fields/DateField';
+// import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+// import { Id } from '../../../../fields/FieldSymbols';
+// import { List } from '../../../../fields/List';
+// import { listSpec } from '../../../../fields/Schema';
+// import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+// import { ComputedField } from '../../../../fields/ScriptField';
+// import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
+// import { ImageField } from '../../../../fields/URLField';
+// import { GetEffectiveAcl } from '../../../../fields/util';
+// import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../../Utils';
+// import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents';
+// import { DocumentType } from '../../../documents/DocumentTypes';
+// import { CompileScript, Transformer, ts } from '../../../util/Scripting';
+// import { Transform } from '../../../util/Transform';
+// import { undoBatch } from '../../../util/UndoManager';
+// import '../../../views/DocumentDecorations.scss';
+// import { ContextMenu } from '../../ContextMenu';
+// import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
+// import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
+// import { PinProps } from '../../nodes/trails';
+// import { DefaultStyleProvider } from '../../StyleProvider';
+// import { CollectionView } from '../CollectionView';
+// import {
+// CellProps,
+// CollectionSchemaButtons,
+// CollectionSchemaCell,
+// CollectionSchemaCheckboxCell,
+// CollectionSchemaDateCell,
+// CollectionSchemaDocCell,
+// CollectionSchemaImageCell,
+// CollectionSchemaListCell,
+// CollectionSchemaNumberCell,
+// CollectionSchemaStringCell,
+// } from './CollectionSchemaCells';
+// import { CollectionSchemaAddColumnHeader, KeysDropdown } from './CollectionSchemaHeaders';
+// import { MovableColumn } from './OldCollectionSchemaMovableColumn';
+// import { MovableRow } from './CollectionSchemaMovableRow';
+// import './CollectionSchemaView.scss';
+
+// enum ColumnType {
+// Any,
+// Number,
+// String,
+// Boolean,
+// Doc,
+// Image,
+// List,
+// Date,
+// }
+
+// // this map should be used for keys that should have a const type of value
+// const columnTypes: Map<string, ColumnType> = new Map([
+// ['title', ColumnType.String],
+// ['x', ColumnType.Number],
+// ['y', ColumnType.Number],
+// ['_width', ColumnType.Number],
+// ['_height', ColumnType.Number],
+// ['_nativeWidth', ColumnType.Number],
+// ['_nativeHeight', ColumnType.Number],
+// ['isPrototype', ColumnType.Boolean],
+// ['_curPage', ColumnType.Number],
+// ['_currentTimecode', ColumnType.Number],
+// ['zIndex', ColumnType.Number],
+// ]);
+
+// export interface SchemaTableProps {
+// Document: Doc; // child doc
+// dataDoc?: Doc;
+// PanelHeight: () => number;
+// PanelWidth: () => number;
+// childDocs?: Doc[];
+// CollectionView: Opt<CollectionView>;
+// ContainingCollectionView: Opt<CollectionView>;
+// ContainingCollectionDoc: Opt<Doc>;
+// fieldKey: string;
+// renderDepth: number;
+// deleteDocument?: (document: Doc | Doc[]) => boolean;
+// addDocument?: (document: Doc | Doc[]) => boolean;
+// moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+// ScreenToLocalTransform: () => Transform;
+// active: (outsideReaction: boolean | undefined) => boolean | undefined;
+// onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
+// addDocTab: (document: Doc, where: OpenWhere) => boolean;
+// pinToPres: (document: Doc, pinProps: PinProps) => void;
+// isSelected: (outsideReaction?: boolean) => boolean;
+// isFocused: (document: Doc, outsideReaction: boolean) => boolean;
+// setFocused: (document: Doc) => void;
+// setPreviewDoc: (document: Opt<Doc>) => void;
+// columns: SchemaHeaderField[];
+// documentKeys: any[];
+// headerIsEditing: boolean;
+// openHeader: (column: any, screenx: number, screeny: number) => void;
+// onClick: (e: React.MouseEvent) => void;
+// onPointerDown: (e: React.PointerEvent) => void;
+// onResizedChange: (newResized: Resize[], event: any) => void;
+// setColumns: (columns: SchemaHeaderField[]) => void;
+// reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => void;
+// changeColumns: (oldKey: string, newKey: string, addNew: boolean) => void;
+// setHeaderIsEditing: (isEditing: boolean) => void;
+// changeColumnSort: (columnField: SchemaHeaderField, descending: boolean | undefined) => void;
+// }
+
+// @observer
+// export class SchemaTable extends React.Component<SchemaTableProps> {
+// @observable _cellIsEditing: boolean = false;
+// @observable _focusedCell: { row: number; col: number } = { row: 0, col: 0 };
+// @observable _openCollections: Set<number> = new Set();
+
+// @observable _showDoc: Doc | undefined;
+// @observable _showDataDoc: any = '';
+// @observable _showDocPos: number[] = [];
+
+// @observable _showTitleDropdown: boolean = false;
+
+// @computed get previewWidth() {
+// return () => NumCast(this.props.Document.schemaPreviewWidth);
+// }
+// @computed get previewHeight() {
+// return () => this.props.PanelHeight() - 2 * this.borderWidth;
+// }
+// @computed get tableWidth() {
+// return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
+// }
+
+// @computed get childDocs() {
+// if (this.props.childDocs) return this.props.childDocs;
+
+// const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
+// return DocListCast(doc[this.props.fieldKey]);
+// }
+// set childDocs(docs: Doc[]) {
+// const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
+// doc[this.props.fieldKey] = new List<Doc>(docs);
+// }
+
+// @computed get textWrappedRows() {
+// return Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []);
+// }
+// set textWrappedRows(textWrappedRows: string[]) {
+// this.props.Document.textwrappedSchemaRows = new List<string>(textWrappedRows);
+// }
+
+// @computed get resized(): { id: string; value: number }[] {
+// return this.props.columns.reduce((resized, shf) => {
+// shf.width > -1 && resized.push({ id: shf.heading, value: shf.width });
+// return resized;
+// }, [] as { id: string; value: number }[]);
+// }
+// @computed get sorted(): SortingRule[] {
+// return this.props.columns.reduce((sorted, shf) => {
+// shf.desc !== undefined && sorted.push({ id: shf.heading, desc: shf.desc });
+// return sorted;
+// }, [] as SortingRule[]);
+// }
+
+// @action
+// changeSorting = (col: any) => {
+// this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true);
+// };
+
+// @action
+// changeTitleMode = () => (this._showTitleDropdown = !this._showTitleDropdown);
+
+// @computed get borderWidth() {
+// return Number(COLLECTION_BORDER_WIDTH);
+// }
+// @computed get tableColumns(): Column<Doc>[] {
+// const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1);
+// const columns: Column<Doc>[] = [];
+// const tableIsFocused = this.props.isFocused(this.props.Document, false);
+// const focusedRow = this._focusedCell.row;
+// const focusedCol = this._focusedCell.col;
+// const isEditable = !this.props.headerIsEditing;
+
+// columns.push({
+// expander: true,
+// Header: '',
+// width: 58,
+// Expander: rowInfo => {
+// return rowInfo.original.type !== DocumentType.COL ? null : (
+// <div className="collectionSchemaView-expander" onClick={action(() => this._openCollections[rowInfo.isExpanded ? 'delete' : 'add'](rowInfo.viewIndex))}>
+// <FontAwesomeIcon icon={rowInfo.isExpanded ? 'caret-down' : 'caret-right'} size="lg" />
+// </div>
+// );
+// },
+// });
+// columns.push(
+// ...this.props.columns.map(col => {
+// const icon: IconProp =
+// this.getColumnType(col) === ColumnType.Number
+// ? 'hashtag'
+// : this.getColumnType(col) === ColumnType.String
+// ? 'font'
+// : this.getColumnType(col) === ColumnType.Boolean
+// ? 'check-square'
+// : this.getColumnType(col) === ColumnType.Doc
+// ? 'file'
+// : this.getColumnType(col) === ColumnType.Image
+// ? 'image'
+// : this.getColumnType(col) === ColumnType.List
+// ? 'list-ul'
+// : this.getColumnType(col) === ColumnType.Date
+// ? 'calendar'
+// : 'align-justify';
+
+// const keysDropdown = (
+// <KeysDropdown
+// keyValue={col.heading}
+// possibleKeys={possibleKeys}
+// existingKeys={this.props.columns.map(c => c.heading)}
+// canAddNew={true}
+// addNew={false}
+// onSelect={this.props.changeColumns}
+// setIsEditing={this.props.setHeaderIsEditing}
+// docs={this.props.childDocs}
+// Document={this.props.Document}
+// dataDoc={this.props.dataDoc}
+// fieldKey={this.props.fieldKey}
+// ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+// ContainingCollectionView={this.props.ContainingCollectionView}
+// active={this.props.active}
+// openHeader={this.props.openHeader}
+// icon={icon}
+// col={col}
+// // try commenting this out
+// width={'100%'}
+// />
+// );
+
+// const sortIcon = col.desc === undefined ? 'caret-right' : col.desc === true ? 'caret-down' : 'caret-up';
+// const header = (
+// <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: '2px', display: 'flex', cursor: 'default', height: '100%' }}>
+// {keysDropdown}
+// <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: 'inline', zIndex: 1, background: 'inherit', cursor: 'pointer' }}>
+// <FontAwesomeIcon icon={sortIcon} size="lg" />
+// </div>
+// {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>} */}
+// </div>
+// );
+
+// return {
+// Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.props.columns} reorderColumns={this.props.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />,
+// accessor: (doc: Doc) => (doc ? Field.toString(doc[col.heading] as Field) : 0),
+// id: col.heading,
+// Cell: (rowProps: CellInfo) => {
+// const rowIndex = rowProps.index;
+// const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
+// const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
+
+// const props: CellProps = {
+// row: rowIndex,
+// col: columnIndex,
+// rowProps: rowProps,
+// isFocused: isFocused,
+// changeFocusedCellByIndex: this.changeFocusedCellByIndex,
+// CollectionView: this.props.CollectionView,
+// ContainingCollection: this.props.ContainingCollectionView,
+// Document: this.props.Document,
+// fieldKey: this.props.fieldKey,
+// renderDepth: this.props.renderDepth,
+// addDocTab: this.props.addDocTab,
+// pinToPres: this.props.pinToPres,
+// moveDocument: this.props.moveDocument,
+// setIsEditing: this.setCellIsEditing,
+// isEditable: isEditable,
+// setPreviewDoc: this.props.setPreviewDoc,
+// setComputed: this.setComputed,
+// getField: this.getField,
+// showDoc: this.showDoc,
+// };
+
+// switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) {
+// case ColumnType.Number:
+// return <CollectionSchemaNumberCell {...props} />;
+// case ColumnType.String:
+// return <CollectionSchemaStringCell {...props} />;
+// case ColumnType.Boolean:
+// return <CollectionSchemaCheckboxCell {...props} />;
+// case ColumnType.Doc:
+// return <CollectionSchemaDocCell {...props} />;
+// case ColumnType.Image:
+// return <CollectionSchemaImageCell {...props} />;
+// case ColumnType.List:
+// return <CollectionSchemaListCell {...props} />;
+// case ColumnType.Date:
+// return <CollectionSchemaDateCell {...props} />;
+// default:
+// return <CollectionSchemaCell {...props} />;
+// }
+// },
+// minWidth: 200,
+// };
+// })
+// );
+// columns.push({
+// Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />,
+// accessor: (doc: Doc) => 0,
+// id: 'add',
+// Cell: (rowProps: CellInfo) => {
+// const rowIndex = rowProps.index;
+// const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
+// const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
+// return (
+// <CollectionSchemaButtons
+// {...{
+// row: rowProps.index,
+// col: columnIndex,
+// rowProps: rowProps,
+// isFocused: isFocused,
+// changeFocusedCellByIndex: this.changeFocusedCellByIndex,
+// CollectionView: this.props.CollectionView,
+// ContainingCollection: this.props.ContainingCollectionView,
+// Document: this.props.Document,
+// fieldKey: this.props.fieldKey,
+// renderDepth: this.props.renderDepth,
+// addDocTab: this.props.addDocTab,
+// pinToPres: this.props.pinToPres,
+// moveDocument: this.props.moveDocument,
+// setIsEditing: this.setCellIsEditing,
+// isEditable: isEditable,
+// setPreviewDoc: this.props.setPreviewDoc,
+// setComputed: this.setComputed,
+// getField: this.getField,
+// showDoc: this.showDoc,
+// }}
+// />
+// );
+// },
+// width: 28,
+// resizable: false,
+// });
+// return columns;
+// }
+
+// constructor(props: SchemaTableProps) {
+// super(props);
+// if (this.props.Document._schemaHeaders === undefined) {
+// this.props.Document._schemaHeaders = new List<SchemaHeaderField>([
+// new SchemaHeaderField('title', '#f1efeb'),
+// new SchemaHeaderField('author', '#f1efeb'),
+// new SchemaHeaderField('*lastModified', '#f1efeb', ColumnType.Date),
+// new SchemaHeaderField('text', '#f1efeb', ColumnType.String),
+// new SchemaHeaderField('type', '#f1efeb'),
+// new SchemaHeaderField('context', '#f1efeb', ColumnType.Doc),
+// ]);
+// }
+// }
+
+// componentDidMount() {
+// document.addEventListener('keydown', this.onKeyDown);
+// }
+
+// componentWillUnmount() {
+// document.removeEventListener('keydown', this.onKeyDown);
+// }
+
+// tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
+// const tableDoc = this.props.Document[DataSym];
+// const effectiveAcl = GetEffectiveAcl(tableDoc);
+
+// if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) {
+// doc.context = this.props.Document;
+// tableDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()));
+// return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
+// }
+// return false;
+// };
+
+// private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
+// return !rowInfo
+// ? {}
+// : {
+// ScreenToLocalTransform: this.props.ScreenToLocalTransform,
+// addDoc: this.tableAddDoc,
+// removeDoc: this.props.deleteDocument,
+// rowInfo,
+// rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true),
+// textWrapRow: this.toggleTextWrapRow,
+// rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1,
+// dropAction: StrCast(this.props.Document.childDropAction),
+// addDocTab: this.props.addDocTab,
+// };
+// };
+
+// private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => {
+// if (!rowInfo || column) return {};
+
+// const row = rowInfo.index;
+// //@ts-ignore
+// const col = this.columns.map(c => c.heading).indexOf(column!.id);
+// const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true);
+// // TODO: editing border doesn't work :(
+// return {
+// style: { border: !this.props.headerIsEditing && isFocused ? '2px solid rgb(255, 160, 160)' : '1px solid #f1efeb' },
+// };
+// };
+
+// @action setCellIsEditing = (isEditing: boolean) => (this._cellIsEditing = isEditing);
+
+// @action
+// onKeyDown = (e: KeyboardEvent): void => {
+// if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {
+// // && this.props.isSelected(true)) {
+// const direction = e.key === 'Tab' ? 'tab' : e.which === 39 ? 'right' : e.which === 37 ? 'left' : e.which === 38 ? 'up' : e.which === 40 ? 'down' : '';
+// this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col);
+
+// if (direction) {
+// const pdoc = FieldValue(this.childDocs[this._focusedCell.row]);
+// pdoc && this.props.setPreviewDoc(pdoc);
+// e.stopPropagation();
+// }
+// } else if (e.keyCode === 27) {
+// this.props.setPreviewDoc(undefined);
+// e.stopPropagation(); // stopPropagation for left/right arrows
+// }
+// };
+
+// changeFocusedCellByDirection = (direction: string, curRow: number, curCol: number) => {
+// switch (direction) {
+// case 'tab':
+// return { row: curRow + 1 === this.childDocs.length ? 0 : curRow + 1, col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 };
+// case 'right':
+// return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 };
+// case 'left':
+// return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 };
+// case 'up':
+// return { row: curRow === 0 ? curRow : curRow - 1, col: curCol };
+// case 'down':
+// return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol };
+// }
+// return this._focusedCell;
+// };
+
+// @action
+// changeFocusedCellByIndex = (row: number, col: number): void => {
+// if (this._focusedCell.row !== row || this._focusedCell.col !== col) {
+// this._focusedCell = { row: row, col: col };
+// }
+// this.props.setFocused(this.props.Document);
+// };
+
+// @undoBatch
+// createRow = action(() => {
+// this.props.addDocument?.(Docs.Create.TextDocument('', { title: '', _width: 100, _height: 30 }));
+// this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col };
+// });
+
+// @undoBatch
+// @action
+// createColumn = () => {
+// const newFieldName = (index: number) => `New field${index ? ` (${index})` : ''}`;
+// for (let index = 0; index < 100; index++) {
+// if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) {
+// this.props.columns.push(new SchemaHeaderField(newFieldName(index), '#f1efeb'));
+// break;
+// }
+// }
+// };
+
+// @action
+// getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => {
+// if (doc && field && column.type === ColumnType.Any) {
+// const val = doc[CollectionSchemaCell.resolvedFieldKey(field, doc)];
+// if (val instanceof ImageField) return ColumnType.Image;
+// if (val instanceof Doc) return ColumnType.Doc;
+// if (val instanceof DateField) return ColumnType.Date;
+// if (val instanceof List) return ColumnType.List;
+// }
+// if (column.type && column.type !== 0) {
+// return column.type;
+// }
+// if (columnTypes.get(column.heading)) {
+// return (column.type = columnTypes.get(column.heading)!);
+// }
+// return (column.type = ColumnType.Any);
+// };
+
+// @undoBatch
+// @action
+// toggleTextwrap = async () => {
+// const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []);
+// if (textwrappedRows.length) {
+// this.props.Document.textwrappedSchemaRows = new List<string>([]);
+// } else {
+// const docs = DocListCast(this.props.Document[this.props.fieldKey]);
+// const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
+// this.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+// }
+// };
+
+// @action
+// toggleTextWrapRow = (doc: Doc): void => {
+// const textWrapped = this.textWrappedRows;
+// const index = textWrapped.findIndex(id => doc[Id] === id);
+
+// index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]);
+
+// this.textWrappedRows = textWrapped;
+// };
+
+// @computed
+// get reactTable() {
+// const children = this.childDocs;
+// const hasCollectionChild = children.reduce((found, doc) => found || doc.type === DocumentType.COL, false);
+// const expanded: { [name: string]: any } = {};
+// Array.from(this._openCollections.keys()).map(col => (expanded[col.toString()] = true));
+// const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :((((
+
+// return (
+// <ReactTable
+// style={{ position: 'relative' }}
+// data={children}
+// page={0}
+// pageSize={children.length}
+// showPagination={false}
+// columns={this.tableColumns}
+// getTrProps={this.getTrProps}
+// getTdProps={this.getTdProps}
+// sortable={false}
+// TrComponent={MovableRow}
+// sorted={this.sorted}
+// expanded={expanded}
+// resized={this.resized}
+// onResizedChange={this.props.onResizedChange}
+// // if it has a child, render another table with the children
+// SubComponent={
+// !hasCollectionChild
+// ? undefined
+// : row =>
+// row.original.type !== DocumentType.COL ? null : (
+// <div style={{ paddingLeft: 57 + 'px' }} className="reactTable-sub">
+// <SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} />
+// </div>
+// )
+// }
+// />
+// );
+// }
+
+// onContextMenu = (e: React.MouseEvent): void => {
+// ContextMenu.Instance.addItem({ description: 'Toggle text wrapping', event: this.toggleTextwrap, icon: 'table' });
+// };
+
+// getField = (row: number, col?: number) => {
+// const docs = this.childDocs;
+
+// row = row % docs.length;
+// while (row < 0) row += docs.length;
+// const columns = this.props.columns;
+// const doc = docs[row];
+// if (col === undefined) {
+// return doc;
+// }
+// if (col >= 0 && col < columns.length) {
+// const column = this.props.columns[col].heading;
+// return doc[column];
+// }
+// return undefined;
+// };
+
+// createTransformer = (row: number, col: number): Transformer => {
+// const self = this;
+// const captures: { [name: string]: Field } = {};
+
+// const transformer: ts.TransformerFactory<ts.SourceFile> = context => {
+// return root => {
+// function visit(node: ts.Node) {
+// node = ts.visitEachChild(node, visit, context);
+// if (ts.isIdentifier(node)) {
+// const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+// const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+// if (isntPropAccess && isntPropAssign) {
+// if (node.text === '$r') {
+// return ts.createNumericLiteral(row.toString());
+// } else if (node.text === '$c') {
+// return ts.createNumericLiteral(col.toString());
+// } else if (node.text === '$') {
+// if (ts.isCallExpression(node.parent)) {
+// // captures.doc = self.props.Document;
+// // captures.key = self.props.fieldKey;
+// }
+// }
+// }
+// }
+
+// return node;
+// }
+// return ts.visitNode(root, visit);
+// };
+// };
+
+// // const getVars = () => {
+// // return { capturedVariables: captures };
+// // };
+
+// return { transformer /*getVars*/ };
+// };
+
+// setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => {
+// script = `const $ = (row:number, col?:number) => {
+// const rval = (doc as any)[key][row + ${row}];
+// return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading];
+// }
+// return ${script}`;
+// const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) });
+// if (compiled.compiled) {
+// doc[field] = new ComputedField(compiled);
+// return true;
+// }
+// return false;
+// };
+
+// @action
+// showDoc = (doc: Doc | undefined, dataDoc?: Doc, screenX?: number, screenY?: number) => {
+// this._showDoc = doc;
+// if (dataDoc && screenX && screenY) {
+// this._showDocPos = this.props.ScreenToLocalTransform().transformPoint(screenX, screenY);
+// }
+// };
+
+// onOpenClick = () => {
+// this._showDoc && this.props.addDocTab(this._showDoc, OpenWhere.addRight);
+// };
+
+// getPreviewTransform = (): Transform => {
+// return this.props.ScreenToLocalTransform().translate(-this.borderWidth - 4 - this.tableWidth, -this.borderWidth);
+// };
+
+// render() {
+// const preview = '';
+// return (
+// <div
+// className="collectionSchemaView-table"
+// onPointerDown={this.props.onPointerDown}
+// onClick={this.props.onClick}
+// onWheel={e => this.props.active(true) && e.stopPropagation()}
+// onDrop={e => this.props.onDrop(e, {})}
+// onContextMenu={this.onContextMenu}>
+// {this.reactTable}
+// {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : (
+// <div className="collectionSchemaView-addRow" onClick={this.createRow}>
+// + new
+// </div>
+// )}
+// {!this._showDoc ? null : (
+// <div
+// className="collectionSchemaView-documentPreview"
+// ref="overlay"
+// style={{
+// position: 'absolute',
+// width: 150,
+// height: 150,
+// background: 'dimgray',
+// display: 'block',
+// top: 0,
+// left: 0,
+// transform: `translate(${this._showDocPos[0]}px, ${this._showDocPos[1] - 180}px)`,
+// }}>
+// <DocumentView
+// Document={this._showDoc}
+// DataDoc={this._showDataDoc}
+// styleProvider={DefaultStyleProvider}
+// docViewPath={returnEmptyDoclist}
+// focus={DocUtils.DefaultFocus}
+// renderDepth={this.props.renderDepth}
+// rootSelected={returnFalse}
+// isContentActive={returnTrue}
+// isDocumentActive={returnFalse}
+// PanelWidth={() => 150}
+// PanelHeight={() => 150}
+// ScreenToLocalTransform={this.getPreviewTransform}
+// docFilters={returnEmptyFilter}
+// docRangeFilters={returnEmptyFilter}
+// searchFilterDocs={returnEmptyDoclist}
+// ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+// ContainingCollectionView={this.props.CollectionView}
+// moveDocument={this.props.moveDocument}
+// whenChildContentsActiveChanged={emptyFunction}
+// addDocTab={this.props.addDocTab}
+// pinToPres={this.props.pinToPres}
+// bringToFront={returnFalse}></DocumentView>
+// </div>
+// )}
+// </div>
+// );
+// }
+// }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 890ecc1b2..5e4315833 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -19,6 +19,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import './AudioBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
+import { SelectionManager } from '../../util/SelectionManager';
import { PinProps, PresBox } from './trails';
/**
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 2d1839dd8..105f7e151 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -43,6 +43,7 @@ import { WebBox } from './WebBox';
import React = require('react');
import XRegExp = require('xregexp');
import { LoadingBox } from './LoadingBox';
+import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -266,6 +267,7 @@ export class DocumentContentsView extends React.Component<
HTMLtag,
ComparisonBox,
LoadingBox,
+ SchemaRowBox,
}}
bindings={bindings}
jsx={layoutFrame}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 964231e9d..c4c14c0ab 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -624,7 +624,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
} else if (!this._longPress && this.onClickHandler?.script && !isScriptBox()) {
// bcz: hack? don't execute script if you're clicking on a scripting box itself
- const { clientX, clientY, shiftKey, altKey } = e;
+ const { clientX, clientY, shiftKey, altKey, metaKey } = e;
const func = () =>
this.onClickHandler.script.run(
{
@@ -638,6 +638,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
clientY,
shiftKey,
altKey,
+ metaKey,
},
console.log
).result?.select === true