diff options
| author | Melissa Zhang <mzhang19096@gmail.com> | 2020-09-30 22:01:44 -0600 |
|---|---|---|
| committer | Melissa Zhang <mzhang19096@gmail.com> | 2020-09-30 22:01:44 -0600 |
| commit | 09aab9558a26a2d7c8e3d485aca578960af72821 (patch) | |
| tree | b53bdc6f2fcb269b74a097f56bfeec248e7f918b /src/client/views/collections/CollectionTreeView.tsx | |
| parent | bd827b97c719abeadf243ba4f8b2ba417badb65b (diff) | |
| parent | 852ddf70b7ed3d027eb5cb8415df4df77b8652a6 (diff) | |
pull from master
Diffstat (limited to 'src/client/views/collections/CollectionTreeView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 926 |
1 files changed, 138 insertions, 788 deletions
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index f23fa8eb6..a7c8a7cdc 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,667 +1,31 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable } from "mobx"; +import { action, computed } from "mobx"; import { observer } from "mobx-react"; -import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; -import { PrefetchProxy } from '../../../fields/Proxy'; -import { Document, listSpec } from '../../../fields/Schema'; -import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils, returnEmptyFilter } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentManager } from '../../util/DocumentManager'; -import { SnappingManager } from '../../util/SnappingManager'; +import { Document } from '../../../fields/Schema'; +import { ScriptField } from '../../../fields/ScriptField'; +import { BoolCast, NumCast, ScriptCast, StrCast, Cast } from '../../../fields/Types'; +import { TraceMobx } from '../../../fields/util'; +import { emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, Utils } from '../../../Utils'; +import { DocUtils } from '../../documents/Documents'; import { DragManager, dropActionType } from "../../util/DragManager"; -import { Scripting } from '../../util/Scripting'; import { SelectionManager } from '../../util/SelectionManager'; -import { Transform } from '../../util/Transform'; +import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from "../EditableView"; -import { MainView } from '../MainView'; import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; -import { DocumentView } from '../nodes/DocumentView'; -import { ImageBox } from '../nodes/ImageBox'; -import { KeyValueBox } from '../nodes/KeyValueBox'; -import { Templates } from '../Templates'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; -import { CollectionViewType } from './CollectionView'; +import { TreeView } from "./TreeView"; import React = require("react"); -import { makeTemplate } from '../../util/DropConverter'; -import { TraceMobx } from '../../../fields/util'; - -export interface TreeViewProps { - document: Doc; - dataDoc?: Doc; - libraryPath: Doc[] | undefined; - containingCollection: Doc; - prevSibling?: Doc; - renderDepth: number; - deleteDoc: (doc: Doc | Doc[]) => boolean; - moveDocument: DragManager.MoveFunction; - dropAction: dropActionType; - addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean; - pinToPres: (document: Doc) => void; - panelWidth: () => number; - panelHeight: () => number; - ChromeHeight: undefined | (() => number); - addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; - indentDocument?: () => void; - outdentDocument?: () => void; - ScreenToLocalTransform: () => Transform; - backgroundColor?: (doc: Doc, renderDepth: number) => string | undefined; - outerXf: () => { translateX: number, translateY: number }; - treeViewDoc: Doc; - parentKey: string; - active: (outsideReaction?: boolean) => boolean; - treeViewHideHeaderFields: () => boolean; - treeViewPreventOpen: boolean; - renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle - onCheckedClick?: () => ScriptField; - onChildClick?: () => ScriptField; - ignoreFields?: string[]; -} - -@observer -/** - * Renders a treeView of a collection of documents - * - * special fields: - * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden - * treeViewPreventOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) - * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree - */ -class TreeView extends React.Component<TreeViewProps> { - 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.doc.title + ")"; } // this makes mobx trace() statements more descriptive - get defaultExpandedView() { return this.childDocs.length ? 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.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.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.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 ? DocListCast(layout[field]) : undefined) || // else if there's a layout doc, display it's fields - DocListCast(this.doc[field])); // otherwise use the document's data field - } - @computed get childDocs() { return this.childDocList(this.fieldKey); } - @computed get childLinks() { return this.childDocList("links"); } - @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); } - @computed get boundsOfCollectionDocument() { - return StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1 || !DocListCast(this.props.document[this.fieldKey]).length ? undefined : - Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey])); - } - - @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.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); - } - @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); - const script = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" }); - this._editTitleScript = script && (() => script); - 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.doc); - } - - onPointerEnter = (e: React.PointerEvent): void => { - this.props.active(true) && Doc.BrushDoc(this.dataDoc); - if (e.buttons === 1 && SnappingManager.GetIsDragging()) { - this._header!.current!.className = "treeViewItem-header"; - document.addEventListener("pointermove", this.onDragMove, true); - } - } - onPointerLeave = (e: React.PointerEvent): void => { - Doc.UnBrushDoc(this.dataDoc); - if (this._header?.current?.className !== "treeViewItem-header-editing") { - this._header!.current!.className = "treeViewItem-header"; - } - document.removeEventListener("pointermove", this.onDragMove, true); - } - onDragMove = (e: PointerEvent): void => { - Doc.UnBrushDoc(this.dataDoc); - const pt = [e.clientX, e.clientY]; - const rect = this._header!.current!.getBoundingClientRect(); - const before = pt[1] < rect.top + rect.height / 2; - const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); - this._header!.current!.className = "treeViewItem-header"; - if (inside) this._header!.current!.className += " treeViewItem-header-inside"; - else if (before) this._header!.current!.className += " treeViewItem-header-above"; - else if (!before) this._header!.current!.className += " treeViewItem-header-below"; - e.stopPropagation(); - } - - editableView = (key: string, style?: string) => (<EditableView - oneLine={true} - display={"inline-block"} - editing={true} - contents={StrCast(this.doc[key])} - height={12} - sizeToContent={true} - fontStyle={style} - fontSize={12} - GetValue={() => StrCast(this.doc[key])} - SetValue={undoBatch((value: string) => { - Doc.SetInPlace(this.doc, key, value, false) || true; - Doc.SetInPlace(this.doc, "editTitle", undefined, false); - })} - OnFillDown={undoBatch((value: string) => { - Doc.SetInPlace(this.doc, key, value, false); - const doc = Docs.Create.FreeformDocument([], { title: "-", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) }); - 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.doc]); - return false; - }} - OnTab={undoBatch((shift?: boolean) => { - shift ? this.props.outdentDocument?.() : this.props.indentDocument?.(); - 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.treeViewDoc === dragData.treeViewDoc ? "same" : dragData.dropAction); - } - - @undoBatch - treeDrop = (e: Event, de: DragManager.DropEvent) => { - 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 && this.childDocList.length); - const complete = de.complete; - if (complete.linkDragData) { - const sourceDoc = complete.linkDragData.linkSourceDocument; - 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.doc) return true; - const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); - let addDoc = parentAddDoc; - if (inside) { - addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce( - (flg: boolean, doc) => flg && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc), true) || parentAddDoc(doc); - } - const move = (!docDragData.dropAction || docDragData.dropAction === "move" || docDragData.dropAction === "same") && docDragData.moveDocument; - return docDragData.droppedDocuments.reduce((added, d) => (move ? docDragData.moveDocument?.(d, undefined, addDoc) : addDoc(d)) || added, false); - } - return false; - } - - 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); - return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); - } - docTransform = () => this.refTransform(this._dref.current!); - getTransform = () => this.refTransform(this._tref.current!); - docWidth = () => { - 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 = 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.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; - })())); - } - - @computed get expandedField() { - const ids: { [key: string]: string } = {}; - const doc = this.doc; - doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); - - const rows: JSX.Element[] = []; - for (const key of Object.keys(ids).slice().sort()) { - if (this.props.ignoreFields?.includes(key) || key === "title" || key === "treeViewOpen") continue; - const contents = doc[key]; - let contentElement: (JSX.Element | null)[] | JSX.Element = []; - - if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) { - const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); - 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.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" - contents={contents !== undefined ? Field.toString(contents as Field) : "null"} - height={13} - fontSize={12} - GetValue={() => Field.toKeyValueString(doc, key)} - SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} />; - } - rows.push(<div style={{ display: "flex" }} key={key}> - <span style={{ fontWeight: "bold" }}>{key + ":"}</span> - - {contentElement} - </div>); - } - rows.push(<div style={{ display: "flex" }} key={"newKeyValue"}> - <EditableView - key="editableView" - contents={"+key:value"} - height={13} - fontSize={12} - GetValue={() => ""} - SetValue={(value: string) => { - value.indexOf(":") !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(":")), value.substring(value.indexOf(":") + 1, value.length), true); - return true; - }} /> - </div>); - return rows; - } - - 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; - if (["links", "annotations", 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 : expandKey === "annotations" ? this.childAnnos : this.childDocs; - const sortKey = `${this.fieldKey}-sortAscending`; - return <ul key={expandKey + "more"} onClick={(e) => { - this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true)); - e.stopPropagation(); - }}> - {!docs ? (null) : - TreeView.GetChildElements(docs, this.props.treeViewDoc, this.layoutDoc, - this.dataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move, - 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.doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields)} - </ul >; - } else if (this.treeViewExpandedView === "fields") { - return <ul key={this.doc[Id] + this.doc.title}><div ref={this._dref} style={{ display: "inline-block" }} > - {this.expandedField} - </div></ul>; - } else { - const layoutDoc = this.layoutDoc; - const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight; - const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.docWidth; - 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} - treeViewDoc={undefined} - backgroundColor={this.props.backgroundColor} - fitToBox={this.boundsOfCollectionDocument !== undefined} - FreezeDimensions={true} - NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : returnZero} - NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : returnZero} - PanelWidth={panelWidth} - PanelHeight={panelHeight} - focus={returnFalse} - ScreenToLocalTransform={this.docTransform} - docFilters={returnEmptyFilter} - ContainingCollectionDoc={this.props.containingCollection} - ContainingCollectionView={undefined} - addDocument={returnFalse} - moveDocument={this.props.moveDocument} - removeDocument={returnFalse} - parentActive={this.props.active} - whenActiveChanged={emptyFunction} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - bringToFront={returnFalse} - ContentScaling={returnOne} - /> - </div>; - } - } - - get onCheckedClick() { return this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); } - - @action - bulletClick = (e: React.MouseEvent) => { - 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.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, - heading: this.props.containingCollection.title, - checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check", - containingTreeView: this.props.treeViewDoc, - }, console.log); - } else { - this.treeViewOpen = !this.treeViewOpen; - } - e.stopPropagation(); - } - - @computed get renderBullet() { - TraceMobx(); - 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.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?.length ? "caret-square-right" : "caret-right") : (this.childDocs?.length ? "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); - } - focusOnDoc = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(doc)?.props.focus(doc, true); - 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) || ""); - onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.editTitleScript)); - /** - * Renders the EditableView title element for placement into the tree. - */ - @computed - get renderTitle() { - TraceMobx(); - const headerElements = this.props.treeViewHideHeaderFields() ? (null) : - <> - <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.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.treeViewExpandedView === "links" || this.treeViewExpandedView === "layout") && DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : - this.childDocs.length ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields"); - } - this.treeViewOpen = true; - })}> - {this.treeViewExpandedView} - </span> - </>; - 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.onChildClick} - 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} - opacity={returnOne} - 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.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" - }} > - {view} - </div > - {headerElements} - <div className="treeViewItem-openRight" onClick={this.openRight}> - <FontAwesomeIcon title="open in a new pane" icon="external-link-alt" size="sm" /> - </div> - </>; - } - - render() { - TraceMobx(); - 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(true) && SelectionManager.DeselectAll()}> - <li className="collection-child"> - <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(); - SelectionManager.DeselectAll(); - } - }} - onPointerDown={e => { - if (this.props.active(true)) { - e.stopPropagation(); - e.preventDefault(); - } - }} - onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> - {this.renderBullet} - {this.renderTitle} - </div> - <div className="treeViewItem-border" style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}> - {!this.treeViewOpen || this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? (null) : this.renderContent} - </div> - </li> - </div>; - } - public static GetChildElements( - childDocs: Doc[], - treeViewDoc: Doc, - containingCollection: Doc, - dataDoc: Doc | undefined, - key: string, - parentCollectionDoc: Doc | undefined, - parentPrevSibling: Doc | undefined, - add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean, - remove: ((doc: Doc | Doc[]) => boolean), - move: DragManager.MoveFunction, - dropAction: dropActionType, - addDocTab: (doc: Doc, where: string) => boolean, - pinToPres: (document: Doc) => void, - backgroundColor: undefined | ((document: Doc, renderDepth: number) => string | undefined), - screenToLocalXf: () => Transform, - outerXf: () => { translateX: number, translateY: number }, - active: (outsideReaction?: boolean) => boolean, - panelWidth: () => number, - ChromeHeight: undefined | (() => number), - renderDepth: number, - treeViewHideHeaderFields: () => boolean, - treeViewPreventOpen: boolean, - renderedIds: string[], - libraryPath: Doc[] | undefined, - onCheckedClick: undefined | (() => ScriptField), - onChildClick: undefined | (() => ScriptField), - ignoreFields: string[] | undefined - ) { - const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); - if (viewSpecScript) { - childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); - } - - const docs = childDocs.slice(); - const ascending = containingCollection?.[key + "-sortAscending"]; - if (ascending !== undefined) { - const sortAlphaNum = (a: string, b: string): 0 | 1 | -1 => { - const reN = /[0-9]*$/; - const aA = a.replace(reN, ""); // get rid of trailing numbers - const bA = b.replace(reN, ""); - if (aA === bA) { // if header string matches, then compare numbers numerically - const aN = parseInt(a.match(reN)![0], 10); - const bN = parseInt(b.match(reN)![0], 10); - return aN === bN ? 0 : aN > bN ? 1 : -1; - } else { - return aA > bA ? 1 : -1; - } - }; - docs.sort(function (a, b): 0 | 1 | -1 { - const descA = ascending ? b : a; - const descB = ascending ? a : b; - const first = descA.title; - const second = descB.title; - // TODO find better way to sort how to sort.................. - if (typeof first === 'number' && typeof second === 'number') { - return (first - second) > 0 ? 1 : -1; - } - if (typeof first === 'string' && typeof second === 'string') { - return sortAlphaNum(first, second); - } - if (typeof first === 'boolean' && typeof second === 'boolean') { - // if (first === second) { // bugfixing?: otherwise, the list "flickers" because the list is resorted during every load - // return Number(descA.x) > Number(descB.x) ? 1 : -1; - // } - return first > second ? 1 : -1; - } - return ascending ? 1 : -1; - }); - } - - const rowWidth = () => panelWidth() - 20; - return docs.map((child, i) => { - const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, child); - if (!pair.layout || pair.data instanceof Promise) { - return (null); - } - - const indent = i === 0 ? undefined : () => { - if (StrCast(docs[i - 1].layout).indexOf('fieldKey') !== -1) { - const fieldKeysub = StrCast(docs[i - 1].layout).split('fieldKey')[1]; - const fieldKey = fieldKeysub.split("\'")[1]; - if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) { - Doc.AddDocToList(docs[i - 1], fieldKey, child); - docs[i - 1].treeViewOpen = true; - remove(child); - } - } - }; - const outdent = !parentCollectionDoc ? undefined : () => { - if (StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) { - const fieldKeysub = StrCast(parentCollectionDoc.layout).split('fieldKey')[1]; - const fieldKey = fieldKeysub.split("\'")[1]; - Doc.AddDocToList(parentCollectionDoc, fieldKey, child, parentPrevSibling, false); - parentCollectionDoc.treeViewOpen = true; - remove(child); - } - }; - const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => { - return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false); - }; - const childLayout = Doc.Layout(pair.layout); - const rowHeight = () => { - const aspect = NumCast(childLayout._nativeWidth, 0) / NumCast(childLayout._nativeHeight, 0); - return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); - }; - return !(child instanceof Doc) ? (null) : <TreeView - document={pair.layout} - dataDoc={pair.data} - libraryPath={libraryPath ? [...libraryPath, containingCollection] : undefined} - containingCollection={containingCollection} - prevSibling={docs[i]} - treeViewDoc={treeViewDoc} - key={child[Id]} - indentDocument={indent} - outdentDocument={outdent} - onCheckedClick={onCheckedClick} - onChildClick={onChildClick} - renderDepth={renderDepth} - deleteDoc={remove} - addDocument={addDocument} - backgroundColor={backgroundColor} - panelWidth={rowWidth} - panelHeight={rowHeight} - ChromeHeight={ChromeHeight} - moveDocument={move} - dropAction={dropAction} - addDocTab={addDocTab} - pinToPres={pinToPres} - ScreenToLocalTransform={screenToLocalXf} - outerXf={outerXf} - parentKey={key} - active={active} - treeViewHideHeaderFields={treeViewHideHeaderFields} - treeViewPreventOpen={treeViewPreventOpen} - renderedIds={renderedIds} - ignoreFields={ignoreFields} />; - }); - } -} +import { DocumentManager } from '../../util/DocumentManager'; +import { FormattedTextBoxComment } from '../nodes/formattedText/FormattedTextBoxComment'; export type collectionTreeViewProps = { treeViewHideTitle?: boolean; @@ -673,16 +37,16 @@ export type collectionTreeViewProps = { @observer export class CollectionTreeView extends CollectionSubView<Document, Partial<collectionTreeViewProps>>(Document) { private treedropDisposer?: DragManager.DragDropDisposer; + private _isChildActive = false; private _mainEle?: HTMLDivElement; + public _uniqueId = Utils.GenerateGuid(); - @computed get doc() { return this.props.Document; } + @computed get doc() { TraceMobx(); 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.doc, this.onInternalPreDrop.bind(this)); - } + if (this._mainEle = ele) this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); } protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { @@ -699,18 +63,26 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll this.treedropDisposer?.(); } - @action - remove = (doc: Doc | Doc[]): boolean => { + @undoBatch + remove = action((doc: Doc | Doc[]): boolean => { const docs = doc instanceof Doc ? [doc] : doc; const targetDataDoc = this.doc[DataSym]; const value = DocListCast(targetDataDoc[this.props.fieldKey]); const result = value.filter(v => !docs.includes(v)); + SelectionManager.DeselectAll(); if (result.length !== value.length) { + const ind = targetDataDoc[this.props.fieldKey].indexOf(doc); targetDataDoc[this.props.fieldKey] = new List<Doc>(result); + if (ind > 0) { + const prev = targetDataDoc[this.props.fieldKey][ind - 1]; + FormattedTextBox.SelectOnLoad = prev[Id]; + const prevView = DocumentManager.Instance.getDocumentView(prev, this.props.CollectionView); + prevView?.select(false); + } return true; } return false; - } + }); @action addDoc = (doc: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => { const doAddDoc = (doc: Doc | Doc[]) => @@ -718,172 +90,150 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll 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 if (relativeTo === undefined) { + this.props.addDocument(doc); } else { doAddDoc(doc); + (doc instanceof Doc ? [doc] : doc).forEach(d => d.context = this.props.Document); } return true; } 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.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.doc), icon: "minus" }); - e.stopPropagation(); - e.preventDefault(); - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - } 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 { + if (!Doc.UserDoc().noviceMode) { const layoutItems: ContextMenuProps[] = []; 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" }); + 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.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit" }); + !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); } - !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ - description: "Buxton Layout", icon: "eye", event: () => { - const { ImageDocument, PdfDocument } = Docs.Create; - const { Document } = this.props; - const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif"; - const detailView = Cast(Cast(Doc.UserDoc()["template-button-detail"], Doc, null)?.dragFactory, Doc, null); - const heroView = ImageDocument(fallbackImg, { title: "heroView", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work? - heroView.proto!.layout = ImageBox.LayoutString("hero"); - heroView._showTitle = "title"; - heroView._showTitleHover = "titlehover"; - - const fallback = ImageDocument("http://cs.brown.edu/~bcz/face.gif", { _width: 400 }); // replace with desired double click target - let pdfContent: string; - this.childDocs?.map(d => { - DocListCast(d.data).map((img, i) => { - const caption = (d.captions as any)[i]; - if (caption) { - Doc.GetProto(img).caption = caption; - Doc.GetProto(img).doubleClickView = (pdfContent = StrCast(img.additionalMedia_pdfs)) ? PdfDocument(pdfContent, { title: pdfContent }) : fallback; - } - }); - Doc.GetProto(d).type = "buxton"; - Doc.GetProto(d).proto = heroView; // all devices "are" heroViews that share the same layout & defaults. Seems better than making them all be independent and copy a layout string // .layout = ImageBox.LayoutString("hero"); - }); - - const iconBuxtonView = ImageDocument(fallbackImg, { title: "hero", _width: 60, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") }); - iconBuxtonView.isTemplateDoc = makeTemplate(iconBuxtonView, true, "icon_buxton"); - Doc.UserDoc()["template-icon-view-buxton"] = new PrefetchProxy(iconBuxtonView); - const tempIcons = Doc.GetProto(Cast(Doc.UserDoc()["template-icons"], Doc, null)); - Doc.AddDocToList(tempIcons, "data", iconBuxtonView); - - Document.childLayoutTemplate = heroView; - Document.childClickedOpenTemplateView = new PrefetchProxy(detailView); - Document._viewType = CollectionViewType.Time; - Document.forceActive = true; - Document._pivotField = "company"; - Document.childDropAction = "alias"; - } - }); - 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.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit" - }); - !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); } outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {}); @computed get renderClearButton() { - return <div id="toolbar" key="toolbar"> - <button className="toolbar-button round-button" title="Empty" - onClick={undoBatch(action(() => Doc.GetProto(this.doc)[this.props.fieldKey] = undefined))}> + return !this.doc.allowClear ? (null) : <div key="toolbar"> + <button className="toolbar-button round-button" title="Empty" onClick={undoBatch(action(() => Doc.GetProto(this.doc)[this.props.fieldKey] = undefined))}> <FontAwesomeIcon icon={"trash"} size="sm" /> </button> </div >; } - onChildClick = () => { - return this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); + @undoBatch + makeTextCollection = action((childDocs: Doc[]) => { + Doc.SetInPlace(this.doc, "editTitle", undefined, false); + const bullet = TreeView.makeTextBullet(); + bullet.context = this.doc; + this.addDoc(bullet, childDocs.length ? childDocs[0] : undefined, true); + }); + + editableTitle = (childDocs: Doc[]) => { + return !this.dataDoc ? (null) : <EditableView + contents={this.dataDoc.title} + editing={false} + display={"block"} + maxHeight={72} + height={"auto"} + GetValue={() => StrCast(this.dataDoc.title)} + SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { + if (this.props.Document.treeViewOutlineMode && enter) { + this.makeTextCollection(childDocs); + } + return Doc.SetInPlace(this.dataDoc, "title", value, false); + })} />; } - render() { + + + rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.props.PanelWidth() - 20); + rtfOutlineHeight = () => Math.min(this.layoutDoc?.[HeightSym](), (StrCast(this.layoutDoc?._fontSize) ? Number(StrCast(this.layoutDoc?._fontSize, "32px").replace("px", "")) : NumCast(this.layoutDoc?._fontSize)) * 2); + titleTransform = () => this.props.ScreenToLocalTransform().translate(-NumCast(this.doc._xPadding, 10), -NumCast(this.doc._yPadding, 20)); + documentTitle = (childDocs: Doc[]) => { + return <div style={{ display: "inline-block", height: this.rtfOutlineHeight() }} key={this.doc[Id]} + onKeyDown={e => { + e.stopPropagation(); + e.key === "Enter" && this.makeTextCollection(childDocs); + }}> + <ContentFittingDocumentView + Document={this.doc} + DataDoc={undefined} + LayoutTemplateString={FormattedTextBox.LayoutString("text")} + LibraryPath={emptyPath} + renderDepth={this.props.renderDepth + 1} + rootSelected={returnTrue} + treeViewDoc={undefined} + //dontRegisterView={true} + backgroundColor={this.props.backgroundColor} + PanelWidth={this.rtfWidth} + PanelHeight={this.rtfOutlineHeight} + focus={this.props.focus} + ScreenToLocalTransform={this.titleTransform} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.doc} + ContainingCollectionView={this.props.CollectionView} + addDocument={this.props.addDocument} + moveDocument={returnFalse} + removeDocument={returnFalse} + parentActive={this.props.active} + whenActiveChanged={this.props.whenActiveChanged} + addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} + bringToFront={returnFalse} + ContentScaling={returnOne} + /> + </div>; + } + + onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); + whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); }; + active = (outsideReaction: boolean | undefined) => this.props.active(outsideReaction) || this._isChildActive; + @computed get treeChildren() { + TraceMobx(); + return this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs; + } + @computed get treeViewElements() { 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) : ( + return TreeView.GetChildElements(this.treeChildren, this, 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.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields), + BoolCast(this.doc.treeViewPreventOpen), [], this.props.onCheckedClick, + this.onChildClick, this.props.ignoreFields, true, this.whenActiveChanged, this.props.dontRegisterView || Cast(this.props.Document.dontRegisterChildViews, "boolean", null)); + } + @computed get titleBar() { + const hideTitle = this.props.treeViewHideTitle || this.doc.treeViewHideTitle; + return hideTitle ? (null) : (this.doc.treeViewOutlineMode ? this.documentTitle : this.editableTitle)(this.treeChildren); + } + render() { + TraceMobx(); + if (!(this.doc instanceof Doc)) return (null); + const background = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.doc.backgroundColor) || this.props.backgroundColor?.(this.doc, this.props.renderDepth); + const paddingX = `${NumCast(this.doc._xPadding, 10)}px`; + const paddingTop = `${NumCast(this.doc._yPadding, 20)}px`; + const pointerEvents = !this.props.active() && !SnappingManager.GetIsDragging() && !this._isChildActive ? "none" : undefined; + + return !this.treeChildren ? (null) : ( <div className="collectionTreeView-container" onContextMenu={this.onContextMenu}> - <div className="collectionTreeView-dropTarget" id="body" - style={{ - background: this.props.backgroundColor?.(this.doc, this.props.renderDepth), - 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 - }} + <div className="collectionTreeView-dropTarget" + style={{ background, paddingLeft: paddingX, paddingRight: paddingX, paddingTop, pointerEvents }} 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, 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.onChildClick, this.props.ignoreFields) - } + {this.titleBar} + {this.renderClearButton} + <ul className="no-indent"> + {this.treeViewElements} </ul> </div > </div> ); } -} - -Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey: string, facetHeader: string) { - const allCollectionDocs = DocListCast(dataDoc[dataKey]); - const facetValues = Array.from(allCollectionDocs.reduce((set, child) => - set.add(Field.toString(child[facetHeader] as Field)), new Set<string>())); - - let nonNumbers = 0; - facetValues.map(val => { - const num = Number(val); - if (Number.isNaN(num)) { - nonNumbers++; - } - }); - const facetValueDocSet = (nonNumbers / facetValues.length > .1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => { - const doc = new Doc(); - doc.system = true; - doc.title = facetValue.toString(); - doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)", {}, { layoutDoc, facetHeader, facetValue }); - return doc; - }); - return new List<Doc>(facetValueDocSet); -}); - -Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { - const docFilters = Cast(layoutDoc._docFilters, listSpec("string"), []); - for (let i = 0; i < docFilters.length; i += 3) { - const [header, value, state] = docFilters.slice(i, i + 3); - if (header === facetHeader && value === facetValue) { - return state; - } - } - return undefined; -});
\ No newline at end of file +}
\ No newline at end of file |
