aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormehekj <mehek.jethani@gmail.com>2022-11-07 12:57:44 -0500
committermehekj <mehek.jethani@gmail.com>2022-11-07 12:57:44 -0500
commit213a92ba3aa39d144754029fde32b9d69b0f51cf (patch)
treeebf2cd7c8626b676f018798304b4bf83dc20df1c /src
parent5425b61d62beef22d068e259ae3e2003f08e0c05 (diff)
basic key selection menu created
Diffstat (limited to 'src')
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss8
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx150
-rw-r--r--src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx105
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx12
4 files changed, 223 insertions, 52 deletions
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 4d7e8c39f..0631cd21d 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -44,6 +44,13 @@
.schema-column-resizer.left {
align-self: flex-start;
}
+
+ .schema-column-menu {
+ background: $light-gray;
+ width: inherit;
+ position: absolute;
+ top: 35px;
+ }
}
}
@@ -72,6 +79,7 @@
.schema-row {
justify-content: flex-end;
+ background: white;
.row-menu {
display: flex;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 84a69d4b9..7516b95b8 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,7 +1,7 @@
import React = require('react');
-import { action, computed, observable, ObservableMap, ObservableSet } from 'mobx';
+import { action, computed, observable, ObservableMap, ObservableSet, untracked } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, DocListCast } 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';
@@ -41,10 +41,26 @@ export class CollectionSchemaView extends CollectionSubView() {
private _minColWidth: number = 120;
@observable _rowMenuWidth: number = 100;
- @observable _selectedDocs: ObservableMap = new ObservableMap<SchemaRowBox, HTMLDivElement>();
+ @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;
+ 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));
+ }
+
@computed get columnKeys() {
return Cast(this.layoutDoc.columnKeys, listSpec('string'), defaultColumnKeys);
}
@@ -64,6 +80,10 @@ export class CollectionSchemaView extends CollectionSubView() {
@undoBatch
@action
changeColumnKey = (index: number, newKey: string) => {
+ if (!this.documentKeys.includes(newKey)) {
+ this.addNewKey(newKey);
+ }
+
let currKeys = this.columnKeys;
currKeys[index] = newKey;
this.layoutDoc.columnKeys = new List<string>(currKeys);
@@ -72,10 +92,10 @@ export class CollectionSchemaView extends CollectionSubView() {
@undoBatch
@action
- addColumn = (index: number) => {
- let currKeys = this.columnKeys;
- currKeys.splice(index, 0, 'title');
- this.layoutDoc.columnKeys = new List<string>(currKeys);
+ addColumn = (index: number, key: string) => {
+ if (!this.documentKeys.includes(key)) {
+ this.addNewKey(key);
+ }
const newColWidth = this._minColWidth;
let currWidths = this.storedColumnWidths;
@@ -83,17 +103,22 @@ export class CollectionSchemaView extends CollectionSubView() {
const proportion = w / (this.props.PanelWidth() - this._rowMenuWidth);
return proportion * (this.props.PanelWidth() - this._rowMenuWidth - newColWidth);
});
- currWidths.splice(index, 0, newColWidth);
+ currWidths.splice(index + 1, 0, newColWidth);
this.layoutDoc.columnWidths = new List<number>(currWidths);
+
+ let currKeys = this.columnKeys;
+ currKeys.splice(index + 1, 0, key);
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+ };
+
+ @action
+ addNewKey = (key: string) => {
+ this.childDocs.forEach(doc => (doc[key] = key + ' default val'));
};
@undoBatch
@action
removeColumn = (index: number) => {
- let currKeys = this.columnKeys;
- currKeys.splice(index, 1);
- this.layoutDoc.columnKeys = new List<string>(currKeys);
-
let currWidths = this.storedColumnWidths;
const removedColWidth = currWidths[index];
currWidths = currWidths.map(w => {
@@ -102,6 +127,10 @@ export class CollectionSchemaView extends CollectionSubView() {
});
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);
};
@action
@@ -143,11 +172,55 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
finishResize = () => {
- console.log('finished');
this.layoutDoc.columnWidths = new List<number>(this._displayColumnWidths);
this._displayColumnWidths = undefined;
};
+ @undoBatch
+ @action
+ swapColumns = (index1: number, index2: number) => {
+ console.log(index1, index2);
+ 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);
+ };
+
+ @action
+ dragColumn = (e: any, index: number) => {
+ console.log(index);
+ e.stopPropagation();
+ e.preventDefault();
+ const rect = e.target.getBoundingClientRect();
+ if (e.clientX < rect.x) {
+ console.log('left', e.clientX, rect.x);
+ if (index < 1) return true;
+ this.swapColumns(index - 1, index);
+ return true;
+ }
+ if (e.clientX > rect.x + rect.width) {
+ console.log('right', e.clientX, rect.x + rect.width);
+ if (index === this.columnKeys.length) return true;
+ console.log(index);
+ this.swapColumns(index, index + 1);
+ return true;
+ }
+ return false;
+ };
+
+ @action
+ addRowRef = (doc: Doc, ref: HTMLDivElement) => {
+ this._rowEles.set(doc, ref);
+ };
+
@action
selectRow = (e: React.PointerEvent, doc: Doc, ref: HTMLDivElement, index: number) => {
const ctrl = e.ctrlKey || e.metaKey;
@@ -157,19 +230,19 @@ export class CollectionSchemaView extends CollectionSubView() {
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.set(currDoc, ref);
+ if (!this._selectedDocs.has(currDoc)) this._selectedDocs.add(currDoc);
}
this._lastSelectedRow = endRow;
} else if (ctrl) {
if (!this._selectedDocs.has(doc)) {
- this._selectedDocs.set(doc, ref);
+ this._selectedDocs.add(doc);
this._lastSelectedRow = index;
} else {
this._selectedDocs.delete(doc);
}
} else {
this._selectedDocs.clear();
- this._selectedDocs.set(doc, ref);
+ this._selectedDocs.add(doc);
this._lastSelectedRow = index;
}
@@ -205,7 +278,6 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
- console.log('hello');
super.onExternalDrop(
e,
{},
@@ -222,7 +294,7 @@ export class CollectionSchemaView extends CollectionSubView() {
startDrag = (e: React.PointerEvent, doc: Doc, ref: HTMLDivElement, index: number) => {
if (!this._selectedDocs.has(doc)) {
this._selectedDocs.clear();
- this._selectedDocs.set(doc, ref);
+ this._selectedDocs.add(doc);
this._lastSelectedRow = index;
SelectionManager.SelectSchemaViewDoc(doc);
}
@@ -230,14 +302,7 @@ export class CollectionSchemaView extends CollectionSubView() {
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());
- // const dragDiv = document.createElement('div');
- // dragDiv.className = 'presItem-multiDrag';
- // dragDiv.innerText = 'Move ' + this._selectedDocs.size + ' row' + (this._selectedDocs.size > 1 ? 's' : '');
- // dragDiv.style.position = 'absolute';
- // dragDiv.style.top = e.clientY + 'px';
- // dragDiv.style.left = e.clientX - 50 + 'px';
- // dragItem.push(dragDiv);
+ const dragItem: HTMLElement[] = Array.from(this._selectedDocs.values()).map((doc: Doc) => this._rowEles.get(doc));
DragManager.StartDocumentDrag(
dragItem.map(ele => ele),
@@ -334,21 +399,27 @@ export class CollectionSchemaView extends CollectionSubView() {
this._ref = ele;
this.createDashEventsTarget(ele);
}}
- onPointerDown={() => this._selectedDocs.clear()}
+ onPointerDown={action(() => {
+ this._selectedDocs.clear();
+ })}
onDrop={this.onExternalDrop.bind(this)}>
<div className="schema-table">
<div className="schema-header-row">
- {this.columnKeys.map((key, index) => (
- <SchemaColumnHeader
- columnIndex={index}
- columnKeys={this.columnKeys}
- columnWidths={this.displayColumnWidths}
- changeColumnKey={this.changeColumnKey}
- addColumn={this.addColumn}
- removeColumn={this.removeColumn}
- resizeColumn={this.startResize}
- />
- ))}
+ {this.columnKeys.map((key, index) => {
+ return (
+ <SchemaColumnHeader
+ 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) => (
@@ -361,11 +432,12 @@ export class CollectionSchemaView extends CollectionSubView() {
columnKeys={this.columnKeys}
columnWidths={this.displayColumnWidths}
rowMenuWidth={this._rowMenuWidth}
- selectedRows={this._selectedDocs}
+ selectedDocs={this._selectedDocs}
selectRow={this.selectRow}
startDrag={this.startDrag}
dragging={this._isDragging}
dropIndex={this.setDropIndex}
+ addRowRef={this.addRowRef}
/>
))}
</div>
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index bee76bb24..a6140bafd 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -1,39 +1,124 @@
import React = require('react');
-import { computed } from 'mobx';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { EditableView } from '../../EditableView';
+import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
import './CollectionSchemaView.scss';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
export interface SchemaColumnHeaderProps {
columnKeys: string[];
columnWidths: number[];
columnIndex: number;
+ possibleKeys: string[];
changeColumnKey: (index: number, newKey: string) => boolean;
- addColumn: (index: number) => void;
+ addColumn: (index: number, key: string) => 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 _menuValue: string = '';
+ @observable _menuOptions: string[] = [];
+ private _makeNewColumn = false;
+
@computed get fieldKey() {
return this.props.columnKeys[this.props.columnIndex];
}
+ @computed get renderColumnMenu() {
+ return (
+ <div className="schema-column-menu">
+ <input type="text" value={this._menuValue} onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
+ {this._menuOptions.map(key => (
+ <div
+ onPointerDown={e => {
+ e.stopPropagation();
+ this.setKey(key);
+ }}>
+ {key}
+ </div>
+ ))}
+ <div
+ onPointerDown={e => {
+ e.stopPropagation();
+ this.setKey(this._menuValue);
+ }}>
+ + new field
+ </div>
+ </div>
+ );
+ }
+
+ onSearchKeyDown = (e: React.KeyboardEvent) => {
+ switch (e.key) {
+ case 'Enter':
+ this.setKey(this._menuOptions.length > 0 ? this._menuOptions[0] : this._menuValue);
+ break;
+ case 'Escape':
+ this.toggleColumnMenu();
+ break;
+ }
+ };
+
+ @action
+ setKey = (key: string) => {
+ if (this._makeNewColumn) {
+ this.props.addColumn(this.props.columnIndex, key);
+ } else {
+ this.props.changeColumnKey(this.props.columnIndex, key);
+ }
+ 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.fieldKey;
+ this._menuOptions = this.props.possibleKeys;
+ if (newCol) {
+ this._makeNewColumn = true;
+ }
+ }
+ };
+
render() {
return (
- <div className="schema-column-header" style={{ width: this.props.columnWidths[this.props.columnIndex] }}>
+ <div className="schema-column-header" style={{ width: this.props.columnWidths[this.props.columnIndex] }} onPointerDown={this.onPointerDown}>
<div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex, true)}></div>
- <div className="schema-column-title">
- <EditableView SetValue={(newKey: string) => this.props.changeColumnKey(this.props.columnIndex, newKey)} GetValue={() => this.fieldKey} contents={this.fieldKey} />
- </div>
+ <div className="schema-column-title">{this.fieldKey}</div>
<div className="schema-header-menu">
<div
className="schema-header-button"
onPointerDown={e => {
- this.props.addColumn(this.props.columnIndex + 1);
+ this.toggleColumnMenu();
+ }}>
+ <FontAwesomeIcon icon="pencil-alt" />
+ </div>
+ <div
+ className="schema-header-button"
+ onPointerDown={e => {
+ this.toggleColumnMenu(true);
}}>
<FontAwesomeIcon icon="plus" />
</div>
@@ -47,6 +132,8 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
</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
index 66cc3a47a..bfce61952 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -17,11 +17,12 @@ export interface SchemaRowBoxProps extends FieldViewProps {
columnKeys: string[];
columnWidths: number[];
rowMenuWidth: number;
- selectedRows: ObservableMap<Doc, HTMLDivElement>;
+ 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
@@ -32,7 +33,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
private _ref: HTMLDivElement | null = null;
- isSelected = () => this.props.selectedRows.has(this.props.Document);
+ isSelected = () => this.props.selectedDocs.has(this.props.Document);
bounds = () => this._ref?.getBoundingClientRect();
@action
@@ -58,7 +59,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
if (!this.props.dragging) return;
let dragIsRow: boolean = true;
DragManager.docsBeingDragged.forEach(doc => {
- dragIsRow = this.props.selectedRows.has(doc);
+ dragIsRow = this.props.selectedDocs.has(doc);
});
if (this._ref && dragIsRow) {
const rect = this._ref.getBoundingClientRect();
@@ -93,7 +94,10 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
onPointerDown={this.onRowPointerDown}
onPointerEnter={this.onPointerEnter}
onPointerLeave={this.onPointerLeave}
- ref={(row: HTMLDivElement | null) => (this._ref = row)}>
+ 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"