diff options
| author | Bob Zeleznik <zzzman@gmail.com> | 2020-06-28 17:02:08 -0400 |
|---|---|---|
| committer | Bob Zeleznik <zzzman@gmail.com> | 2020-06-28 17:02:08 -0400 |
| commit | 777030e88d177daa1f61365c22b945b8808ec61a (patch) | |
| tree | 0e03fb85b8df64367b6eae36cacf42430ace408c /src/client/views/collections/CollectionTreeView.tsx | |
| parent | b79b5ebfd4f8ea8b4ae793e59f05b5bfe79b9b8a (diff) | |
| parent | 7a78bc39fa2b005d67499bcd6baf19b9dce0eb18 (diff) | |
Merge branch 'master' into 3D_carousel
Diffstat (limited to 'src/client/views/collections/CollectionTreeView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 399 |
1 files changed, 202 insertions, 197 deletions
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 747eb36a1..d54f4d6e6 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -13,7 +13,7 @@ import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from '../../util/DocumentManager'; import { SnappingManager } from '../../util/SnappingManager'; -import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; +import { DragManager, dropActionType } from "../../util/DragManager"; import { Scripting } from '../../util/Scripting'; import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; @@ -55,7 +55,7 @@ export interface TreeViewProps { ScreenToLocalTransform: () => Transform; backgroundColor?: (doc: Doc) => string | undefined; outerXf: () => { translateX: number, translateY: number }; - treeViewId: Doc; + treeViewDoc: Doc; parentKey: string; active: (outsideReaction?: boolean) => boolean; treeViewHideHeaderFields: () => boolean; @@ -76,31 +76,32 @@ export interface TreeViewProps { * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree */ class TreeView extends React.Component<TreeViewProps> { - static _editTitleScript: ScriptField | undefined; + private _editTitleScript: ScriptField | undefined; private _header?: React.RefObject<HTMLDivElement> = React.createRef(); private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef<HTMLDivElement>(); private _tref = React.createRef<HTMLDivElement>(); private _docRef = React.createRef<DocumentView>(); + private _uniqueId = Utils.GenerateGuid(); + private _editMaxWidth: number | string = 0; + get doc() { return this.props.document; } get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); } - get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive - get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, this.noviceMode ? "layout" : "fields"); } + get displayName() { return "TreeView(" + this.doc.title + ")"; } // this makes mobx trace() statements more descriptive + get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.doc.defaultExpandedView, this.noviceMode ? "layout" : "fields"); } @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state - set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; } - @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.props.document.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; } - @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); } + set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.doc.treeViewOpen = this._overrideTreeViewOpen = c; } + @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && BoolCast(this.doc.treeViewOpen)) || this._overrideTreeViewOpen; } + @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.defaultExpandedView); } @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); } - @computed get dataDoc() { return this.props.document[DataSym]; } - @computed get fieldKey() { - const splits = StrCast(Doc.LayoutField(this.props.document)).split("fieldKey={\'"); - return splits.length > 1 ? splits[1].split("\'")[0] : "data"; - } + @computed get dataDoc() { return this.doc[DataSym]; } + @computed get layoutDoc() { return Doc.Layout(this.doc); } + @computed get fieldKey() { const splits = StrCast(Doc.LayoutField(this.doc)).split("fieldKey={\'"); return splits.length > 1 ? splits[1].split("\'")[0] : "data"; } childDocList(field: string) { - const layout = Doc.LayoutField(this.props.document) instanceof Doc ? Doc.LayoutField(this.props.document) as Doc : undefined; + const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined; return ((this.props.dataDoc ? DocListCast(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field (layout ? Cast(layout[field], listSpec(Doc)) : undefined) || // else if there's a layout doc, display it's fields - Cast(this.props.document[field], listSpec(Doc))) as Doc[]; // otherwise use the document's data field + Cast(this.doc[field], listSpec(Doc))) as Doc[]; // otherwise use the document's data field } @computed get childDocs() { return this.childDocList(this.fieldKey); } @computed get childLinks() { return this.childDocList("links"); } @@ -109,22 +110,27 @@ class TreeView extends React.Component<TreeViewProps> { Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey])); } - @undoBatch openRight = () => this.props.addDocTab(this.props.document, "onRight", this.props.libraryPath); + @undoBatch openRight = () => this.props.addDocTab(this.doc, "onRight", this.props.libraryPath); @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc); + return this.doc !== target && this.props.deleteDoc(doc) && addDoc(doc); } @undoBatch @action remove = (doc: Doc | Doc[], key: string) => { - return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => - flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); + return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); } @undoBatch @action removeDoc = (doc: Doc | Doc[]) => { return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.props.containingCollection, Doc.LayoutFieldKey(this.props.containingCollection), doc), true); } + constructor(props: any) { + super(props); + this._editTitleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); selectDoc(self);} `); + if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false); + } + protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); - ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.props.document); + ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.doc); } onPointerEnter = (e: React.PointerEvent): void => { @@ -136,7 +142,9 @@ class TreeView extends React.Component<TreeViewProps> { } onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.dataDoc); - this._header!.current!.className = "treeViewItem-header"; + if (this._header?.current?.className !== "treeViewItem-header-editing") { + this._header!.current!.className = "treeViewItem-header"; + } document.removeEventListener("pointermove", this.onDragMove, true); } onDragMove = (e: PointerEvent): void => { @@ -144,7 +152,7 @@ class TreeView extends React.Component<TreeViewProps> { 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 && DocListCast(this.dataDoc[this.fieldKey]).length); + const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); this._header!.current!.className = "treeViewItem-header"; if (inside) this._header!.current!.className += " treeViewItem-header-inside"; else if (before) this._header!.current!.className += " treeViewItem-header-above"; @@ -155,42 +163,38 @@ class TreeView extends React.Component<TreeViewProps> { editableView = (key: string, style?: string) => (<EditableView oneLine={true} display={"inline-block"} - editing={true /*this.dataDoc[Id] === EditableView.loadId*/} - contents={StrCast(this.props.document[key])} + editing={true} + contents={StrCast(this.doc[key])} height={12} + sizeToContent={true} fontStyle={style} fontSize={12} - GetValue={() => StrCast(this.props.document[key])} + GetValue={() => StrCast(this.doc[key])} SetValue={undoBatch((value: string) => { - Doc.SetInPlace(this.props.document, key, value, false) || true; - Doc.SetInPlace(this.props.document, "editTitle", undefined, false); + Doc.SetInPlace(this.doc, key, value, false) || true; + Doc.SetInPlace(this.doc, "editTitle", undefined, false); })} OnFillDown={undoBatch((value: string) => { - Doc.SetInPlace(this.props.document, key, value, false); + Doc.SetInPlace(this.doc, key, value, false); const doc = Docs.Create.FreeformDocument([], { title: "-", x: 0, y: 0, _width: 100, _height: 25, _LODdisable: true, templates: new List<string>([Templates.Title.Layout]) }); - Doc.SetInPlace(this.props.document, "editTitle", undefined, false); - Doc.SetInPlace(doc, "editTitle", true, false); + Doc.SetInPlace(this.doc, "editTitle", undefined, false); + Doc.SetInPlace(doc, "editTitle", "*", false); return this.props.addDocument(doc); })} onClick={() => { SelectionManager.DeselectAll(); - Doc.UserDoc().activeSelection = new List([this.props.document]); + Doc.UserDoc().activeSelection = new List([this.doc]); return false; }} OnTab={undoBatch((shift?: boolean) => { - EditableView.loadId = this.dataDoc[Id]; shift ? this.props.outdentDocument?.() : this.props.indentDocument?.(); - setTimeout(() => { // unsetting/setting brushing for this doc will recreate & refocus this editableView after all other treeview changes have been made to the Dom (which may remove focus from this document). - Doc.UnBrushDoc(this.props.document); - Doc.BrushDoc(this.props.document); - EditableView.loadId = ""; - }, 0); + setTimeout(() => Doc.SetInPlace(this.doc, "editTitle", "*", false), 0); })} />) preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { const dragData = de.complete.docDragData; - dragData && (dragData.dropAction = this.props.treeViewId[Id] === dragData.treeViewId ? "same" : dragData.dropAction); + dragData && (dragData.dropAction = this.props.treeViewDoc === dragData.treeViewDoc ? "same" : dragData.dropAction); } @undoBatch @@ -198,18 +202,18 @@ class TreeView extends React.Component<TreeViewProps> { const pt = [de.x, de.y]; 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 && DocListCast(this.dataDoc[this.fieldKey]).length); + const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); const complete = de.complete; if (complete.linkDragData) { const sourceDoc = complete.linkDragData.linkSourceDocument; - const destDoc = this.props.document; + const destDoc = this.doc; DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link"); e.stopPropagation(); } const docDragData = complete.docDragData; if (docDragData) { e.stopPropagation(); - if (docDragData.draggedDocuments[0] === this.props.document) return true; + if (docDragData.draggedDocuments[0] === this.doc) return true; const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); let addDoc = parentAddDoc; if (inside) { @@ -222,35 +226,28 @@ class TreeView extends React.Component<TreeViewProps> { return false; } - docTransform = () => { - const { scale, translateX, translateY } = Utils.GetScreenTransform(this._dref.current!); - const outerXf = this.props.outerXf(); - const offset = this.props.ScreenToLocalTransform().transformDirection((outerXf.translateX - translateX), outerXf.translateY - translateY); - const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); - - return finalXf; - } - getTransform = () => { - const { scale, translateX, translateY } = Utils.GetScreenTransform(this._tref.current!); + refTransform = (ref: HTMLDivElement) => { + const { scale, translateX, translateY } = Utils.GetScreenTransform(ref); const outerXf = this.props.outerXf(); const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); - return finalXf; + return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); } + docTransform = () => this.refTransform(this._dref.current!); + getTransform = () => this.refTransform(this._tref.current!); docWidth = () => { - const layoutDoc = Doc.Layout(this.props.document); + const layoutDoc = this.layoutDoc; const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]()); if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; } docHeight = () => { - const layoutDoc = Doc.Layout(this.props.document); + const layoutDoc = this.layoutDoc; const bounds = this.boundsOfCollectionDocument; return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => { const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]()); if (aspect) return this.docWidth() * aspect; if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); - return layoutDoc._fitWidth ? (!this.props.document._nativeHeight ? NumCast(this.props.containingCollection._height) : + return layoutDoc._fitWidth ? (!this.doc._nativeHeight ? NumCast(this.props.containingCollection._height) : Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, NumCast(this.props.containingCollection._height)))) : NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50; @@ -259,7 +256,7 @@ class TreeView extends React.Component<TreeViewProps> { @computed get expandedField() { const ids: { [key: string]: string } = {}; - const doc = this.props.document; + const doc = this.doc; doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); const rows: JSX.Element[] = []; @@ -273,13 +270,12 @@ class TreeView extends React.Component<TreeViewProps> { const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce( (flg, doc) => flg && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true), true); contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : - DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move, + DocListCast(contents), this.props.treeViewDoc, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen, [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields); } else { - contentElement = <EditableView - key="editableView" + contentElement = <EditableView key="editableView" contents={contents !== undefined ? Field.toString(contents as Field) : "null"} height={13} fontSize={12} @@ -307,45 +303,45 @@ class TreeView extends React.Component<TreeViewProps> { return rows; } - rtfWidth = () => Math.min(Doc.Layout(this.props.document)?.[WidthSym](), this.props.panelWidth() - 20); - rtfHeight = () => this.rtfWidth() < Doc.Layout(this.props.document)?.[WidthSym]() ? Math.min(Doc.Layout(this.props.document)?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; + rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.props.panelWidth() - 20); + rtfHeight = () => this.rtfWidth() < this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; @computed get renderContent() { TraceMobx(); - const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined; - if (expandKey !== undefined) { + const expandKey = this.treeViewExpandedView; + if (["links", this.fieldKey].includes(expandKey)) { const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey); const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true), true); const docs = expandKey === "links" ? this.childLinks : this.childDocs; const sortKey = `${this.fieldKey}-sortAscending`; return <ul key={expandKey + "more"} onClick={(e) => { - this.props.document[sortKey] = (this.props.document[sortKey] ? false : (this.props.document[sortKey] === false ? undefined : true)); + this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true)); e.stopPropagation(); }}> {!docs ? (null) : - TreeView.GetChildElements(docs, this.props.treeViewId, Doc.Layout(this.props.document), + TreeView.GetChildElements(docs, this.props.treeViewDoc, this.layoutDoc, this.dataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move, - StrCast(this.props.document.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, + StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen, - [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields)} + [...this.props.renderedIds, this.doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields)} </ul >; } else if (this.treeViewExpandedView === "fields") { - return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}> + return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.doc[Id] + this.doc.title}> {this.expandedField} </div></ul>; } else { - const layoutDoc = Doc.Layout(this.props.document); + const layoutDoc = this.layoutDoc; const panelHeight = layoutDoc.type === DocumentType.RTF ? this.rtfHeight : this.docHeight; const panelWidth = layoutDoc.type === DocumentType.RTF ? this.rtfWidth : this.docWidth; - return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.props.document[Id] + this.props.document.title}> + return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.doc[Id]}> <ContentFittingDocumentView Document={layoutDoc} DataDoc={this.dataDoc} LibraryPath={emptyPath} renderDepth={this.props.renderDepth + 1} rootSelected={returnTrue} - treeViewId={this.props.treeViewId[Id]} + treeViewDoc={undefined} backgroundColor={this.props.backgroundColor} fitToBox={this.boundsOfCollectionDocument !== undefined} FreezeDimensions={true} @@ -372,17 +368,17 @@ class TreeView extends React.Component<TreeViewProps> { } } - get onCheckedClick() { return this.props.onCheckedClick || ScriptCast(this.props.document.onCheckedClick); } + get onCheckedClick() { return this.props.onCheckedClick || ScriptCast(this.doc.onCheckedClick); } @action bulletClick = (e: React.MouseEvent) => { - if (this.onCheckedClick && this.props.document.type !== DocumentType.COL) { + if (this.onCheckedClick && this.doc.type !== DocumentType.COL) { // this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check"; this.onCheckedClick.script.run({ - this: this.props.document.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.props.document, + this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, heading: this.props.containingCollection.title, - checked: this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check", - containingTreeView: this.props.treeViewId, + checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check", + containingTreeView: this.props.treeViewDoc, }, console.log); } else { this.treeViewOpen = !this.treeViewOpen; @@ -392,104 +388,110 @@ class TreeView extends React.Component<TreeViewProps> { @computed get renderBullet() { - const checked = this.props.document.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined; + const checked = this.doc.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.doc.treeViewChecked ?? "unchecked") : undefined; return <div className="bullet" title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"} onClick={this.bulletClick} - style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}> + style={{ color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}> {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />} </div>; } showContextMenu = (e: React.MouseEvent) => { this._docRef.current?.ContentDiv && simulateMouseClick(this._docRef.current.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); - e.stopPropagation(); } focusOnDoc = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(doc)?.props.focus(doc, true); - contextMenuItems = () => { - const focusScript = ScriptField.MakeFunction(`DocFocus(self)`); - return [{ script: focusScript!, label: "Focus" }]; - } + contextMenuItems = () => [{ script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }]; + truncateTitleWidth = () => NumCast(this.props.treeViewDoc.treeViewTruncateTitleWidth, 0); + showTitleEdit = () => ["*", this._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || ""); /** * Renders the EditableView title element for placement into the tree. */ @computed get renderTitle() { TraceMobx(); - (!TreeView._editTitleScript) && (TreeView._editTitleScript = ScriptField.MakeFunction("setInPlace(self, 'editTitle', true)")); - const headerElements = ( + const headerElements = this.props.treeViewHideHeaderFields() ? (null) : <> - <FontAwesomeIcon icon="cog" size="sm" onClick={e => this.showContextMenu(e)}></FontAwesomeIcon> + <FontAwesomeIcon icon="cog" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} /> <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} onPointerDown={action(() => { if (this.treeViewOpen) { - this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") : - this.treeViewExpandedView === "fields" && Doc.Layout(this.props.document) ? "layout" : - this.treeViewExpandedView === "layout" && this.props.document.links ? "links" : + this.doc.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") : + this.treeViewExpandedView === "fields" && this.layoutDoc ? "layout" : + this.treeViewExpandedView === "layout" && DocListCast(this.doc.links).length ? "links" : this.childDocs ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields"); } this.treeViewOpen = true; })}> {this.treeViewExpandedView} </span> - </>); - const openRight = (<div className="treeViewItem-openRight" onClick={this.openRight}> - <FontAwesomeIcon title="open in pane on right" icon="angle-right" size="lg" /> - </div>); + </>; + const view = this.showTitleEdit() ? this.editableView("title") : + <DocumentView + ref={this._docRef} + Document={this.doc} + DataDoc={undefined} + treeViewDoc={this.props.treeViewDoc} + LibraryPath={this.props.libraryPath || emptyPath} + addDocument={undefined} + addDocTab={this.props.addDocTab} + rootSelected={returnTrue} + pinToPres={emptyFunction} + onClick={this.props.onChildClick || this._editTitleScript} + dropAction={this.props.dropAction} + moveDocument={this.move} + removeDocument={this.removeDoc} + ScreenToLocalTransform={this.getTransform} + ContentScaling={returnOne} + PanelWidth={this.truncateTitleWidth} + PanelHeight={returnZero} + NativeHeight={returnZero} + NativeWidth={returnZero} + contextMenuItems={this.contextMenuItems} + renderDepth={1} + focus={returnTrue} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + dontRegisterView={BoolCast(this.props.treeViewDoc.dontRegisterChildViews)} + docFilters={returnEmptyFilter} + ContainingCollectionView={undefined} + ContainingCollectionDoc={this.props.containingCollection} + />; return <> <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`} style={{ - fontWeight: this.props.document.searchMatch ? "bold" : undefined, - textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined, - outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined, + fontWeight: this.doc.searchMatch ? "bold" : undefined, + textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined, + outline: BoolCast(this.doc.workspaceBrush) ? "dashed 1px #06123232" : undefined, pointerEvents: this.props.active() || SnappingManager.GetIsDragging() ? undefined : "none" }} > - {Doc.GetT(this.props.document, "editTitle", "boolean", true) ? - this.editableView("title") : - <DocumentView - ref={this._docRef} - Document={this.props.document} - DataDoc={undefined} - treeViewId={this.props.treeViewId[Id]} - LibraryPath={this.props.libraryPath || emptyPath} - addDocument={undefined} - addDocTab={this.props.addDocTab} - rootSelected={returnTrue} - pinToPres={emptyFunction} - onClick={this.props.onChildClick || TreeView._editTitleScript} - dropAction={this.props.dropAction} - moveDocument={this.move} - removeDocument={this.removeDoc} - ScreenToLocalTransform={this.getTransform} - ContentScaling={returnOne} - PanelWidth={returnZero} - PanelHeight={returnZero} - NativeHeight={returnZero} - NativeWidth={returnZero} - contextMenuItems={this.contextMenuItems} - renderDepth={1} - focus={returnTrue} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} - bringToFront={emptyFunction} - dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildViews)} - docFilters={returnEmptyFilter} - ContainingCollectionView={undefined} - ContainingCollectionDoc={this.props.containingCollection} - />} + {view} </div > - {this.props.treeViewHideHeaderFields() ? (null) : headerElements} - {openRight} + {headerElements} + <div className="treeViewItem-openRight" onClick={this.openRight}> + <FontAwesomeIcon title="open in pane on right" icon="external-link-alt" size="sm" /> + </div> </>; } render() { TraceMobx(); - const sorting = this.props.document[`${this.fieldKey}-sortAscending`]; - //setTimeout(() => runInAction(() => untracked(() => this._overrideTreeViewOpen = this.treeViewOpen)), 0); + const sorting = this.doc[`${this.fieldKey}-sortAscending`]; + if (this.showTitleEdit()) { // find containing CollectionTreeView and set our maximum width so the containing tree view won't have to scroll + let par: any = this._header?.current; + if (par) { + while (par && par.className !== "collectionTreeView-dropTarget") par = par.parentNode; + if (par) { + const par_rect = (par as HTMLElement).getBoundingClientRect(); + const my_recct = this._docRef.current?.ContentDiv?.getBoundingClientRect(); + this._editMaxWidth = Math.max(100, par_rect.right - (my_recct?.left || 0)); + } + } + } else this._editMaxWidth = ""; return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active() && SelectionManager.DeselectAll()}> <li className="collection-child"> - <div className="treeViewItem-header" ref={this._header} onClick={e => { + <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); @@ -507,14 +509,14 @@ class TreeView extends React.Component<TreeViewProps> { {this.renderTitle} </div> <div className="treeViewItem-border" style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}> - {!this.treeViewOpen || this.props.renderedIds.indexOf(this.props.document[Id]) !== -1 ? (null) : this.renderContent} + {!this.treeViewOpen || this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? (null) : this.renderContent} </div> </li> </div>; } public static GetChildElements( childDocs: Doc[], - treeViewId: Doc, + treeViewDoc: Doc, containingCollection: Doc, dataDoc: Doc | undefined, key: string, @@ -624,7 +626,7 @@ class TreeView extends React.Component<TreeViewProps> { libraryPath={libraryPath ? [...libraryPath, containingCollection] : undefined} containingCollection={containingCollection} prevSibling={docs[i]} - treeViewId={treeViewId} + treeViewDoc={treeViewDoc} key={child[Id]} indentDocument={indent} outdentDocument={outdent} @@ -665,21 +667,22 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll private treedropDisposer?: DragManager.DragDropDisposer; private _mainEle?: HTMLDivElement; - @computed get dataDoc() { return this.props.DataDoc || this.props.Document; } + @computed get doc() { return this.props.Document; } + @computed get dataDoc() { return this.props.DataDoc || this.doc; } protected createTreeDropTarget = (ele: HTMLDivElement) => { this.treedropDisposer?.(); if (this._mainEle = ele) { - this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.props.Document, this.onInternalPreDrop.bind(this)); + this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); } } protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { const dragData = de.complete.docDragData; if (dragData) { - if (targetAction && !dragData.draggedDocuments.some(d => d.context === this.props.Document && this.childDocs.includes(d))) { + if (targetAction && !dragData.draggedDocuments.some(d => d.context === this.doc && this.childDocs.includes(d))) { dragData.dropAction = targetAction; - } else dragData.dropAction = this.props.Document[Id] === dragData?.treeViewId ? "same" : dragData.dropAction; + } else dragData.dropAction = this.doc === dragData?.treeViewDoc ? "same" : dragData.dropAction; } } @@ -691,7 +694,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll @action remove = (doc: Doc | Doc[]): boolean => { const docs = doc instanceof Doc ? [doc] : doc; - const targetDataDoc = this.props.Document[DataSym]; + const targetDataDoc = this.doc[DataSym]; const value = DocListCast(targetDataDoc[this.props.fieldKey]); const result = value.filter(v => !docs.includes(v)); if (result.length !== value.length) { @@ -704,9 +707,9 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll addDoc = (doc: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => { const doAddDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => - flg && Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false), true); - if (this.props.Document.resolvedDataDoc instanceof Promise) { - this.props.Document.resolvedDataDoc.then((resolved: any) => doAddDoc(doc)); + flg && Doc.AddDocToList(this.doc[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false), true); + if (this.doc.resolvedDataDoc instanceof Promise) { + this.doc.resolvedDataDoc.then((resolved: any) => doAddDoc(doc)); } else { doAddDoc(doc); } @@ -714,25 +717,26 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll } onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout - if (!e.isPropagationStopped() && this.props.Document === Doc.UserDoc().myWorkspaces) { + if (!e.isPropagationStopped() && this.doc === Doc.UserDoc().myWorkspaces) { ContextMenu.Instance.addItem({ description: "Create Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" }); - ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.remove(this.props.Document), icon: "minus" }); + ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.remove(this.doc), icon: "minus" }); e.stopPropagation(); e.preventDefault(); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - } else if (!e.isPropagationStopped() && this.props.Document === Doc.UserDoc().myRecentlyClosed) { + } else if (!e.isPropagationStopped() && this.doc === Doc.UserDoc().myRecentlyClosed) { ContextMenu.Instance.addItem({ description: "Clear All", event: () => Doc.UserDoc().myRecentlyClosed = new List<Doc>(), icon: "plus" }); e.stopPropagation(); e.preventDefault(); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } else { const layoutItems: ContextMenuProps[] = []; - layoutItems.push({ description: (this.props.Document.treeViewPreventOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.treeViewPreventOpen = !this.props.Document.treeViewPreventOpen, icon: "paint-brush" }); - layoutItems.push({ description: (this.props.Document.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.treeViewHideHeaderFields = !this.props.Document.treeViewHideHeaderFields, icon: "paint-brush" }); - layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" }); + layoutItems.push({ description: (this.doc.treeViewPreventOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.doc.treeViewPreventOpen = !this.doc.treeViewPreventOpen, icon: "paint-brush" }); + layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields, icon: "paint-brush" }); + layoutItems.push({ description: (this.doc.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.doc.treeViewHideTitle = !this.doc.treeViewHideTitle, icon: "paint-brush" }); + layoutItems.push({ description: (this.doc.treeViewHideLinkLines ? "Show" : "Hide") + " Link Lines", event: () => this.doc.treeViewHideLinkLines = !this.doc.treeViewHideLinkLines, icon: "paint-brush" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" }); } - ContextMenu.Instance.addItem({ + !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Buxton Layout", icon: "eye", event: () => { const { ImageDocument, PdfDocument } = Docs.Create; const { Document } = this.props; @@ -745,7 +749,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll const fallback = ImageDocument("http://cs.brown.edu/~bcz/face.gif", { _width: 400 }); // replace with desired double click target let pdfContent: string; - DocListCast(this.dataDoc[this.props.fieldKey]).map(d => { + this.childDocs?.map(d => { DocListCast(d.data).map((img, i) => { const caption = (d.captions as any)[i]; if (caption) { @@ -774,7 +778,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; onClicks.push({ - description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit" + description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit" }); !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); } @@ -784,7 +788,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll @computed get renderClearButton() { return <div id="toolbar" key="toolbar"> <button className="toolbar-button round-button" title="Empty" - onClick={undoBatch(action(() => Doc.GetProto(this.props.Document)[this.props.fieldKey] = undefined))}> + onClick={undoBatch(action(() => Doc.GetProto(this.doc)[this.props.fieldKey] = undefined))}> <FontAwesomeIcon icon={"trash"} size="sm" /> </button> </div >; @@ -794,51 +798,52 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll console.log(e); } render() { - if (!(this.props.Document instanceof Doc)) return (null); - const dropAction = StrCast(this.props.Document.childDropAction) as dropActionType; + TraceMobx(); + if (!(this.doc instanceof Doc)) return (null); + const dropAction = StrCast(this.doc.childDropAction) as dropActionType; const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(d, target, addDoc); const childDocs = this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs; return !childDocs ? (null) : ( - <div className="collectionTreeView-dropTarget" id="body" - style={{ - background: this.props.backgroundColor?.(this.props.Document), - paddingLeft: `${NumCast(this.props.Document._xPadding, 10)}px`, - paddingRight: `${NumCast(this.props.Document._xPadding, 10)}px`, - paddingTop: `${NumCast(this.props.Document._yPadding, 20)}px`, - pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined - }} - onKeyPress={this.onKeyPress} - onContextMenu={this.onContextMenu} - onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()} - onDrop={this.onTreeDrop} - ref={this.createTreeDropTarget}> - {(this.props.treeViewHideTitle || this.props.Document.treeViewHideTitle ? (null) : <EditableView - contents={this.dataDoc.title} - editing={false} - display={"block"} - maxHeight={72} - height={"auto"} - GetValue={() => StrCast(this.dataDoc.title)} - SetValue={undoBatch((value: string) => Doc.SetInPlace(this.dataDoc, "title", value, false) || true)} - OnFillDown={undoBatch((value: string) => { - Doc.SetInPlace(this.dataDoc, "title", value, false); - const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, _LODdisable: true, templates: new List<string>([Templates.Title.Layout]) }); - EditableView.loadId = doc[Id]; - Doc.SetInPlace(doc, "editTitle", true, false); - this.addDoc(doc, childDocs.length ? childDocs[0] : undefined, true); - })} />)} - {this.props.Document.allowClear ? this.renderClearButton : (null)} - <ul className="no-indent" style={{ width: "max-content" }} > - { - TreeView.GetChildElements(childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove, - moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, - this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.props.Document.treeViewHideHeaderFields), - BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick, - this.props.onChildClick || ScriptCast(this.props.Document.onChildClick), this.props.ignoreFields) - } - </ul> - </div > + <div className="collectionTreeView-container" onContextMenu={this.onContextMenu}> + <div className="collectionTreeView-dropTarget" id="body" + style={{ + background: this.props.backgroundColor?.(this.doc), + paddingLeft: `${NumCast(this.doc._xPadding, 10)}px`, + paddingRight: `${NumCast(this.doc._xPadding, 10)}px`, + paddingTop: `${NumCast(this.doc._yPadding, 20)}px`, + pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined + }} + onKeyPress={this.onKeyPress} + onWheel={(e) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()} + onDrop={this.onTreeDrop} + ref={this.createTreeDropTarget}> + {this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? (null) : <EditableView + contents={this.dataDoc.title} + editing={false} + display={"block"} + maxHeight={72} + height={"auto"} + GetValue={() => StrCast(this.dataDoc.title)} + SetValue={undoBatch((value: string) => Doc.SetInPlace(this.dataDoc, "title", value, false) || true)} + OnFillDown={undoBatch((value: string) => { + Doc.SetInPlace(this.dataDoc, "title", value, false); + const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, _LODdisable: true, templates: new List<string>([Templates.Title.Layout]) }); + Doc.SetInPlace(doc, "editTitle", "*", false); + this.addDoc(doc, childDocs.length ? childDocs[0] : undefined, true); + })} />} + {this.doc.allowClear ? this.renderClearButton : (null)} + <ul className="no-indent" style={{ width: "max-content" }} > + { + TreeView.GetChildElements(childDocs, this.doc, this.doc, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove, + moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, + this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields), + BoolCast(this.doc.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick, + this.props.onChildClick || ScriptCast(this.doc.onChildClick), this.props.ignoreFields) + } + </ul> + </div > + </div> ); } } |
