From f62fb0f5f90eee7c946deaa3a6e856b76e88f9f1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 18 Sep 2020 13:17:07 -0400 Subject: cleaned up warnings and split collection tree view into Tree View a --- src/Utils.ts | 3 +- src/client/views/DocumentButtonBar.tsx | 4 +- src/client/views/EditableView.tsx | 2 +- .../views/collections/CollectionTreeView.scss | 112 --- .../views/collections/CollectionTreeView.tsx | 810 +-------------------- src/client/views/collections/TreeView.scss | 115 +++ src/client/views/collections/TreeView.tsx | 760 +++++++++++++++++++ src/client/views/nodes/FilterBox.tsx | 10 + src/client/views/nodes/PresBox.tsx | 27 +- .../views/presentationview/PresElementBox.tsx | 2 +- 10 files changed, 921 insertions(+), 924 deletions(-) create mode 100644 src/client/views/collections/TreeView.scss create mode 100644 src/client/views/collections/TreeView.tsx (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 7dff1ac55..cc7ee9537 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -518,7 +518,8 @@ export function clearStyleSheetRules(sheet: any) { return false; } -export function simulateMouseClick(element: Element, x: number, y: number, sx: number, sy: number, rightClick = true) { +export function simulateMouseClick(element: Element | null | undefined, x: number, y: number, sx: number, sy: number, rightClick = true) { + if (!element) return; ["pointerdown", "pointerup"].map(event => element.dispatchEvent( new PointerEvent(event, { view: window, diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 575ebaf3b..c5fd3c777 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -184,7 +184,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV @computed get pinButton() { const targetDoc = this.view0?.props.Document; - let isPinned = targetDoc && Doc.isDocPinned(targetDoc); + const isPinned = targetDoc && Doc.isDocPinned(targetDoc); return !targetDoc ? (null) :
{"Pin to presentation"}
}>
(DocumentV this.props.views()[0]?.select(false); this._tooltipOpen = false; setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction); - }) + }); onAliasButtonMoved = () => { if (this._dragRef.current) { const dragDocView = this.view0!; diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index cbbd78a20..d35271ffd 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -204,7 +204,7 @@ export class EditableView extends React.Component { setTimeout(() => this.props.autosuggestProps?.resetValue(), 0); return this.props.contents instanceof ObjectField ? (null) :
{ this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()} diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index aaf17153a..20bfc0e9d 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -30,24 +30,6 @@ padding-left: 0; } - .outline-bullet { - position: relative; - width: 15px; - color: $intermediate-color; - transform: scale(0.5); - display: inline-block; - } - - .bullet { - position: relative; - width: 15px; - color: $intermediate-color; - margin-top: 3px; - transform: scale(1.3, 1.3); - border: #80808030 1px solid; - border-radius: 4px; - } - .editableView-container { font-weight: bold; } @@ -101,98 +83,4 @@ padding-left: 3px; padding-right: 3px; padding-bottom: 2px; -} - -.treeViewItem-container-active { - z-index: 100; - position: relative;; - .formattedTextbox-sidebar { - background-color: #ffff001f !important; - height: 500px !important; - } -} - -.treeViewItem-openRight { - display: none; - height: 17px; - width: 15px; -} - -.treeViewItem-openRight:hover { - background: #797777; - cursor: pointer; -} - -.treeViewItem-border-outline, -.treeViewItem-border { - display: flex; - overflow: hidden; -} -.treeViewItem-border{ - border-left: dashed 1px #00000042; -} - -.treeViewItem-header-editing, -.treeViewItem-header { - border: transparent 1px solid; - display: flex; - //align-items: center; - ::-webkit-scrollbar { - display: none; - } - .formattedTextBox-cont { - .formattedTextbox-sidebar { - overflow: visible !important; - border-left: unset; - } - overflow: visible !important; - } - - .editableView-container-editing-oneLine { - min-width: 15px; - } - - .documentView-node-topmost { - width: unset; - } - - >svg { - display: none; - } - -} - -.treeViewItem-header:hover { - .collectionTreeView-keyHeader { - display: inherit; - } - - >svg { - display: inherit; - } - - .treeViewItem-openRight { - display: inline-block; - height: 17px; - width: 15px; - - // display: inline; - svg { - display: block; - padding: 0px; - margin-left: 3px; - } - } -} - -.treeViewItem-header-above { - border-top: black 1px solid; -} - -.treeViewItem-header-below { - border-bottom: black 1px solid; -} - -.treeViewItem-header-inside { - border: black 1px solid; } \ No newline at end of file diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 19b8400c8..46f18099a 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,791 +1,29 @@ 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, DocListCastOrNull } from '../../../fields/Doc'; +import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; -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, returnEmptyDoclist } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; -import { DocumentType } from "../../documents/DocumentTypes"; -import { SnappingManager } from '../../util/SnappingManager'; +import { Document } from '../../../fields/Schema'; +import { ScriptField } from '../../../fields/ScriptField'; +import { BoolCast, NumCast, ScriptCast, StrCast } 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 { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; -import { DocumentView } from '../nodes/DocumentView'; -import { KeyValueBox } from '../nodes/KeyValueBox'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; -import { CollectionViewType, CollectionView } from './CollectionView'; +import { TreeView } from "./TreeView"; import React = require("react"); -import { TraceMobx } from '../../../fields/util'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; -import { RichTextField } from '../../../fields/RichTextField'; -import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { DocumentManager } from '../../util/DocumentManager'; - -export interface TreeViewProps { - document: Doc; - dataDoc?: Doc; - containingCollection: Doc; - prevSibling?: Doc; - renderDepth: number; - removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined; - 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: Opt, renderDepth: number) => string | undefined; - outerXf: () => { translateX: number, translateY: number }; - treeView: CollectionTreeView; - 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[]; - firstLevel: boolean; - whenActiveChanged: (isActive: boolean) => void; -} - -@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 { - private _editTitleScript: (() => ScriptField) | undefined; - private _openScript: (() => ScriptField) | undefined; - private _header?: React.RefObject = React.createRef(); - private _treedropDisposer?: DragManager.DragDropDisposer; - private _dref = React.createRef(); - private _tref = React.createRef(); - private _docRef = React.createRef(); - 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 treeViewLockExpandedView() { return this.doc.treeViewLockExpandedView; } - get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.noviceMode || this.outlineMode ? "layout" : "fields"); } - get treeViewDefaultExpandedView() { return this.treeViewLockExpandedView ? this.defaultExpandedView : (this.childDocs ? this.fieldKey : this.defaultExpandedView); } - @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 outlineMode() { return this.props.treeView.doc.treeViewOutlineMode; } - @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.treeViewDefaultExpandedView); } - @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 ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field - (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields - DocListCastOrNull(this.doc[field])); // otherwise use the document's data field - } - @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, "add:right"); - @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc); - } - @undoBatch @action remove = (doc: Doc | Doc[], key: string) => { - this.props.treeView.props.select(false); - return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); - } - @undoBatch @action removeDoc = (doc: Doc | Doc[]) => this.remove(doc, Doc.LayoutFieldKey(this.doc)); - - constructor(props: any) { - super(props); - const titleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" }); - const openScript = ScriptField.MakeScript(`openOnRight(self)`); - const treeOpenScript = ScriptField.MakeScript(`self.treeViewOpen = !self.treeViewOpen`); - this._editTitleScript = !Doc.IsSystem(this.props.document) ? titleScript && (() => titleScript) : treeOpenScript && (() => treeOpenScript); - this._openScript = !Doc.IsSystem(this.props.document) ? openScript && (() => openScript) : undefined; - 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); - } - - componentWillUnmount() { - document.removeEventListener("pointermove", this.onDragMove, true); - document.removeEventListener("pointermove", this.onDragUp, true); - } - - onDragUp = (e: PointerEvent) => { - document.removeEventListener("pointerup", this.onDragUp, true); - document.removeEventListener("pointermove", this.onDragMove, true); - } - 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.removeEventListener("pointermove", this.onDragMove, true); - document.addEventListener("pointermove", this.onDragMove, true); - document.removeEventListener("pointerup", this.onDragUp, true); - document.addEventListener("pointerup", this.onDragUp, 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("pointerup", this.onDragUp, true); - document.removeEventListener("pointermove", this.onDragMove, true); - } - onDragMove = (e: PointerEvent): void => { - Doc.UnBrushDoc(this.dataDoc); - const pt = [e.clientX, e.clientY]; - const rect = this._header!.current!.getBoundingClientRect(); - const before = pt[1] < rect.top + rect.height / 2; - const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); - this._header!.current!.className = "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(); - } - - public static makeTextBullet() { - const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10 }); - Doc.GetProto(bullet).layout = CollectionView.LayoutString("data"); - Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); - Doc.GetProto(bullet).data = new List([]); - Doc.SetInPlace(bullet, "editTitle", "*", false); - FormattedTextBox.SelectOnLoad = bullet[Id]; - return bullet; - } - - makeTextCollection = () => { - Doc.SetInPlace(this.doc, "editTitle", undefined, false); - const bullet = TreeView.makeTextBullet(); - const added = this.props.addDocument(bullet); - bullet.context = this.props.treeView.Document; - return added; - } - - editableView = (key: string, style?: string) => ( StrCast(this.doc[key])} - SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { - if (this.outlineMode && enterKey) { - Doc.SetInPlace(this.doc, key, value, false); - this.makeTextCollection(); - } else { - Doc.SetInPlace(this.doc, key, value, false) || true; - Doc.SetInPlace(this.doc, "editTitle", undefined, false); - } - })} - onClick={() => { - SelectionManager.DeselectAll(); - Doc.UserDoc().activeSelection = new List([this.doc]); - return false; - }} - OnEmpty={undoBatch(() => this.props.treeView.doc.treeViewOutlineMode && this.props.removeDoc?.(this.doc))} - OnTab={undoBatch((shift?: boolean) => { - shift ? this.props.outdentDocument?.() : this.props.indentDocument?.(); - setTimeout(() => Doc.SetInPlace(this.doc, "editTitle", `${this.props.treeView._uniqueId}`, false), 0); - })} - />) - - preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { - const dragData = de.complete.docDragData; - dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? "same" : dragData.dropAction); - } - - @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) { - const localAdd = (doc: Doc) => { - const added = Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - added && (doc.context = this.doc.context); - return added; - }; - addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce( - (flg: boolean, doc) => flg && localAdd(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 localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { - const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - added && (doc.context = this.doc.context); - return added; - }; - const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); - contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), - this.props.treeView, 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.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged); - } else { - contentElement = Field.toKeyValueString(doc, key)} - SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} />; - } - rows.push(
- {key + ":"} -   - {contentElement} -
); - } - rows.push(
- ""} - SetValue={(value: string) => { - value.indexOf(":") !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(":")), value.substring(value.indexOf(":") + 1, value.length), true); - return true; - }} /> -
); - 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; - rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), 20); - - @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 localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { - const added = Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true); - added && (doc.context = this.doc.context); - return added; - }; - const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); - const docs = expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs; - const sortKey = `${this.fieldKey}-sortAscending`; - return
    { - !this.outlineMode && (this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true))); - e.stopPropagation(); - }}> - {!docs ? (null) : - TreeView.GetChildElements(docs, this.props.treeView, 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.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged)} -
