aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/DocumentTypes.ts1
-rw-r--r--src/client/documents/Documents.ts11
-rw-r--r--src/client/views/LightboxView.tsx1
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx513
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss720
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx901
-rw-r--r--src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx229
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx127
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx23
-rw-r--r--src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx (renamed from src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx)0
-rw-r--r--src/client/views/collections/old_collectionSchema/OldCollectionSchemaHeaders.tsx510
-rw-r--r--src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx (renamed from src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx)0
-rw-r--r--src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx (renamed from src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx)0
-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.tsx (renamed from src/client/views/collections/collectionSchema/SchemaTable.tsx)2
-rw-r--r--src/client/views/nodes/AudioBox.tsx1
17 files changed, 2657 insertions, 1630 deletions
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index d99cd2dac..b910b26b0 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -40,6 +40,7 @@ export enum DocumentType {
SEARCHITEM = 'searchitem',
COMPARISON = 'comparison',
GROUP = 'group',
+ SCHEMAROW = 'schemarow',
LINKDB = 'linkdb', // database of links ??? why do we have this
SCRIPTDB = 'scriptdb', // database of scripts
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a5190e66c..1fd07d61d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -26,6 +26,7 @@ import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { undoBatch, UndoManager } from '../util/UndoManager';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
+import { SchemaRowBox } from '../views/collections/collectionSchema/SchemaRowBox';
import { CollectionView } from '../views/collections/CollectionView';
import { ContextMenu } from '../views/ContextMenu';
import { ContextMenuProps } from '../views/ContextMenuItem';
@@ -650,6 +651,12 @@ export namespace Docs {
},
],
[
+ DocumentType.SCHEMAROW,
+ {
+ layout: { view: SchemaRowBox, dataField: defaultDataKey },
+ },
+ ],
+ [
DocumentType.LOADING,
{
layout: { view: LoadingBox, dataField: '' },
@@ -1139,8 +1146,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/LightboxView.tsx b/src/client/views/LightboxView.tsx
index d79b696a3..140be018b 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -196,6 +196,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
})
.filter(m => m)
.map(m => m!);
+ console.log(LightboxView._tourMap);
}
@action public static Previous() {
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/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 19401c7f0..4fa5d80e2 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -1,599 +1,179 @@
@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;
+.collectionSchemaView {
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;
+ .schema-table {
+ background-color: $white;
+
+ .schema-header-row {
+ justify-content: flex-end;
+
+ .schema-column-header {
+ font-weight: bold;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0;
+
+ .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 {
+ align-self: flex-end;
+ }
+
+ .schema-column-resizer.left {
+ align-self: flex-start;
+ }
+
+ .schema-column-menu {
+ background: $light-gray;
+ width: inherit;
+ position: absolute;
+ top: 35px;
+ min-width: 200px;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+
+ .schema-key-search-input {
+ width: calc(100% - 20px);
+ margin: 10px;
+ }
+
+ .schema-key-search-result {
+ cursor: pointer;
+ padding: 2px 10px;
+ width: 100%;
+
+ &:hover {
+ background-color: $medium-gray;
+ }
+ }
+
+ .schema-key-search,
+ .schema-new-key-options {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .schema-new-key-options {
+ margin: 10px;
+ .schema-key-warning {
+ color: red;
+ font-weight: normal;
+ align-self: center;
+ }
+ }
+
+ .schema-key-list {
+ width: 100%;
+ max-height: 300px;
+ overflow-y: auto;
+ }
+
+ .schema-key-type-option {
+ margin: 2px 0px;
+
+ input {
+ margin-right: 5px;
+ }
+ }
+
+ .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;
+ }
+ }
}
}
- }
- .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;
+ .schema-header-menu {
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;
+ flex-direction: row;
}
}
}
-.collectionSchemaView-cellContainer {
- width: 100%;
- height: unset;
-}
-
-.collectionSchemaView-cellContents {
- width: 100%;
-}
-
-.collectionSchemaView-cellWrapper {
+.schema-header-row,
+.schema-row {
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 {
+ flex-direction: row;
+ 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;
+ flex-direction: row;
justify-content: center;
+ min-width: 50px;
}
-}
-
-#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 {
+ width: 20px;
+ height: 20px;
+ border-radius: 100%;
+ background-color: $dark-gray;
+ color: white;
+ margin: 3px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
svg {
- position: absolute;
- top: 50%;
- left: 10;
- transform: translate(-50%, -50%);
+ width: 12px;
}
}
-
-.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/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index c4ee1805f..f7c68c803 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,108 +1,50 @@
import React = require('react');
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked } from 'mobx';
+import { action, computed, observable, ObservableMap, ObservableSet, trace, 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, 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 { Cast, StrCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { emptyFunction, returnEmptyString, setupMoveUpEvents } 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 { 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';
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', 'text', 'data', 'tags'];
@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 _lastSelectedRow: number | undefined;
+ private _selectedDocSortedArray: Doc[] = [];
+ private _closestDropIndex: number = 0;
+ private _minColWidth: number = 150;
+
+ @observable _rowMenuWidth: number = 100;
+ @observable _selectedDocs: ObservableSet = new ObservableSet<Doc>();
+ @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
+ @observable _isDragging: boolean = false;
+ @observable _displayColumnWidths: number[] | undefined;
get documentKeys() {
const docs = this.childDocs;
@@ -115,532 +57,403 @@ 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 columnKeys() {
+ return Cast(this.layoutDoc.columnKeys, listSpec('string'), defaultColumnKeys);
+ }
- @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 storedColumnWidths() {
+ let widths = Cast(
+ this.layoutDoc.columnWidths,
+ listSpec('number'),
+ this.columnKeys.map(() => (this.props.PanelWidth() - this._rowMenuWidth) / this.columnKeys.length)
+ );
+
+ const totalWidth = widths.reduce((sum, width) => sum + width, 0);
+ if (totalWidth !== this.props.PanelWidth() - this._rowMenuWidth) {
+ widths = widths.map(w => {
+ const proportion = w / totalWidth;
+ return proportion * (this.props.PanelWidth() - this._rowMenuWidth);
+ });
+ // this.layoutDoc.columnWidths = new List<number>(widths);
}
- });
+
+ return widths;
+ }
+
+ @computed get displayColumnWidths() {
+ return this._displayColumnWidths ?? this.storedColumnWidths;
+ }
@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
+ @action
+ changeColumnKey = (index: number, newKey: string, defaultVal?: any) => {
+ if (!this.documentKeys.includes(newKey)) {
+ this.addNewKey(newKey, defaultVal);
}
+
+ let currKeys = [...this.columnKeys];
+ currKeys[index] = newKey;
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
};
@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;
+ addColumn = (index: number, key: string, defaultVal?: any) => {
+ if (!this.documentKeys.includes(key)) {
+ this.addNewKey(key, defaultVal);
+ }
- 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>
- );
- };
+ let currWidths = [...this.storedColumnWidths];
+ const newColWidth = this.props.PanelWidth() / (currWidths.length + 1);
+ currWidths = currWidths.map(w => {
+ const proportion = w / (this.props.PanelWidth() - this._rowMenuWidth);
+ return proportion * (this.props.PanelWidth() - this._rowMenuWidth - newColWidth);
+ });
+ currWidths.splice(index + 1, 0, newColWidth);
+ this.layoutDoc.columnWidths = new List<number>(currWidths);
- 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>
- );
+ let currKeys = [...this.columnKeys];
+ currKeys.splice(index + 1, 0, key);
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
};
- 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>
- );
+ @action
+ addNewKey = (key: string, defaultVal: any) => {
+ this.childDocs.forEach(doc => (doc[key] = defaultVal));
};
@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;
- }
- }
- }
- }
+ 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.props.PanelWidth() - this._rowMenuWidth - removedColWidth);
+ return proportion * (this.props.PanelWidth() - this._rowMenuWidth);
+ });
+ currWidths.splice(index, 1);
+ this.layoutDoc.columnWidths = new List<number>(currWidths);
+
+ let currKeys = [...this.columnKeys];
+ currKeys.splice(index, 1);
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+ console.log([...this.storedColumnWidths]);
+ console.log([...this.columnKeys]);
};
@action
- openHeader = (col: any, screenx: number, screeny: number) => {
- this._col = col;
- this._headerOpen = true;
- this._pointerX = screenx;
- this._pointerY = screeny;
+ startResize = (e: any, index: number, left: boolean) => {
+ this._displayColumnWidths = this.storedColumnWidths;
+ setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index, left), this.finishResize, emptyFunction);
};
@action
- closeHeader = () => {
- this._headerOpen = false;
- };
+ 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;
+ }
- @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;
+ if (shrinking === undefined || growing === undefined) return true;
+
+ change = Math.abs(change);
+ if (this._displayColumnWidths[shrinking] - change < this._minColWidth) {
+ change = this._displayColumnWidths[shrinking] - this._minColWidth;
}
- }
- this.closeHeader();
- };
- getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(-this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, -this.borderWidth);
+ this._displayColumnWidths[shrinking] -= change;
+ this._displayColumnWidths[growing] += change;
+
+ return false;
+ }
+ return true;
};
@action
- onHeaderClick = (e: React.PointerEvent) => {
- e.stopPropagation();
+ finishResize = () => {
+ this.layoutDoc.columnWidths = new List<number>(this._displayColumnWidths);
+ this._displayColumnWidths = undefined;
};
+ @undoBatch
@action
- onWheel(e: React.WheelEvent) {
- const scale = this.props.ScreenToLocalTransform().Scale;
- this.props.isContentActive(true) && e.stopPropagation();
- }
+ 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);
+ };
- @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>
- );
- }
+ // @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;
+ // };
- private createTarget = (ele: HTMLDivElement) => {
- this._previewCont = ele;
- super.CreateDropTarget(ele);
+ @action
+ addRowRef = (doc: Doc, ref: HTMLDivElement) => {
+ this._rowEles.set(doc, ref);
};
- isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable;
-
- @action setFocused = (doc: Doc) => (this._focusedTable = doc);
+ @action
+ selectRow = (e: React.PointerEvent, doc: Doc, ref: HTMLDivElement, index: number) => {
+ const ctrl = e.ctrlKey || e.metaKey;
+ const shift = e.shiftKey;
+ 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: Doc = this.childDocs[i];
+ if (!this._selectedDocs.has(currDoc)) this._selectedDocs.add(currDoc);
+ }
+ this._lastSelectedRow = endRow;
+ } else if (ctrl) {
+ if (!this._selectedDocs.has(doc)) {
+ this._selectedDocs.add(doc);
+ this._lastSelectedRow = index;
+ } else {
+ this._selectedDocs.delete(doc);
+ }
+ } else {
+ this._selectedDocs.clear();
+ this._selectedDocs.add(doc);
+ this._lastSelectedRow = index;
+ }
- @action setPreviewDoc = (doc: Opt<Doc>) => {
- SelectionManager.SelectSchemaViewDoc(doc);
- this._previewDoc = doc;
+ if (this._lastSelectedRow && this._selectedDocs.size > 0) {
+ SelectionManager.SelectSchemaViewDoc(this.childDocs[this._lastSelectedRow]);
+ } else {
+ SelectionManager.SelectSchemaViewDoc(undefined);
+ }
};
- //toggles preview side-panel of schema
@action
- toggleExpander = () => {
- this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0;
+ sortedSelectedDocs = (): Doc[] => {
+ return this.childDocs.filter(doc => this._selectedDocs.has(doc));
};
- 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;
+ setDropIndex = (index: number) => {
+ this._closestDropIndex = index;
};
- 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
+ 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);
+ return true;
}
+ return 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>
+ @action
+ onExternalDrop = async (e: React.DragEvent): Promise<void> => {
+ super.onExternalDrop(
+ e,
+ {},
+ undoBatch(
+ action(docus => {
+ this._isDragging = false;
+ docus.map((doc: Doc) => this.addDocument(doc));
+ })
+ )
);
- }
-
- 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();
+ startDrag = (e: React.PointerEvent, doc: Doc, ref: HTMLDivElement, index: number) => {
+ if (!this._selectedDocs.has(doc)) {
+ this._selectedDocs.clear();
+ this._selectedDocs.add(doc);
+ this._lastSelectedRow = index;
+ SelectionManager.SelectSchemaViewDoc(doc);
}
- 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;
+ 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;
};
@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;
+ addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
+ if (!value && !forceEmptyNote) return false;
+ const newDoc = Docs.Create.TextDocument(value, { title: value });
+ FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
+ return this.props.addDocument?.(newDoc) || false;
+ };
- if (oldIndex === newIndex) return;
+ 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.addDocument(doc);
+ },
+ this.addDocument,
+ x,
+ y,
+ true
+ );
- columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
- this.columns = columns;
+ 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.props.addDocument?.(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.props.addDocument?.(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, _width: 250, _autoHeight: true });
+ if (created) {
+ if (this.props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ }
+ this.props.addDocument?.(created);
+ }
+ });
+ ContextMenu.Instance.displayMenu(x, y, undefined, true);
};
- 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}
+ className="collectionSchemaView"
+ ref={(ele: HTMLDivElement | null) => {
+ this._ref = ele;
+ this.createDashEventsTarget(ele);
+ }}
+ onPointerDown={action(() => {
+ this._selectedDocs.clear();
+ })}
+ onDrop={this.onExternalDrop.bind(this)}>
+ <div className="schema-table">
+ <div className="schema-header-row">
+ <div className="row-menu" style={{ width: this._rowMenuWidth }}></div>
+ {this.columnKeys.map((key, index) => {
+ return (
+ <SchemaColumnHeader
+ key={index}
+ columnIndex={index}
+ columnKeys={this.columnKeys}
+ columnWidths={this.displayColumnWidths}
+ possibleKeys={this.documentKeys}
+ changeColumnKey={this.changeColumnKey}
+ addColumn={this.addColumn}
+ removeColumn={this.removeColumn}
+ resizeColumn={this.startResize}
+ // dragColumn={this.dragColumn}
+ />
+ );
+ })}
+ </div>
+ <div className="schema-table-content">
+ {this.childDocs.map((doc: Doc, index: number) => (
+ <SchemaRowBox
+ {...this.props}
+ key={index}
+ Document={doc}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ rowIndex={index}
+ columnKeys={this.columnKeys}
+ columnWidths={this.displayColumnWidths}
+ rowMenuWidth={this._rowMenuWidth}
+ selectedDocs={this._selectedDocs}
+ selectRow={this.selectRow}
+ startDrag={this.startDrag}
+ dragging={this._isDragging}
+ dropIndex={this.setDropIndex}
+ addRowRef={this.addRowRef}
+ />
+ ))}
+ </div>
</div>
- {this.dividerDragger}
- {!this.previewWidth() ? null : this.previewPanel}
- {this._headerOpen && this.props.isContentActive() ? menu : null}
+ <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
</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..a6a5f66ab
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -0,0 +1,229 @@
+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';
+
+export interface SchemaColumnHeaderProps {
+ columnKeys: string[];
+ columnWidths: number[];
+ columnIndex: number;
+ possibleKeys: string[];
+ changeColumnKey: (index: number, newKey: string, defaultVal?: any) => void;
+ addColumn: (index: number, key: string, defaultVal?: any) => void;
+ removeColumn: (index: number) => void;
+ resizeColumn: (e: any, index: number, left: boolean) => void;
+ // dragColumn: (e: any, index: number) => boolean;
+}
+
+@observer
+export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> {
+ @observable _menuVisible: boolean = false;
+ @observable _makeNewField: boolean = false;
+ @observable _newFieldType: ColumnType = ColumnType.Number;
+ @observable _newFieldDefault: any = 0;
+ @observable _newFieldWarning: string = '';
+ @observable _menuValue: string = '';
+ @observable _menuOptions: string[] = [];
+ private _makeNewColumn = false;
+
+ @computed get fieldKey() {
+ return this.props.columnKeys[this.props.columnIndex];
+ }
+
+ @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 ?? false} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />
+ {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))} />;
+ }
+ }
+
+ @computed get renderColumnMenu() {
+ return (
+ <div className="schema-column-menu">
+ <input className="schema-key-search-input" type="text" value={this._menuValue} onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
+ {this._makeNewField ? (
+ <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.props.possibleKeys.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);
+ }
+ })}>
+ done
+ </div>
+ </div>
+ ) : (
+ <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>
+ )}
+ </div>
+ );
+ }
+
+ onSearchKeyDown = (e: React.KeyboardEvent) => {
+ switch (e.key) {
+ case 'Enter':
+ this.setKey(this._menuOptions.length > 0 && this._menuValue.length > 0 ? this._menuOptions[0] : this._menuValue);
+ break;
+ case 'Escape':
+ this.toggleColumnMenu();
+ break;
+ }
+ };
+
+ @action
+ setKey = (key: string, defaultVal?: any) => {
+ if (this._makeNewColumn) {
+ this.props.addColumn(this.props.columnIndex, key, defaultVal);
+ } else {
+ this.props.changeColumnKey(this.props.columnIndex, key, defaultVal);
+ }
+ this.toggleColumnMenu();
+ };
+
+ @action
+ updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._menuValue = e.target.value;
+ this._menuOptions = this.props.possibleKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
+ };
+
+ // @action
+ // onPointerDown = (e: React.PointerEvent) => {
+ // e.stopPropagation();
+
+ // setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction);
+ // };
+
+ @action
+ toggleColumnMenu = (newCol?: boolean) => {
+ this._makeNewColumn = false;
+ if (this._menuVisible) {
+ this._menuVisible = false;
+ } else {
+ this._menuVisible = true;
+ this._menuValue = '';
+ this._menuOptions = this.props.possibleKeys;
+ this._makeNewField = false;
+ this._newFieldWarning = '';
+ this._makeNewField = false;
+ if (newCol) {
+ this._makeNewColumn = true;
+ }
+ }
+ };
+
+ 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.toggleColumnMenu();
+ }}>
+ <FontAwesomeIcon icon="pencil-alt" />
+ </div>
+ <div
+ className="schema-header-button"
+ onPointerDown={e => {
+ this.toggleColumnMenu(true);
+ }}>
+ <FontAwesomeIcon icon="plus" />
+ </div>
+ <div
+ className="schema-header-button"
+ onPointerDown={e => {
+ this.props.removeColumn(this.props.columnIndex);
+ }}>
+ <FontAwesomeIcon icon="trash" />
+ </div>
+ </div>
+
+ <div className="schema-column-resizer right" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex, false)}></div>
+
+ {this._menuVisible && this.renderColumnMenu}
+ </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..5c1f32565
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -0,0 +1,127 @@
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, ObservableMap, ObservableSet } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../../fields/Doc';
+import { undoBatch } from '../../../util/UndoManager';
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { Colors } from '../../global/globalEnums';
+import { FieldView, FieldViewProps } from '../../nodes/FieldView';
+import './CollectionSchemaView.scss';
+import { SchemaTableCell } from './SchemaTableCell';
+import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { DragManager } from '../../../util/DragManager';
+
+export interface SchemaRowBoxProps extends FieldViewProps {
+ rowIndex: number;
+ columnKeys: string[];
+ columnWidths: number[];
+ rowMenuWidth: number;
+ selectedDocs: ObservableSet<Doc>;
+ selectRow: (e: any, doc: Doc, ref: HTMLDivElement, index: number) => void;
+ startDrag: (e: any, doc: Doc, ref: HTMLDivElement, index: number) => boolean;
+ dragging: boolean;
+ dropIndex: (index: number) => void;
+ addRowRef: (doc: Doc, ref: HTMLDivElement) => void;
+}
+
+@observer
+export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(SchemaRowBox, fieldKey);
+ }
+
+ private _ref: HTMLDivElement | null = null;
+
+ isSelected = () => this.props.selectedDocs.has(this.props.Document);
+ bounds = () => this._ref?.getBoundingClientRect();
+
+ @action
+ onRowPointerDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+
+ setupMoveUpEvents(
+ this,
+ e,
+ e => this.props.startDrag(e, this.props.Document, this._ref!, this.props.rowIndex),
+ emptyFunction,
+ e => this.props.selectRow(e, this.props.Document, this._ref!, this.props.rowIndex)
+ );
+ };
+
+ onPointerEnter = (e: any) => {
+ if (!this.props.dragging) return;
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.addEventListener('pointermove', this.onPointerMove);
+ };
+
+ onPointerMove = (e: any) => {
+ if (!this.props.dragging) return;
+ let dragIsRow: boolean = true;
+ DragManager.docsBeingDragged.forEach(doc => {
+ dragIsRow = this.props.selectedDocs.has(doc);
+ });
+ 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.props.dropIndex(this.props.rowIndex);
+ } else if (y > halfLine) {
+ this._ref.style.borderTop = '0px';
+ this._ref.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ this.props.dropIndex(this.props.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.isSelected() ? { backgroundColor: Colors.LIGHT_BLUE, opacity: this.props.dragging ? 0.5 : 1 } : {}}
+ onPointerDown={this.onRowPointerDown}
+ onPointerEnter={this.onPointerEnter}
+ onPointerLeave={this.onPointerLeave}
+ ref={(row: HTMLDivElement | null) => {
+ row && this.props.addRowRef(this.props.Document, row);
+ this._ref = row;
+ }}>
+ <div className="row-menu" style={{ width: this.props.rowMenuWidth }}>
+ <div
+ className="schema-row-button"
+ onPointerDown={undoBatch(e => {
+ e.stopPropagation();
+ this.props.removeDocument?.(this.props.Document);
+ })}>
+ <FontAwesomeIcon icon="times" />
+ </div>
+ <div
+ className="schema-row-button"
+ onPointerDown={e => {
+ e.stopPropagation();
+ this.props.addDocTab(this.props.Document, 'add:right');
+ }}>
+ <FontAwesomeIcon icon="external-link-alt" />
+ </div>
+ </div>
+ <div className="row-cells">
+ {this.props.columnKeys.map((key, index) => (
+ <SchemaTableCell key={key} Document={this.props.Document} fieldKey={key} columnWidth={this.props.columnWidths[index]} />
+ ))}
+ </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..7e2fa1f6f
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -0,0 +1,23 @@
+import React = require('react');
+import { observer } from 'mobx-react';
+import { Doc, Field } from '../../../../fields/Doc';
+import './CollectionSchemaView.scss';
+
+export interface SchemaTableCellProps {
+ Document: Doc;
+ fieldKey: string;
+ columnWidth: number;
+}
+
+@observer
+export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
+ render() {
+ return (
+ <div className="schema-table-cell" style={{ width: this.props.columnWidth }}>
+ {/* {StrCast(this.props.Document[this.props.fieldKey])} */}
+ {/* Field.toKeyValueString(this.props.Document, this.props.fieldKey) */}
+ {Field.toString(this.props.Document[this.props.fieldKey] as Field)}
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx
index 97a6c5c18..97a6c5c18 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx
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/collectionSchema/CollectionSchemaMovableColumn.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx
index 28d2e6ab1..28d2e6ab1 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
+++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx
index 3cb2df7d3..3cb2df7d3 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
+++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx
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..260db4b88
--- /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/collectionSchema/SchemaTable.tsx b/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx
index 16910cc83..1b4fcf0a4 100644
--- a/src/client/views/collections/collectionSchema/SchemaTable.tsx
+++ b/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx
@@ -40,7 +40,7 @@ import {
CollectionSchemaStringCell,
} from './CollectionSchemaCells';
import { CollectionSchemaAddColumnHeader, KeysDropdown } from './CollectionSchemaHeaders';
-import { MovableColumn } from './CollectionSchemaMovableColumn';
+import { MovableColumn } from './OldCollectionSchemaMovableColumn';
import { MovableRow } from './CollectionSchemaMovableRow';
import './CollectionSchemaView.scss';
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index d95668c89..a5b5ea664 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -18,6 +18,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import './AudioBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
+import { SelectionManager } from '../../util/SelectionManager';
/**
* AudioBox