import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import { Doc, DocListCast, Field } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; import { Cast, DocCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { Transform } from '../../../util/Transform'; import { undoable, undoBatch } from '../../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell'; import { FilterPanel } from '../../FilterPanel'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; import { DocData } from '../../../../fields/DocSymbols'; export class DashFieldView { dom: HTMLDivElement; // container for label and value root: any; node: any; tbox: FormattedTextBox; unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview); constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { this.node = node; this.tbox = tbox; this.dom = document.createElement('div'); this.dom.style.width = node.attrs.width; this.dom.style.height = node.attrs.height; this.dom.style.position = 'relative'; this.dom.style.display = 'inline-block'; this.dom.onkeypress = function (e: any) { e.stopPropagation(); }; this.dom.onkeydown = function (e: any) { e.stopPropagation(); }; this.dom.onkeyup = function (e: any) { e.stopPropagation(); }; this.dom.onmousedown = function (e: any) { e.stopPropagation(); }; this.root = ReactDOM.createRoot(this.dom); this.root.render( ); } destroy() { setTimeout(() => { try { this.root.unmount(); } catch {} }); } deselectNode() { this.dom.classList.remove('ProseMirror-selectednode'); } selectNode() { this.dom.classList.add('ProseMirror-selectednode'); } } interface IDashFieldViewInternal { fieldKey: string; docId: string; hideKey: boolean; tbox: FormattedTextBox; width: number; height: number; editable: boolean; expanded: boolean; dataDoc: boolean; node: any; getPos: any; unclickable: () => boolean; } @observer export class DashFieldViewInternal extends ObservableReactComponent { _reactionDisposer: IReactionDisposer | undefined; _textBoxDoc: Doc; _fieldKey: string; _fieldRef = React.createRef(); @observable _dashDoc: Doc | undefined = undefined; @observable _expanded = this._props.expanded; constructor(props: IDashFieldViewInternal) { super(props); makeObservable(this); this._fieldKey = this._props.fieldKey; this._textBoxDoc = this._props.tbox.Document; const setDoc = (doc: Doc) => (this._dashDoc = this._props.dataDoc ? doc[DocData] : doc); if (this._props.docId) { DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc)); } else { setDoc(this._props.tbox.Document); } } componentDidMount() { this._reactionDisposer = reaction( () => (this._dashDoc ? Field.toKeyValueString(this._dashDoc, this._props.fieldKey) : undefined), keyvalue => keyvalue && this._props.tbox.tryUpdateDoc(true) ); } componentWillUnmount() { this._reactionDisposer?.(); } isRowActive = () => this._expanded && this._props.editable; finishEdit = action(() => (this._expanded = false)); selectedCell = (): [Doc, number] => [this._dashDoc!, 0]; // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { return !this._dashDoc ? null : (
(this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this._props.hideKey ? this._props.tbox._props.PanelWidth() - 20 : undefined }}>
); } createPivotForField = (e: React.MouseEvent) => { let container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement(); if (container) { const embedding = Doc.MakeEmbedding(container.Document); embedding._type_collection = CollectionViewType.Time; const colHdrKey = '_' + container.LayoutFieldKey + '_columnHeaders'; let list = Cast(embedding[colHdrKey], listSpec(SchemaHeaderField)); if (!list) { embedding[colHdrKey] = list = new List(); } list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb')); list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb')); embedding._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey; this._props.tbox._props.addDocTab(embedding, OpenWhere.addRight); } }; toggleFieldHide = undoable( action(() => this._dashDoc && (this._dashDoc[this._fieldKey + '_hideKey'] = !this._dashDoc[this._fieldKey + '_hideKey'])), 'hideKey' ); @computed get _hideKey() { return this._dashDoc && this._dashDoc[this._fieldKey + '_hideKey']; } // clicking on the label creates a pivot view collection of all documents // in the same collection. The pivot field is the fieldKey of this label onPointerDownLabelSpan = (e: any) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, e => { DashFieldViewMenu.createFieldView = this.createPivotForField; DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide; DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey); }); }; @undoBatch selectVal = (event: React.ChangeEvent | undefined) => { event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value); }; @computed get values() { if (this._props.expanded) return []; const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []); return vals.strings.map(facet => ({ value: facet, label: facet })); } render() { return (
{this._props.hideKey || this._hideKey ? null : ( {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey} )} {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent} {!this.values.length ? null : ( )}
); } } @observer export class DashFieldViewMenu extends AntimodeMenu { static Instance: DashFieldViewMenu; static createFieldView: (e: React.MouseEvent) => void = emptyFunction; static toggleFieldHide: () => void = emptyFunction; constructor(props: any) { super(props); DashFieldViewMenu.Instance = this; } showFields = (e: React.MouseEvent) => { DashFieldViewMenu.createFieldView(e); DashFieldViewMenu.Instance.fadeOut(true); }; toggleFieldHide = (e: React.MouseEvent) => { DashFieldViewMenu.toggleFieldHide(); DashFieldViewMenu.Instance.fadeOut(true); }; @observable _fieldKey = ''; @action public show = (x: number, y: number, fieldKey: string) => { this._fieldKey = fieldKey; this.jumpTo(x, y, true); const hideMenu = () => { this.fadeOut(true); document.removeEventListener('pointerdown', hideMenu, true); }; document.addEventListener('pointerdown', hideMenu, true); }; render() { return this.getElement( {`Show Pivot Viewer for '${this._fieldKey}'`}}> ); } }