diff options
Diffstat (limited to 'src/client/views/collections/collectionSchema/CollectionSchemaView.tsx')
-rw-r--r-- | src/client/views/collections/collectionSchema/CollectionSchemaView.tsx | 1241 |
1 files changed, 737 insertions, 504 deletions
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index c4ee1805f..6d5a73e55 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,108 +1,66 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, untracked } from 'mobx'; +import { action, computed, observable, ObservableMap, 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 { computedFn } from 'mobx-utils'; +import { Doc, Field, StrListCast } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; 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 { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; +import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; +import { Docs, DocUtils } from '../../../documents/Documents'; +import { DocumentManager } from '../../../util/DocumentManager'; +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 { EditableView } from '../../EditableView'; +import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView'; +import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; +import { KeyValueBox } from '../../nodes/KeyValueBox'; 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 +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', 'creationDate', 'text']; @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; + private _closestDropIndex: number = 0; + private _previewRef: HTMLDivElement | null = null; + private _makeNewColumn: 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); - } + public static _rowHeight: number = 40; + public static _minColWidth: number = 25; + public static _rowMenuWidth: number = 60; + public static _previewDividerWidth: number = 4; - @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); + @computed get _selectedDocs() { + return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.props.Document)); } + @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>(); + @observable _colEles: HTMLDivElement[] = []; + @observable _displayColumnWidths: number[] | undefined; + @observable _columnMenuIndex: number | undefined; + @observable _menuOptions: string[] = []; + @observable _newFieldWarning: string = ''; + @observable _makeNewField: boolean = false; + @observable _newFieldDefault: any = 0; + @observable _newFieldType: ColumnType = ColumnType.Number; + @observable _menuValue: string = ''; + @observable _filterColumnIndex: number | undefined; + @observable _filterValue: string = ''; get documentKeys() { const docs = this.childDocs; @@ -115,532 +73,807 @@ export class CollectionSchemaView extends CollectionSubView() { //TODO Types untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false))))); - this.columns.forEach(key => (keys[key.heading] = true)); + // this.columns.forEach(key => (keys[key.heading] = true)); return Array.from(Object.keys(keys)); } - @action setHeaderIsEditing = (isEditing: boolean) => (this._headerIsEditing = isEditing); + @computed get previewWidth() { + return NumCast(this.layoutDoc.schemaPreviewWidth); + } - @undoBatch - setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => { - this._openTypes = false; - if (columnTypes.get(columnField.heading)) return; - - const columns = this.columns; - const index = columns.indexOf(columnField); - if (index > -1) { - columnField.setType(NumCast(type)); - columns[index] = columnField; - this.columns = columns; + @computed get tableWidth() { + return this.props.PanelWidth() - this.previewWidth - (this.previewWidth === 0 ? 0 : CollectionSchemaView._previewDividerWidth); + } + + @computed get columnKeys() { + return Cast(this.layoutDoc.columnKeys, listSpec('string'), defaultColumnKeys); + } + + @computed get storedColumnWidths() { + let widths = Cast( + this.layoutDoc.columnWidths, + listSpec('number'), + this.columnKeys.map(() => (this.tableWidth - CollectionSchemaView._rowMenuWidth) / this.columnKeys.length) + ); + + const totalWidth = widths.reduce((sum, width) => sum + width, 0); + if (totalWidth !== this.tableWidth - CollectionSchemaView._rowMenuWidth) { + widths = widths.map(w => { + const proportion = w / totalWidth; + return proportion * (this.tableWidth - CollectionSchemaView._rowMenuWidth); + }); } - }); - @undoBatch - setColumnColor = (columnField: SchemaHeaderField, color: string): void => { - const columns = this.columns; - const index = columns.indexOf(columnField); - if (index > -1) { - columnField.setColor(color); - columns[index] = columnField; - this.columns = columns; // need to set the columns to trigger rerender + return widths; + } + + @computed get displayColumnWidths() { + return this._displayColumnWidths ?? this.storedColumnWidths; + } + + @computed get sortField() { + return StrCast(this.layoutDoc.sortField); + } + + @computed get sortDesc() { + return BoolCast(this.layoutDoc.sortDesc); + } + + rowIndex(doc: Doc) { + return this.childDocs.indexOf(doc); + } + + componentDidMount() { + this.props.setContentView?.(this); + document.addEventListener('keydown', this.onKeyDown); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.onKeyDown); + } + + @action + onKeyDown = (e: KeyboardEvent) => { + if (this._selectedDocs.length > 0) { + switch (e.key) { + case 'ArrowDown': + { + const lastDoc = this._selectedDocs.lastElement(); + const lastIndex = this.rowIndex(lastDoc); + const curDoc = this.childDocs[lastIndex]; + if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) { + !e.shiftKey && this.clearSelection(); + const newDoc = this.childDocs[lastIndex + 1]; + if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc)); + else this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1); + } + e.stopPropagation(); + } + break; + case 'ArrowUp': + { + const firstDoc = this._selectedDocs.lastElement(); + const firstIndex = this.rowIndex(firstDoc); + const curDoc = this.childDocs[firstIndex]; + if (firstIndex > 0 && firstIndex < this.childDocs.length) { + !e.shiftKey && this.clearSelection(); + const newDoc = this.childDocs[firstIndex - 1]; + if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc)); + else this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1); + } + e.stopPropagation(); + } + break; + } } }; @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; + setSort = (field: string | undefined, desc: boolean = false) => { + this.layoutDoc.sortField = field; + this.layoutDoc.sortDesc = desc; }; - renderTypes = (col: any) => { - if (columnTypes.get(col.heading)) return null; + addRow = (doc: Doc | Doc[]) => { + const result: boolean = this.addDocument(doc); + this.setSort(this.sortField, this.sortDesc); + return result; + }; - const type = col.type; + @undoBatch + @action + changeColumnKey = (index: number, newKey: string, defaultVal?: any) => { + if (!this.documentKeys.includes(newKey)) { + this.addNewKey(newKey, defaultVal); + } - const anyType = ( - <div className={'columnMenu-option' + (type === ColumnType.Any ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Any)}> - <FontAwesomeIcon icon={'align-justify'} size="sm" /> - Any - </div> - ); + let currKeys = [...this.columnKeys]; + currKeys[index] = newKey; + this.layoutDoc.columnKeys = new List<string>(currKeys); + }; - const numType = ( - <div className={'columnMenu-option' + (type === ColumnType.Number ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Number)}> - <FontAwesomeIcon icon={'hashtag'} size="sm" /> - Number - </div> - ); + @undoBatch + @action + addColumn = (key: string, defaultVal?: any) => { + if (!this.documentKeys.includes(key)) { + this.addNewKey(key, defaultVal); + } - const textType = ( - <div className={'columnMenu-option' + (type === ColumnType.String ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.String)}> - <FontAwesomeIcon icon={'font'} size="sm" /> - Text - </div> - ); + const newColWidth = this.tableWidth / (this.storedColumnWidths.length + 1); + const currWidths = this.storedColumnWidths.slice(); + currWidths.splice(0, 0, newColWidth); + const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0); + this.layoutDoc.columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth))); - const boolType = ( - <div className={'columnMenu-option' + (type === ColumnType.Boolean ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Boolean)}> - <FontAwesomeIcon icon={'check-square'} size="sm" /> - Checkbox - </div> - ); + let currKeys = this.columnKeys.slice(); + currKeys.splice(0, 0, key); + this.layoutDoc.columnKeys = new List<string>(currKeys); + }; - const listType = ( - <div className={'columnMenu-option' + (type === ColumnType.List ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.List)}> - <FontAwesomeIcon icon={'list-ul'} size="sm" /> - List - </div> - ); + @action + addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal)); - const docType = ( - <div className={'columnMenu-option' + (type === ColumnType.Doc ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Doc)}> - <FontAwesomeIcon icon={'file'} size="sm" /> - Document - </div> - ); + @undoBatch + @action + removeColumn = (index: number) => { + if (this.columnKeys.length === 1) return; + const currWidths = this.storedColumnWidths.slice(); + currWidths.splice(index, 1); + const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0); + this.layoutDoc.columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth))); + + let currKeys = this.columnKeys.slice(); + currKeys.splice(index, 1); + this.layoutDoc.columnKeys = new List<string>(currKeys); + }; - const imageType = ( - <div className={'columnMenu-option' + (type === ColumnType.Image ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Image)}> - <FontAwesomeIcon icon={'image'} size="sm" /> - Image - </div> - ); + @action + startResize = (e: any, index: number) => { + this._displayColumnWidths = this.storedColumnWidths; + setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index), this.finishResize, emptyFunction); + }; - const dateType = ( - <div className={'columnMenu-option' + (type === ColumnType.Date ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Date)}> - <FontAwesomeIcon icon={'calendar'} size="sm" /> - Date - </div> - ); + @action + resizeColumn = (e: PointerEvent, index: number) => { + if (this._displayColumnWidths) { + let shrinking; + let growing; - const allColumnTypes = ( - <div className="columnMenu-types"> - {anyType} - {numType} - {textType} - {boolType} - {listType} - {docType} - {imageType} - {dateType} - </div> - ); + let change = e.movementX; + + if (index !== 0) { + growing = change < 0 ? index : index - 1; + shrinking = change < 0 ? index - 1 : index; + } - 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; + if (shrinking === undefined || growing === undefined) return true; - 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> - ); + change = Math.abs(change); + if (this._displayColumnWidths[shrinking] - change < CollectionSchemaView._minColWidth) { + change = this._displayColumnWidths[shrinking] - CollectionSchemaView._minColWidth; + } + + this._displayColumnWidths[shrinking] -= change * this.props.ScreenToLocalTransform().Scale; + this._displayColumnWidths[growing] += change * this.props.ScreenToLocalTransform().Scale; + + return false; + } + return true; }; - renderSorting = (col: any) => { - const sort = col.desc; - return ( - <div className="collectionSchema-headerMenu-group"> - <label>Sort by:</label> - <div className="columnMenu-sort"> - <div className={'columnMenu-option' + (sort === true ? ' active' : '')} onClick={() => this.setColumnSort(col, true)}> - <FontAwesomeIcon icon="sort-amount-down" size="sm" /> - Sort descending - </div> - <div className={'columnMenu-option' + (sort === false ? ' active' : '')} onClick={() => this.setColumnSort(col, false)}> - <FontAwesomeIcon icon="sort-amount-up" size="sm" /> - Sort ascending - </div> - <div className="columnMenu-option" onClick={() => this.setColumnSort(col, undefined)}> - <FontAwesomeIcon icon="times" size="sm" /> - Clear sorting - </div> - </div> - </div> - ); + @action + finishResize = () => { + this.layoutDoc.columnWidths = new List<number>(this._displayColumnWidths); + this._displayColumnWidths = undefined; }; - renderColors = (col: any) => { - const selected = col.color; + @undoBatch + @action + swapColumns = (index1: number, index2: number) => { + const tempKey = this.columnKeys[index1]; + const tempWidth = this.storedColumnWidths[index1]; + + let currKeys = this.columnKeys; + currKeys[index1] = currKeys[index2]; + currKeys[index2] = tempKey; + this.layoutDoc.columnKeys = new List<string>(currKeys); + + let currWidths = this.storedColumnWidths; + currWidths[index1] = currWidths[index2]; + currWidths[index2] = tempWidth; + this.layoutDoc.columnWidths = new List<number>(currWidths); + }; - const pink = PastelSchemaPalette.get('pink2'); - const purple = PastelSchemaPalette.get('purple2'); - const blue = PastelSchemaPalette.get('bluegreen1'); - const yellow = PastelSchemaPalette.get('yellow4'); - const red = PastelSchemaPalette.get('red2'); - const gray = '#f1efeb'; + @action + dragColumn = (e: PointerEvent, index: number) => { + const dragData = new DragManager.ColumnDragData(index); + const dragEles = [this._colEles[index]]; + this.childDocs.forEach(doc => { + dragEles.push(this._rowEles.get(doc).children[1].children[index]); + }); + DragManager.StartColumnDrag(dragEles, dragData, e.x, e.y); - 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> - ); + return true; }; - @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')]); + addRowRef = (doc: Doc, ref: HTMLDivElement) => this._rowEles.set(doc, ref); + + @action + setColRef = (index: number, ref: HTMLDivElement) => { + if (this._colEles.length <= index) { + this._colEles.push(ref); } 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; - } - } - } + this._colEles[index] = ref; } }; @action - openHeader = (col: any, screenx: number, screeny: number) => { - this._col = col; - this._headerOpen = true; - this._pointerX = screenx; - this._pointerY = screeny; + addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => { + const rowDocView = DocumentManager.Instance.getDocumentView(doc); + if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection); }; @action - closeHeader = () => { - this._headerOpen = false; + clearSelection = () => SelectionManager.DeselectAll(); + + selectRows = (rootDoc: Doc, lastSelected: Doc) => { + const index = this.childDocs.indexOf(rootDoc); + const lastSelectedRow = this.childDocs.indexOf(lastSelected); + const startRow = Math.min(lastSelectedRow, index); + const endRow = Math.max(lastSelectedRow, index); + for (let i = startRow; i <= endRow; i++) { + const currDoc = this.childDocs[i]; + if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true, i); + } }; - @undoBatch + sortedSelectedDocs = () => this.childDocs.filter(doc => this._selectedDocs.includes(doc)); + + setDropIndex = (index: number) => (this._closestDropIndex = index); + @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; - } + onInternalDrop = (e: Event, de: DragManager.DropEvent) => { + if (de.complete.columnDragData) { + e.stopPropagation(); + const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0]; + let i = de.complete.columnDragData.colIndex; + this.displayColumnWidths.reduce((total, curr, index) => { + if (total <= mouseX && total + curr >= mouseX) { + i = index; + } + return total + curr; + }, CollectionSchemaView._rowMenuWidth); + this.swapColumns(de.complete.columnDragData.colIndex, i); + e.stopPropagation(); + return true; } - this.closeHeader(); + const draggedDocs = de.complete.docDragData?.draggedDocuments; + if (draggedDocs && super.onInternalDrop(e, de)) { + const pushedDocs = this.childDocs.filter((doc, index) => index >= this._closestDropIndex && !draggedDocs.includes(doc)); + const pushedAndDraggedDocs = [...pushedDocs, ...draggedDocs]; + const removed = this.childDocs.slice().filter(doc => !pushedAndDraggedDocs.includes(doc)); + this.dataDoc[this.fieldKey ?? 'data'] = new List<Doc>([...removed, ...draggedDocs, ...pushedDocs]); + this.setSort(undefined); + SelectionManager.DeselectAll(); + draggedDocs.forEach(doc => { + const draggedView = DocumentManager.Instance.getFirstDocumentView(doc); + if (draggedView) DocumentManager.Instance.RemoveView(draggedView); + DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true)); + }); + e.stopPropagation(); + return true; + } + return false; }; - getPreviewTransform = (): Transform => { - return this.props.ScreenToLocalTransform().translate(-this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, -this.borderWidth); + @action + onExternalDrop = async (e: React.DragEvent): Promise<void> => { + super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc))))); + this.setSort(undefined); }; + onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction); + @action - onHeaderClick = (e: React.PointerEvent) => { - e.stopPropagation(); + onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { + const nativeWidth = this._previewRef!.getBoundingClientRect(); + const minWidth = 40; + const maxWidth = 1000; + const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0]; + const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth; + this.layoutDoc.schemaPreviewWidth = width; + return false; }; @action - onWheel(e: React.WheelEvent) { - const scale = this.props.ScreenToLocalTransform().Scale; - this.props.isContentActive(true) && e.stopPropagation(); - } + addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { + if (!value && !forceEmptyNote) return false; + const newDoc = Docs.Create.TextDocument(value, { title: value, _autoHeight: true }); + FormattedTextBox.SelectOnLoad = newDoc[Id]; + FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' '; + return this.addRow(newDoc) || false; + }; - @computed get 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> - ); - } + menuCallback = (x: number, y: number) => { + ContextMenu.Instance.clearItems(); - private createTarget = (ele: HTMLDivElement) => { - this._previewCont = ele; - super.CreateDropTarget(ele); + DocUtils.addDocumentCreatorMenuItems(doc => this.addRow(doc), this.addRow, x, y, true); + + ContextMenu.Instance.setDefaultItem('::', (name: string): void => { + Doc.GetProto(this.props.Document)[name] = ''; + this.addRow(Docs.Create.TextDocument('', { title: name, _autoHeight: true })); + }); + ContextMenu.Instance.displayMenu(x, y, undefined, true); }; - isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable; + focusDocument = (doc: Doc, options: DocFocusOptions) => { + Doc.BrushDoc(doc); + + const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); + if (found) { + const top = found.getBoundingClientRect().top; + const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); + if (Math.floor(localTop[1]) !== 0) { + let focusSpeed = options.zoomTime ?? 500; + smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + return focusSpeed; + } + } + return undefined; + }; - @action setFocused = (doc: Doc) => (this._focusedTable = doc); + @computed get fieldDefaultInput() { + switch (this._newFieldType) { + case ColumnType.Number: + return <input type="number" name="" id="" value={this._newFieldDefault ?? 0} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />; + case ColumnType.Boolean: + return ( + <> + <input type="checkbox" name="" id="" value={this._newFieldDefault} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.checked))} /> + {this._newFieldDefault ? 'true' : 'false'} + </> + ); + case ColumnType.String: + return <input type="text" name="" id="" value={this._newFieldDefault ?? ''} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />; + } + } - @action setPreviewDoc = (doc: Opt<Doc>) => { - SelectionManager.SelectSchemaViewDoc(doc); - this._previewDoc = doc; + onSearchKeyDown = (e: React.KeyboardEvent) => { + switch (e.key) { + case 'Enter': + this._menuOptions.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuOptions[0]) : action(() => (this._makeNewField = true))(); + break; + case 'Escape': + this.closeColumnMenu(); + break; + } }; - //toggles preview side-panel of schema @action - toggleExpander = () => { - this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0; + setKey = (key: string, defaultVal?: any) => { + if (this._makeNewColumn) { + this.addColumn(key, defaultVal); + } else { + this.changeColumnKey(this._columnMenuIndex!, key, defaultVal); + } + this.closeColumnMenu(); }; - onDividerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander); + setColumnValues = (key: string, value: string) => { + let success: boolean = true; + this.childDocs.forEach(doc => success && KeyValueBox.SetField(doc, key, value)); + return success; }; + @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; + openColumnMenu = (index: number, newCol: boolean) => { + this._makeNewColumn = false; + this._columnMenuIndex = index; + this._menuValue = ''; + this._menuOptions = this.documentKeys; + this._makeNewField = false; + this._newFieldWarning = ''; + this._makeNewField = false; + this._makeNewColumn = newCol; + }; + + @action + closeColumnMenu = () => (this._columnMenuIndex = undefined); + + @action + openFilterMenu = (index: number) => { + this._filterColumnIndex = index; + this._filterValue = this.getFieldFilters(this.columnKeys[this._filterColumnIndex!]).map(filter => filter.split(':')[1])[0]; }; - 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 + closeFilterMenu = (setValue: boolean) => { + if (setValue) { + if (this._filterValue !== '') { + Doc.setDocFilter(this.Document, this.columnKeys[this._filterColumnIndex!], this._filterValue, 'check', false, undefined, false); + } else { + this.removeFieldFilters(this.columnKeys[this._filterColumnIndex!]); + } } + this._filterColumnIndex = undefined; }; - @computed - get previewDocument(): Doc | undefined { - return this._previewDoc; - } + openContextMenu = (x: number, y: number, index: number) => { + this.closeColumnMenu(); + this.closeFilterMenu(false); + ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ + description: 'Change field', + event: () => this.openColumnMenu(index, false), + icon: 'pencil-alt', + }); + ContextMenu.Instance.addItem({ + description: 'Filter field', + event: () => this.openFilterMenu(index), + icon: 'filter', + }); + ContextMenu.Instance.addItem({ + description: 'Delete column', + event: () => this.removeColumn(index), + icon: 'trash', + }); + ContextMenu.Instance.displayMenu(x, y, undefined, false); + }; - @computed - get dividerDragger() { - return this.previewWidth() === 0 ? null : ( - <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown}> - <div className="collectionSchemaView-dividerDragger" /> - </div> - ); - } + @action + updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => { + this._menuValue = e.target.value; + this._menuOptions = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase())); + }; + + getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field); + + removeFieldFilters = (field: string) => { + this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove')); + }; - @computed - get previewPanel() { + onFilterKeyDown = (e: React.KeyboardEvent) => { + //prettier-ignore + switch (e.key) { + case 'Enter' : this.closeFilterMenu(true); break; + case 'Escape': this.closeFilterMenu(false);break; + } + }; + + @action + updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterValue = e.target.value); + + @computed get newFieldMenu() { 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 className="schema-new-key-options"> + <div className="schema-key-type-option"> + <input + type="radio" + name="newFieldType" + 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" + 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" + checked={this._newFieldType == ColumnType.String} + onChange={action(() => { + this._newFieldType = ColumnType.String; + this._newFieldDefault = ''; + })} + /> + string + </div> + <div className="schema-key-default-val">value: {this.fieldDefaultInput}</div> + <div className="schema-key-warning">{this._newFieldWarning}</div> + <div + className="schema-column-menu-button" + onPointerDown={action(e => { + if (this.documentKeys.includes(this._menuValue)) { + this._newFieldWarning = 'Field already exists'; + } else if (this._menuValue.length === 0) { + this._newFieldWarning = 'Field cannot be an empty string'; + } else { + this.setKey(this._menuValue, this._newFieldDefault); + } + })}> + done + </div> </div> ); } - @computed - get schemaTable() { + @computed get keysDropdown() { 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} - /> + <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" + ref={r => + r?.addEventListener( + 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) + (e: WheelEvent) => { + if (!r.scrollTop && e.deltaY <= 0) e.preventDefault(); + e.stopPropagation(); + }, + { passive: false } + ) + }> + {this._menuOptions.map(key => ( + <div + className="schema-key-search-result" + onPointerDown={e => { + e.stopPropagation(); + this.setKey(key); + }}> + {key} + </div> + ))} + </div> + </div> ); } - @computed - public get schemaToolbar() { + @computed get renderColumnMenu() { + const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth); return ( - <div className="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 className="schema-column-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}> + <input className="schema-key-search-input" type="text" value={this._menuValue} onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} /> + {this._makeNewField ? this.newFieldMenu : this.keysDropdown} + <div + className="schema-column-menu-button" + onPointerDown={action(e => { + e.stopPropagation(); + this.closeColumnMenu(); + })}> + cancel </div> </div> ); } - 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(); - } - }; + @computed get renderFilterOptions() { + const keyOptions: string[] = []; + const columnKey = this.columnKeys[this._filterColumnIndex!]; + this.childDocs.forEach(doc => { + const key = StrCast(doc[columnKey]); + if (keyOptions.includes(key) === false && (key.includes(this._filterValue) || !this._filterValue) && key !== '') { + keyOptions.push(key); + } + }); - @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(); + const filters = StrListCast(this.Document._docFilters); + for (let i = 0; i < (filters?.length ?? 0) - 1; i++) { + if (filters[i] === columnKey && keyOptions.includes(filters[i].split(':')[1]) === false) { + keyOptions.push(filters[i + 1]); + } } - 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; + 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="schema-filter-option"> + <input + type="checkbox" + onPointerDown={e => e.stopPropagation()} + onClick={e => e.stopPropagation()} + onChange={action(e => { + if (e.target.checked) { + Doc.setDocFilter(this.props.Document, columnKey, key, 'check'); + } else { + Doc.setDocFilter(this.props.Document, columnKey, key, 'remove'); + } + })} + checked={bool} + /> + <span style={{ paddingLeft: 4 }}>{key}</span> + </div> + ); }); - 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(); + return options; + } - render() { - TraceMobx(); - if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0); - const menuContent = this.renderMenuContent; - const menu = ( - <div className="collectionSchema-header-menu" onWheel={e => this.onZoomMenu(e)} onPointerDown={e => this.onHeaderClick(e)} style={{ transform: `translate(${this.menuCoordinates[0]}px, ${this.menuCoordinates[1]}px)` }}> - <Measure - offset - onResize={action((r: any) => { - const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height); - this._menuWidth = dim[0]; - this._menuHeight = dim[1]; + @computed get renderFilterMenu() { + const x = this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._filterColumnIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth); + return ( + <div className="schema-filter-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}> + <input className="schema-filter-input" type="text" value={this._filterValue} onKeyDown={this.onFilterKeyDown} onChange={this.updateFilterSearch} onPointerDown={e => e.stopPropagation()} /> + {this.renderFilterOptions} + <div + className="schema-column-menu-button" + onPointerDown={action(e => { + e.stopPropagation(); + this.closeFilterMenu(true); })}> - {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>} - </Measure> + done + </div> </div> ); + } + + @computed get sortedDocs() { + const field = StrCast(this.layoutDoc.sortField); + const desc = BoolCast(this.layoutDoc.sortDesc); + const docs = !field + ? this.childDocs + : this.childDocs.sort((docA, docB) => { + const aStr = Field.toString(docA[field] as Field); + const bStr = Field.toString(docB[field] as Field); + var out = 0; + if (aStr < bStr) out = -1; + if (aStr > bStr) out = 1; + if (desc) out *= -1; + return out; + }); + return { docs }; + } + sortedDocsFunc = () => this.sortedDocs; + isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + screenToLocal = () => this.props.ScreenToLocalTransform().translate(-this.tableWidth, 0); + previewWidthFunc = () => this.previewWidth; + render() { return ( - <div - className={'collectionSchemaView' + (this.props.Document._searchDoc ? '-searchContainer' : '-container')} - style={{ - overflow: this.props.scrollOverflow === true ? 'scroll' : undefined, - backgroundColor: 'white', - pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined, - width: this.props.PanelWidth() || '100%', - height: this.props.PanelHeight() || '100%', - position: 'relative', - }}> + <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}> <div - className="collectionSchemaView-tableContainer" - style={{ width: `calc(100% - ${this.previewWidth()}px)` }} - onContextMenu={this.onSpecificMenu} - onPointerDown={this.onPointerDown} - onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} - onDrop={e => this.onExternalDrop(e, {})} - ref={this.createTarget}> - {this.schemaTable} + className="schema-table" + 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 }); + }}> + <div className="schema-header-row" style={{ height: CollectionSchemaView._rowHeight }}> + <div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth }}> + <div className="schema-header-button" onPointerDown={e => (this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true))}> + <FontAwesomeIcon icon="plus" /> + </div> + </div> + {this.columnKeys.map((key, index) => ( + <SchemaColumnHeader + key={index} + columnIndex={index} + columnKeys={this.columnKeys} + columnWidths={this.displayColumnWidths} + sortField={this.sortField} + sortDesc={this.sortDesc} + setSort={this.setSort} + removeColumn={this.removeColumn} + resizeColumn={this.startResize} + openContextMenu={this.openContextMenu} + dragColumn={this.dragColumn} + setColRef={this.setColRef} + /> + ))} + </div> + {this._columnMenuIndex !== undefined && this.renderColumnMenu} + {this._filterColumnIndex !== undefined && this.renderFilterMenu} + <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} /> + + <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} /> </div> - {this.dividerDragger} - {!this.previewWidth() ? null : this.previewPanel} - {this._headerOpen && this.props.isContentActive() ? menu : null} + {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>} + {this.previewWidth > 0 && ( + <div style={{ width: `${this.previewWidth}px` }} ref={ref => (this._previewRef = ref)}> + {Array.from(this._selectedDocs).lastElement() && ( + <DocumentView + Document={Array.from(this._selectedDocs).lastElement()} + DataDoc={undefined} + fitContentsToBox={returnTrue} + dontCenter={'y'} + onClickScriptDisable="always" + focus={emptyFunction} + renderDepth={this.props.renderDepth + 1} + rootSelected={this.rootSelected} + PanelWidth={this.previewWidthFunc} + PanelHeight={this.props.PanelHeight} + isContentActive={returnTrue} + isDocumentActive={returnFalse} + ScreenToLocalTransform={this.screenToLocal} + docFilters={this.childDocFilters} + docRangeFilters={this.childDocRangeFilters} + searchFilterDocs={this.searchFilterDocs} + styleProvider={DefaultStyleProvider} + docViewPath={returnEmptyDoclist} + moveDocument={this.props.moveDocument} + addDocument={this.addRow} + removeDocument={this.props.removeDocument} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} + bringToFront={returnFalse} + /> + )} + </div> + )} + </div> + ); + } +} + +interface CollectionSchemaViewDocsProps { + schema: CollectionSchemaView; + childDocs: () => { docs: Doc[] }; +} + +@observer +class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsProps> { + tableWidthFunc = () => this.props.schema.tableWidth; + rowHeightFunc = () => CollectionSchemaView._rowHeight; + childScreenToLocal = computedFn((index: number) => () => this.props.schema.props.ScreenToLocalTransform().translate(0, -CollectionSchemaView._rowHeight - index * this.rowHeightFunc())); + render() { + return ( + <div className="schema-table-content"> + {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={{ maxHeight: CollectionSchemaView._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} + renderDepth={this.props.schema.props.renderDepth + 1} + PanelWidth={this.tableWidthFunc} + PanelHeight={this.rowHeightFunc} + styleProvider={DefaultStyleProvider} + waitForDoubleClickToClick={returnNever} + defaultDoubleClick={returnDefault} + enableDragWhenActive={true} + onClickScriptDisable="always" + focus={this.props.schema.focusDocument} + docFilters={this.props.schema.childDocFilters} + docRangeFilters={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={active => this.props.schema.props.whenChildContentsActiveChanged(active)} + hideDecorations={true} + hideTitle={true} + hideDocumentButtonBar={true} + hideLinkAnchors={true} + fitWidth={returnTrue} + scriptContext={this} + /> + </div> + ); + })} </div> ); } |