import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc'; import { DocData, Height, Width } 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 } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { StyleProp } from '../StyleProvider'; import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView } from './CollectionView'; import './TreeView.scss'; import React = require('react'); import { IconButton, Size } from 'browndash-components'; import { TreeSort } from './TreeSort'; import { SettingsManager } from '../../util/SettingsManager'; export interface TreeViewProps { treeView: CollectionTreeView; parentTreeView: TreeView | CollectionTreeView | undefined; observeHeight: (ref: any) => void; unobserveHeight: (ref: any) => void; prevSibling?: Doc; document: Doc; dataDoc?: Doc; treeViewParent: Doc; renderDepth: number; dragAction: dropActionType; addDocTab: (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?: StyleProviderFunc | undefined; treeViewHideHeaderFields: () => boolean; renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle onCheckedClick?: () => ScriptField; onChildClick?: () => ScriptField; 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 React.Component { 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; private _disposers: { [name: string]: IReactionDisposer } = {}; private _editTitleScript: (() => ScriptField) | undefined; private _openScript: (() => ScriptField) | undefined; private _treedropDisposer?: DragManager.DragDropDisposer; get treeViewOpenIsTransient() { return this.props.treeView.doc.treeView_OpenIsTransient || Doc.IsDataProto(this.doc); } set treeViewOpen(c: boolean) { if (this.treeViewOpenIsTransient) this._transientOpenState = c; else { this.doc.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; get displayName() { return 'TreeView(' + this.props.document.title + ')'; } // this makes mobx trace() statements more descriptive get defaultExpandedView() { return this.doc._type_collection === CollectionViewType.Docking ? this.fieldKey : this.props.treeView.dashboardMode ? this.fieldKey : this.props.treeView.fileSysMode ? this.doc.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.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.noviceMode ? 'layout' : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields'); } @computed get doc() { return this.props.document; } @computed get treeViewOpen() { return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, 'treeView_Open', 'boolean', true)) || this._transientOpenState; } @computed get treeViewExpandedView() { return this.validExpandViewTypes.includes(StrCast(this.doc.treeView_ExpandedView)) ? StrCast(this.doc.treeView_ExpandedView) : this.defaultExpandedView; } @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.treeViewParent.maxEmbedHeight, 200); } @computed get dataDoc() { return this.props.document.treeView_ChildrenOnRoot ? this.doc : this.doc[DocData]; } @computed get layoutDoc() { return Doc.Layout(this.doc); } @computed get fieldKey() { return StrCast(this.doc._treeView_FieldKey, Doc.LayoutFieldKey(this.doc)); } @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 SelectionManager.IsSelected(this._docRef); } childDocList(field: string) { const layout = Cast(Doc.LayoutField(this.doc), Doc, null); return DocListCast(this.props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.doc[field]))); } moving: boolean = false; @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { if (this.doc !== target && addDoc !== returnFalse) { const canAdd1 = (this.props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this.props.parentTreeView?.doc.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 @action remove = (doc: Doc | Doc[], key: string) => { this.props.treeView.props.select(false); const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement()); const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.props.treeView.props.DocumentView?.())?.select(false); return res; }; @action setEditTitle = (docView?: DocumentView) => { this._disposers.selection?.(); if (!docView) { this._editTitle = false; } else if (docView.isSelected()) { const doc = docView.Document; SelectionManager.SelectSchemaViewDoc(doc); this._editTitle = true; this._disposers.selection = reaction( () => SelectionManager.SelectedSchemaDoc(), seldoc => seldoc !== doc && this.setEditTitle(undefined) ); } else { docView.select(false); } }; @action openLevel = (docView: DocumentView) => { if (this.props.document.isFolder || Doc.IsSystem(this.props.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.rootDoc.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.rootDoc : Doc.BestEmbedding(docView.rootDoc); this.props.addDocTab(bestEmbedding, OpenWhere.lightbox); } }; @undoBatch @action recurToggle = (childList: Doc[]) => { if (childList.length > 0) { childList.forEach(child => { child.runProcess = !!!child.runProcess; TreeView.ToggleChildrenRun.get(child)?.(); }); } }; @undoBatch @action getRunningChildren = (childList: Doc[]) => { if (childList.length === 0) { return []; } const runningChildren: FieldResult[] = []; 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(); static ToggleChildrenRun = new Map void>(); constructor(props: any) { super(props); 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.props.document) ? undefined : () => TreeView._openLevelScript!; this._editTitleScript = Doc.IsSystem(this.props.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.doc, () => { this.recurToggle(this.childDocs); }); TreeView.GetRunningChildren.set(this.doc, () => { return this.getRunningChildren(this.childDocs); }); } _treeEle: any; protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.doc); if (this._treeEle) this.props.unobserveHeight(this._treeEle); this.props.observeHeight((this._treeEle = ele)); }; componentWillUnmount() { 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.doc, this.props.hierarchyIndex); } componentDidUpdate() { this._disposers.opening = reaction( () => this.treeViewOpen, open => !open && (this._renderCount = 20) ); this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex); } componentDidMount() { this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex); } onDragUp = (e: PointerEvent) => { 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.GetIsDragging() && 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 = (e: React.PointerEvent): 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 (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.rootDoc) { 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, }); Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); Doc.GetProto(bullet).data = new List([]); DocumentManager.Instance.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.props.addDocument(folder); }; preTreeDrop = (e: Event, de: DragManager.DropEvent) => { const dragData = de.complete.docDragData; dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction); }; @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.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); const destDoc = this.doc; DocUtils.MakeLink(sourceDoc, destDoc, { link_relationship: 'tree link' }); e.stopPropagation(); return true; } const docDragData = de.complete.docDragData; if (docDragData && pt[0] < rect.left + rect.width) { if (docDragData.draggedDocuments[0] === this.doc) return true; const added = this.dropDocuments( docDragData.droppedDocuments, // before, inside, docDragData.dropAction, docDragData.removeDocument, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document ); e.stopPropagation(); !added && e.preventDefault(); return added; } return false; }; dropping: boolean = false; dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) { const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, undefined, before); const localAdd = (doc: Doc | Doc[]) => { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer)); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); }; const addDoc = inside ? localAdd : parentAddDoc; const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument; const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd; if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this.props.parentTreeView?.doc))) { this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true); const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === '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.props.ScreenToLocalTransform(); const { scale, translateX, translateY } = Utils.GetScreenTransform(ref); const outerXf = Utils.GetScreenTransform(this.props.treeView.MainEle()); const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); }; docTransform = () => this.refTransform(this._dref?.ContentRef?.current); getTransform = () => this.refTransform(this._tref.current); embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1); embeddedPanelHeight = () => { const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; return Math.min( 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() * layoutDoc[Height]()) / layoutDoc[Width](); })() ); }; @computed get expandedField() { const ids: { [key: string]: string } = {}; const rows: JSX.Element[] = []; const doc = this.doc; 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 = []; let 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 = (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 addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer)); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); }; contentElement = TreeView.GetChildElements( contents instanceof Doc ? [contents] : DocListCast(contents), this.props.treeView, this, doc, undefined, this.props.treeViewParent, this.props.prevSibling, addDoc, remDoc, moveDoc, this.props.dragAction, this.props.addDocTab, this.titleStyleProvider, this.props.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) => KeyValueBox.SetField(doc, key, value, true)} /> ); } rows.push(
{ if (r) leftOffset.width = r.getBoundingClientRect().width; })} style={{ fontWeight: 'bold' }}> {key + ':'}   {contentElement}
); } rows.push(
value.indexOf(':') !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(':')), value.substring(value.indexOf(':') + 1, value.length), true)} />
); return rows; } _renderTimer: any; @observable _renderCount = 1; @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; const sortings = (this.props.styleProvider?.(this.doc, this.props.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.doc.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.doc.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.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); !dataIsComputed && added && Doc.SetContainer(doc, this.doc); return added; }; const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).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, 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.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.props.treeView.outlineMode && (this.doc.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.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); e.stopPropagation(); } }, 'sort order')}> {!docs ? null : TreeView.GetChildElements( docs, this.props.treeView, this, this.layoutDoc, this.dataDoc, this.props.treeViewParent, this.props.prevSibling, addDoc, remDoc, moveDoc, StrCast(this.doc.childDragAction, this.props.dragAction) as dropActionType, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, [...this.props.renderedIds, this.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, this._renderCount )}
); } else if (this.treeViewExpandedView === 'fields') { return (
    {this.expandedField}
); } return (
    { e.preventDefault(); e.stopPropagation(); }}> {this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)}
); // "layout" } get onCheckedClick() { return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); } @action bulletClick = (e: React.MouseEvent) => { if (this.onCheckedClick) { this.onCheckedClick?.script.run( { this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, heading: this.props.treeViewParent.title, checked: this.doc.treeView_Checked === 'check' ? 'x' : this.doc.treeView_Checked === 'x' ? 'remove' : 'check', containingTreeView: this.props.treeView.props.Document, }, console.log ); } else { this.treeViewOpen = !this.treeViewOpen; } e.stopPropagation(); }; @computed get renderBullet() { TraceMobx(); const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question'; const color = SettingsManager.userColor; const checked = this.onCheckedClick ? this.doc.treeView_Checked ?? 'unchecked' : undefined; return (
{this.props.treeView.outlineMode ? ( !(this.doc.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.doc[this.fieldKey + '_annotations']).length && !this.props.treeView.dashboardMode ? 'annotations' : ''); const links = () => (LinkManager.Links(this.doc).length && !this.props.treeView.dashboardMode ? 'links' : ''); const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : ''); const embeddings = () => (this.props.treeView.dashboardMode ? '' : 'embeddings'); const fields = () => (Doc.noviceMode ? '' : 'fields'); const layout = Doc.noviceMode || this.doc._type_collection === CollectionViewType.Docking ? [] : ['layout']; return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m); } @action expandNextviewType = () => { if (this.treeViewOpen && !this.doc.isFolder && !this.props.treeView.outlineMode && !this.doc.treeView_ExpandedViewLock) { const next = (modes: any[]) => modes[(modes.indexOf(StrCast(this.treeViewExpandedView)) + 1) % modes.length]; this.doc.treeView_ExpandedView = next(this.validExpandViewTypes); } this.treeViewOpen = true; }; @observable headerEleWidth = 0; @computed get titleButtons() { const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations); const color = SettingsManager.userColor; return this.props.treeViewHideHeaderFields() || this.doc.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.doc.treeView_ExpandedViewLock || Doc.IsSystem(this.doc) ? 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 folderOp = this.childDocs?.length ? [makeFolder] : []; const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(self), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' }; const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' }; const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Reopen' }; return [ ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)), ...(this.doc.isFolder ? folderOp : Doc.IsSystem(this.doc) ? [] : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ? [openEmbedding, makeFolder] : this.doc._type_collection === CollectionViewType.Docking ? [] : this.props.treeView.rootDoc === Doc.MyRecentlyClosed ? [reopenDoc] : [openEmbedding, focusDoc]), ]; }; childContextMenuItems = () => { const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []); const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []); const icons = StrListCast(this.doc.childContextMenuIcons); return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeView_ChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document, {}); ignoreEvent = (e: any) => { if (this.props.isContentActive(true)) { e.stopPropagation(); e.preventDefault(); } }; titleStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (!doc || doc !== this.doc) 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.props.treeView; // prettier-ignore switch (property.split(':')[0]) { case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1; case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); case StyleProp.Highlighting: if (this.props.treeView.outlineMode) return undefined; case StyleProp.BoxShadow: return undefined; case StyleProp.DocContents: const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc); const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; return treeView.outlineMode ? null : (
{StrCast(doc?.title)}
); } return treeView.props.styleProvider?.(doc, props, property); }; embeddedStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { 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 }; onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { if (this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.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.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) { e.stopPropagation?.(); e.preventDefault?.(); return true; } break; case 'Enter': e.stopPropagation?.(); e.preventDefault?.(); return UndoManager.RunInBatch(this.makeTextCollection, 'bullet'); } } return false; }; titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.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.doc.title)} OnTab={undoBatch((shift?: boolean) => { if (!shift) this.props.indentDocument?.(true); else this.props.outdentDocument?.(true); })} OnEmpty={undoBatch(() => this.props.treeView.outlineMode && this.props.removeDoc?.(this.doc))} OnFillDown={val => this.props.treeView.fileSysMode && this.makeFolder()} SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { Doc.SetInPlace(this.doc, 'title', value, false); this.props.treeView.outlineMode && enterKey && this.makeTextCollection(); })} /> ) : ( { this._docRef = r ? r : undefined; if (this._docRef && TreeView._editTitleOnLoad?.id === this.props.document[Id] && TreeView._editTitleOnLoad.parent === this.props.parentTreeView) { this._docRef.select(false); this.setEditTitle(this._docRef); TreeView._editTitleOnLoad = undefined; } })} Document={this.doc} DataDoc={undefined} // or this.dataDoc? layout_fitWidth={returnTrue} scriptContext={this} hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} styleProvider={this.titleStyleProvider} onClickScriptDisable="never" // tree docViews have a script to show fields, etc. docViewPath={this.props.treeView.props.docViewPath} treeViewDoc={this.props.treeView.props.Document} addDocument={undefined} addDocTab={this.props.addDocTab} rootSelected={returnTrue} pinToPres={emptyFunction} onClick={this.onChildClick} onDoubleClick={this.onChildDoubleClick} dragAction={this.props.dragAction} moveDocument={this.move} removeDocument={this.props.removeDoc} ScreenToLocalTransform={this.getTransform} NativeHeight={return18} NativeWidth={returnZero} shouldNotScale={returnTrue} 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} bringToFront={emptyFunction} disableBrushing={this.props.treeView.props.disableBrushing} hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)} yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} /> ); return ( <>
{view}
r && (this.headerEleWidth = r.getBoundingClientRect().width))}> {this.titleButtons}
); } renderBulletHeader = (contents: JSX.Element, editing: boolean) => { return ( <>
{contents}
{this.renderBorder} ); }; fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined); renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { return (
(this._dref = r))} Document={this.doc} DataDoc={undefined} layout_fitWidth={this.fitWidthFilter} PanelWidth={this.embeddedPanelWidth} PanelHeight={this.embeddedPanelHeight} LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} LayoutTemplate={this.props.treeView.props.childLayoutTemplate} isContentActive={isActive} isDocumentActive={isActive} styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} hideTitle={asText} //fitContentsToBox={returnTrue} hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} onClick={this.onChildClick} focus={this.refocus} onKey={this.onKeyDown} hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} ScreenToLocalTransform={this.docTransform} renderDepth={this.props.renderDepth + 1} treeViewDoc={this.props.treeView?.props.Document} rootSelected={returnTrue} docViewPath={this.props.treeView.props.docViewPath} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} addDocument={this.props.addDocument} moveDocument={this.move} removeDocument={this.props.removeDoc} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)} yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)} addDocTab={this.props.addDocTab} pinToPres={this.props.treeView.props.pinToPres} disableBrushing={this.props.treeView.props.disableBrushing} bringToFront={returnFalse} 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.props.treeView.Document.treeView_HideUnrendered && this.doc.layout_unrendered && !this.doc.treeView_FieldKey ? (
) : ( <> {this.renderBullet} {this.renderTitle} ); } // renders the document in the header field instead of a text proxy. renderDocumentAsHeader = (asText: boolean) => { return ( <> {this.renderBullet} {this.renderEmbeddedDocument(asText, this.props.isContentActive)} ); }; @computed get renderBorder() { const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded); const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } }; 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.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false)); }; render() { TraceMobx(); const hideTitle = this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode; return this.props.renderedIds?.indexOf(this.doc[Id]) !== -1 ? ( '<' + this.doc.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles ) : (
this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document // onKeyDown={this.onKeyDown} >
  • {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader" ? this.renderEmbeddedDocument(false, returnFalse) : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.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; } else { return aA > bA ? 1 : -1; } }; docs.sort(function (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, treeView_Parent: 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, where: OpenWhere) => boolean, styleProvider: undefined | StyleProviderFunc, screenToLocalXf: () => Transform, isContentActive: (outsideReaction?: boolean) => boolean, panelWidth: () => number, renderDepth: number, treeView_HideHeaderFields: () => boolean, renderedIds: string[], onCheckedClick: undefined | (() => ScriptField), onChildClick: undefined | (() => ScriptField), skipFields: string[] | undefined, firstLevel: boolean, whenChildContentsActiveChanged: (isActive: boolean) => void, dontRegisterView: boolean | undefined, observerHeight: (ref: any) => void, unobserveHeight: (ref: any) => 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 viewSpecScript = Cast(treeView_Parent.viewSpecScript, ScriptField); if (viewSpecScript) { childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.WhenAdded)); const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1); const treeView_Refs = new Map(); return docs .filter(child => child instanceof Doc) .map((child, i) => { if (renderCount && i > renderCount) return null; const pair = Doc.GetLayoutDataDocPair(treeView_Parent, dataDoc, child); if (!pair.layout || pair.data instanceof Promise) { return null; } 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.LayoutFieldKey(newParent); if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) { remove(child); FormattedTextBox.SelectOnLoad = child[Id]; 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, treeView_Refs.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 = Doc.Layout(pair.layout); const rowHeight = () => { const aspect = Doc.NativeAspect(childLayout); return aspect ? Math.min(childLayout[Width](), rowWidth()) / aspect : childLayout[Height](); }; return ( treeView_Refs.set(child, r ? r : undefined)} document={pair.layout} dataDoc={pair.data} treeViewParent={treeView_Parent} prevSibling={docs[i]} // TODO: [AL] add these hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined} AddToMap={AddToMap} RemFromMap={RemFromMap} treeView={treeView} indentDocument={indent} outdentDocument={outdent} onCheckedClick={onCheckedClick} onChildClick={onChildClick} renderDepth={renderDepth} removeDoc={StrCast(treeView_Parent.treeView_FreezeChildren).includes('remove') ? undefined : remove} addDocument={addDocument} styleProvider={styleProvider} panelWidth={rowWidth} panelHeight={rowHeight} dontRegisterView={dontRegisterView} moveDocument={move} dragAction={dragAction} addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} isContentActive={isContentActive} treeViewHideHeaderFields={treeView_HideHeaderFields} renderedIds={renderedIds} skipFields={skipFields} firstLevel={firstLevel} whenChildContentsActiveChanged={whenChildContentsActiveChanged} parentTreeView={parentTreeView} observeHeight={observerHeight} unobserveHeight={unobserveHeight} contextMenuItems={contextMenuItems} /> ); }); } } ScriptingGlobals.add(function TreeView_addNewFolder() { TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined }; const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }; return Doc.AddDocToList(Doc.MyFilesystem, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); });