diff options
author | Bob Zeleznik <zzzman@gmail.com> | 2020-06-27 19:47:32 -0400 |
---|---|---|
committer | Bob Zeleznik <zzzman@gmail.com> | 2020-06-27 19:47:32 -0400 |
commit | f2bbf54077432852b2a078e70d675bada8b54961 (patch) | |
tree | f8bb1400efa8251015d7fb63939037ebeebafc58 | |
parent | 99d2b19dba130353ac3b021bc7eab079bb125408 (diff) |
cleaned up a bunch of stuff related to TreeViews and link dots. added option for link dots to show up in tree views. changed titles in tree views to be truncatable. fixed edting of treeview titles when an alias is in the tree view as well.
-rw-r--r-- | src/client/documents/Documents.ts | 1 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 19 | ||||
-rw-r--r-- | src/client/util/DragManager.ts | 6 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 2 | ||||
-rw-r--r-- | src/client/views/EditableView.tsx | 60 | ||||
-rw-r--r-- | src/client/views/collections/CollectionTreeView.scss | 3 | ||||
-rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 391 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx | 5 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.scss | 11 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 44 | ||||
-rw-r--r-- | src/client/views/nodes/LinkAnchorBox.tsx | 10 | ||||
-rw-r--r-- | src/client/views/search/SearchItem.tsx | 2 |
12 files changed, 287 insertions, 267 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d5b7f07b5..b175c018e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -168,6 +168,7 @@ export interface DocumentOptions { treeViewOpen?: boolean; // whether this document is expanded in a tree view treeViewExpandedView?: string; // which field/thing is displayed when this item is opened in tree view treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked + treeViewTruncateTitleWidth?: number; limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents // [key: string]: Opt<Field>; pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9a4c95571..2b0b2c738 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -306,21 +306,17 @@ export class CurrentUserUtils { // setup templates for different document types when they are iconified from Document Decorations static setupDefaultIconTemplates(doc: Doc) { if (doc["template-icon-view"] === undefined) { - const iconView = Docs.Create.TextDocument("", { - title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + const iconView = Docs.Create.LabelDocument({ + title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimGray", + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") }); - Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', ""); + // Docs.Create.TextDocument("", { + // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + // }); + // Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', ""); iconView.isTemplateDoc = makeTemplate(iconView); doc["template-icon-view"] = new PrefetchProxy(iconView); } - if (doc["template-icon-view-pdf"] === undefined) { - const iconPdfView = Docs.Create.LabelDocument({ - title: "icon_" + DocumentType.PDF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimGray", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") - }); - iconPdfView.isTemplateDoc = makeTemplate(iconPdfView, true, "icon_" + DocumentType.PDF); - doc["template-icon-view-pdf"] = new PrefetchProxy(iconPdfView); - } if (doc["template-icon-view-rtf"] === undefined) { const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), @@ -605,6 +601,7 @@ export class CurrentUserUtils { if (doc["tabs-button-library"] === undefined) { const libraryStack = new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], { title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", + treeViewTruncateTitleWidth: 150, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same" })) as any as Doc; doc["tabs-button-library"] = new PrefetchProxy(Docs.Create.ButtonDocument({ diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 417ddf989..0db3963b2 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -19,8 +19,6 @@ export function SetupDrag( docFunc: () => Doc | Promise<Doc> | undefined, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, - treeViewId?: string, - dontHideOnDrop?: boolean, dragStarted?: () => void ) { const onRowMove = async (e: PointerEvent) => { @@ -34,8 +32,6 @@ export function SetupDrag( const dragData = new DragManager.DocumentDragData([doc]); dragData.dropAction = dropAction; dragData.moveDocument = moveFunc; - dragData.treeViewId = treeViewId; - dragData.dontHideOnDrop = dontHideOnDrop; DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y); dragStarted?.(); } @@ -128,7 +124,7 @@ export namespace DragManager { draggedDocuments: Doc[]; droppedDocuments: Doc[]; dragDivName?: string; - treeViewId?: string; + treeViewDoc?: Doc; dontHideOnDrop?: boolean; offset: number[]; dropAction: dropActionType; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index f5454216a..6e28ad654 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -77,7 +77,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> get Bounds(): { x: number, y: number, b: number, r: number } { return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => { if (documentView.props.renderDepth === 0 || - documentView.props.treeViewId || + documentView.props.treeViewDoc || Doc.AreProtosEqual(documentView.props.Document, Doc.UserDoc())) { return bounds; } diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index ee3ce1cf3..bab3a1634 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -31,6 +31,7 @@ export interface EditableProps { fontStyle?: string; fontSize?: number; height?: number | "auto"; + sizeToContent?: boolean; maxHeight?: number; display?: string; autosuggestProps?: { @@ -60,13 +61,11 @@ export interface EditableProps { */ @observer export class EditableView extends React.Component<EditableProps> { - public static loadId = ""; @observable _editing: boolean = false; constructor(props: EditableProps) { super(props); this._editing = this.props.editing ? true : false; - EditableView.loadId = ""; } // @action @@ -150,37 +149,44 @@ export class EditableView extends React.Component<EditableProps> { @action setIsFocused = (value: boolean) => { const wasFocused = this._editing; - this._editing = value; + //this._editing = value; return wasFocused !== this._editing; } _ref = React.createRef<HTMLDivElement>(); + renderEditor() { + return this.props.autosuggestProps + ? <Autosuggest + {...this.props.autosuggestProps.autosuggestProps} + inputProps={{ + className: "editableView-input", + onKeyDown: this.onKeyDown, + autoFocus: true, + onBlur: e => this.finalizeEdit(e.currentTarget.value, false, true), + onPointerDown: this.stopPropagation, + onClick: this.stopPropagation, + onPointerUp: this.stopPropagation, + value: this.props.autosuggestProps.value, + onChange: this.props.autosuggestProps.onChange + }} + /> + : <input className="editableView-input" + defaultValue={this.props.GetValue()} + onKeyDown={this.onKeyDown} + autoFocus={true} + onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true)} + onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation} + style={{ display: this.props.display, fontSize: this.props.fontSize, minWidth: 20 }} + placeholder={this.props.placeholder} + />; + } render() { if (this._editing && this.props.GetValue() !== undefined) { - return this.props.autosuggestProps - ? <Autosuggest - {...this.props.autosuggestProps.autosuggestProps} - inputProps={{ - className: "editableView-input", - onKeyDown: this.onKeyDown, - autoFocus: true, - onBlur: e => this.finalizeEdit(e.currentTarget.value, false, true), - onPointerDown: this.stopPropagation, - onClick: this.stopPropagation, - onPointerUp: this.stopPropagation, - value: this.props.autosuggestProps.value, - onChange: this.props.autosuggestProps.onChange - }} - /> - : <input className="editableView-input" - defaultValue={this.props.GetValue()} - onKeyDown={this.onKeyDown} - autoFocus={true} - onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true)} - onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation} - style={{ display: this.props.display, fontSize: this.props.fontSize }} - placeholder={this.props.placeholder} - />; + return this.props.sizeToContent ? + <div style={{ display: "grid", minWidth: 100 }}> + <div style={{ display: "inline-block", position: "relative", height: 0, width: "100%", overflow: "hidden" }}>{this.props.GetValue()}</div> + {this.renderEditor()} + </div> : this.renderEditor(); } else { this.props.autosuggestProps?.resetValue(); return (this.props.contents instanceof ObjectField ? (null) : diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 2aac81146..50f0534bd 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -82,8 +82,6 @@ text-overflow: ellipsis; white-space: pre-wrap; min-width: 10px; - // width:100%;//width: max-content; - } .treeViewItem-openRight { @@ -100,6 +98,7 @@ border-left: dashed 1px #00000042; } +.treeViewItem-header-editing, .treeViewItem-header { border: transparent 1px solid; display: flex; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 68dc0ced2..7ffecdc26 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"); } @@ -110,21 +111,27 @@ class TreeView extends React.Component<TreeViewProps> { } @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.MakeFunction(`setInPlace(self, 'editTitle', '${this._uniqueId}')`); + 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 +143,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 +153,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 +164,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 +203,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 +227,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 +257,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 +271,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,8 +304,8 @@ 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(); @@ -320,32 +317,32 @@ class TreeView extends React.Component<TreeViewProps> { 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]}> + 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 +369,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 +389,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 +510,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 +627,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 +668,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 +695,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 +708,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,22 +718,23 @@ 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" }); } !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ @@ -745,7 +750,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 +779,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 +789,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 >; @@ -795,51 +800,51 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll } render() { TraceMobx(); - if (!(this.props.Document instanceof Doc)) return (null); - const dropAction = StrCast(this.props.Document.childDropAction) as dropActionType; + 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> ); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index ae81b4b36..1a2421bfd 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -10,6 +10,7 @@ import React = require("react"); import { Utils, emptyFunction } from "../../../../Utils"; import { DocumentType } from "../../../documents/DocumentTypes"; import { SnappingManager } from "../../../util/SnappingManager"; +import { Cast } from "../../../../fields/Types"; @observer export class CollectionFreeFormLinksView extends React.Component { @@ -30,8 +31,8 @@ export class CollectionFreeFormLinksView extends React.Component { return drawnPairs; }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]); return connections.filter(c => - c.a.props.Document.type === DocumentType.LINK && - c.a.props.pinToPres !== emptyFunction && c.b.props.pinToPres !== emptyFunction // bcz: this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed + c.a.props.Document.type === DocumentType.LINK + && !c.a.props.treeViewDoc?.treeViewHideLinkLines && !c.b.props.treeViewDoc?.treeViewHideLinkLines ).map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />); } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index b7726f7ba..b978f6245 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -42,6 +42,17 @@ width:10px !important; } } + .documentView-treeView { + max-height: 1.5em; + text-overflow: ellipsis; + display: inline-block; + white-space: pre; + width: 100%; + overflow: hidden; + > .documentView-node { + position: absolute; + } + } .documentView-lock { width: 20; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a2bb9700e..22917f18b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -69,7 +69,7 @@ export interface DocumentViewProps { onDoubleClick?: ScriptField; onPointerDown?: ScriptField; onPointerUp?: ScriptField; - treeViewId?: string; + treeViewDoc?: Doc; dropAction?: dropActionType; dragDivName?: string; nudge?: (x: number, y: number) => void; @@ -237,7 +237,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu dragData.removeDocument = this.props.removeDocument; dragData.moveDocument = this.props.moveDocument;// this.layoutDoc.onDragStart ? undefined : this.props.moveDocument; dragData.dragDivName = this.props.dragDivName; - dragData.treeViewId = this.props.treeViewId; + dragData.treeViewDoc = this.props.treeViewDoc; DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.layoutDoc.onDragStart }); } } @@ -1071,30 +1071,34 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu anchorPanelHeight = () => this.props.PanelHeight() || 1; @computed get anchors() { TraceMobx(); - return this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.presBox || this.props.dontRegisterView ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => - <DocumentView {...this.props} key={i + 1} - Document={d} - ContainingCollectionView={this.props.ContainingCollectionView} - ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection Need a better prop for passing the containing document to the LinkAnchorBox - PanelWidth={this.anchorPanelWidth} - PanelHeight={this.anchorPanelHeight} - ContentScaling={returnOne} - dontRegisterView={false} - forcedBackgroundColor={returnTransparent} - removeDocument={this.hideLinkAnchor} - pointerEvents={false} - LayoutTemplate={undefined} - LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} - />); + return (this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots + this.layoutDoc.presBox || // presentationbox nodes + this.props.dontRegisterView ? (null) : // view that are not registered + DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => + <DocumentView {...this.props} key={i + 1} + Document={d} + ContainingCollectionView={this.props.ContainingCollectionView} + ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection Need a better prop for passing the containing document to the LinkAnchorBox + PanelWidth={this.anchorPanelWidth} + PanelHeight={this.anchorPanelHeight} + ContentScaling={returnOne} + dontRegisterView={false} + forcedBackgroundColor={returnTransparent} + removeDocument={this.hideLinkAnchor} + pointerEvents={false} + LayoutTemplate={undefined} + LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} + />); } @computed get innards() { TraceMobx(); - if (!this.props.PanelWidth()) { // this happens when the document is a tree view label - return <div className="documentView-linkAnchorBoxAnchor" > + if (this.props.treeViewDoc && !this.props.LayoutTemplateString) { // this happens when the document is a tree view label (but not an anchor dot) + return <div className="documentView-treeView" style={{ maxWidth: this.props.PanelWidth() || undefined }}> {StrCast(this.props.Document.title)} {this.anchors} </div>; } + const showTitle = StrCast(this.layoutDoc._showTitle); const showTitleHover = StrCast(this.layoutDoc._showTitleHover); const showCaption = StrCast(this.layoutDoc._showCaption); @@ -1156,7 +1160,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu renderLock() { return (this.Document.isBackground !== undefined || this.isSelected(false)) && ((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) && - this.props.renderDepth > 0 && this.props.PanelWidth() > 0 ? + this.props.renderDepth > 0 && !this.props.treeViewDoc ? <div className="documentView-lock" onClick={() => this.toggleBackground(true)}> <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" /> </div> diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 2bcc6168b..d4ab70200 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -115,8 +115,9 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch render() { TraceMobx(); - const x = this.props.PanelWidth() > 1 ? NumCast(this.rootDoc[this.fieldKey + "_x"], 100) : 0; - const y = this.props.PanelWidth() > 1 ? NumCast(this.rootDoc[this.fieldKey + "_y"], 100) : 0; + const small = this.props.PanelWidth() <= 1; // this happens when rendered in a treeView + const x = NumCast(this.rootDoc[this.fieldKey + "_x"], 100); + const y = NumCast(this.rootDoc[this.fieldKey + "_y"], 100); const c = StrCast(this.layoutDoc._backgroundColor, StrCast(this.layoutDoc.backgroundColor, StrCast(this.dataDoc.backgroundColor, "lightBlue"))); // note this is not where the typical lightBlue default color comes from. See Documents.Create.LinkDocument() const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1"; const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25; @@ -131,7 +132,6 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch </div>} </div> ); - const small = this.props.PanelWidth() <= 1; return <div className={`linkAnchorBox-cont${small ? "-small" : ""} ${this.rootDoc[Id]}`} onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)} onPointerEnter={action(e => LinkDocPreview.LinkInfo = { @@ -143,8 +143,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu} ref={this._ref} style={{ background: c, - left: !small ? `calc(${x}% - 7.5px)` : undefined, - top: !small ? `calc(${y}% - 7.5px)` : undefined, + left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`, + top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`, transform: `scale(${anchorScale / this.props.ContentScaling()})` }} > {!this._editing && !this._forceOpen ? (null) : diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 74262d81a..2436bf418 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -85,7 +85,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> { const item = React.createRef<HTMLDivElement>(); return <div className="collection" key={doc.col[Id] + doc.target[Id]} ref={item}> <div className="collection-item" onPointerDown={ - SetupDrag(item, () => doc.col, undefined, undefined, undefined, undefined, () => SearchBox.Instance.closeSearch())}> + SetupDrag(item, () => doc.col, undefined, undefined, () => SearchBox.Instance.closeSearch())}> <FontAwesomeIcon icon={faStickyNote} /> </div> <a onClick={this.getOnClick(doc)}>{doc.col.title}</a> |