import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils'; import { Doc, DocListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { CompileScript } from '../../util/Scripting'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { CollectionStackingView } from './CollectionStackingView'; import './CollectionStackingView.scss'; interface CMVFieldRowProps { rows: () => number; headings: () => object[]; Document: Doc; chromeHidden?: boolean; heading: string; headingObject: SchemaHeaderField | undefined; docList: Doc[]; parent: CollectionStackingView; pivotField: string; type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; setDocHeight: (key: string, thisHeight: number) => void; refList: any[]; showHandle: boolean; } @observer export class CollectionMasonryViewFieldRow extends ObservableReactComponent { constructor(props: any) { super(props); makeObservable(this); } @observable private _background = 'inherit'; @observable private _createEmbeddingSelected: boolean = false; @observable private heading: string = ''; @observable private color: string = '#f1efeb'; @observable private collapsed: boolean = false; @observable private _paletteOn = false; private set _heading(value: string) { runInAction(() => this._props.headingObject && (this._props.headingObject.heading = this.heading = value)); } private set _color(value: string) { runInAction(() => this._props.headingObject && (this._props.headingObject.color = this.color = value)); } private set _collapsed(value: boolean) { runInAction(() => this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value)); } private _dropDisposer?: DragManager.DragDropDisposer; private _headerRef: React.RefObject = React.createRef(); private _contRef: React.RefObject = React.createRef(); private _ele: any; createRowDropRef = (ele: HTMLDivElement | null) => { this._dropDisposer?.(); if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Document); else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1); this._ele = ele; }; @action componentDidMount() { this.heading = this._props.headingObject?.heading || ''; this.color = this._props.headingObject?.color || '#f1efeb'; this.collapsed = this._props.headingObject?.collapsed || false; this._ele && this.props.refList.push(this._ele); } componentWillUnmount() { this._ele && this.props.refList.splice(this.props.refList.indexOf(this._ele), 1); this._ele = null; } getTrueHeight = () => { if (this.collapsed) { this._props.setDocHeight(this.heading, 20); } else { const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header const transformScale = this._props.screenToLocalTransform().Scale; const trueHeight = rawHeight * transformScale; this._props.setDocHeight(this.heading, trueHeight); } }; @undoBatch rowDrop = action((e: Event, de: DragManager.DropEvent) => { this._createEmbeddingSelected = false; if (de.complete.docDragData) { const key = this._props.pivotField; const castedValue = this.getValue(this.heading); if (this._props.parent.onInternalDrop(e, de)) { key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !this.onLayoutDoc(key))); } return true; } return false; }); getValue = (value: string): any => { const parsed = parseInt(value); if (!isNaN(parsed)) return parsed; if (value.toLowerCase().indexOf('true') > -1) return true; if (value.toLowerCase().indexOf('false') > -1) return false; return value; }; @action headingChanged = (value: string, shiftDown?: boolean) => { this._createEmbeddingSelected = false; const key = this._props.pivotField; const castedValue = this.getValue(value); if (castedValue) { if (this._props.parent.colHeaderData) { if (this._props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) { return false; } } key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true)); this._heading = castedValue.toString(); return true; } return false; }; @action changeColumnColor = (color: string) => { this._createEmbeddingSelected = false; this._color = color; }; pointerEnteredRow = action(() => SnappingManager.IsDragging && (this._background = '#b4b4b4')); @action pointerLeaveRow = () => { this._createEmbeddingSelected = false; this._background = 'inherit'; }; @action addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { if (!value && !forceEmptyNote) return false; this._createEmbeddingSelected = false; const key = this._props.pivotField; const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value }); FormattedTextBox.SetSelectOnLoad(newDoc); FormattedTextBox.SelectOnLoadChar = value; key && ((this.onLayoutDoc(key) ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading)); const docs = this._props.parent.childDocList; return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list) }; deleteRow = undoBatch( action(() => { this._createEmbeddingSelected = false; const key = this._props.pivotField; key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true)); if (this._props.parent.colHeaderData && this._props.headingObject) { const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject); this._props.parent.colHeaderData.splice(index, 1); } }) ); @action collapseSection = (e: any) => { this._createEmbeddingSelected = false; this.toggleVisibility(); e.stopPropagation(); }; headerMove = (e: PointerEvent) => { const embedding = Doc.MakeEmbedding(this._props.Document); const key = this._props.pivotField; let value = this.getValue(this.heading); value = typeof value === 'string' ? `"${value}"` : value; const script = `return doc.${key} === ${value}`; const compiled = CompileScript(script, { params: { doc: Doc.name } }); if (compiled.compiled) { embedding.viewSpecScript = new ScriptField(compiled); DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY); } return true; }; @action headerDown = (e: React.PointerEvent) => { if (e.button === 0 && !e.ctrlKey) { setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this._props.chromeHidden && this.collapseSection(e)); this._createEmbeddingSelected = false; } }; /** * Returns true if a key is on the layout doc of the documents in the collection. */ onLayoutDoc = (key: string): boolean => { DocListCast(this._props.parent.Document.data).forEach(doc => { if (Doc.Get(doc, key, true)) return true; }); return false; }; renderColorPicker = () => { const selected = this.color; const pink = PastelSchemaPalette.get('pink2'); const purple = PastelSchemaPalette.get('purple4'); const blue = PastelSchemaPalette.get('bluegreen1'); const yellow = PastelSchemaPalette.get('yellow4'); const red = PastelSchemaPalette.get('red2'); const green = PastelSchemaPalette.get('bluegreen7'); const cyan = PastelSchemaPalette.get('bluegreen5'); const orange = PastelSchemaPalette.get('orange1'); const gray = '#f1efeb'; return (
this.changeColumnColor(pink!)}>
this.changeColumnColor(purple!)}>
this.changeColumnColor(blue!)}>
this.changeColumnColor(yellow!)}>
this.changeColumnColor(red!)}>
this.changeColumnColor(gray)}>
this.changeColumnColor(green!)}>
this.changeColumnColor(cyan!)}>
this.changeColumnColor(orange!)}>
); }; toggleEmbedding = action(() => (this._createEmbeddingSelected = true)); toggleVisibility = () => (this._collapsed = !this.collapsed); @action textCallback = (char: string) => { return this.addDocument('', false); }; @computed get contentLayout() { const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor((this._props.parent._props.PanelWidth() - 2 * this._props.parent.xMargin) / (this._props.parent.columnWidth + this._props.parent.gridGap)))); const showChrome = !this._props.chromeHidden; const stackPad = showChrome ? `0px ${this._props.parent.xMargin}px` : `${this._props.parent.yMargin}px ${this._props.parent.xMargin}px 0px ${this._props.parent.xMargin}px `; return this.collapsed ? null : (
{this._props.showHandle && this._props.parent._props.isContentActive() ? this._props.parent.columnDragger : null} {showChrome ? (
) : null}
list + ` ${this._props.parent.columnWidth}px`, ''), }}> {this._props.parent.children(this._props.docList)}
); } @computed get headingView() { const noChrome = this._props.chromeHidden; const key = this._props.pivotField; const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`; const editableHeaderView = evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />; return this._props.Document.miniHeaders ? (
{editableHeaderView}
) : !this._props.headingObject ? null : (
{noChrome ? evContents :
{editableHeaderView}
} {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
{this._paletteOn ? this.renderColorPicker() : null}
)} {noChrome ? null : ( )} {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
e.stopPropagation()}>
)}
); } render() { const background = this._background; return (
{this.headingView} {this.contentLayout}
); } }