aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2024-01-25 11:35:26 -0500
committerSophie Zhang <sophie_zhang@brown.edu>2024-01-25 11:35:26 -0500
commitf3dab2a56db5e4a6a3dca58185d94e1ff7d1dc32 (patch)
treea7bc895266b53bb620dbd2dd71bad2e83b555446 /src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
parentb5c5410b4af5d2c68d2107d3f064f6e3ec4ac3f2 (diff)
parent136f3d9f349d54e8bdd73b6380ea47c19e5edebf (diff)
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/collections/collectionSchema/CollectionSchemaView.tsx')
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx225
1 files changed, 123 insertions, 102 deletions
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index f73c037f4..581425d77 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,9 +1,8 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, ObservableMap, observe, trace } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableMap, observe } from 'mobx';
import { observer } from 'mobx-react';
-import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, Field, NumListCast, StrListCast } from '../../../../fields/Doc';
+import * as React from 'react';
+import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
@@ -17,9 +16,11 @@ import { undoable, undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { EditableView } from '../../EditableView';
import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView';
import { KeyValueBox } from '../../nodes/KeyValueBox';
-import { DefaultStyleProvider } from '../../StyleProvider';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
import { SchemaColumnHeader } from './SchemaColumnHeader';
@@ -58,6 +59,11 @@ export class CollectionSchemaView extends CollectionSubView() {
private _tableContentRef: HTMLDivElement | null = null;
private _menuTarget = React.createRef<HTMLDivElement>();
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
static _rowHeight: number = 50;
static _rowSingleLineHeight: number = 32;
public static _minColWidth: number = 25;
@@ -69,16 +75,16 @@ export class CollectionSchemaView extends CollectionSubView() {
@observable _menuKeys: string[] = [];
@observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
@observable _colEles: HTMLDivElement[] = [];
- @observable _displayColumnWidths: number[] | undefined;
- @observable _columnMenuIndex: number | undefined;
+ @observable _displayColumnWidths: number[] | undefined = undefined;
+ @observable _columnMenuIndex: number | undefined = undefined;
@observable _newFieldWarning: string = '';
@observable _makeNewField: boolean = false;
@observable _newFieldDefault: any = 0;
@observable _newFieldType: ColumnType = ColumnType.Number;
@observable _menuValue: string = '';
- @observable _filterColumnIndex: number | undefined;
+ @observable _filterColumnIndex: number | undefined = undefined;
@observable _filterSearchValue: string = '';
- @observable _selectedCell: [Doc, number] | undefined;
+ @observable _selectedCell: [Doc, number] | undefined = undefined;
// target HTMLelement portal for showing a popup menu to edit cell values.
public get MenuTarget() {
@@ -86,7 +92,17 @@ export class CollectionSchemaView extends CollectionSubView() {
}
@computed get _selectedDocs() {
- return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.rootDoc));
+ const selected = SelectionManager.Docs.filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document));
+ if (!selected.length) {
+ for (const sel of SelectionManager.Docs) {
+ const contextPath = DocumentManager.GetContextPath(sel, true);
+ if (contextPath.includes(this.Document)) {
+ const parentInd = contextPath.indexOf(this.Document);
+ return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : [];
+ }
+ }
+ }
+ return selected;
}
@computed get documentKeys() {
@@ -98,7 +114,7 @@ export class CollectionSchemaView extends CollectionSubView() {
}
@computed get tableWidth() {
- return this.props.PanelWidth() - this.previewWidth - (this.previewWidth === 0 ? 0 : CollectionSchemaView._previewDividerWidth);
+ return this._props.PanelWidth() - this.previewWidth - (this.previewWidth === 0 ? 0 : CollectionSchemaView._previewDividerWidth);
}
@computed get columnKeys() {
@@ -130,14 +146,13 @@ export class CollectionSchemaView extends CollectionSubView() {
return BoolCast(this.layoutDoc.sortDesc);
}
- @action
componentDidMount() {
- this.props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
document.addEventListener('keydown', this.onKeyDown);
Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
this._keysDisposer = observe(
- this.rootDoc[this.fieldKey ?? 'data'] as List<Doc>,
+ this.dataDoc[this.fieldKey ?? 'data'] as List<Doc>,
change => {
switch (change.type as any) {
case 'splice':
@@ -145,7 +160,7 @@ export class CollectionSchemaView extends CollectionSubView() {
(change as any).added.forEach((doc: Doc) => // for each document added
Doc.GetAllPrototypes(doc.value as Doc).forEach(proto => // for all of its prototypes (and itself)
Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
- !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(''))))));
+ !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(key, key === 'author'))))));
break;
case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
}
@@ -230,19 +245,14 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@undoBatch
- @action
setColumnSort = (field: string | undefined, desc: boolean = false) => {
this.layoutDoc.sortField = field;
this.layoutDoc.sortDesc = desc;
};
- addRow = (doc: Doc | Doc[]) => {
- const result: boolean = this.addDocument(doc);
- return result;
- };
+ addRow = (doc: Doc | Doc[]) => this.addDocument(doc);
@undoBatch
- @action
changeColumnKey = (index: number, newKey: string, defaultVal?: any) => {
if (!this.documentKeys.includes(newKey)) {
this.addNewKey(newKey, defaultVal);
@@ -254,7 +264,6 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@undoBatch
- @action
addColumn = (key: string, defaultVal?: any) => {
if (!this.documentKeys.includes(key)) {
this.addNewKey(key, defaultVal);
@@ -275,7 +284,6 @@ export class CollectionSchemaView extends CollectionSubView() {
addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal));
@undoBatch
- @action
removeColumn = (index: number) => {
if (this.columnKeys.length === 1) return;
const currWidths = this.storedColumnWidths.slice();
@@ -314,8 +322,8 @@ export class CollectionSchemaView extends CollectionSubView() {
change = this._displayColumnWidths[shrinking] - CollectionSchemaView._minColWidth;
}
- this._displayColumnWidths[shrinking] -= change * this.props.ScreenToLocalTransform().Scale;
- this._displayColumnWidths[growing] += change * this.props.ScreenToLocalTransform().Scale;
+ this._displayColumnWidths[shrinking] -= change * this.ScreenToLocalBoxXf().Scale;
+ this._displayColumnWidths[growing] += change * this.ScreenToLocalBoxXf().Scale;
return false;
}
@@ -329,7 +337,6 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@undoBatch
- @action
moveColumn = (fromIndex: number, toIndex: number) => {
let currKeys = this.columnKeys.slice();
currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]);
@@ -373,7 +380,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
highlightDropColumn = (e: PointerEvent) => {
e.stopPropagation();
- const mouseX = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+ const mouseX = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY)[0];
const index = this.findDropIndex(mouseX);
this._colEles.forEach((colRef, i) => {
let leftStyle = '';
@@ -410,8 +417,8 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
clearSelection = () => SelectionManager.DeselectAll();
- selectRows = (rootDoc: Doc, lastSelected: Doc) => {
- const index = this.rowIndex(rootDoc);
+ selectRows = (doc: Doc, lastSelected: Doc) => {
+ const index = this.rowIndex(doc);
const lastSelectedRow = this.rowIndex(lastSelected);
const startRow = Math.min(lastSelectedRow, index);
const endRow = Math.max(lastSelectedRow, index);
@@ -431,10 +438,9 @@ export class CollectionSchemaView extends CollectionSubView() {
setDropIndex = (index: number) => (this._closestDropIndex = index);
- @action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.columnDragData) {
- const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
+ const mouseX = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y)[0];
const index = this.findDropIndex(mouseX);
this.moveColumn(de.complete.columnDragData.colIndex, index ?? de.complete.columnDragData.colIndex);
@@ -467,7 +473,6 @@ export class CollectionSchemaView extends CollectionSubView() {
return false;
};
- @action
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc)))));
};
@@ -479,7 +484,7 @@ export class CollectionSchemaView extends CollectionSubView() {
const nativeWidth = this._previewRef!.getBoundingClientRect();
const minWidth = 40;
const maxWidth = 1000;
- const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
+ const movedWidth = this.ScreenToLocalBoxXf().transformDirection(nativeWidth.right - e.clientX, 0)[0];
const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
this.layoutDoc.schema_previewWidth = width;
return false;
@@ -493,18 +498,18 @@ export class CollectionSchemaView extends CollectionSubView() {
ContextMenu.Instance.displayMenu(x, y, undefined, true);
};
- focusDocument = (doc: Doc, options: DocFocusOptions) => {
+ focusDocument = (doc: Doc, options: FocusViewOptions) => {
Doc.BrushDoc(doc);
this.scrollToDoc(doc, options);
return undefined;
};
- scrollToDoc = (doc: Doc, options: DocFocusOptions) => {
+ scrollToDoc = (doc: Doc, options: FocusViewOptions) => {
const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
const rect = found.getBoundingClientRect();
- const localRect = this.props.ScreenToLocalTransform().transformBounds(rect.left, rect.top, rect.width, rect.height);
- if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this.props.PanelHeight()) {
+ const localRect = this.ScreenToLocalBoxXf().transformBounds(rect.left, rect.top, rect.width, rect.height);
+ if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this._props.PanelHeight()) {
let focusSpeed = options.zoomTime ?? 50;
smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc);
return focusSpeed;
@@ -550,9 +555,8 @@ export class CollectionSchemaView extends CollectionSubView() {
};
setColumnValues = (key: string, value: string) => {
- let success: boolean = true;
- this.childDocs.forEach(doc => success && KeyValueBox.SetField(doc, key, value));
- return success;
+ this.childDocs.forEach(doc => KeyValueBox.SetField(doc, key, value));
+ return true;
};
@action
@@ -577,9 +581,7 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- closeFilterMenu = () => {
- this._filterColumnIndex = undefined;
- };
+ closeFilterMenu = () => (this._filterColumnIndex = undefined);
openContextMenu = (x: number, y: number, index: number) => {
this.closeColumnMenu();
@@ -754,7 +756,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@computed get renderFilterOptions() {
const keyOptions: string[] = [];
const columnKey = this.columnKeys[this._filterColumnIndex!];
- const allDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
+ const allDocs = DocListCast(this.dataDoc[this._props.fieldKey]);
allDocs.forEach(doc => {
const value = StrCast(doc[columnKey]);
if (!keyOptions.includes(value) && value !== '' && (this._filterSearchValue === '' || value.includes(this._filterSearchValue))) {
@@ -778,9 +780,9 @@ export class CollectionSchemaView extends CollectionSubView() {
onClick={e => e.stopPropagation()}
onChange={action(e => {
if (e.target.checked) {
- Doc.setDocFilter(this.props.Document, columnKey, key, 'check');
+ Doc.setDocFilter(this.Document, columnKey, key, 'check');
} else {
- Doc.setDocFilter(this.props.Document, columnKey, key, 'remove');
+ Doc.setDocFilter(this.Document, columnKey, key, 'remove');
}
})}
checked={bool}
@@ -827,8 +829,8 @@ export class CollectionSchemaView extends CollectionSubView() {
}
rowHeightFunc = () => (BoolCast(this.layoutDoc._schema_singleLine) ? CollectionSchemaView._rowSingleLineHeight : CollectionSchemaView._rowHeight);
sortedDocsFunc = () => this.sortedDocs;
- isContentActive = () => this.props.isSelected() || this.props.isContentActive();
- screenToLocal = () => this.props.ScreenToLocalTransform().translate(-this.tableWidth, 0);
+ isContentActive = () => this._props.isSelected() || this._props.isContentActive();
+ screenToLocal = () => this.ScreenToLocalBoxXf().translate(-this.tableWidth, 0);
previewWidthFunc = () => this.previewWidth;
render() {
return (
@@ -836,8 +838,8 @@ export class CollectionSchemaView extends CollectionSubView() {
<div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }}></div>
<div
className="schema-table"
- style={{ width: `calc(100% - ${this.previewWidth + 4}px)` }}
- onWheel={e => this.props.isContentActive() && e.stopPropagation()}
+ style={{ width: `calc(100% - ${this.previewWidth}px)` }}
+ onWheel={e => this._props.isContentActive() && e.stopPropagation()}
ref={r => {
// prevent wheel events from passively propagating up through containers
r?.addEventListener('wheel', (e: WheelEvent) => {}, { passive: false });
@@ -863,7 +865,7 @@ export class CollectionSchemaView extends CollectionSubView() {
openContextMenu={this.openContextMenu}
dragColumn={this.dragColumn}
setColRef={this.setColRef}
- isContentActive={this.props.isContentActive}
+ isContentActive={this._props.isContentActive}
/>
))}
</div>
@@ -889,16 +891,15 @@ export class CollectionSchemaView extends CollectionSubView() {
{Array.from(this._selectedDocs).lastElement() && (
<DocumentView
Document={Array.from(this._selectedDocs).lastElement()}
- DataDoc={undefined}
fitContentsToBox={returnTrue}
dontCenter={'y'}
onClickScriptDisable="always"
focus={emptyFunction}
defaultDoubleClick={returnIgnore}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
rootSelected={this.rootSelected}
PanelWidth={this.previewWidthFunc}
- PanelHeight={this.props.PanelHeight}
+ PanelHeight={this._props.PanelHeight}
isContentActive={returnTrue}
isDocumentActive={returnFalse}
ScreenToLocalTransform={this.screenToLocal}
@@ -906,14 +907,13 @@ export class CollectionSchemaView extends CollectionSubView() {
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
styleProvider={DefaultStyleProvider}
- docViewPath={returnEmptyDoclist}
- moveDocument={this.props.moveDocument}
+ containerViewPath={returnEmptyDoclist}
+ moveDocument={this._props.moveDocument}
addDocument={this.addRow}
- removeDocument={this.props.removeDocument}
+ removeDocument={this._props.removeDocument}
whenChildContentsActiveChanged={returnFalse}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}
+ addDocTab={this._props.addDocTab}
+ pinToPres={this._props.pinToPres}
/>
)}
</div>
@@ -932,51 +932,72 @@ interface CollectionSchemaViewDocsProps {
@observer
class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsProps> {
- tableWidthFunc = () => this.props.schema.tableWidth;
- childScreenToLocal = computedFn((index: number) => () => this.props.schema.props.ScreenToLocalTransform().translate(0, -this.props.rowHeight() - index * this.props.rowHeight()));
render() {
return (
<div className="schema-table-content" ref={this.props.setRef} style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + this.props.rowHeight()}px)` }}>
- {this.props.childDocs().docs.map((doc: Doc, index: number) => {
- const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.schema.props.DataDoc;
- return (
- <div className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}>
- <DocumentView
- key={doc[Id]}
- {...this.props.schema.props}
- LayoutTemplate={this.props.schema.props.childLayoutTemplate}
- LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey)}
- Document={doc}
- DataDoc={dataDoc}
- yPadding={index}
- renderDepth={this.props.schema.props.renderDepth + 1}
- PanelWidth={this.tableWidthFunc}
- PanelHeight={this.props.rowHeight}
- styleProvider={DefaultStyleProvider}
- waitForDoubleClickToClick={returnNever}
- defaultDoubleClick={returnIgnore}
- dragAction="move"
- onClickScriptDisable="always"
- focus={this.props.schema.focusDocument}
- childFilters={this.props.schema.childDocFilters}
- childFiltersByRanges={this.props.schema.childDocRangeFilters}
- searchFilterDocs={this.props.schema.searchFilterDocs}
- rootSelected={this.props.schema.rootSelected}
- ScreenToLocalTransform={this.childScreenToLocal(index)}
- bringToFront={emptyFunction}
- isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive}
- isContentActive={emptyFunction}
- whenChildContentsActiveChanged={this.props.schema.props.whenChildContentsActiveChanged}
- hideDecorations={true}
- hideTitle={true}
- hideDocumentButtonBar={true}
- hideLinkAnchors={true}
- layout_fitWidth={returnTrue}
- />
- </div>
- );
- })}
+ {this.props.childDocs().docs.map((doc: Doc, index: number) => (
+ <div key={doc[Id]} className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}>
+ <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} />
+ </div>
+ ))}
</div>
);
}
}
+
+interface CollectionSchemaViewDocProps {
+ schema: CollectionSchemaView;
+ index: number;
+ doc: Doc;
+ rowHeight: () => number;
+}
+
+@observer
+class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaViewDocProps> {
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ tableWidthFunc = () => this._props.schema.tableWidth;
+ screenToLocalXf = () => this._props.schema.ScreenToLocalBoxXf().translate(0, -this._props.rowHeight() - this._props.index * this._props.rowHeight());
+ noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
+ if (property === StyleProp.Opacity) return 1;
+ return DefaultStyleProvider(doc, props, property);
+ };
+ render() {
+ return (
+ <DocumentView
+ key={this._props.doc[Id]}
+ {...this._props.schema._props}
+ containerViewPath={this._props.schema.childContainerViewPath}
+ LayoutTemplate={this._props.schema._props.childLayoutTemplate}
+ LayoutTemplateString={SchemaRowBox.LayoutString(this._props.schema._props.fieldKey, this._props.index)}
+ Document={this._props.doc}
+ renderDepth={this._props.schema._props.renderDepth + 1}
+ PanelWidth={this.tableWidthFunc}
+ PanelHeight={this._props.rowHeight}
+ styleProvider={this.noOpacityStyleProvider}
+ waitForDoubleClickToClick={returnNever}
+ defaultDoubleClick={returnIgnore}
+ dragAction="move"
+ onClickScriptDisable="always"
+ focus={this._props.schema.focusDocument}
+ childFilters={this._props.schema.childDocFilters}
+ childFiltersByRanges={this._props.schema.childDocRangeFilters}
+ searchFilterDocs={this._props.schema.searchFilterDocs}
+ rootSelected={this._props.schema.rootSelected}
+ ScreenToLocalTransform={this.screenToLocalXf}
+ dragWhenActive={true}
+ isDocumentActive={this._props.schema._props.childDocumentsActive?.() ? this._props.schema._props.isDocumentActive : this._props.schema.isContentActive}
+ isContentActive={emptyFunction}
+ whenChildContentsActiveChanged={this._props.schema._props.whenChildContentsActiveChanged}
+ hideDecorations={true}
+ hideTitle={true}
+ hideDocumentButtonBar={true}
+ hideLinkAnchors={true}
+ layout_fitWidth={returnTrue}
+ />
+ );
+ }
+}