diff options
| author | bobzel <zzzman@gmail.com> | 2022-07-08 00:17:26 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2022-07-08 00:17:26 -0400 |
| commit | 146f8622d5bac2edc6b09f57c173bd057dfbcfad (patch) | |
| tree | f871089c438a476543ca96bac163c0532b9557c7 /src/client/views/collections/TreeView.tsx | |
| parent | b7e66da6b23cdb41c127000dfe13843d35f7d0cc (diff) | |
restructured currentUserUtils to avoid having import cycles.
Diffstat (limited to 'src/client/views/collections/TreeView.tsx')
| -rw-r--r-- | src/client/views/collections/TreeView.tsx | 1168 |
1 files changed, 689 insertions, 479 deletions
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 704b8989a..eb5faf4e1 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1,7 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx'; +import { observer } from 'mobx-react'; import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -12,26 +12,25 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnTrue, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; -import { DocumentType } from "../../documents/DocumentTypes"; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { DocumentManager, DocFocusOrOpen } from '../../util/DocumentManager'; -import { DragManager, dropActionType } from "../../util/DragManager"; +import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { DocumentManager } from '../../util/DocumentManager'; +import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; -import { EditableView } from "../EditableView"; +import { EditableView } from '../EditableView'; import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; import { DocumentView, DocumentViewInternal, DocumentViewProps, 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, CollectionViewType } from './CollectionView'; -import "./TreeView.scss"; -import React = require("react"); -import { KeyValueBox } from '../nodes/KeyValueBox'; -import { FieldViewProps } from '../nodes/FieldView'; +import { CollectionView } from './CollectionView'; +import './TreeView.scss'; +import React = require('react'); export interface TreeViewProps { treeView: CollectionTreeView; @@ -55,7 +54,7 @@ export interface TreeViewProps { indentDocument?: (editTitle: boolean) => void; outdentDocument?: (editTitle: boolean) => void; ScreenToLocalTransform: () => Transform; - contextMenuItems: { script: ScriptField, filter: ScriptField, icon: string, label: string }[]; + contextMenuItems: { script: ScriptField; filter: ScriptField; icon: string; label: string }[]; dontRegisterView?: boolean; styleProvider?: StyleProviderFunc | undefined; treeViewHideHeaderFields: () => boolean; @@ -70,24 +69,26 @@ export interface TreeViewProps { hierarchyIndex?: number[]; } -const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace("px", "")); }; +const treeBulletWidth = function () { + return Number(TREE_BULLET_WIDTH.replace('px', '')); +}; export enum TreeSort { - Up = "up", - Down = "down", - Zindex = "z", - None = "none" + Up = 'up', + Down = 'down', + Zindex = 'z', + None = 'none', } /** * Renders a treeView of a collection of documents - * + * * special fields: * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree */ @observer export class TreeView extends React.Component<TreeViewProps> { - static _editTitleOnLoad: Opt<{ id: string, parent: TreeView | CollectionTreeView | undefined }>; + static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>; static _openTitleScript: Opt<ScriptField | undefined>; static _openLevelScript: Opt<ScriptField | undefined>; private _header: React.RefObject<HTMLDivElement> = React.createRef(); @@ -98,7 +99,9 @@ export class TreeView extends React.Component<TreeViewProps> { private _openScript: (() => ScriptField) | undefined; private _treedropDisposer?: DragManager.DragDropDisposer; - get treeViewOpenIsTransient() { return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsPrototype(this.doc); } + get treeViewOpenIsTransient() { + return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsPrototype(this.doc); + } set treeViewOpen(c: boolean) { if (this.treeViewOpenIsTransient) this._transientOpenState = c; else { @@ -109,26 +112,61 @@ export class TreeView extends React.Component<TreeViewProps> { @observable _transientOpenState = false; // override of the treeViewOpen 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 displayName() { + return 'TreeView(' + this.props.document.title + ')'; + } // this makes mobx trace() statements more descriptive get defaultExpandedView() { - return this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : - this.props.treeView.dashboardMode ? this.fieldKey : - this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") : // for displaying - this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields"); - } - - @computed get doc() { return this.props.document; } - @computed get treeViewOpen() { return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, "treeViewOpen", "boolean", true)) || this._transientOpenState; } - @computed get treeViewExpandedView() { return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView; } - @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containerCollection.maxEmbedHeight, 200); } - @computed get dataDoc() { return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym]; } - @computed get layoutDoc() { return Doc.Layout(this.doc); } - @computed get fieldKey() { return StrCast(this.doc._treeViewFieldKey, Doc.LayoutFieldKey(this.doc)); } - @computed get childDocs() { return this.childDocList(this.fieldKey); } - @computed get childLinks() { return this.childDocList("links"); } - @computed get childAliases() { return this.childDocList("aliases"); } - @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); } - @computed get selected() { return SelectionManager.IsSelected(this._docRef); } + return this.doc.viewType === CollectionViewType.Docking + ? this.fieldKey + : this.props.treeView.dashboardMode + ? this.fieldKey + : this.props.treeView.fileSysMode + ? this.doc.isFolder + ? this.fieldKey + : 'aliases' // for displaying + : this.props.treeView.outlineMode || this.childDocs + ? this.fieldKey + : Doc.noviceMode + ? 'layout' + : StrCast(this.props.treeView.doc.treeViewExpandedView, 'fields'); + } + + @computed get doc() { + return this.props.document; + } + @computed get treeViewOpen() { + return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, 'treeViewOpen', 'boolean', true)) || this._transientOpenState; + } + @computed get treeViewExpandedView() { + return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView; + } + @computed get MAX_EMBED_HEIGHT() { + return NumCast(this.props.containerCollection.maxEmbedHeight, 200); + } + @computed get dataDoc() { + return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym]; + } + @computed get layoutDoc() { + return Doc.Layout(this.doc); + } + @computed get fieldKey() { + return StrCast(this.doc._treeViewFieldKey, Doc.LayoutFieldKey(this.doc)); + } + @computed get childDocs() { + return this.childDocList(this.fieldKey); + } + @computed get childLinks() { + return this.childDocList('links'); + } + @computed get childAliases() { + return this.childDocList('aliases'); + } + @computed get childAnnos() { + return this.childDocList(this.fieldKey + '-annotations'); + } + @computed get selected() { + return SelectionManager.IsSelected(this._docRef); + } // SelectionManager.Views().lastElement()?.props.Document === this.props.document; } @observable _presTimer!: NodeJS.Timeout; @@ -136,64 +174,76 @@ export class TreeView extends React.Component<TreeViewProps> { @observable _selectedArray: ObservableMap = new ObservableMap<Doc, any>(); // the selected item's index - @computed get itemIndex() { return NumCast(this.doc._itemIndex); } - // the item that's active - @computed get activeItem() { return this.childDocs ? Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null) : undefined; } - @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } + @computed get itemIndex() { + return NumCast(this.doc._itemIndex); + } + // the item that's active + @computed get activeItem() { + return this.childDocs ? Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null) : undefined; + } + @computed get targetDoc() { + return Cast(this.activeItem?.presentationTargetDoc, Doc, null); + } childDocList(field: string) { const layout = Cast(Doc.LayoutField(this.doc), Doc, null); - return (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field + return ( + (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields - DocListCastOrNull(this.doc[field]); // otherwise use the document's data field + DocListCastOrNull(this.doc[field]) + ); // otherwise use the document's data field } @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - if (this.doc !== target && addDoc !== returnFalse) { // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse + if (this.doc !== target && addDoc !== returnFalse) { + // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse if (this.props.removeDoc?.(doc) === true) { return addDoc(doc); } } return false; - } + }; @undoBatch @action remove = (doc: Doc | Doc[], key: string) => { this.props.treeView.props.select(false); const ind = this.dataDoc[key].indexOf(doc); const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.CollectionView)?.select(false); return res; - } + }; @action setEditTitle = (docView?: DocumentView) => { this._selDisposer?.(); if (!docView) { this._editTitle = false; - } - else if (docView.isSelected()) { + } else if (docView.isSelected()) { const doc = docView.Document; SelectionManager.SelectSchemaViewDoc(doc); this._editTitle = true; - this._selDisposer = reaction(() => SelectionManager.SelectedSchemaDoc(), seldoc => seldoc !== doc && this.setEditTitle(undefined)); + this._selDisposer = 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 alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias - const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); + const bestAlias = + docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail); - this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), "lightbox"); + this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), 'lightbox'); } - } + }; 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" }); + 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!; @@ -202,16 +252,16 @@ export class TreeView extends React.Component<TreeViewProps> { _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); + 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); - } + this.props.observeHeight((this._treeEle = ele)); + }; componentWillUnmount() { this._selDisposer?.(); this._treeEle && this.props.unobserveHeight(this._treeEle); - document.removeEventListener("pointermove", this.onDragMove, true); - document.removeEventListener("pointermove", this.onDragUp, true); + 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); } @@ -225,49 +275,60 @@ export class TreeView extends React.Component<TreeViewProps> { } onDragUp = (e: PointerEvent) => { - document.removeEventListener("pointerup", this.onDragUp, true); - document.removeEventListener("pointermove", this.onDragMove, true); - } + 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._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); + 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"; + 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); - } + 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] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); - this._header.current!.className = "treeView-header"; + const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.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"; + 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("-text-", { - layout: CollectionView.LayoutString("data"), - title: "-title-", - treeViewExpandedViewLock: true, treeViewExpandedView: "data", - _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: TreeViewType.outline, - x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _width: 1000, _height: 10 + const bullet = Docs.Create.TextDocument('-text-', { + layout: CollectionView.LayoutString('data'), + title: '-title-', + treeViewExpandedViewLock: true, + treeViewExpandedView: 'data', + _viewType: CollectionViewType.Tree, + hideLinkButton: true, + _showSidebar: true, + treeViewType: TreeViewType.outline, + x: 0, + y: 0, + _xMargin: 0, + _yMargin: 0, + _autoHeight: true, + _singleLine: true, + _width: 1000, + _height: 10, }); Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); Doc.GetProto(bullet).data = new List<Doc>([]); @@ -279,19 +340,19 @@ export class TreeView extends React.Component<TreeViewProps> { 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", _stayInCollection: true, isFolder: true }); + const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true }); TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView }; return this.props.addDocument(folder); - } + }; deleteItem = () => this.props.removeDoc?.(this.doc); preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { const dragData = de.complete.docDragData; - dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? "same" : dragData.dropAction); - } + dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction); + }; @undoBatch treeDrop = (e: Event, de: DragManager.DropEvent) => { @@ -299,11 +360,11 @@ export class TreeView extends React.Component<TreeViewProps> { if (!this._header.current) return; 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] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); + const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); const destDoc = this.doc; - DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", ""); + DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, 'tree link', ''); e.stopPropagation(); } const docDragData = de.complete.docDragData; @@ -313,22 +374,16 @@ export class TreeView extends React.Component<TreeViewProps> { e.stopPropagation(); } } - } + }; dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) { const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); - const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes("add")) || forceAdd; - const localAdd = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false; - const addDoc = !inside ? parentAddDoc : - (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean); - const move = (!dropAction || dropAction === "proto" || dropAction === "move" || dropAction === "same") && moveDocument; + const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes('add')) || forceAdd; + const localAdd = (doc: Doc) => (Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false); + const addDoc = !inside ? parentAddDoc : (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean); + const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument; if (canAdd) { - return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => - (move ? - move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false) - : - addDoc(d)) || added, - false)); + return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false)); } return false; } @@ -339,7 +394,7 @@ export class TreeView extends React.Component<TreeViewProps> { 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); docWidth = () => { @@ -348,22 +403,25 @@ export class TreeView extends React.Component<TreeViewProps> { if (layoutDoc._fitWidth) return Math.min(this.props.panelWidth() - treeBulletWidth(), layoutDoc[WidthSym]()); if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.panelWidth() - treeBulletWidth())); return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.scaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]()); - } + }; docHeight = () => { const layoutDoc = this.layoutDoc; - return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => { - const aspect = Doc.NativeAspect(layoutDoc); - if (aspect) return this.docWidth() / (aspect || 1); - return layoutDoc._fitWidth ? - (!Doc.NativeHeight(this.doc) ? - NumCast(this.props.containerCollection._height) - : - Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc)) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)) - )) - : - (layoutDoc[HeightSym]() || 50); - })())); - } + return Math.max( + 70, + Math.min( + this.MAX_EMBED_HEIGHT, + (() => { + const aspect = Doc.NativeAspect(layoutDoc); + if (aspect) return this.docWidth() / (aspect || 1); + return layoutDoc._fitWidth + ? !Doc.NativeHeight(this.doc) + ? NumCast(this.props.containerCollection._height) + : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))) + : layoutDoc[HeightSym]() || 50; + })() + ) + ); + }; @computed get expandedField() { const ids: { [key: string]: string } = {}; @@ -372,11 +430,11 @@ export class TreeView extends React.Component<TreeViewProps> { 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 === "treeViewOpen") continue; + if (this.props.skipFields?.includes(key) || key === 'title' || key === 'treeViewOpen') continue; const contents = doc[key]; let contentElement: (JSX.Element | null)[] | JSX.Element = []; - if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) { + 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 localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); @@ -384,74 +442,109 @@ export class TreeView extends React.Component<TreeViewProps> { 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); - contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), - this.props.treeView, this, doc, undefined, this.props.containerCollection, this.props.prevSibling, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive, - this.props.panelWidth, 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(), + contentElement = TreeView.GetChildElements( + contents instanceof Doc ? [contents] : DocListCast(contents), + this.props.treeView, + this, + doc, + undefined, + this.props.containerCollection, + this.props.prevSibling, + addDoc, + remDoc, + this.move, + this.props.dropAction, + this.props.addDocTab, + this.titleStyleProvider, + this.props.ScreenToLocalTransform, + this.props.isContentActive, + this.props.panelWidth, + 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 = <EditableView key="editableView" - contents={contents !== undefined ? Field.toString(contents as Field) : "null"} - height={13} - fontSize={12} - GetValue={() => Field.toKeyValueString(doc, key)} - SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} />; + contentElement = ( + <EditableView + key="editableView" + contents={contents !== undefined ? Field.toString(contents as Field) : 'null'} + height={13} + fontSize={12} + GetValue={() => Field.toKeyValueString(doc, key)} + SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} + /> + ); } - rows.push(<div style={{ display: "flex" }} key={key}> - <span style={{ fontWeight: "bold" }}>{key + ":"}</span> - - {contentElement} - </div>); + rows.push( + <div style={{ display: 'flex' }} key={key}> + <span style={{ fontWeight: 'bold' }}>{key + ':'}</span> + + {contentElement} + </div> + ); } - rows.push(<div style={{ display: "flex" }} key={"newKeyValue"}> - <EditableView - key="editableView" - contents={"+key:value"} - height={13} - fontSize={12} - GetValue={returnEmptyString} - SetValue={value => value.indexOf(":") !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(":")), value.substring(value.indexOf(":") + 1, value.length), true)} /> - </div>); + rows.push( + <div style={{ display: 'flex' }} key={'newKeyValue'}> + <EditableView + key="editableView" + contents={'+key:value'} + height={13} + fontSize={12} + GetValue={returnEmptyString} + SetValue={value => value.indexOf(':') !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(':')), value.substring(value.indexOf(':') + 1, value.length), true)} + /> + </div> + ); return rows; } rtfWidth = () => { - const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ""))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; - return Math.min(layout[WidthSym](), (this.props.panelWidth() - treeBulletWidth())) / (this.props.treeView.props.scaling?.() || 1); - } + const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + return Math.min(layout[WidthSym](), this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.scaling?.() || 1); + }; rtfHeight = () => { - const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ""))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; return this.rtfWidth() <= layout[WidthSym]() ? Math.min(layout[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; - } + }; rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), treeBulletWidth()); expandPanelHeight = () => { if (this.layoutDoc._fitWidth) return this.docHeight(); const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym](); const docAspect = this.docWidth() / this.docHeight(); - return (docAspect < aspect) ? this.docWidth() / aspect : this.docHeight(); - } + return docAspect < aspect ? this.docWidth() / aspect : this.docHeight(); + }; expandPanelWidth = () => { if (this.layoutDoc._fitWidth) return this.docWidth(); const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym](); const docAspect = this.docWidth() / this.docHeight(); - return (docAspect > aspect) ? this.docHeight() * aspect : this.docWidth(); - } + return docAspect > aspect ? this.docHeight() * aspect : this.docWidth(); + }; @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, label: string } }; - if (["links", "annotations", "aliases", this.fieldKey].includes(expandKey)) { + const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }; + if (['links', 'annotations', 'aliases', this.fieldKey].includes(expandKey)) { const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); const sortKeys = Object.keys(sortings); - const curSortIndex = Math.max(0, sortKeys.findIndex(val => val === sorting)); - const key = (expandKey === "annotations" ? `${this.fieldKey}-` : "") + expandKey; + 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 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), @@ -461,107 +554,161 @@ export class TreeView extends React.Component<TreeViewProps> { 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); + docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => (d.zIndex = i)); } const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); added && (doc.context = this.doc.context); 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 === "aliases" ? this.childAliases : expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs; - let downX = 0, downY = 0; - return <> - {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? (null) : <div className={'treeView-sorting'} style={{ background: sortings[sorting]?.color }} > - {sortings[sorting]?.label} - </div>} - <ul key={expandKey + "more"} title="click to change sort order" className={this.doc.treeViewHideTitle ? "no-indent" : ""} - onPointerDown={e => { downX = e.clientX; downY = e.clientY; e.stopPropagation(); }} - onClick={(e) => { - if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { - !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); + const docs = expandKey === 'aliases' ? this.childAliases : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs; + let downX = 0, + downY = 0; + return ( + <> + {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : ( + <div className={'treeView-sorting'} style={{ background: sortings[sorting]?.color }}> + {sortings[sorting]?.label} + </div> + )} + <ul + key={expandKey + 'more'} + title="click to change sort order" + className={this.doc.treeViewHideTitle ? 'no-indent' : ''} + onPointerDown={e => { + downX = e.clientX; + downY = e.clientY; e.stopPropagation(); - } - }}> - {!docs ? (null) : - TreeView.GetChildElements(docs, this.props.treeView, this, this.layoutDoc, - this.dataDoc, this.props.containerCollection, this.props.prevSibling, addDoc, remDoc, this.move, - StrCast(this.doc.childDropAction, this.props.dropAction) 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)} - </ul > - </>; - } else if (this.treeViewExpandedView === "fields") { - return <ul key={this.doc[Id] + this.doc.title}> - <div style={{ display: "inline-block" }} > - {this.expandedField} - </div> - </ul>; + }} + onClick={e => { + if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { + !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); + e.stopPropagation(); + } + }}> + {!docs + ? null + : TreeView.GetChildElements( + docs, + this.props.treeView, + this, + this.layoutDoc, + this.dataDoc, + this.props.containerCollection, + this.props.prevSibling, + addDoc, + remDoc, + this.move, + StrCast(this.doc.childDropAction, this.props.dropAction) 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 + )} + </ul> + </> + ); + } else if (this.treeViewExpandedView === 'fields') { + return ( + <ul key={this.doc[Id] + this.doc.title}> + <div style={{ display: 'inline-block' }}>{this.expandedField}</div> + </ul> + ); } - return <ul onPointerDown={e => { e.preventDefault(); e.stopPropagation(); }}>{this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)}</ul>; // "layout" + return ( + <ul + onPointerDown={e => { + e.preventDefault(); + e.stopPropagation(); + }}> + {this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)} + </ul> + ); // "layout" } - get onCheckedClick() { return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); } + 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.containerCollection.title, - checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? "remove" : "check", - containingTreeView: this.props.treeView.props.Document, - }, console.log); + this.onCheckedClick?.script.run( + { + this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, + heading: this.props.containerCollection.title, + checked: this.doc.treeViewChecked === 'check' ? 'x' : this.doc.treeViewChecked === '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" : "")) || "question"; - const checked = this.onCheckedClick ? (this.doc.treeViewChecked ?? "unchecked") : undefined; - return <div className={`bullet${this.props.treeView.outlineMode ? "-outline" : ""}`} key={"bullet"} - title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"} - onClick={this.bulletClick} - style={this.props.treeView.outlineMode ? - { - opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity) - } : - { - pointerEvents: this.props.isContentActive() ? "all" : undefined, - opacity: checked === "unchecked" || typeof iconType !== "string" ? undefined : 0.4, - color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), - }}> - {this.props.treeView.outlineMode ? - !(this.doc.text as RichTextField)?.Text ? (null) : - <FontAwesomeIcon size="sm" icon={[this.childDocs?.length && !this.treeViewOpen ? "fas" : "far", "circle"]} /> : - <div className="treeView-bulletIcons" > - <div className={`treeView-${this.onCheckedClick ? "checkIcon" : "expandIcon"}`}> - <FontAwesomeIcon size="sm" icon={ - checked === "check" ? "check" : - checked === "x" ? "times" : - checked === "unchecked" ? "square" : - !this.treeViewOpen ? "caret-right" : "caret-down"} /> + const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : '')) || 'question'; + const checked = this.onCheckedClick ? this.doc.treeViewChecked ?? 'unchecked' : undefined; + return ( + <div + className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`} + key={'bullet'} + title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : 'view fields'} + onClick={this.bulletClick} + style={ + this.props.treeView.outlineMode + ? { + opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity), + } + : { + pointerEvents: this.props.isContentActive() ? 'all' : undefined, + opacity: checked === 'unchecked' || typeof iconType !== 'string' ? undefined : 0.4, + color: StrCast(this.doc.color, checked === 'unchecked' ? 'white' : 'inherit'), + } + }> + {this.props.treeView.outlineMode ? ( + !(this.doc.text as RichTextField)?.Text ? null : ( + <FontAwesomeIcon size="sm" icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} /> + ) + ) : ( + <div className="treeView-bulletIcons"> + <div className={`treeView-${this.onCheckedClick ? 'checkIcon' : 'expandIcon'}`}> + <FontAwesomeIcon size="sm" icon={checked === 'check' ? 'check' : checked === 'x' ? 'times' : checked === 'unchecked' ? 'square' : !this.treeViewOpen ? 'caret-right' : 'caret-down'} /> + </div> + {this.onCheckedClick ? null : typeof iconType === 'string' ? <FontAwesomeIcon icon={iconType as IconProp} /> : iconType} </div> - {this.onCheckedClick ? (null) : typeof iconType === "string" ? <FontAwesomeIcon icon={iconType as IconProp} /> : iconType} - </div> - } - </div>; + )} + </div> + ); } @computed get validExpandViewTypes() { - const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length && !this.props.treeView.dashboardMode ? "annotations" : ""; - const links = () => DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? "links" : ""; - const data = () => this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : ""; - const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases"; - const fields = () => Doc.noviceMode ? "" : "fields"; - const layout = (Doc.noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"]; + const annos = () => (DocListCast(this.doc[this.fieldKey + '-annotations']).length && !this.props.treeView.dashboardMode ? 'annotations' : ''); + const links = () => (DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? 'links' : ''); + const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : ''); + const aliases = () => (this.props.treeView.dashboardMode ? '' : 'aliases'); + const fields = () => (Doc.noviceMode ? '' : 'fields'); + const layout = Doc.noviceMode || this.doc.viewType === CollectionViewType.Docking ? [] : ['layout']; return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m); } @action @@ -571,49 +718,68 @@ export class TreeView extends React.Component<TreeViewProps> { this.doc.treeViewExpandedView = next(this.validExpandViewTypes); } this.treeViewOpen = true; - } + }; @computed get headerElements() { - return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? (null) - : <> - {this.doc.hideContextMenu ? (null) : <FontAwesomeIcon title="context menu" key="bars" icon="bars" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} />} - {this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? (null) : + return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : ( + <> + {this.doc.hideContextMenu ? null : ( + <FontAwesomeIcon + title="context menu" + key="bars" + icon="bars" + size="sm" + onClick={e => { + this.showContextMenu(e); + e.stopPropagation(); + }} + /> + )} + {this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? null : ( <span className="collectionTreeView-keyHeader" title="type of expanded data" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}> {this.treeViewExpandedView} - </span>} - </>; + </span> + )} + </> + ); } 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 deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: "any" })!, icon: "folder-plus", label: "Delete" }; + const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' }; + const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'Delete' }; const folderOp = this.childDocs?.length ? [makeFolder] : []; - const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: "copy", label: "Open Alias" }; - const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: "eye", label: "Focus or Open" }; - 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) ? - [openAlias, makeFolder] : - this.doc.viewType === CollectionViewType.Docking ? [] : - [deleteItem, openAlias, focusDoc])]; - } + const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' }; + const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' }; + 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) + ? [openAlias, makeFolder] + : this.doc.viewType === CollectionViewType.Docking + ? [] + : [deleteItem, openAlias, 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 = () => { return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); - } + }; - onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick,(!this.props.treeView.outlineMode ? this._openScript?.():null)); + onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); ignoreEvent = (e: any) => { @@ -621,51 +787,59 @@ export class TreeView extends React.Component<TreeViewProps> { e.stopPropagation(); e.preventDefault(); } - } - titleStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => { + }; + titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, 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 - 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.DocContents: return this.props.treeView.outlineMode ? (null) : - <div className="treeView-label" style={{ // just render a title for a tree view label (identified by treeViewDoc being set in 'props') - maxWidth: props?.PanelWidth() || undefined, - background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor), - }}> - {StrCast(doc?.title)} - </div>; - default: return this.props?.treeView?.props.styleProvider?.(doc, props, property); + 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.DocContents: + return this.props.treeView.outlineMode ? null : ( + <div + className="treeView-label" + style={{ + // just render a title for a tree view label (identified by treeViewDoc being set in 'props') + maxWidth: props?.PanelWidth() || undefined, + background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor), + }}> + {StrCast(doc?.title)} + </div> + ); + default: + return this.props?.treeView?.props.styleProvider?.(doc, props, property); } - } - embeddedStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => { - if (property.startsWith(StyleProp.Decorations)) return (null); + }; + embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, 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.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) { switch (e.key) { - case "Tab": + 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"); + UndoManager.RunInBatch(() => (e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true)), 'tab'); return true; - case "Backspace": + case 'Backspace': if (!(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) { e.stopPropagation?.(); e.preventDefault?.(); return true; } break; - case "Enter": + case 'Enter': e.stopPropagation?.(); e.preventDefault?.(); - return UndoManager.RunInBatch(this.makeTextCollection, "bullet"); + return UndoManager.RunInBatch(this.makeTextCollection, 'bullet'); } } return false; - } + }; titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth())); return18 = () => 18; @@ -675,28 +849,32 @@ export class TreeView extends React.Component<TreeViewProps> { @computed get renderTitle() { TraceMobx(); - const view = this._editTitle ? <EditableView key="_editTitle" - oneLine={true} - display={"inline-block"} - editing={this._editTitle} - background={"#7089bb"} - contents={StrCast(this.doc.title)} - height={12} - sizeToContent={true} - fontSize={12} - 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(); - })} - /> - : <DocumentView key="title" + const view = this._editTitle ? ( + <EditableView + key="_editTitle" + oneLine={true} + display={'inline-block'} + editing={this._editTitle} + background={'#7089bb'} + contents={StrCast(this.doc.title)} + height={12} + sizeToContent={true} + fontSize={12} + 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(); + })} + /> + ) : ( + <DocumentView + key="title" ref={action((r: any) => { this._docRef = r ? r : undefined; if (this._docRef && TreeView._editTitleOnLoad?.id === this.props.document[Id] && TreeView._editTitleOnLoad.parent === this.props.parentTreeView) { @@ -743,139 +921,161 @@ export class TreeView extends React.Component<TreeViewProps> { ContainingCollectionView={undefined} ContainingCollectionDoc={this.props.treeView.props.Document} ContentScaling={returnOne} - />; - - const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ":afterHeader" : "")); - return <> - <div className={`docContainer${Doc.IsSystem(this.props.document) || this.props.document.isFolder ? "-system" : ""}`} ref={this._tref} title="click to edit title. Double Click or Drag to Open" - style={{ - fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? "bold" : undefined, - textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined, - outline: this.doc === CurrentUserUtils.ActiveDashboard ? "dashed 1px #06123232" : undefined, - pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined - }} > - {view} - </div > - <div className="treeView-rightButtons"> - {buttons} {/* hide and lock buttons */} - {this.headerElements} - </div> - </>; - } + /> + ); - renderBulletHeader = (contents: JSX.Element, editing: boolean) => { - return <> - <div className={`treeView-header` + (editing ? "-editing" : "")} key="titleheader" - style={{ width: StrCast(this.doc.treeViewHeaderWidth, "max-content") }} - ref={this._header} - onClick={this.ignoreEvent} - onPointerDown={this.ignoreEvent} - onPointerEnter={this.onPointerEnter} - onPointerLeave={this.onPointerLeave}> - {contents} - </div> - {this.renderBorder} - </>; + const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ':afterHeader' : '')); + return ( + <> + <div + className={`docContainer${Doc.IsSystem(this.props.document) || this.props.document.isFolder ? '-system' : ''}`} + ref={this._tref} + title="click to edit title. Double Click or Drag to Open" + style={{ + fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? 'bold' : undefined, + textDecoration: Doc.GetT(this.doc, 'title', 'string', true) ? 'underline' : undefined, + outline: this.doc === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined, + pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined, + }}> + {view} + </div> + <div className="treeView-rightButtons"> + {buttons} {/* hide and lock buttons */} + {this.headerElements} + </div> + </> + ); } + renderBulletHeader = (contents: JSX.Element, editing: boolean) => { + return ( + <> + <div + className={`treeView-header` + (editing ? '-editing' : '')} + key="titleheader" + style={{ width: StrCast(this.doc.treeViewHeaderWidth, 'max-content') }} + ref={this._header} + onClick={this.ignoreEvent} + onPointerDown={this.ignoreEvent} + onPointerEnter={this.onPointerEnter} + onPointerLeave={this.onPointerLeave}> + {contents} + </div> + {this.renderBorder} + </> + ); + }; renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { const isExpandable = this.doc._treeViewGrowsHorizontally; const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth; const panelHeight = asText ? this.rtfOutlineHeight : isExpandable ? this.rtfHeight : this.expandPanelHeight; - return <DocumentView key={this.doc[Id]} ref={action((r: DocumentView | null) => this._dref = r)} - Document={this.doc} - DataDoc={undefined} - PanelWidth={panelWidth} - PanelHeight={panelHeight} - NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} - NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} - 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} - ContentScaling={returnOne} - 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} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.containerCollection} - ContainingCollectionView={undefined} - addDocument={this.props.addDocument} - moveDocument={this.move} - removeDocument={this.props.removeDoc} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.props.addDocTab} - pinToPres={this.props.treeView.props.pinToPres} - disableDocBrushing={this.props.treeView.props.disableDocBrushing} - bringToFront={returnFalse} - />; - } + return ( + <DocumentView + key={this.doc[Id]} + ref={action((r: DocumentView | null) => (this._dref = r))} + Document={this.doc} + DataDoc={undefined} + PanelWidth={panelWidth} + PanelHeight={panelHeight} + NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} + NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} + 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} + ContentScaling={returnOne} + 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} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.props.containerCollection} + ContainingCollectionView={undefined} + addDocument={this.props.addDocument} + moveDocument={this.move} + removeDocument={this.props.removeDoc} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + addDocTab={this.props.addDocTab} + pinToPres={this.props.treeView.props.pinToPres} + disableDocBrushing={this.props.treeView.props.disableDocBrushing} + bringToFront={returnFalse} + /> + ); + }; // 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.renderBullet} - {this.renderTitle} - </>; + return ( + <> + {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)} - </>; - } + return ( + <> + {this.renderBullet} + {this.renderEmbeddedDocument(asText, this.props.isContentActive)} + </> + ); + }; @computed get renderBorder() { const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); - const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string, label: string } }; - return <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ""}`} style={{ borderColor: sortings[sorting]?.color }}> - {!this.treeViewOpen ? (null) : this.renderContent} - </div>; + const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }; + return ( + <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}> + {!this.treeViewOpen ? null : this.renderContent} + </div> + ); } 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] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); - - const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, "copy", undefined, false)); - } + const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); + const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, false)); + }; render() { TraceMobx(); const hideTitle = this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && 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 - <div className={`treeView-container${this.props.isContentActive() ? "-active" : ""}`} + 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 + ) : ( + <div + className={`treeView-container${this.props.isContentActive() ? '-active' : ''}`} ref={this.createTreeDropTarget} onDrop={this.onTreeDrop} - //onPointerDown={e => 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} + //onPointerDown={e => 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} > <li className="collection-child"> - {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader ? // should test for prop 'treeViewRenderDocWithBulletAsHeader" - this.renderEmbeddedDocument(false, returnFalse) : - this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeViewRenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)} + {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader // should test for prop 'treeViewRenderDocWithBulletAsHeader" + ? this.renderEmbeddedDocument(false, returnFalse) + : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeViewRenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)} </li> - </div>; + </div> + ); } public static sortDocs(childDocs: Doc[], criterion: string | undefined) { @@ -883,9 +1083,10 @@ export class TreeView extends React.Component<TreeViewProps> { if (criterion !== TreeSort.None) { 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 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; @@ -894,11 +1095,11 @@ export class TreeView extends React.Component<TreeViewProps> { } }; docs.sort(function (d1, d2): 0 | 1 | -1 { - const a = (criterion === TreeSort.Up ? d2 : d1); - const b = (criterion === TreeSort.Up ? 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; + const a = criterion === TreeSort.Up ? d2 : d1; + const b = criterion === TreeSort.Up ? 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; }); @@ -934,11 +1135,11 @@ export class TreeView extends React.Component<TreeViewProps> { dontRegisterView: boolean | undefined, observerHeight: (ref: any) => void, unobserveHeight: (ref: any) => void, - contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[]), + contextMenuItems: { script: ScriptField; filter: ScriptField; label: string; icon: string }[], // TODO: [AL] add these AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[], RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[], - hierarchyIndex?: number[], + hierarchyIndex?: number[] ) { const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField); if (viewSpecScript) { @@ -948,68 +1149,77 @@ export class TreeView extends React.Component<TreeViewProps> { const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None)); const rowWidth = () => panelWidth() - treeBulletWidth(); const treeViewRefs = new Map<Doc, TreeView | undefined>(); - return docs.filter(child => child instanceof Doc).map((child, i) => { - const pair = Doc.GetLayoutDataDocPair(containerCollection, 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.treeViewOpen = true; - child.context = treeView.Document; + return docs + .filter(child => child instanceof Doc) + .map((child, i) => { + const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child); + if (!pair.layout || pair.data instanceof Promise) { + return null; } - }; - const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); - const outdent = parentCollectionDoc?._viewType !== CollectionViewType.Tree ? undefined : ((editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined)); - const addDocument = (doc: Doc | Doc[], 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[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]()); - }; - return <TreeView key={child[Id]} ref={r => treeViewRefs.set(child, r ? r : undefined)} - document={pair.layout} - dataDoc={pair.data} - containerCollection={containerCollection} - 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(containerCollection.freezeChildren).includes("remove") ? undefined : remove} - addDocument={addDocument} - styleProvider={styleProvider} - panelWidth={rowWidth} - panelHeight={rowHeight} - dontRegisterView={dontRegisterView} - moveDocument={move} - dropAction={dropAction} - addDocTab={addDocTab} - ScreenToLocalTransform={screenToLocalXf} - isContentActive={isContentActive} - treeViewHideHeaderFields={treeViewHideHeaderFields} - renderedIds={renderedIds} - skipFields={skipFields} - firstLevel={firstLevel} - whenChildContentsActiveChanged={whenChildContentsActiveChanged} - parentTreeView={parentTreeView} - observeHeight={observerHeight} - unobserveHeight={unobserveHeight} - contextMenuItems={contextMenuItems} - />; - }); + + 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.treeViewOpen = true; + child.context = treeView.Document; + } + }; + const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); + const outdent = + parentCollectionDoc?._viewType !== CollectionViewType.Tree + ? undefined + : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined); + const addDocument = (doc: Doc | Doc[], 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[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); + }; + return ( + <TreeView + key={child[Id]} + ref={r => treeViewRefs.set(child, r ? r : undefined)} + document={pair.layout} + dataDoc={pair.data} + containerCollection={containerCollection} + 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(containerCollection.freezeChildren).includes('remove') ? undefined : remove} + addDocument={addDocument} + styleProvider={styleProvider} + panelWidth={rowWidth} + panelHeight={rowHeight} + dontRegisterView={dontRegisterView} + moveDocument={move} + dropAction={dropAction} + addDocTab={addDocTab} + ScreenToLocalTransform={screenToLocalXf} + isContentActive={isContentActive} + treeViewHideHeaderFields={treeViewHideHeaderFields} + renderedIds={renderedIds} + skipFields={skipFields} + firstLevel={firstLevel} + whenChildContentsActiveChanged={whenChildContentsActiveChanged} + parentTreeView={parentTreeView} + observeHeight={observerHeight} + unobserveHeight={unobserveHeight} + contextMenuItems={contextMenuItems} + /> + ); + }); } -}
\ No newline at end of file +} |
