diff options
| author | Sophie Zhang <sophie_zhang@brown.edu> | 2024-01-25 11:35:26 -0500 |
|---|---|---|
| committer | Sophie Zhang <sophie_zhang@brown.edu> | 2024-01-25 11:35:26 -0500 |
| commit | f3dab2a56db5e4a6a3dca58185d94e1ff7d1dc32 (patch) | |
| tree | a7bc895266b53bb620dbd2dd71bad2e83b555446 /src/client/views/collections/collectionSchema/CollectionSchemaView.tsx | |
| parent | b5c5410b4af5d2c68d2107d3f064f6e3ec4ac3f2 (diff) | |
| parent | 136f3d9f349d54e8bdd73b6380ea47c19e5edebf (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.tsx | 225 |
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} + /> + ); + } +} |