; - } else if (this.treeViewExpandedView === "fields") { - return
    - {this.expandedField} -
; - } 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
- -
; - } - } - - 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.treeView.props.Document, - }, console.log); - } else { - this.treeViewOpen = !this.treeViewOpen; - } - e.stopPropagation(); - } - - @computed get renderOutlineBullet() { - TraceMobx(); - return
- {(this.doc.text as RichTextField)?.Text ? : (null)} -
; - } - @computed get renderBullet() { - TraceMobx(); - const checked = this.doc.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.doc.treeViewChecked ?? "unchecked") : undefined; - return
- {} -
; - } - - showContextMenu = (e: React.MouseEvent) => { - this._docRef.current?.ContentDiv && simulateMouseClick(this._docRef.current.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); - } - contextMenuItems = () => Doc.IsSystem(this.doc) ? [] : [{ script: ScriptField.MakeFunction(`openOnRight(self)`)!, label: "Open" }, { script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }]; - truncateTitleWidth = () => NumCast(this.props.treeView.props.Document.treeViewTruncateTitleWidth, 0); - @computed get showTitleEdit() { - return ["*", this._uniqueId, this.props.treeView._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || ""); - } - onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); - onChildDoubleClick = () => (!this.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); - /** - * Renders the EditableView title element for placement into the tree. - */ - @computed - get renderTitle() { - TraceMobx(); - const headerElements = this.props.treeViewHideHeaderFields() ? (null) : - <> - { this.showContextMenu(e); e.stopPropagation(); }} /> - { - if (this.treeViewOpen) { - this.doc.treeViewExpandedView = this.treeViewLockExpandedView ? this.doc.treeViewExpandedView : - this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode || this.outlineMode ? "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 ? this.fieldKey : (Doc.UserDoc().noviceMode || this.outlineMode ? "layout" : "fields"); - } - this.treeViewOpen = true; - })}> - {this.treeViewExpandedView} - - ; - const view = this.showTitleEdit ? this.editableView("title") : - ; - return <> -
- {view} -
- {Doc.IsSystem(this.doc) && Doc.UserDoc().noviceMode ? (null) : headerElements} - ; - } - - refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); - - render() { - TraceMobx(); - if (this.props.renderedIds.indexOf(this.doc[Id]) !== -1) return null; - 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 = ""; - const selected = SelectionManager.IsSelected(DocumentManager.Instance.getFirstDocumentView(this.doc)); - return this.doc.treeViewHideHeader || this.outlineMode ? - !StrCast(Doc.LayoutField(this.doc)).includes("CollectionView") ? this.renderContent : -
this.props.active(true) && SelectionManager.DeselectAll()} - onKeyDown={e => { - e.stopPropagation(); - e.key === "Backspace" && this.doc.text && !(this.doc.text as RichTextField)?.Text && UndoManager.RunInBatch(() => this.props.removeDoc?.(this.doc), "delete"); - e.key === "Tab" && UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.() : this.props.indentDocument?.(), "tab"); - e.key === "Enter" && UndoManager.RunInBatch(() => this.makeTextCollection(), "bullet"); - e.key === "Tab" && setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); - }} - > -
{ if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }} - onPointerDown={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }} - onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> - {this.outlineMode ? this.renderOutlineBullet : this.renderBullet} -
- -
-
- -
- {!this.treeViewOpen ? (null) : this.renderContent} -
-
: -
this.props.active(true) && SelectionManager.DeselectAll()}> -
  • -
    { - 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.outlineMode ? this.renderOutlineBullet : this.renderBullet} - {this.renderTitle} -
    -
    - {!this.treeViewOpen ? (null) : this.renderContent} -
    -
  • -
    ; - } - public static GetChildElements( - childDocs: Doc[], - treeView: CollectionTreeView, - 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: Opt, 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[], - onCheckedClick: undefined | (() => ScriptField), - onChildClick: undefined | (() => ScriptField), - ignoreFields: string[] | undefined, - firstLevel: boolean, - whenActiveChanged: (isActive: boolean) => void - ) { - 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 (remove && 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) { - remove(child); - FormattedTextBox.SelectOnLoad = child[Id]; - Doc.AddDocToList(docs[i - 1], fieldKey, child); - docs[i - 1].treeViewOpen = true; - child.context = treeView.Document; - } - } - }; - const outdent = !parentCollectionDoc ? undefined : () => { - if (remove && StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) { - const fieldKeysub = StrCast(parentCollectionDoc.layout).split('fieldKey')[1]; - const fieldKey = fieldKeysub.split("\'")[1]; - remove(child); - FormattedTextBox.SelectOnLoad = child[Id]; - Doc.AddDocToList(parentCollectionDoc, fieldKey, child, parentPrevSibling, false); - parentCollectionDoc.treeViewOpen = true; - child.context = treeView.Document; - } - }; - const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => { - return add(doc, 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) : ; - }); - } -} export type collectionTreeViewProps = { treeViewHideTitle?: boolean; @@ -797,18 +35,16 @@ export type collectionTreeViewProps = { @observer export class CollectionTreeView extends CollectionSubView>(Document) { private treedropDisposer?: DragManager.DragDropDisposer; + private _isChildActive = false; private _mainEle?: HTMLDivElement; - public _uniqueId = Utils.GenerateGuid(); - _isChildActive = false; + @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.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) => { @@ -873,8 +109,7 @@ export class CollectionTreeView extends CollectionSubView -
    ; @@ -984,15 +219,4 @@ export class CollectionTreeView extends CollectionSubView ); } -} - -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 diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss new file mode 100644 index 000000000..17c6b0750 --- /dev/null +++ b/src/client/views/collections/TreeView.scss @@ -0,0 +1,115 @@ +@import "../globalCssVariables"; + +.treeView-container, +.treeView-container-active { + .bullet-outline { + position: relative; + width: 15px; + color: $intermediate-color; + transform: scale(0.5); + display: inline-block; + } + + .bullet { + position: relative; + width: 15px; + color: $intermediate-color; + margin-top: 3px; + transform: scale(1.3, 1.3); + border: #80808030 1px solid; + border-radius: 4px; + } +} +.treeView-container-active { + z-index: 100; + position: relative;; + .formattedTextbox-sidebar { + background-color: #ffff001f !important; + height: 500px !important; + } +} + +.treeView-openRight { + display: none; + height: 17px; + width: 15px; +} + +.treeView-openRight:hover { + background: #797777; + cursor: pointer; +} + +.treeView-border-outline, +.treeView-border { + display: flex; + overflow: hidden; +} +.treeView-border{ + border-left: dashed 1px #00000042; +} + +.treeView-header-editing, +.treeView-header { + border: transparent 1px solid; + display: flex; + //align-items: center; + ::-webkit-scrollbar { + display: none; + } + .formattedTextBox-cont { + .formattedTextbox-sidebar { + overflow: visible !important; + border-left: unset; + } + overflow: visible !important; + } + + .editableView-container-editing-oneLine { + min-width: 15px; + } + + .documentView-node-topmost { + width: unset; + } + + >svg { + display: none; + } + +} + +.treeView-header:hover { + .collectionTreeView-keyHeader { + display: inherit; + } + + >svg { + display: inherit; + } + + .treeView-openRight { + display: inline-block; + height: 17px; + width: 15px; + + // display: inline; + svg { + display: block; + padding: 0px; + margin-left: 3px; + } + } +} + +.treeView-header-above { + border-top: black 1px solid; +} + +.treeView-header-below { + border-bottom: black 1px solid; +} + +.treeView-header-inside { + border: black 1px solid; +} \ No newline at end of file diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx new file mode 100644 index 000000000..e509eb78d --- /dev/null +++ b/src/client/views/collections/TreeView.tsx @@ -0,0 +1,760 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { RichTextField } from '../../../fields/RichTextField'; +import { listSpec } from '../../../fields/Schema'; +import { ComputedField, ScriptField } from '../../../fields/ScriptField'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { TraceMobx } from '../../../fields/util'; +import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; +import { Docs, DocUtils } from '../../documents/Documents'; +import { DocumentType } from "../../documents/DocumentTypes"; +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; +import { DocumentManager } from '../../util/DocumentManager'; +import { DragManager, dropActionType } from "../../util/DragManager"; +import { SelectionManager } from '../../util/SelectionManager'; +import { SnappingManager } from '../../util/SnappingManager'; +import { Transform } from '../../util/Transform'; +import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { EditableView } from "../EditableView"; +import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; +import { DocumentView } from '../nodes/DocumentView'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; +import { KeyValueBox } from '../nodes/KeyValueBox'; +import { CollectionTreeView } from './CollectionTreeView'; +import { CollectionView, CollectionViewType } from './CollectionView'; +import "./TreeView.scss"; +import React = require("react"); + +export interface TreeViewProps { + document: Doc; + dataDoc?: Doc; + containingCollection: Doc; + prevSibling?: Doc; + renderDepth: number; + removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined; + 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: Opt, renderDepth: number) => string | undefined; + outerXf: () => { translateX: number, translateY: number }; + treeView: CollectionTreeView; + 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[]; + firstLevel: boolean; + whenActiveChanged: (isActive: boolean) => void; +} + +@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 + */ +export class TreeView extends React.Component { + private _editTitleScript: (() => ScriptField) | undefined; + private _openScript: (() => ScriptField) | undefined; + private _header?: React.RefObject = React.createRef(); + private _treedropDisposer?: DragManager.DragDropDisposer; + private _dref = React.createRef(); + private _tref = React.createRef(); + private _docRef = React.createRef(); + 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 treeViewLockExpandedView() { return this.doc.treeViewLockExpandedView; } + get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.noviceMode || this.outlineMode ? "layout" : "fields"); } + get treeViewDefaultExpandedView() { return this.treeViewLockExpandedView ? this.defaultExpandedView : (this.childDocs ? this.fieldKey : this.defaultExpandedView); } + @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 outlineMode() { return this.props.treeView.doc.treeViewOutlineMode; } + @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.treeViewDefaultExpandedView); } + @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 ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field + (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields + DocListCastOrNull(this.doc[field])); // otherwise use the document's data field + } + @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, "add:right"); + @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { + return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc); + } + @undoBatch @action remove = (doc: Doc | Doc[], key: string) => { + this.props.treeView.props.select(false); + return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); + } + @undoBatch @action removeDoc = (doc: Doc | Doc[]) => this.remove(doc, Doc.LayoutFieldKey(this.doc)); + + constructor(props: any) { + super(props); + const titleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" }); + const openScript = ScriptField.MakeScript(`openOnRight(self)`); + const treeOpenScript = ScriptField.MakeScript(`self.treeViewOpen = !self.treeViewOpen`); + this._editTitleScript = !Doc.IsSystem(this.props.document) ? titleScript && (() => titleScript) : treeOpenScript && (() => treeOpenScript); + this._openScript = !Doc.IsSystem(this.props.document) ? openScript && (() => openScript) : undefined; + 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); + } + + componentWillUnmount() { + document.removeEventListener("pointermove", this.onDragMove, true); + document.removeEventListener("pointermove", this.onDragUp, true); + } + + onDragUp = (e: PointerEvent) => { + document.removeEventListener("pointerup", this.onDragUp, true); + document.removeEventListener("pointermove", this.onDragMove, true); + } + onPointerEnter = (e: React.PointerEvent): void => { + this.props.active(true) && Doc.BrushDoc(this.dataDoc); + if (e.buttons === 1 && SnappingManager.GetIsDragging()) { + this._header!.current!.className = "treeView-header"; + document.removeEventListener("pointermove", this.onDragMove, true); + document.addEventListener("pointermove", this.onDragMove, true); + document.removeEventListener("pointerup", this.onDragUp, true); + document.addEventListener("pointerup", this.onDragUp, true); + } + } + onPointerLeave = (e: React.PointerEvent): void => { + Doc.UnBrushDoc(this.dataDoc); + if (this._header?.current?.className !== "treeView-header-editing") { + this._header!.current!.className = "treeView-header"; + } + document.removeEventListener("pointerup", this.onDragUp, true); + document.removeEventListener("pointermove", this.onDragMove, true); + } + onDragMove = (e: PointerEvent): void => { + Doc.UnBrushDoc(this.dataDoc); + const pt = [e.clientX, e.clientY]; + const rect = this._header!.current!.getBoundingClientRect(); + const before = pt[1] < rect.top + rect.height / 2; + const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); + this._header!.current!.className = "treeView-header"; + if (inside) this._header!.current!.className += " treeView-header-inside"; + else if (before) this._header!.current!.className += " treeView-header-above"; + else if (!before) this._header!.current!.className += " treeView-header-below"; + e.stopPropagation(); + } + + public static makeTextBullet() { + const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10 }); + Doc.GetProto(bullet).layout = CollectionView.LayoutString("data"); + Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); + Doc.GetProto(bullet).data = new List([]); + Doc.SetInPlace(bullet, "editTitle", "*", false); + FormattedTextBox.SelectOnLoad = bullet[Id]; + return bullet; + } + + makeTextCollection = () => { + Doc.SetInPlace(this.doc, "editTitle", undefined, false); + const bullet = TreeView.makeTextBullet(); + const added = this.props.addDocument(bullet); + bullet.context = this.props.treeView.Document; + return added; + } + + editableView = (key: string, style?: string) => ( StrCast(this.doc[key])} + SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { + Doc.SetInPlace(this.doc, key, value, false); + if (this.outlineMode && enterKey) { + this.makeTextCollection(); + } else { + Doc.SetInPlace(this.doc, "editTitle", undefined, false); + } + })} + onClick={() => { + SelectionManager.DeselectAll(); + Doc.UserDoc().activeSelection = new List([this.doc]); + return false; + }} + OnEmpty={undoBatch(() => this.props.treeView.doc.treeViewOutlineMode && this.props.removeDoc?.(this.doc))} + OnTab={undoBatch((shift?: boolean) => { + shift ? this.props.outdentDocument?.() : this.props.indentDocument?.(); + setTimeout(() => Doc.SetInPlace(this.doc, "editTitle", `${this.props.treeView._uniqueId}`, false), 0); + })} + />) + + preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { + const dragData = de.complete.docDragData; + dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? "same" : dragData.dropAction); + } + + @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); + if (de.complete.linkDragData) { + const sourceDoc = de.complete.linkDragData.linkSourceDocument; + const destDoc = this.doc; + DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", ""); + e.stopPropagation(); + } + const docDragData = de.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) { + const localAdd = (doc: Doc) => { + const added = Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); + added && (doc.context = this.doc.context); + return added; + }; + addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce( + (flg: boolean, doc) => flg && localAdd(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 rows: JSX.Element[] = []; + const doc = this.doc; + doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); + + for (const key of Object.keys(ids).slice().sort()) { + if (this.props.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 localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { + const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); + added && (doc.context = this.doc.context); + return added; + }; + const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); + contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), + this.props.treeView, 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.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged); + } else { + contentElement = Field.toKeyValueString(doc, key)} + SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} />; + } + rows.push(
    + {key + ":"} +   + {contentElement} +
    ); + } + rows.push(
    + value.indexOf(":") !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(":")), value.substring(value.indexOf(":") + 1, value.length), true)} /> +
    ); + return rows; + } + + 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; + rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), 20); + + @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 localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { + const added = Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true); + added && (doc.context = this.doc.context); + return added; + }; + const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); + const docs = expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs; + const sortKey = `${this.fieldKey}-sortAscending`; + return
      { + !this.outlineMode && (this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true))); + e.stopPropagation(); + }}> + {!docs ? (null) : + TreeView.GetChildElements(docs, this.props.treeView, 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.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged)} +
    ; + } else if (this.treeViewExpandedView === "fields") { + return
      + {this.expandedField} +
    ; + } 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
    + +
    ; + } + } + + get onCheckedClick() { return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); } + + @action + bulletClick = (e: React.MouseEvent) => { + if (this.onCheckedClick) { + this.onCheckedClick?.script.run({ + this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, + heading: this.props.containingCollection.title, + checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check", + containingTreeView: this.props.treeView.props.Document, + }, console.log); + } else { + this.treeViewOpen = !this.treeViewOpen; + } + e.stopPropagation(); + } + + @computed get renderBullet() { + TraceMobx(); + const checked = this.onCheckedClick ? (this.doc.treeViewChecked ?? "unchecked") : undefined; + return
    + {this.outlineMode && !(this.doc.text as RichTextField)?.Text ? (null) : + } +
    ; + } + @computed get showTitleEditorControl() { return ["*", this._uniqueId, this.props.treeView._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || ""); } + @computed get headerElements() { + return (Doc.IsSystem(this.doc) && Doc.UserDoc().noviceMode) || this.props.treeViewHideHeaderFields() ? (null) : + <> + { this.showContextMenu(e); e.stopPropagation(); }} /> + { + if (this.treeViewOpen) { + this.doc.treeViewExpandedView = this.treeViewLockExpandedView ? this.doc.treeViewExpandedView : + this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode || this.outlineMode ? "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 ? this.fieldKey : (Doc.UserDoc().noviceMode || this.outlineMode ? "layout" : "fields"); + } + this.treeViewOpen = true; + })}> + {this.treeViewExpandedView} + + ; + } + + showContextMenu = (e: React.MouseEvent) => simulateMouseClick(this._docRef.current?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); + contextMenuItems = () => Doc.IsSystem(this.doc) ? [] : [{ script: ScriptField.MakeFunction(`openOnRight(self)`)!, label: "Open" }, { script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }]; + truncateTitleWidth = () => NumCast(this.props.treeView.props.Document.treeViewTruncateTitleWidth, 0); + onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); + onChildDoubleClick = () => (!this.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); + /** + * Renders the EditableView title element for placement into the tree. + */ + @computed + get renderTitle() { + TraceMobx(); + const view = this.showTitleEditorControl ? this.editableView("title") : + ; + return <> +
    + {view} +
    + {this.headerElements} + ; + } + + refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); + + render() { + TraceMobx(); + if (this.props.renderedIds.indexOf(this.doc[Id]) !== -1) return null; + const sorting = this.doc[`${this.fieldKey}-sortAscending`]; + if (this.showTitleEditorControl) { // find containing CollectionTreeView and set our maximum width so the containing tree view won't have to scroll + let par: any = this._header?.current; + 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 = ""; + const selected = SelectionManager.IsSelected(DocumentManager.Instance.getFirstDocumentView(this.doc)); + return this.doc.treeViewHideHeader || this.outlineMode ? + !StrCast(Doc.LayoutField(this.doc)).includes("CollectionView") ? + this.renderContent + :
    this.props.active(true) && SelectionManager.DeselectAll()} + onKeyDown={e => { + e.stopPropagation(); + switch (e.key) { + case "Backspace": return this.doc.text && !(this.doc.text as RichTextField)?.Text && UndoManager.RunInBatch(() => this.props.removeDoc?.(this.doc), "delete"); + case "Enter": return UndoManager.RunInBatch(() => this.makeTextCollection(), "bullet"); + case "Tab": setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); + return UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.() : this.props.indentDocument?.(), "tab"); + } + }} > +
    { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }} + onPointerDown={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }} + onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> + {this.renderBullet} +
    + +
    +
    + +
    + {!this.treeViewOpen ? (null) : this.renderContent} +
    +
    : +
    this.props.active(true) && SelectionManager.DeselectAll()}> +
  • +
    { + 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} +
    +
    + {!this.treeViewOpen ? (null) : this.renderContent} +
    +
  • +
    ; + } + + + public static GetChildElements( + childDocs: Doc[], + treeView: CollectionTreeView, + 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: Opt, 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[], + onCheckedClick: undefined | (() => ScriptField), + onChildClick: undefined | (() => ScriptField), + ignoreFields: string[] | undefined, + firstLevel: boolean, + whenActiveChanged: (isActive: boolean) => void + ) { + 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 first = (ascending ? b : a).title; + const second = (ascending ? a : b).title; + if (typeof first === 'number' && typeof second === 'number') return (first - second) > 0 ? 1 : -1; + if (typeof first === 'string' && typeof second === 'string') return sortAlphaNum(first, second); + return 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 (remove && 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) { + remove(child); + FormattedTextBox.SelectOnLoad = child[Id]; + Doc.AddDocToList(docs[i - 1], fieldKey, child); + docs[i - 1].treeViewOpen = true; + child.context = treeView.Document; + } + } + }; + const outdent = !parentCollectionDoc ? undefined : () => { + if (remove && StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) { + const fieldKeysub = StrCast(parentCollectionDoc.layout).split('fieldKey')[1]; + const fieldKey = fieldKeysub.split("\'")[1]; + remove(child); + FormattedTextBox.SelectOnLoad = child[Id]; + Doc.AddDocToList(parentCollectionDoc, fieldKey, child, parentPrevSibling, false); + parentCollectionDoc.treeViewOpen = true; + child.context = treeView.Document; + } + }; + const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); + const childLayout = Doc.Layout(pair.layout); + const rowHeight = () => { + const aspect = NumCast(childLayout._nativeWidth, 0) / NumCast(childLayout._nativeHeight, 0); + return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); + }; + return !(child instanceof Doc) ? (null) : ; + }); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 9fd81bd90..748af89ef 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -193,6 +193,16 @@ export class FilterBox extends ViewBoxBaseComponent(); diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 2df550b47..c5a64af3e 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -222,10 +222,10 @@ export class PresBox extends ViewBoxBaseComponent const bestTarget = DocumentManager.Instance.getFirstDocumentView(presTargetDoc)?.props.Document; bestTarget && runInAction(() => { if (activeItem.presMovement === PresMovement.Jump) { - bestTarget!._viewTransition = '0s'; + bestTarget._viewTransition = '0s'; } else { - bestTarget!._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 1s'; - setTimeout(() => bestTarget!._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 1010); + bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 1s'; + setTimeout(() => bestTarget._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 1010); } }); } else { @@ -301,10 +301,10 @@ export class PresBox extends ViewBoxBaseComponent // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; bestTarget && runInAction(() => { - bestTarget!._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; - bestTarget!._panX = activeItem.presPinViewX; - bestTarget!._panY = activeItem.presPinViewY; - bestTarget!._viewScale = activeItem.presPinViewScale; + bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; + bestTarget._panX = activeItem.presPinViewX; + bestTarget._panY = activeItem.presPinViewY; + bestTarget._viewScale = activeItem.presPinViewScale; }); //setTimeout(() => targetDoc._viewTransition = undefined, 1010); } @@ -571,7 +571,7 @@ export class PresBox extends ViewBoxBaseComponent if (this.childDocs.includes(doc)) { if (docs.length === i + 1) return false; } else if (doc.type === DocumentType.LABEL) { - const audio = Cast(doc.annotationOn, Doc, null) as Doc; + const audio = Cast(doc.annotationOn, Doc, null); if (audio) { audio.aliasOf instanceof Doc; audio.presStartTime = NumCast(doc.audioStart); @@ -617,9 +617,8 @@ export class PresBox extends ViewBoxBaseComponent const list = this._selectedArray.map((doc: Doc, index: any) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc!, Doc, null); - if (tagDoc) return ( -
    {index + 1}. {curDoc.title}
    - ); else if (curDoc) return
    {index + 1}. {curDoc.title}
    + if (tagDoc) return
    {index + 1}. {curDoc.title}
    ; + else if (curDoc) return
    {index + 1}. {curDoc.title}
    ; }); return list; } @@ -996,7 +995,7 @@ export class PresBox extends ViewBoxBaseComponent @computed get optionsDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - const presPinWithViewIcon = ; + const presPinWithViewIcon = ; if (activeItem && targetDoc) { return (
    @@ -1047,8 +1046,8 @@ export class PresBox extends ViewBoxBaseComponent const scale = targetDoc._viewScale; activeItem.presPinViewX = x; activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale - }}>Update
    : (null)} + activeItem.presPinViewScale = scale; + }}>Update
    : (null)};
    diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 0c8b20ae3..e9ab5911d 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -115,7 +115,7 @@ export class PresElementBox extends ViewBoxBaseComponent