import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconButton, Size } from '@dash/components'; import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, lightOrDark, return18, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, DocListCast, Field, FieldType, Opt, StrListCast, returnEmptyDoclist } from '../../../fields/Doc'; import { DocData, DocLayout } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { DocUtils } from '../../documents/DocUtils'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { SettingsManager } from '../../util/SettingsManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { UndoManager, undoBatch, undoable } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { StyleProp } from '../StyleProp'; import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { FieldViewProps, StyleProviderFuncType } from '../nodes/FieldView'; import { OpenWhere } from '../nodes/OpenWhere'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { CollectionTreeView } from './CollectionTreeView'; import { TreeViewType } from './CollectionTreeViewType'; import { CollectionView } from './CollectionView'; import { TreeSort } from './TreeSort'; import './TreeView.scss'; // eslint-disable-next-line @typescript-eslint/no-require-imports const { TREE_BULLET_WIDTH } = require('../global/globalCssVariables.module.scss'); // prettier-ignore export interface TreeViewProps { treeView: CollectionTreeView; // eslint-disable-next-line no-use-before-define parentTreeView: TreeView | CollectionTreeView | undefined; observeHeight: (ref: HTMLDivElement) => void; unobserveHeight: (ref: HTMLDivElement) => void; prevSibling?: Doc; Document: Doc; dataDoc?: Doc; treeViewParent: Doc; renderDepth: number; dragAction: dropActionType; addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean; panelWidth: () => number; panelHeight: () => number; addDocument: (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => boolean; removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined; moveDocument: DragManager.MoveFunction; isContentActive: (outsideReaction?: boolean) => boolean; whenChildContentsActiveChanged: (isActive: boolean) => void; indentDocument?: (editTitle: boolean) => void; outdentDocument?: (editTitle: boolean) => void; ScreenToLocalTransform: () => Transform; contextMenuItems?: { script: ScriptField; filter: ScriptField; icon: string; label: string }[]; dontRegisterView?: boolean; styleProvider?: StyleProviderFuncType | undefined; treeViewHideHeaderFields: () => boolean; renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle onCheckedClick?: () => ScriptField; onChildClick?: () => ScriptField | undefined; skipFields?: string[]; firstLevel: boolean; // TODO: [AL] add these AddToMap?: (treeViewDoc: Doc, index: number[]) => void; RemFromMap?: (treeViewDoc: Doc, index: number[]) => void; hierarchyIndex?: number[]; } const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace('px', '')); }; /** * Renders a treeView of a collection of documents * * special fields: * treeView_Open : flag denoting whether the documents sub-tree (contents) is visible or hidden * treeView_ExpandedView : name of field whose contents are being displayed as the document's subtree */ @observer export class TreeView extends ObservableReactComponent { // eslint-disable-next-line no-use-before-define static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>; static _openTitleScript: Opt; static _openLevelScript: Opt; private _header: React.RefObject = React.createRef(); private _tref = React.createRef(); @observable _docRef: Opt = undefined; private _disposers: { [name: string]: IReactionDisposer } = {}; private _editTitleScript: (() => ScriptField) | undefined; private _openScript: (() => ScriptField) | undefined; private _treedropDisposer?: DragManager.DragDropDisposer; get treeViewOpenIsTransient() { return this.treeView.Document.treeView_OpenIsTransient || Doc.IsDataProto(this.Document); } @computed get treeViewOpen() { return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState; } set treeViewOpen(c: boolean) { if (this.treeViewOpenIsTransient) this._transientOpenState = c; else { this.Document.treeView_Open = c; this._transientOpenState = false; } } @observable _transientOpenState = false; // override of the treeView_Open field allowing the display state to be independent of the document's state @observable _editTitle: boolean = false; @observable _dref: DocumentView | undefined | null = undefined; get displayName() { return 'TreeView(' + this.Document.title + ')'; } // this makes mobx trace() statements more descriptive get defaultExpandedView() { return this.Document._type_collection === CollectionViewType.Docking ? this.fieldKey : this.treeView.dashboardMode ? this.fieldKey : this.treeView.fileSysMode ? this.Document.isFolder ? this.fieldKey : 'data' // file system folders display their contents (data). used to be they displayed their embeddings but now its a tree structure and not a flat list : this.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.noviceMode ? 'layout' : StrCast(this.treeView.Document.treeView_ExpandedView, 'fields'); } @computed get treeView() { return this._props.treeView; } @computed get Document() { return this._props.Document; } @computed get treeViewExpandedView() { return this.validExpandViewTypes.includes(StrCast(this.Document.treeView_ExpandedView)) ? StrCast(this.Document.treeView_ExpandedView) : this.defaultExpandedView; } @computed get MAX_EMBED_HEIGHT() { return NumCast(this._props.treeViewParent.maxEmbedHeight, 200); } @computed get dataDoc() { return this.Document[DocData]; } @computed get layoutDoc() { return this.Document[DocLayout]; } @computed get fieldKey() { return StrCast(this.Document._treeView_FieldKey, Doc.LayoutDataKey(this.Document)); } @computed get childDocs() { return this.childDocList(this.fieldKey); } @computed get childLinks() { return this.childDocList('links'); } @computed get childEmbeddings() { return this.childDocList('proto_embeddings'); } @computed get childAnnos() { return this.childDocList(this.fieldKey + '_annotations'); } @computed get selected() { return this._docRef?.IsSelected; } ScreenToLocalTransform = () => this._props.ScreenToLocalTransform(); childDocList(field: string) { const layout = Cast(Doc.LayoutField(this.Document), Doc, null); return DocListCast(this._props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.Document[field]))); } moving: boolean = false; @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { if (this.Document !== target && addDoc !== returnFalse) { const canAdd1 = (this._props.parentTreeView as TreeView).dropping || !(ComputedField.DisableCompute(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField); // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse if (canAdd1 && this._props.removeDoc?.(doc) === true) { this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.moving = true); const res = addDoc(doc); this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.moving = false); return res; } } return false; }; @undoBatch remove = (docIn: Doc | Doc[], key: string) => { const docs = toList(docIn); this.treeView._props.select(false); const ind = DocListCast(this.dataDoc[key]).indexOf(docs.lastElement()); const res = docs.reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); res && ind > 0 && DocumentView.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false); return res; }; @action setEditTitle = (docView?: DocumentView) => { this._disposers.selection?.(); if (!docView) { this._editTitle = false; } else if (docView.IsSelected) { this._editTitle = true; this._disposers.selection = reaction( () => docView.IsSelected, isSel => !isSel && this.setEditTitle(undefined) ); } else { docView.select(false); } }; @action openLevel = (docView: DocumentView) => { if (this.Document.isFolder || Doc.IsSystem(this.Document)) { this.treeViewOpen = !this.treeViewOpen; } else { // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding const bestEmbedding = docView.Document.author === ClientUtils.CurrentUserEmail() && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document); this._props.addDocTab(bestEmbedding, OpenWhere.lightboxAlways); } }; @undoBatch recurToggle = (childList: Doc[]) => { if (childList.length > 0) { childList.forEach(child => { child.runProcess = !child.runProcess; TreeView.ToggleChildrenRun.get(child)?.(); }); } }; @undoBatch getRunningChildren = (childList: Doc[]) => { if (childList.length === 0) { return []; } const runningChildren: Doc[] = []; childList.forEach(child => { if (child.runProcess && TreeView.GetRunningChildren.get(child)) { if (child.runProcess) { runningChildren.push(child); } runningChildren.push(...(TreeView.GetRunningChildren.get(child)?.() ?? [])); } }); return runningChildren; }; static GetRunningChildren = new Map Doc[]>(); static ToggleChildrenRun = new Map void>(); constructor(props: TreeViewProps) { super(props); makeObservable(this); if (!TreeView._openLevelScript) { TreeView._openTitleScript = ScriptField.MakeScript('scriptContext.setEditTitle(documentView)', { scriptContext: 'any', documentView: 'any' }); TreeView._openLevelScript = ScriptField.MakeScript(`scriptContext.openLevel(documentView)`, { scriptContext: 'any', documentView: 'any' }); } this._openScript = Doc.IsSystem(this.Document) ? undefined : () => TreeView._openLevelScript!; this._editTitleScript = Doc.IsSystem(this.Document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!; // set for child processing highligting this.dataDoc.hasChildren = this.childDocs.length > 0; // this.dataDoc.children = this.childDocs; TreeView.ToggleChildrenRun.set(this.Document, () => { this.recurToggle(this.childDocs); }); TreeView.GetRunningChildren.set(this.Document, () => this.getRunningChildren(this.childDocs)); } _treeEle: HTMLDivElement | null = null; protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), this.Document, this.preTreeDrop.bind(this))), this.Document); if (this._treeEle) this._props.unobserveHeight(this._treeEle); this._props.observeHeight((this._treeEle = ele)); }; componentWillUnmount() { this._treedropDisposer?.(); this._renderTimer && clearTimeout(this._renderTimer); Object.values(this._disposers).forEach(disposer => disposer?.()); this._treeEle && this._props.unobserveHeight(this._treeEle); document.removeEventListener('pointermove', this.onDragMove, true); document.removeEventListener('pointermove', this.onDragUp, true); // TODO: [AL] add these this._props.hierarchyIndex !== undefined && this._props.RemFromMap?.(this.Document, this._props.hierarchyIndex); } componentDidUpdate(prevProps: Readonly) { super.componentDidUpdate(prevProps); this._disposers.opening = reaction( () => this.treeViewOpen, open => { !open && (this._renderCount = 20); } ); this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex); } componentDidMount() { this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex); } onDragUp = () => { document.removeEventListener('pointerup', this.onDragUp, true); document.removeEventListener('pointermove', this.onDragMove, true); }; onPointerEnter = (e: React.PointerEvent): void => { this._props.isContentActive(true) && Doc.BrushDoc(this.dataDoc); if (e.buttons === 1 && SnappingManager.IsDragging && this._props.isContentActive()) { this._header.current!.className = 'treeView-header'; document.removeEventListener('pointermove', this.onDragMove, true); document.removeEventListener('pointerup', this.onDragUp, true); document.addEventListener('pointermove', this.onDragMove, true); document.addEventListener('pointerup', this.onDragUp, true); } }; onPointerLeave = (): void => { Doc.UnBrushDoc(this.dataDoc); if (this._header.current?.className !== 'treeView-header-editing') { this._header.current!.className = 'treeView-header'; } document.removeEventListener('pointerup', this.onDragUp, true); document.removeEventListener('pointermove', this.onDragMove, true); }; onDragMove = (e: PointerEvent): void => { Doc.UnBrushDoc(this.dataDoc); const pt = [e.clientX, e.clientY]; const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; const inside = pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length); this._header.current!.className = 'treeView-header'; if (inside) this._header.current!.className += ' treeView-header-inside'; else if (before) this._header.current!.className += ' treeView-header-above'; else if (!before) this._header.current!.className += ' treeView-header-below'; e.stopPropagation(); }; public static makeTextBullet() { const bullet = Docs.Create.TextDocument('', { layout: CollectionView.LayoutString('data'), title: '-title-', treeView_ExpandedViewLock: true, treeView_ExpandedView: 'data', _type_collection: CollectionViewType.Tree, layout_hideLinkButton: true, _layout_showSidebar: true, _layout_fitWidth: true, treeView_Type: TreeViewType.outline, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _layout_autoHeight: true, _createDocOnCR: true, _width: 1000, _height: 10, }); bullet.$title = ComputedField.MakeFunction('this.text?.Text'); bullet.$data = new List([]); DocumentView.addViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.()); return bullet; } makeTextCollection = () => { const bullet = TreeView.makeTextBullet(); TreeView._editTitleOnLoad = { id: bullet[Id], parent: this }; return this._props.addDocument(bullet); }; makeFolder = () => { const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }); TreeView._editTitleOnLoad = { id: folder[Id], parent: this._props.parentTreeView }; return this.localAdd(folder); }; preTreeDrop = () => { // fall through and let the CollectionTreeView handle this since treeView items have no special properties of their own }; @undoBatch treeDrop = (e: Event, de: DragManager.DropEvent) => { const pt = [de.x, de.y]; if (!this._header.current) return false; const rect = this._header.current.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || !!(!before && this.treeViewOpen && this.childDocs?.length); if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); const destDoc = this.Document; DocUtils.MakeLink(sourceDoc, destDoc, { link_relationship: 'tree link' }); e.stopPropagation(); return true; } const { docDragData } = de.complete; if (docDragData && pt[0] < rect.left + rect.width) { if (docDragData.draggedDocuments[0] === this.Document) return true; const added = this.dropDocuments( docDragData.droppedDocuments, // before, inside, docDragData.dropAction, docDragData.removeDocument, docDragData.moveDocument, docDragData.treeViewDoc === this.treeView.Document, de.embedKey ); e.stopPropagation(); !added && e.preventDefault(); return added; } return false; }; localAdd = (docs: Doc | Doc[]): boolean => { const innerAdd = (doc: Doc): boolean => { const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); dataIsComputed && DocCast(this.Document.embedContainer) && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)!); return added; }; return toList(docs).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); }; dropping: boolean = false; dropDocuments( droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType | undefined, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean, canEmbed?: boolean ) { const parentAddDoc = (doc: Doc | Doc[]) => this._props.addDocument(doc, undefined, undefined, before); const addDoc = inside ? this.localAdd : parentAddDoc; const canAdd = !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add') || forceAdd; if (canAdd && (dropAction !== dropActionType.inPlace || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) { const move = (!dropAction || (canEmbed && dropAction !== dropActionType.copy) || dropAction === dropActionType.proto || dropAction === dropActionType.move || dropAction === dropActionType.same || dropAction === dropActionType.inPlace) && moveDocument; this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = true); const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === dropActionType.proto ? addDoc(d) : false) : addDoc(d)) || added, false); this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = false); return res; } return false; } refTransform = (ref: HTMLDivElement | undefined | null) => { if (!ref) return this.ScreenToLocalTransform(); const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(ref); return new Transform(-translateX, -translateY, 1).scale(1 / scale); }; docTransform = () => this.refTransform(this._dref?.ContentDiv); getTransform = () => this.refTransform(this._tref.current); embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1) - 3 /* paddingRight for bullet */; embeddedPanelHeight = () => { const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.Document))(this.treeView._props.childLayoutTemplate?.()) || this.layoutDoc; return Math.min( NumCast(layoutDoc._height), this.MAX_EMBED_HEIGHT, (() => { const aspect = Doc.NativeAspect(layoutDoc); if (aspect) return this.embeddedPanelWidth() / (aspect || 1); return layoutDoc._layout_fitWidth ? !Doc.NativeHeight(layoutDoc) ? NumCast(layoutDoc._height) : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this._props.treeViewParent._height))) : (this.embeddedPanelWidth() * NumCast(layoutDoc._height)) / NumCast(layoutDoc._width); })() ); }; @computed get expandedField() { const ids: { [key: string]: string } = {}; const rows: JSX.Element[] = []; const doc = this.Document; doc && Object.keys(doc).forEach(key => { !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key); }); for (const key of Object.keys(ids).slice().sort()) { if (this._props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue; const contents = doc[key]; let contentElement: (JSX.Element | null)[] | JSX.Element = []; const leftOffset = observable({ width: 0 }); const expandedWidth = () => this._props.panelWidth() - leftOffset.width; if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) { const remDoc = (docs: Doc | Doc[]) => this.remove(docs, key); const moveDoc = (docs: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(docs, target, addDoc); const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => { const innerAdd = (iDoc: Doc) => { const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, iDoc, addBefore, before, false, true); dataIsComputed && DocCast(this.Document.embedContainer) && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer)!); return added; }; return toList(docs).reduce((flg, iDoc) => flg && innerAdd(iDoc), true as boolean); }; contentElement = TreeView.GetChildElements( contents instanceof Doc ? [contents] : DocListCast(contents), this.treeView, this, doc, undefined, this._props.treeViewParent, this._props.prevSibling, addDoc, remDoc, moveDoc, this._props.dragAction, this._props.addDocTab, this.titleStyleProvider, this.ScreenToLocalTransform, this._props.isContentActive, expandedWidth, this._props.renderDepth, this._props.treeViewHideHeaderFields, [...this._props.renderedIds, doc[Id]], this._props.onCheckedClick, this._props.onChildClick, this._props.skipFields, false, this._props.whenChildContentsActiveChanged, this._props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(), // TODO: [AL] Add these this._props.AddToMap, this._props.RemFromMap, this._props.hierarchyIndex ); } else { contentElement = ( Field.toKeyValueString(doc, key)} SetValue={(value: string) => Doc.SetField(doc, key, value, true)} /> ); } rows.push(
runInAction(() => { if (r) leftOffset.width = r.getBoundingClientRect().width; }) } style={{ fontWeight: 'bold' }}> {key + ':'}   {contentElement}
); } rows.push(
{ const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@?+\-*/ 0-9()]+)/); if (match) { const key = match[1]; const assign = match[2]; const val = match[3]; Doc.SetField(doc, key, assign + val); return true; } return false; }} />
); return rows; } _renderTimer: NodeJS.Timeout | undefined; @observable _renderCount = 1; @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {}; if (['links', 'annotations', 'embeddings', this.fieldKey].includes(expandKey)) { const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded); const sortKeys = Object.keys(sortings); const curSortIndex = Math.max( 0, sortKeys.findIndex(val => val === sorting) ); const key = (expandKey === 'annotations' ? `${this.fieldKey}-` : '') + expandKey; const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); const moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc); const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { // if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't), // then the modification would be done here const ordering = StrCast(this.Document.treeView_SortCriterion); if (ordering === TreeSort.Zindex) { const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering); doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000; docs.push(doc); docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => { d.zIndex = i; }); } const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false); !dataIsComputed && added && Doc.SetContainer(doc, this.Document); return added; }; const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => toList(docs).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); const docs = expandKey === 'embeddings' ? this.childEmbeddings : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs; let downX = 0; let downY = 0; if (docs?.length && this._renderCount < docs?.length) { this._renderTimer && clearTimeout(this._renderTimer); this._renderTimer = setTimeout( action(() => { this._renderCount = Math.min(docs!.length, this._renderCount + 20); }) ); } return (
{!docs?.length || this.treeView.outlineMode || this._props.AddToMap /* hack to identify pres box trees */ ? null : (
{ downX = e.clientX; downY = e.clientY; e.stopPropagation(); }} onClick={undoable(e => { if (this._props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { !this.treeView.outlineMode && (this.Document.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); e.stopPropagation(); } }, 'sort order')} />
)}
    { downX = e.clientX; downY = e.clientY; e.stopPropagation(); }} onClick={undoable(e => { if (this._props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { !this.treeView.outlineMode && (this.Document.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); e.stopPropagation(); } }, 'sort order')}> {!docs ? null : TreeView.GetChildElements( docs, this.treeView, this, this.layoutDoc, this.dataDoc, this._props.treeViewParent, this._props.prevSibling, addDoc, remDoc, moveDoc, StrCast(this.Document.childDragAction, this._props.dragAction) as dropActionType, this._props.addDocTab, this.titleStyleProvider, this.ScreenToLocalTransform, this._props.isContentActive, this._props.panelWidth, this._props.renderDepth, this._props.treeViewHideHeaderFields, [...this._props.renderedIds, this.Document[Id]], this._props.onCheckedClick, this._props.onChildClick, this._props.skipFields, false, this._props.whenChildContentsActiveChanged, this._props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(), // TODO: [AL] add these this._props.AddToMap, this._props.RemFromMap, this._props.hierarchyIndex, this._renderCount )}
); } if (this.treeViewExpandedView === 'fields') { return (
    {this.expandedField}
); } return (
    { e.preventDefault(); e.stopPropagation(); }}> {this.renderEmbeddedDocument(false, this.treeView._props.childDocumentsActive ?? returnFalse)}
); // "layout" } get onCheckedClick() { return this.Document.type === DocumentType.COL ? undefined : (this._props.onCheckedClick?.() ?? ScriptCast(this.Document.onCheckedClick)); } @action bulletClick = (e: React.MouseEvent) => { if (this.onCheckedClick) { this.onCheckedClick?.script.run( { this: this.Document.isTemplateForField && this._props.dataDoc ? this._props.dataDoc : this.Document, heading: this._props.treeViewParent.title, checked: this.Document.treeView_Checked === 'check' ? 'x' : this.Document.treeView_Checked === 'x' ? 'remove' : 'check', containingTreeView: this.treeView.Document, }, console.log ); } else { this.treeViewOpen = !this.treeViewOpen; } e.stopPropagation(); }; @computed get renderBullet() { TraceMobx(); const iconType = (this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':treeOpen' : !this.childDocs.length ? ':empty' : '')) as string) || 'question'; const color = SettingsManager.userColor; const checked = this.onCheckedClick ? (this.Document.treeView_Checked ?? 'unchecked') : undefined; return (
{this.treeView.outlineMode ? ( !(this.Document.text as RichTextField)?.Text ? null : ( } size={Size.XSMALL} /> ) ) : (
= 1 ? 'block' : 'none' }} icon={checked === 'check' ? 'check' : checked === 'x' ? 'times' : checked === 'unchecked' ? 'square' : !this.treeViewOpen ? 'caret-right' : 'caret-down'} />
{this.onCheckedClick ? null : typeof iconType === 'string' ? : iconType}
)}
); } @computed get validExpandViewTypes() { const annos = () => (DocListCast(this.Document[this.fieldKey + '_annotations']).length && !this.treeView.dashboardMode ? 'annotations' : ''); const links = () => (Doc.Links(this.Document).length && !this.treeView.dashboardMode ? 'links' : ''); const data = () => (this.childDocs || this.treeView.dashboardMode ? this.fieldKey : ''); const embeddings = () => (this.treeView.dashboardMode ? '' : 'embeddings'); const fields = () => (Doc.noviceMode ? '' : 'fields'); const layout = Doc.noviceMode || this.Document._type_collection === CollectionViewType.Docking ? [] : ['layout']; return [data(), ...layout, ...(this.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m); } @action expandNextviewType = () => { if (this.treeViewOpen && !this.Document.isFolder && !this.treeView.outlineMode && !this.Document.treeView_ExpandedViewLock) { const next = (modes: string[]) => modes[(modes.indexOf(StrCast(this.treeViewExpandedView)) + 1) % modes.length]; this.Document.treeView_ExpandedView = next(this.validExpandViewTypes); } this.treeViewOpen = true; }; @observable headerEleWidth = 0; @computed get titleButtons() { const customHeaderButtons = this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.Decorations) as JSX.Element; const color = SettingsManager.userColor; return this._props.treeViewHideHeaderFields() || this.Document.treeView_HideHeaderFields ? null : ( <> {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */} } size={Size.XSMALL} onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} /> {Doc.noviceMode ? null : this.Document.treeView_ExpandedViewLock || Doc.IsSystem(this.Document) ? null : ( {this.treeViewExpandedView} )} ); } showContextMenu = (e: React.MouseEvent) => { DocumentViewInternal.SelectAfterContextMenu = false; simulateMouseClick(this._docRef?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); DocumentViewInternal.SelectAfterContextMenu = true; }; contextMenuItems = () => { const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' }; const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(this), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' }; const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Focus or Open' }; const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Reopen' }; return [ ...(this._props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.Document })?.result)), ...(this.Document.isFolder ? [makeFolder] : Doc.IsSystem(this.Document) ? [] : this.treeView.fileSysMode && this.Document === this.Document[DocData] ? [openEmbedding, makeFolder] : this.Document._type_collection === CollectionViewType.Docking ? [] : this.treeView.Document === Doc.MyRecentlyClosed ? [reopenDoc] : [openEmbedding, focusDoc]), ]; }; childContextMenuItems = () => { const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), [])!; const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), [])!; const icons = StrListCast(this.Document.childContextMenuIcons); return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; onChildClick = () => this._props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!); onChildDoubleClick = () => ScriptCast(this.treeView.Document.treeView_ChildDoubleClick, !this.treeView.outlineMode ? this._openScript?.() : null); refocus = () => this.treeView._props.focus(this.treeView.Document, {}); ignoreEvent = (e: React.MouseEvent) => { if (this._props.isContentActive(true)) { e.stopPropagation(); e.preventDefault(); } }; titleStyleProvider = (doc: Doc | undefined, props: Opt, property: string) => { if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView const { treeView } = this; // prettier-ignore switch (property.split(':')[0]) { case StyleProp.Opacity: return this.treeView.outlineMode ? undefined : 1; case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined; // StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); case StyleProp.Highlighting: if (this.treeView.outlineMode) return undefined; break; case StyleProp.BoxShadow: return undefined; case StyleProp.DocContents: { const highlightIndex = this.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc); const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; return treeView.outlineMode ? null : (
{StrCast(doc?.title)}
); } default: } return treeView._props.styleProvider?.(doc, props, property); }; embeddedStyleProvider = (doc: Doc | undefined, props: Opt, property: string) => { if (property.startsWith(StyleProp.Decorations)) return null; return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView }; onKey = (e: KeyboardEvent) => { if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) { switch (e.key) { case 'Tab': e.stopPropagation?.(); e.preventDefault?.(); setTimeout(() => RichTextMenu.Instance?.TextView?.EditorView?.focus(), 150); UndoManager.RunInBatch(() => (e.shiftKey ? this._props.outdentDocument?.(true) : this._props.indentDocument?.(true)), 'tab'); return true; case 'Backspace': if (!(this.Document.text as RichTextField)?.Text && this._props.removeDoc?.(this.Document)) { e.stopPropagation?.(); e.preventDefault?.(); return true; } break; case 'Enter': e.stopPropagation?.(); e.preventDefault?.(); return UndoManager.RunInBatch(this.makeTextCollection, 'bullet'); default: } } return false; }; titleWidth = () => Math.max(20, Math.min(this.treeView.truncateTitleWidth(), this._props.panelWidth())) / (this.treeView._props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth(); /** * Renders the EditableView title element for placement into the tree. */ @computed get renderTitle() { TraceMobx(); const view = this._editTitle ? ( { this._editTitle = e; })} GetValue={() => StrCast(this.Document.title)} OnTab={undoable((shift?: boolean) => { if (!shift) this._props.indentDocument?.(true); else this._props.outdentDocument?.(true); }, 'create new tree Doc')} OnEmpty={undoable(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document), 'remove tree doc')} OnFillDown={() => this.treeView.fileSysMode && this.makeFolder()} SetValue={undoable((value: string, shiftKey: boolean, enterKey: boolean) => { Doc.SetInPlace(this.Document, 'title', value, false); return this.treeView.outlineMode && enterKey && this.makeTextCollection(); }, 'set tree doc title')} /> ) : ( runInAction(() => { this._docRef = r || undefined; if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) { this._docRef.select(false); this.setEditTitle(this._docRef); TreeView._editTitleOnLoad = undefined; } }) } Document={this.Document} fitWidth={returnTrue} scriptContext={this} hideDecorations hideClickBehaviors styleProvider={this.titleStyleProvider} onClickScriptDisable="never" // tree docViews have a script to show fields, etc. containerViewPath={this.treeView.childContainerViewPath} addDocument={undefined} addDocTab={this._props.addDocTab} pinToPres={this.treeView._props.pinToPres} onClickScript={this.onChildClick} onDoubleClickScript={this.onChildDoubleClick} dragAction={this._props.dragAction} dragConfig={this.treeView.dragConfig} moveDocument={this.move} removeDocument={this._props.removeDoc} ScreenToLocalTransform={this.getTransform} NativeHeight={returnZero} NativeWidth={returnZero} PanelWidth={this.titleWidth} PanelHeight={return18} contextMenuItems={this.contextMenuItems} renderDepth={1} isContentActive={emptyFunction} // this._props.isContentActive} isDocumentActive={this._props.isContentActive} focus={this.refocus} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} disableBrushing={this.treeView._props.disableBrushing} hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)} xMargin={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} yMargin={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} /> ); return ( <>
{view}
runInAction(() => { r && (this.headerEleWidth = r.getBoundingClientRect().width); }) }> {this.titleButtons}
); } renderBulletHeader = (contents: JSX.Element, editing: boolean) => ( <>
{ this.treeView.isContentActive() && setupMoveUpEvents( this, e, () => { (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, undefined); return true; }, returnFalse, emptyFunction ); }} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
{contents}
{this.renderBorder} ); fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined); renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => (
{ this._dref = r; })} Document={this.Document} fitWidth={this.fitWidthFilter} PanelWidth={this.embeddedPanelWidth} PanelHeight={this.embeddedPanelHeight} LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} LayoutTemplate={this.treeView._props.childLayoutTemplate} isContentActive={isActive} isDocumentActive={isActive} styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} fitContentsToBox={returnTrue} hideTitle={asText} hideDecorations hideClickBehaviors hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)} ScreenToLocalTransform={this.docTransform} renderDepth={this._props.renderDepth + 1} onClickScript={this.onChildClick} onKey={this.onKey} containerViewPath={this.treeView.childContainerViewPath} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} focus={this.refocus} addDocument={this._props.addDocument} moveDocument={this.move} removeDocument={this._props.removeDoc} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} xMargin={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} yMargin={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} addDocTab={this._props.addDocTab} pinToPres={this.treeView._props.pinToPres} disableBrushing={this.treeView._props.disableBrushing} scriptContext={this} />
); // renders the text version of a document as the header. This is used in the file system mode and in other vanilla tree views. @computed get renderTitleAsHeader() { return this.treeView.Document.treeView_HideUnrendered && this.Document.layout_unrendered && !this.Document.treeView_FieldKey ? (
) : ( <> {this.renderBullet} {this.renderTitle} ); } // renders the document in the header field instead of a text proxy. renderDocumentAsHeader = (asText: boolean) => ( <> {this.renderBullet} {this.renderEmbeddedDocument(asText, this._props.isContentActive)} ); @computed get renderBorder() { const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded); const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; icon: JSX.Element } }; return (
{!this.treeViewOpen ? null : this.renderContent}
); } onTreeDrop = (de: React.DragEvent) => { const pt = [de.clientX, de.clientY]; const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || !!(!before && this.treeViewOpen && this.childDocs?.length); this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, dropActionType.copy, undefined, undefined, false, false)); }; render() { TraceMobx(); const hideTitle = this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode; return this._props.renderedIds?.indexOf(this.Document[Id]) !== -1 ? ( '<' + this.Document.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles ) : (
this._props.isContentActive(true) && DocumentView.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document // onKeyDown={this.onKeyDown} >
  • {hideTitle && this.Document.type !== DocumentType.RTF && !this.Document.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader" ? this.renderEmbeddedDocument(false, returnFalse) : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.Document.treeView_RenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
  • ); } public static sortDocs(childDocs: Doc[], criterion: string | undefined) { const docs = childDocs.slice(); if (criterion !== TreeSort.WhenAdded) { const sortAlphaNum = (a: string, b: string): 0 | 1 | -1 => { const reN = /[0-9]*$/; const aA = a.replace(reN, '') ? a.replace(reN, '') : +a; // get rid of trailing numbers const bA = b.replace(reN, '') ? b.replace(reN, '') : +b; if (aA === bA) { // if header string matches, then compare numbers numerically const aN = parseInt(a.match(reN)![0], 10); const bN = parseInt(b.match(reN)![0], 10); return aN === bN ? 0 : aN > bN ? 1 : -1; } return aA > bA ? 1 : -1; }; docs.sort((d1, d2): 0 | 1 | -1 => { const a = criterion === TreeSort.AlphaUp ? d2 : d1; const b = criterion === TreeSort.AlphaUp ? d1 : d2; const first = a[criterion === TreeSort.Zindex ? 'zIndex' : 'title']; const second = b[criterion === TreeSort.Zindex ? 'zIndex' : 'title']; if (typeof first === 'number' && typeof second === 'number') return first - second > 0 ? 1 : -1; if (typeof first === 'string' && typeof second === 'string') return sortAlphaNum(first, second); return criterion ? 1 : -1; }); } return docs; } public static GetChildElements( childDocs: Doc[], treeView: CollectionTreeView, parentTreeView: CollectionTreeView | TreeView | undefined, treeViewParent: Doc, dataDoc: Doc | undefined, parentCollectionDoc: Doc | undefined, containerPrevSibling: Doc | undefined, add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean, remove: undefined | ((doc: Doc | Doc[]) => boolean), move: DragManager.MoveFunction, dragAction: dropActionType, addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean, styleProvider: undefined | StyleProviderFuncType, screenToLocalXf: () => Transform, isContentActive: (outsideReaction?: boolean) => boolean, panelWidth: () => number, renderDepth: number, treeViewHideHeaderFields: () => boolean, renderedIds: string[], onCheckedClick: undefined | (() => ScriptField), onChildClick: undefined | (() => ScriptField | undefined), skipFields: string[] | undefined, firstLevel: boolean, whenChildContentsActiveChanged: (isActive: boolean) => void, dontRegisterView: boolean | undefined, observerHeight: (ref: HTMLElement) => void, unobserveHeight: (ref: HTMLElement) => void, contextMenuItems: { script: ScriptField; filter: ScriptField; label: string; icon: string }[], // TODO: [AL] add these AddToMap?: (treeViewDoc: Doc, index: number[]) => void, RemFromMap?: (treeViewDoc: Doc, index: number[]) => void, hierarchyIndex?: number[], renderCount?: number ) { const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeView_SortCriterion, TreeSort.WhenAdded)); const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1); const treeViewRefs = new Map(); return docs .filter(child => child instanceof Doc) .map((child, i) => { if (renderCount && i > renderCount) return null; const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child); if (!pair.layout || pair.data instanceof Promise) { return null; } const setRef = (r: TreeView | null) => treeViewRefs.set(child, r || undefined); const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => { if (parent instanceof TreeView && parent._props.treeView.fileSysMode && !newParent.isFolder) return; const fieldKey = Doc.LayoutDataKey(newParent); if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) { remove(child); DocumentView.SetSelectOnLoad(child); TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined; Doc.AddDocToList(newParent, fieldKey, child, addAfter, false); newParent.treeView_Open = true; Doc.SetContainer(child, treeView.Document); } }; const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView._props.parentTreeView : undefined); const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); const childLayout = pair.layout[DocLayout]; const rowHeight = () => { const aspect = Doc.NativeAspect(childLayout); return aspect ? Math.min(NumCast(childLayout._width), rowWidth()) / aspect : NumCast(childLayout._height); }; return ( ); }); } }