import React = require('react'); import { action, computed, observable, ObservableSet } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { Cast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { SelectionManager } from '../../../util/SelectionManager'; import { undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { EditableView } from '../../EditableView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; export enum ColumnType { Number, String, Boolean, Doc, Image, } const defaultColumnKeys: string[] = ['title', 'type', 'author', 'text', 'data', 'tags']; @observer export class CollectionSchemaView extends CollectionSubView() { private _ref: HTMLDivElement | null = null; private _lastSelectedRow: number | undefined; private _selectedDocSortedArray: Doc[] = []; private _closestDropIndex: number = 0; private _minColWidth: number = 120; @observable _rowMenuWidth: number = 100; @observable _selectedDocs: ObservableSet = new ObservableSet(); @observable _isDragging: boolean = false; @observable _displayColumnWidths: number[] | undefined; @computed get columnKeys() { return Cast(this.layoutDoc.columnKeys, listSpec('string'), defaultColumnKeys); } @computed get storedColumnWidths() { return Cast( this.layoutDoc.columnWidths, listSpec('number'), this.columnKeys.map(() => (this.props.PanelWidth() - this._rowMenuWidth) / this.columnKeys.length) ); } @computed get displayColumnWidths() { return this._displayColumnWidths ?? this.storedColumnWidths; } @undoBatch @action changeColumnKey = (index: number, newKey: string) => { let currKeys = this.columnKeys; currKeys[index] = newKey; this.layoutDoc.columnKeys = new List(currKeys); return true; }; @undoBatch @action addColumn = (index: number) => { let currKeys = this.columnKeys; currKeys.splice(index, 0, 'title'); this.layoutDoc.columnKeys = new List(currKeys); }; @undoBatch @action removeColumn = (index: number) => { let currKeys = this.columnKeys; currKeys.splice(index, 1); this.layoutDoc.columnKeys = new List(currKeys); }; @action startResize = (e: any, index: number, left: boolean) => { this._displayColumnWidths = this.storedColumnWidths; setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index, left), this.finishResize, emptyFunction); }; @action resizeColumn = (e: PointerEvent, index: number, left: boolean) => { if (this._displayColumnWidths) { let shrinking; let growing; let change = e.movementX; if (left && index !== 0) { growing = change < 0 ? index : index - 1; shrinking = change < 0 ? index - 1 : index; } else if (!left && index !== this.columnKeys.length - 1) { growing = change > 0 ? index : index + 1; shrinking = change > 0 ? index + 1 : index; } if (shrinking === undefined || growing === undefined) return true; change = Math.abs(change); if (this._displayColumnWidths[shrinking] - change < this._minColWidth) { change = this._displayColumnWidths[shrinking] - this._minColWidth; } this._displayColumnWidths[shrinking] -= change; this._displayColumnWidths[growing] += change; return false; } return true; }; // @undoBatch @action finishResize = () => { console.log('finished'); this.layoutDoc.columnWidths = new List(this._displayColumnWidths); this._displayColumnWidths = undefined; }; @action selectRow = (e: React.PointerEvent, doc: Doc, index: number) => { const ctrl = e.ctrlKey || e.metaKey; const shift = e.shiftKey; if (shift && this._lastSelectedRow !== undefined) { const startRow = Math.min(this._lastSelectedRow, index); const endRow = Math.max(this._lastSelectedRow, index); for (let i = startRow; i <= endRow; i++) { const currDoc: Doc = this.childDocs[i]; if (!this._selectedDocs.has(currDoc)) this._selectedDocs.add(currDoc); } this._lastSelectedRow = endRow; } else if (ctrl) { if (!this._selectedDocs.has(doc)) { this._selectedDocs.add(doc); this._lastSelectedRow = index; } else { this._selectedDocs.delete(doc); } } else { this._selectedDocs.clear(); this._selectedDocs.add(doc); this._lastSelectedRow = index; } if (this._lastSelectedRow && this._selectedDocs.size > 0) { SelectionManager.SelectSchemaViewDoc(this.childDocs[this._lastSelectedRow]); } else { SelectionManager.SelectSchemaViewDoc(undefined); } }; @action sortedSelectedDocs = (): Doc[] => { return this.childDocs.filter(doc => this._selectedDocs.has(doc)); }; setDropIndex = (index: number) => { this._closestDropIndex = index; }; @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { if (super.onInternalDrop(e, de)) { this._isDragging = false; const pushedDocs: Doc[] = this.childDocs.filter((doc: Doc, index: number) => index >= this._closestDropIndex && !this._selectedDocs.has(doc)); this.props.removeDocument?.(pushedDocs); this.props.removeDocument?.(this._selectedDocSortedArray); this.addDocument(this._selectedDocSortedArray); this.addDocument(pushedDocs); return true; } return false; }; @action onExternalDrop = async (e: React.DragEvent): Promise => { console.log('hello'); super.onExternalDrop( e, {}, undoBatch( action(docus => { this._isDragging = false; docus.map((doc: Doc) => this.addDocument(doc)); }) ) ); }; @action startDrag = (e: React.PointerEvent, doc: Doc) => { if (!this._selectedDocs.has(doc)) { this._selectedDocs.clear(); this._selectedDocs.add(doc); this._lastSelectedRow = this.childDocs.indexOf(doc); SelectionManager.SelectSchemaViewDoc(doc); } this._isDragging = true; this._selectedDocSortedArray = this.sortedSelectedDocs(); const dragData = new DragManager.DocumentDragData(this._selectedDocSortedArray, 'move'); dragData.moveDocument = this.props.moveDocument; const dragItem: HTMLElement[] = []; const dragDiv = document.createElement('div'); dragDiv.className = 'presItem-multiDrag'; dragDiv.innerText = 'Move ' + this._selectedDocs.size + ' row' + (this._selectedDocs.size > 1 ? 's' : ''); dragDiv.style.position = 'absolute'; dragDiv.style.top = e.clientY + 'px'; dragDiv.style.left = e.clientX - 50 + 'px'; dragItem.push(dragDiv); DragManager.StartDocumentDrag( dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined ); return true; }; @action addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { if (!value && !forceEmptyNote) return false; const newDoc = Docs.Create.TextDocument(value, { title: value }); FormattedTextBox.SelectOnLoad = newDoc[Id]; FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' '; return this.props.addDocument?.(newDoc) || false; }; menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; const docItems: ContextMenuProps[] = []; const dataDoc = this.props.DataDoc || this.props.Document; DocUtils.addDocumentCreatorMenuItems( doc => { FormattedTextBox.SelectOnLoad = StrCast(doc[Id]); return this.addDocument(doc); }, this.addDocument, x, y, true ); Array.from(Object.keys(Doc.GetProto(dataDoc))) .filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string') .map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => { const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document)); if (created) { if (this.props.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this.props.Document); } return this.props.addDocument?.(created); } }, icon: 'compress-arrows-alt', }) ); Array.from(Object.keys(Doc.GetProto(dataDoc))) .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length) .map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => { const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey }); if (created) { const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document; if (container.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, container); return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); } return this.props.addDocument?.(created) || false; } }, icon: 'compress-arrows-alt', }) ); !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' }); !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' }); ContextMenu.Instance.setDefaultItem('::', (name: string): void => { Doc.GetProto(this.props.Document)[name] = ''; const created = Docs.Create.TextDocument('', { title: name, _width: 250, _autoHeight: true }); if (created) { if (this.props.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this.props.Document); } this.props.addDocument?.(created); } }); ContextMenu.Instance.displayMenu(x, y, undefined, true); }; render() { return (
{ this._ref = ele; this.createDashEventsTarget(ele); }} onPointerDown={() => this._selectedDocs.clear()} onDrop={this.onExternalDrop.bind(this)}>
{this.columnKeys.map((key, index) => ( ))}
{this.childDocs.map((doc: Doc, index: number) => ( ))}
); } }