diff options
| author | bobzel <zzzman@gmail.com> | 2023-12-10 20:19:27 -0500 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2023-12-10 20:19:27 -0500 |
| commit | 380ee1acac1c0b7972d7d423cf804af146dc0edf (patch) | |
| tree | 1d77244a600e6eb1fb6d56356b3ce01ca6add89d /src/client/views/collections | |
| parent | b7b7105fac83ec11480204c5c7ac0ae6579774e1 (diff) | |
massive changes to use mobx 6 which means not accessing props directly in @computed functions.
Diffstat (limited to 'src/client/views/collections')
26 files changed, 1320 insertions, 1131 deletions
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 858090c05..4ddf9e69b 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,4 +1,4 @@ -import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx'; import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom/client'; import * as GoldenLayout from '../../../client/goldenLayout'; @@ -10,7 +10,7 @@ import { List } from '../../../fields/List'; import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, incrementTitleCopy } from '../../../Utils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; @@ -36,7 +36,7 @@ const _global = (window /* browser */ || global) /* node */ as any; @observer export class CollectionDockingView extends CollectionSubView() { - @observable public static Instance: CollectionDockingView | undefined; + @observable public static Instance: CollectionDockingView | undefined = undefined; public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) { return { type: 'react-component', @@ -62,9 +62,14 @@ export class CollectionDockingView extends CollectionSubView() { } private _goldenLayout: any = null; static _highlightStyleSheet: any = addStyleSheet(); + + _prevProps: SubCollectionViewProps; + @override _props: SubCollectionViewProps; constructor(props: SubCollectionViewProps) { super(props); - if (this.props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this)); + this._props = this._prevProps = props; + makeObservable(this); + if (this._props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this)); //Why is this here? (window as any).React = React; (window as any).ReactDOM = ReactDOM; @@ -72,6 +77,10 @@ export class CollectionDockingView extends CollectionSubView() { this.Document.myTrails; // this is equivalent to having a prefetchProxy for myTrails which is needed for the My Trails button in the UI which assumes that Doc.ActiveDashboard.myTrails is legit... } + componentDidUpdate() { + copyProps(this); + } + /** * Switches from dragging a document around a freeform canvas to dragging it as a tab to be docked. * @@ -274,8 +283,8 @@ export class CollectionDockingView extends CollectionSubView() { return true; } setupGoldenLayout = async () => { - //const config = StrCast(this.props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.props.Document))); - const config = StrCast(this.props.Document.dockingConfig); + //const config = StrCast(this._props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this._props.Document))); + const config = StrCast(this._props.Document.dockingConfig); if (config) { const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g); const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? []; @@ -318,7 +327,7 @@ export class CollectionDockingView extends CollectionSubView() { ); new _global.ResizeObserver(this.onResize).observe(this._containerRef.current); this._reactionDisposer = reaction( - () => StrCast(this.props.Document.dockingConfig), + () => StrCast(this._props.Document.dockingConfig), config => { if (!this._goldenLayout || this._ignoreStateChange !== config) { // bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one. @@ -328,7 +337,7 @@ export class CollectionDockingView extends CollectionSubView() { } ); reaction( - () => this.props.PanelWidth(), + () => this._props.PanelWidth(), width => !this._goldenLayout && width > 20 && setTimeout(() => this.setupGoldenLayout()), // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows { fireImmediately: true } ); @@ -375,7 +384,7 @@ export class CollectionDockingView extends CollectionSubView() { .map(id => DocServer.GetCachedRefField(id)) .filter(f => f) .map(f => f as Doc); - const changesMade = this.props.Document.dockingConfig !== json; + const changesMade = this._props.Document.dockingConfig !== json; if (changesMade) { if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) { this.layoutDoc.dockingConfig = json; @@ -425,7 +434,7 @@ export class CollectionDockingView extends CollectionSubView() { }; public CaptureThumbnail() { - const content = this.props.DocumentView?.()?.ContentDiv; + const content = this._props.DocumentView?.()?.ContentDiv; if (content) { const _width = Number(getComputedStyle(content).width.replace('px', '')); const _height = Number(getComputedStyle(content).height.replace('px', '')); @@ -472,7 +481,7 @@ export class CollectionDockingView extends CollectionSubView() { stateChanged = () => { this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); const json = JSON.stringify(this._goldenLayout.toConfig()); - const changesMade = this.props.Document.dockingConfig !== json; + const changesMade = this._props.Document.dockingConfig !== json; return changesMade; }; @@ -506,8 +515,8 @@ export class CollectionDockingView extends CollectionSubView() { if (dashboard && e.target === stack.header?.element[0] && e.button === 2) { dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1; const docToAdd = Docs.Create.FreeformDocument([], { - _width: this.props.PanelWidth(), - _height: this.props.PanelHeight(), + _width: this._props.PanelWidth(), + _height: this._props.PanelHeight(), _freeform_backgroundGrid: true, _layout_fitWidth: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, @@ -524,8 +533,8 @@ export class CollectionDockingView extends CollectionSubView() { if (dashboard) { dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1; const docToAdd = Docs.Create.FreeformDocument([], { - _width: this.props.PanelWidth(), - _height: this.props.PanelHeight(), + _width: this._props.PanelWidth(), + _height: this._props.PanelHeight(), _layout_fitWidth: true, _freeform_backgroundGrid: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, @@ -573,14 +582,14 @@ export class CollectionDockingView extends CollectionSubView() { render() { const href = ImageCast(this.Document.thumb)?.url?.href; - return this.props.renderDepth > -1 ? ( + return this._props.renderDepth > -1 ? ( <div> {href ? ( <img style={{ background: 'white', top: 0, position: 'absolute' }} src={href} // + '?d=' + (new Date()).getTime()} - width={this.props.PanelWidth()} - height={this.props.PanelHeight()} + width={this._props.PanelWidth()} + height={this._props.PanelHeight()} /> ) : ( <p>nested dashboards has no thumbnail</p> diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 61a5738ec..1c6f97c14 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -1,10 +1,9 @@ import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; -import { Id } from '../../../fields/FieldSymbols'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils'; @@ -49,14 +48,20 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @observable private color: string = '#f1efeb'; @observable private collapsed: boolean = false; @observable private _paletteOn = false; + @observable _props: React.PropsWithChildren<CMVFieldRowProps>; + constructor(props: React.PropsWithChildren<CMVFieldRowProps>) { + super(props); + this._props = props; + makeObservable(this); + } private set _heading(value: string) { - runInAction(() => this.props.headingObject && (this.props.headingObject.heading = this.heading = value)); + runInAction(() => this._props.headingObject && (this._props.headingObject.heading = this.heading = value)); } private set _color(value: string) { - runInAction(() => this.props.headingObject && (this.props.headingObject.color = this.color = value)); + runInAction(() => this._props.headingObject && (this._props.headingObject.color = this.color = value)); } private set _collapsed(value: boolean) { - runInAction(() => this.props.headingObject && (this.props.headingObject.collapsed = this.collapsed = value)); + runInAction(() => this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value)); } private _dropDisposer?: DragManager.DragDropDisposer; @@ -68,28 +73,28 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr this._dropDisposer?.(); if (ele) { this._ele = ele; - this.props.observeHeight(ele); + this._props.observeHeight(ele); this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this)); } }; @action componentDidMount() { - this.heading = this.props.headingObject?.heading || ''; - this.color = this.props.headingObject?.color || '#f1efeb'; - this.collapsed = this.props.headingObject?.collapsed || false; + this.heading = this._props.headingObject?.heading || ''; + this.color = this._props.headingObject?.color || '#f1efeb'; + this.collapsed = this._props.headingObject?.collapsed || false; } componentWillUnmount() { - this.props.unobserveHeight(this._ele); + this._props.unobserveHeight(this._ele); } getTrueHeight = () => { if (this.collapsed) { - this.props.setDocHeight(this.heading, 20); + this._props.setDocHeight(this.heading, 20); } else { const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header - const transformScale = this.props.screenToLocalTransform().Scale; + const transformScale = this._props.screenToLocalTransform().Scale; const trueHeight = rawHeight * transformScale; - this.props.setDocHeight(this.heading, trueHeight); + this._props.setDocHeight(this.heading, trueHeight); } }; @@ -97,10 +102,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr rowDrop = action((e: Event, de: DragManager.DropEvent) => { this._createEmbeddingSelected = false; if (de.complete.docDragData) { - const key = this.props.pivotField; + const key = this._props.pivotField; const castedValue = this.getValue(this.heading); const onLayoutDoc = this.onLayoutDoc(key); - if (this.props.parent.onInternalDrop(e, de)) { + if (this._props.parent.onInternalDrop(e, de)) { de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc)); } return true; @@ -119,15 +124,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @action headingChanged = (value: string, shiftDown?: boolean) => { this._createEmbeddingSelected = false; - const key = this.props.pivotField; + const key = this._props.pivotField; const castedValue = this.getValue(value); if (castedValue) { - if (this.props.parent.colHeaderData) { - if (this.props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) { + if (this._props.parent.colHeaderData) { + if (this._props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) { return false; } } - this.props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true)); + this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true)); this._heading = castedValue.toString(); return true; } @@ -152,24 +157,24 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { if (!value && !forceEmptyNote) return false; this._createEmbeddingSelected = false; - const key = this.props.pivotField; + const key = this._props.pivotField; const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value }); const onLayoutDoc = this.onLayoutDoc(key); FormattedTextBox.SetSelectOnLoad(newDoc); FormattedTextBox.SelectOnLoadChar = value; - (onLayoutDoc ? newDoc : newDoc[DocData])[key] = this.getValue(this.props.heading); - const docs = this.props.parent.childDocList; - return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this.props.parent.props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list) + (onLayoutDoc ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading); + const docs = this._props.parent.childDocList; + return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list) }; deleteRow = undoBatch( action(() => { this._createEmbeddingSelected = false; - const key = this.props.pivotField; - this.props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true)); - if (this.props.parent.colHeaderData && this.props.headingObject) { - const index = this.props.parent.colHeaderData.indexOf(this.props.headingObject); - this.props.parent.colHeaderData.splice(index, 1); + const key = this._props.pivotField; + this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true)); + if (this._props.parent.colHeaderData && this._props.headingObject) { + const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject); + this._props.parent.colHeaderData.splice(index, 1); } }) ); @@ -182,8 +187,8 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr }; headerMove = (e: PointerEvent) => { - const embedding = Doc.MakeEmbedding(this.props.Document); - const key = this.props.pivotField; + const embedding = Doc.MakeEmbedding(this._props.Document); + const key = this._props.pivotField; let value = this.getValue(this.heading); value = typeof value === 'string' ? `"${value}"` : value; const script = `return doc.${key} === ${value}`; @@ -198,7 +203,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @action headerDown = (e: React.PointerEvent<HTMLDivElement>) => { if (e.button === 0 && !e.ctrlKey) { - setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this.props.chromeHidden && this.collapseSection(e)); + setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this._props.chromeHidden && this.collapseSection(e)); this._createEmbeddingSelected = false; } }; @@ -207,7 +212,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr * Returns true if a key is on the layout doc of the documents in the collection. */ onLayoutDoc = (key: string): boolean => { - DocListCast(this.props.parent.Document.data).forEach(doc => { + DocListCast(this._props.parent.Document.data).forEach(doc => { if (Doc.Get(doc, key, true)) return true; }); return false; @@ -264,19 +269,19 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr }; @computed get contentLayout() { - const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap)))); - const showChrome = !this.props.chromeHidden; - const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `; + const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor((this._props.parent._props.PanelWidth() - 2 * this._props.parent.xMargin) / (this._props.parent.columnWidth + this._props.parent.gridGap)))); + const showChrome = !this._props.chromeHidden; + const stackPad = showChrome ? `0px ${this._props.parent.xMargin}px` : `${this._props.parent.yMargin}px ${this._props.parent.xMargin}px 0px ${this._props.parent.xMargin}px `; return this.collapsed ? null : ( <div style={{ position: 'relative' }}> - {this.props.showHandle && this.props.parent.props.isContentActive() ? this.props.parent.columnDragger : null} + {this._props.showHandle && this._props.parent._props.isContentActive() ? this._props.parent.columnDragger : null} {showChrome ? ( <div className="collectionStackingView-addDocumentButton" style={ { //width: style.columnWidth / style.numGroupColumns, - //padding: `${NumCast(this.props.parent.layoutDoc._yPadding, this.props.parent.yMargin)}px 0px 0px 0px`, + //padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`, } }> <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents={'+ NEW'} /> @@ -287,25 +292,25 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr ref={this._contRef} style={{ padding: stackPad, - minHeight: this.props.showHandle && this.props.parent.props.isContentActive() ? '10px' : undefined, - width: this.props.parent.NodeWidth, - gridGap: this.props.parent.gridGap, - gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ''), + minHeight: this._props.showHandle && this._props.parent._props.isContentActive() ? '10px' : undefined, + width: this._props.parent.NodeWidth, + gridGap: this._props.parent.gridGap, + gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this._props.parent.columnWidth}px`, ''), }}> - {this.props.parent.children(this.props.docList)} + {this._props.parent.children(this._props.docList)} </div> </div> ); } @computed get headingView() { - const noChrome = this.props.chromeHidden; - const key = this.props.pivotField; - const evContents = this.heading ? this.heading : this.props.type && this.props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`; + const noChrome = this._props.chromeHidden; + const key = this._props.pivotField; + const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`; const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />; - return this.props.Document.miniHeaders ? ( + return this._props.Document.miniHeaders ? ( <div className="collectionStackingView-miniHeader">{editableHeaderView}</div> - ) : !this.props.headingObject ? null : ( + ) : !this._props.headingObject ? null : ( <div className="collectionStackingView-sectionHeader" ref={this._headerRef}> <div className="collectionStackingView-sectionHeader-subCont" @@ -352,7 +357,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr render() { const background = this._background; return ( - <div className="collectionStackingView-masonrySection" style={{ width: this.props.parent.NodeWidth, background }} ref={this.createRowDropRef} onPointerEnter={this.pointerEnteredRow} onPointerLeave={this.pointerLeaveRow}> + <div className="collectionStackingView-masonrySection" style={{ width: this._props.parent.NodeWidth, background }} ref={this.createRowDropRef} onPointerEnter={this.pointerEnteredRow} onPointerLeave={this.pointerLeaveRow}> {this.headingView} {this.contentLayout} </div> diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 3edf4135f..f8e80ed87 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -33,7 +33,7 @@ interface CollectionMenuProps { export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { @observable static Instance: CollectionMenu; - @observable SelectedCollection: DocumentView | undefined; + @observable SelectedCollection: DocumentView | undefined = undefined; @observable FieldKey: string; private _docBtnRef = React.createRef<HTMLDivElement>(); @@ -41,7 +41,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { constructor(props: any) { super(props); this.FieldKey = ''; - runInAction(() => (CollectionMenu.Instance = this)); + CollectionMenu.Instance = this; this._canFade = false; // don't let the inking menu fade away runInAction(() => (this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true))); this.jumpTo(300, 300); @@ -69,19 +69,19 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { @action toggleTopBar = () => { - if (SettingsManager.headerBarHeight > 0) { - SettingsManager.headerBarHeight = 0; + if (SettingsManager.Instance.headerBarHeight > 0) { + SettingsManager.Instance.headerBarHeight = 0; } else { - SettingsManager.headerBarHeight = 60; + SettingsManager.Instance.headerBarHeight = 60; } }; @action toggleProperties = () => { if (MainView.Instance.propertiesWidth() > 0) { - SettingsManager.propertiesWidth = 0; + SettingsManager.Instance.propertiesWidth = 0; } else { - SettingsManager.propertiesWidth = 300; + SettingsManager.Instance.propertiesWidth = 300; } }; @@ -94,7 +94,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { @computed get contMenuButtons() { const selDoc = Doc.MyContextMenuBtns; return !(selDoc instanceof Doc) ? null : ( - <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this.props.panelHeight() }}> + <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this._props.panelHeight() }}> <CollectionLinearView Document={selDoc} fieldKey="data" @@ -113,8 +113,8 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { pinToPres={emptyFunction} removeDocument={returnFalse} ScreenToLocalTransform={this.buttonBarXf} - PanelWidth={this.props.panelWidth} - PanelHeight={this.props.panelHeight} + PanelWidth={this._props.panelWidth} + PanelHeight={this._props.panelHeight} renderDepth={0} focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} @@ -127,10 +127,10 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { } render() { - const headerIcon = SettingsManager.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down'; - const headerTitle = SettingsManager.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar'; - const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; - const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties'; + const headerIcon = SettingsManager.Instance.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down'; + const headerTitle = SettingsManager.Instance.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar'; + const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; + const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties'; const hardCodedButtons = ( <div className={`hardCodedButtons`}> @@ -139,7 +139,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { type={Type.PRIM} color={SettingsManager.userColor} onClick={this.toggleTopBar} - toggleStatus={SettingsManager.headerBarHeight > 0} + toggleStatus={SettingsManager.Instance.headerBarHeight > 0} icon={<FontAwesomeIcon icon={headerIcon} size="lg" />} tooltip={headerTitle} /> @@ -148,7 +148,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { type={Type.PRIM} color={SettingsManager.userColor} onClick={this.toggleProperties} - toggleStatus={SettingsManager.propertiesWidth > 0} + toggleStatus={SettingsManager.Instance.propertiesWidth > 0} icon={<FontAwesomeIcon icon={propIcon} size="lg" />} tooltip={propTitle} /> @@ -185,7 +185,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\) get document() { - return this.props.docView?.props.Document; + return this.props.docView?.Document; } get target() { return this.document; @@ -577,13 +577,18 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu return this.props.docView.props.Document; } + @computed get panelWidth() { + return this.props.docView.props.PanelWidth(); + } + componentDidMount() { runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700)); // listener to reduce text on chrome resize (panel resize) - this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => { - runInAction(() => (this.resize = newValue < 700)); - }); + this.resizeListenerDisposer = reaction( + () => this.panelWidth, + newValue => (this.resize = newValue < 700) + ); } componentWillUnmount() { diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index ba00c4fa0..f9d99f1e5 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -416,7 +416,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { // onInternalDrop is used when dragging and dropping a document within the view, such as dragging // a document to a new column or changing its order within the column. @undoBatch - @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { if (super.onInternalDrop(e, de)) { diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index c5677e2ba..a0f8b9c89 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -43,7 +43,7 @@ export class CollectionPileView extends CollectionSubView() { removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d)); const ret = this.props.moveDocument?.(doc, targetCollection, addDoc) || false; - if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.Document); + if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.()._props.removeDocument?.(this.Document); return ret; }; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 92b5470ae..28d15be71 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -1,7 +1,7 @@ -import * as React from 'react'; -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; +import * as React from 'react'; import { Doc, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -9,7 +9,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { Cast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; +import { copyProps, emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; @@ -20,9 +20,9 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; -import { AudioWaveform } from '../AudioWaveform'; -import { CollectionSubView } from '../collections/CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from '../collections/CollectionSubView'; import { LightboxView } from '../LightboxView'; +import { AudioWaveform } from '../nodes/audio/AudioWaveform'; import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView'; import { LabelBox } from '../nodes/LabelBox'; import { VideoBox } from '../nodes/VideoBox'; @@ -53,17 +53,28 @@ export enum TrimScope { } @observer -export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() { +export class CollectionStackedTimeline extends CollectionSubView<SubCollectionViewProps & CollectionStackedTimelineProps>() { @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; @observable public static CurrentlyPlaying: DocumentView[]; + _prevProps: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>; + @override _props: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>; + constructor(props: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>) { + super(props); + this._props = this._prevProps = props; + makeObservable(this); + } + componentDidUpdate() { + copyProps(this); + } + static LabelScript: ScriptField; static LabelPlayScript: ScriptField; private _timeline: HTMLDivElement | null = null; // ref to actual timeline div private _timelineWrapper: HTMLDivElement | null = null; // ref to timeline wrapper div for zooming and scrolling private _markerStart: number = 0; - @observable _markerEnd: number | undefined; + @observable _markerEnd: number | undefined = undefined; @observable _trimming: number = TrimScope.None; @observable _trimStart: number = 0; // trim controls start pos @observable _trimEnd: number = 0; // trim controls end pos @@ -73,14 +84,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @observable _hoverTime: number = 0; - @observable _thumbnail: string | undefined; + @observable _thumbnail: string | undefined = undefined; // ensures that clip doesn't get trimmed so small that controls cannot be adjusted anymore get minTrimLength() { return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5); } @computed get thumbnails() { - return this.props.thumbnails?.(); + return this._props.thumbnails?.(); } @computed get trimStart() { @@ -100,7 +111,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack return this.clipEnd - this.clipStart; } @computed get clipEnd() { - return this.IsTrimming === TrimScope.All ? this.props.rawDuration : NumCast(this.layoutDoc.clipEnd, this.props.rawDuration); + return this.IsTrimming === TrimScope.All ? this._props.rawDuration : NumCast(this.layoutDoc.clipEnd, this._props.rawDuration); } @computed get currentTime() { @@ -115,11 +126,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack document.addEventListener('keydown', this.keyEvents, true); } - @action componentWillUnmount() { document.removeEventListener('keydown', this.keyEvents, true); if (CollectionStackedTimeline.SelectingRegion === this) { - CollectionStackedTimeline.SelectingRegion = undefined; + runInAction(() => (CollectionStackedTimeline.SelectingRegion = undefined)); } } @@ -149,8 +159,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._zoomFactor = zoom; } - anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); - anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val) ?? null); + anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this._props.startTag])); + anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null); // converts screen pixel offset to time toTimeline = (screen_delta: number, width: number) => { @@ -177,13 +187,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack if ( // need to include range inputs because after dragging video time slider it becomes target element !(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) && - this.props.isContentActive() + this._props.isContentActive() ) { // if shift pressed scrub 1 second otherwise 1/10th const jump = e.shiftKey ? 1 : 0.1; switch (e.key) { case ' ': - this.props.playing() ? this.props.Pause() : this.props.Play(); + this._props.playing() ? this._props.Pause() : this._props.Play(); break; case '^': if (!CollectionStackedTimeline.SelectingRegion) { @@ -191,7 +201,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack CollectionStackedTimeline.SelectingRegion = this; } else { this._markerEnd = this.currentTime; - CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true); + CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true); this._markerEnd = undefined; CollectionStackedTimeline.SelectingRegion = undefined; } @@ -204,11 +214,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._trimming = TrimScope.None; break; case 'ArrowLeft': - this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd)); + this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd)); e.stopPropagation(); break; case 'ArrowRight': - this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd)); + this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd)); e.stopPropagation(); break; } @@ -218,7 +228,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack getLinkData(l: Doc) { let la1 = l.link_anchor_1 as Doc; let la2 = l.link_anchor_2 as Doc; - const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag])); + const linkTime = NumCast(la2[this._props.startTag], NumCast(la1[this._props.startTag])); if (Doc.AreProtosEqual(la1, this.dataDoc)) { la1 = l.link_anchor_2 as Doc; la2 = l.link_anchor_1 as Doc; @@ -233,9 +243,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack const clientX = e.clientX; const diff = rect ? clientX - rect?.x : null; const shiftKey = e.shiftKey; - if (rect && this.props.isContentActive()) { - const wasPlaying = this.props.playing(); - if (wasPlaying) this.props.Pause(); + if (rect && this._props.isContentActive()) { + const wasPlaying = this._props.playing(); + if (wasPlaying) this._props.Pause(); var wasSelecting = this._markerEnd !== undefined; setupMoveUpEvents( this, @@ -257,7 +267,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._markerEnd = tmp; } if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) { - const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true); + const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true); setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false)); } (!isClick || !wasSelecting) && (this._markerEnd = undefined); @@ -265,17 +275,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack }), (e, doubleTap) => { if (e.button !== 2) { - this.props.select(false); - !wasPlaying && doubleTap && this.props.Play(); + this._props.select(false); + !wasPlaying && doubleTap && this._props.Play(); } }, - this.props.isSelected() || this.props.isContentActive(), + this._props.isSelected() || this._props.isContentActive(), undefined, () => { if (shiftKey) { - CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this.currentTime, undefined, undefined, true); + CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this.currentTime, undefined, undefined, true); } else { - !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); + !wasPlaying && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } } ); @@ -290,7 +300,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack if (rect) { this._hoverTime = this.toTimeline(clientX - rect.x, rect.width); if (this.thumbnails) { - const nearest = Math.floor((this._hoverTime / this.props.rawDuration) * VideoBox.numThumbnails); + const nearest = Math.floor((this._hoverTime / this._props.rawDuration) * VideoBox.numThumbnails); const imgField = this.thumbnails.length > 0 ? new ImageField(this.thumbnails[nearest]) : undefined; this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_m.png') : undefined; } @@ -305,7 +315,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this, e, action((e, [], []) => { - if (rect && this.props.isContentActive()) { + if (rect && this._props.isContentActive()) { this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength); } return false; @@ -325,7 +335,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this, e, action((e, [], []) => { - if (rect && this.props.isContentActive()) { + if (rect && this._props.isContentActive()) { this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength); } return false; @@ -348,8 +358,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @action scrollToTime = (time: number) => { if (this._timelineWrapper) { - if (time > this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)) { - this._scroll = Math.min(this._scroll + this.props.PanelWidth(), this.timelineContentWidth - this.props.PanelWidth()); + if (time > this.toTimeline(this._scroll + this._props.PanelWidth(), this.timelineContentWidth)) { + this._scroll = Math.min(this._scroll + this._props.PanelWidth(), this.timelineContentWidth - this._props.PanelWidth()); smoothScrollHorizontal(200, this._timelineWrapper, this._scroll); } else if (time < this.toTimeline(this._scroll, this.timelineContentWidth)) { this._scroll = (time / this.timelineContentWidth) * this.clipDuration; @@ -363,15 +373,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) { if (super.onInternalDrop(e, de)) { // determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view - const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); + const localPt = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y); const x = localPt[0] - docDragData.offset[0]; const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth); docDragData.droppedDocuments.forEach(drop => { const anchorEnd = this.anchorEnd(drop); if (anchorEnd !== undefined) { - Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false); + Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this._props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false); } - Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false); + Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this._props.startTag : 'timecodeToShow', timelinePt, false); }); return true; @@ -386,7 +396,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // creates marker on timeline @undoBatch - @action static createAnchor(doc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) { if (anchorStartTime === undefined) return doc; const startTag = '_timecodeToShow'; @@ -422,20 +431,20 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; const endTime = this.anchorEnd(anchorDoc); if (this.layoutDoc.autoPlayAnchors) { - if (this.props.playing()) this.props.Pause(); + if (this._props.playing()) this._props.Pause(); else { - this.props.playFrom(seekTimeInSeconds, endTime); + this._props.playFrom(seekTimeInSeconds, endTime); this.scrollToTime(seekTimeInSeconds); } } else { if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) { - if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) { - this.props.Pause(); + if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) { + this._props.Pause(); } else { - this.props.Play(); + this._props.Play(); } } else { - this.props.playFrom(seekTimeInSeconds, endTime); + this._props.playFrom(seekTimeInSeconds, endTime); this.scrollToTime(seekTimeInSeconds); } } @@ -450,17 +459,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; const endTime = this.anchorEnd(anchorDoc); if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._layout_currentTimecode) - 1e-4) { - if (this.props.playing()) this.props.Pause(); - else if (this.layoutDoc.autoPlayAnchors) this.props.Play(); + if (this._props.playing()) this._props.Pause(); + else if (this.layoutDoc.autoPlayAnchors) this._props.Play(); else if (!this.layoutDoc.autoPlayAnchors) { const rect = this._timeline?.getBoundingClientRect(); - rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); + rect && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } } else { if (this.layoutDoc.autoPlayAnchors) { - this.props.playFrom(seekTimeInSeconds, endTime); + this._props.playFrom(seekTimeInSeconds, endTime); } else { - this.props.setTime(seekTimeInSeconds); + this._props.setTime(seekTimeInSeconds); } } return { select: true }; @@ -490,18 +499,18 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack }; dictationHeightPercent = 50; - dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100; + dictationHeight = () => (this._props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100; @computed get timelineContentHeight() { - return (this.props.PanelHeight() * this.dictationHeightPercent) / 100; + return (this._props.PanelHeight() * this.dictationHeightPercent) / 100; } @computed get timelineContentWidth() { - return this.props.PanelWidth() * this.zoomFactor; + return this._props.PanelWidth() * this.zoomFactor; } // subtract size of container border - dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight); + dictationScreenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight); - isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + isContentActive = () => this._props.isSelected() || this._props.isContentActive(); currentTimecode = () => this.currentTime; @@ -520,7 +529,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack } @computed get timelineEvents() { - return this.props.isContentActive() ? 'all' : this.props.isContentActive() === false ? 'none' : undefined; + return this._props.isContentActive() ? 'all' : this._props.isContentActive() === false ? 'none' : undefined; } render() { const overlaps: { @@ -537,7 +546,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack <div ref={this.createDashEventsTarget} style={{ pointerEvents: this.timelineEvents }}> <div className="collectionStackedTimeline-timelineContainer" - style={{ width: this.props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }} + style={{ width: this._props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }} onWheel={e => this.isContentActive() && e.stopPropagation()} onScroll={this.setScroll} onMouseMove={e => this.isContentActive() && this.onHover(e)} @@ -553,11 +562,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack const end = this.anchorEnd(d.anchor, start + (10 / this.timelineContentWidth) * this.clipDuration); if (end < this.clipStart || start > this.clipEnd) return null; const left = Math.max(((start - this.clipStart) / this.clipDuration) * this.timelineContentWidth, 0); - const top = (d.level / maxLevel) * this.props.PanelHeight(); + const top = (d.level / maxLevel) * this._props.PanelHeight(); const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart); const width = (timespan / this.clipDuration) * this.timelineContentWidth; - const height = this.props.PanelHeight() / maxLevel; - return this.props.Document.hideAnchors ? null : ( + const height = this._props.PanelHeight() / maxLevel; + return this._props.Document.hideAnchors ? null : ( <div className={'collectionStackedTimeline-marker-timeline'} key={d.anchor[Id]} @@ -569,7 +578,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack pointerEvents: 'none', }}> <StackedTimelineAnchor - {...this.props} + {...this._props} mark={d.anchor} rangeClickScript={this.rangeClickScript} rangePlayScript={this.rangePlayScript} @@ -590,18 +599,19 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack ); })} {!this.IsTrimming && this.selectionContainer} - {!this.props.PanelHeight() ? null : ( + {!this._props.PanelHeight() ? null : ( <AudioWaveform - rawDuration={this.props.rawDuration} - fieldKey={this.props.dataFieldKey} + rawDuration={this._props.rawDuration} + fieldKey={this._props.dataFieldKey} duration={this.clipDuration} - mediaPath={this.props.mediaPath} + mediaPath={this._props.mediaPath} layoutDoc={this.layoutDoc} clipStart={this.clipStart} clipEnd={this.clipEnd} zoomFactor={this.zoomFactor} PanelHeight={this.timelineContentHeight} PanelWidth={this.timelineContentWidth} + progress={(this.currentTime - this.clipStart) / this.clipDuration} /> )} @@ -691,37 +701,44 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> _lastTimecode: number; _disposer: IReactionDisposer | undefined; - constructor(props: any) { + _prevProps: React.PropsWithChildren<StackedTimelineAnchorProps>; + @observable _props: React.PropsWithChildren<StackedTimelineAnchorProps>; + constructor(props: React.PropsWithChildren<StackedTimelineAnchorProps>) { super(props); - this._lastTimecode = this.props.currentTimecode(); + this._props = this._prevProps = props; + makeObservable(this); + this._lastTimecode = this._props.currentTimecode(); + } + componentDidUpdate() { + copyProps(this); } // updates marker document title to reflect correct timecodes computeTitle = () => { - if (this.props.mark.type !== DocumentType.LABEL) return undefined; - const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart; - const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart; + if (this._props.mark.type !== DocumentType.LABEL) return undefined; + const start = Math.max(NumCast(this._props.mark[this._props.startTag]), this._props.trimStart) - this._props.trimStart; + const end = Math.min(NumCast(this._props.mark[this._props.endTag]), this._props.trimEnd) - this._props.trimStart; return `#${formatTime(start)}-${formatTime(end)}`; }; componentDidMount() { this._disposer = reaction( - () => this.props.currentTimecode(), + () => this._props.currentTimecode(), time => { - const dictationDoc = Cast(this.props.layoutDoc.data_dictation, Doc, null); - const isDictation = dictationDoc && LinkManager.Links(this.props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc); + const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null); + const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc); if ( !LightboxView.LightboxDoc && // bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront. // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video. - /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/ - !this.props.layoutDoc.dontAutoFollowLinks && - LinkManager.Links(this.props.mark).length && - time > NumCast(this.props.mark[this.props.startTag]) && - time < NumCast(this.props.mark[this.props.endTag]) && - this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5 + /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc))*/ + !this._props.layoutDoc.dontAutoFollowLinks && + LinkManager.Links(this._props.mark).length && + time > NumCast(this._props.mark[this._props.startTag]) && + time < NumCast(this._props.mark[this._props.endTag]) && + this._lastTimecode < NumCast(this._props.mark[this._props.startTag]) - 1e-5 ) { - LinkFollower.FollowLink(undefined, this.props.mark, false); + LinkFollower.FollowLink(undefined, this._props.mark, false); } this._lastTimecode = time; } @@ -736,16 +753,16 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> // starting the drag event for anchor resizing @action onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { - //this.props._timeline?.setPointerCapture(e.pointerId); + //this._props._timeline?.setPointerCapture(e.pointerId); const newTime = (e: PointerEvent) => { const rect = (e.target as any).getBoundingClientRect(); - return this.props.toTimeline(e.clientX - rect.x, rect.width); + return this._props.toTimeline(e.clientX - rect.x, rect.width); }; const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => { - const timelineOnly = Cast(anchor[this.props.startTag], 'number', null) !== undefined; + const timelineOnly = Cast(anchor[this._props.startTag], 'number', null) !== undefined; if (timelineOnly) { - if (!left && time !== undefined && time <= NumCast(anchor[this.props.startTag])) time = undefined; - Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true); + if (!left && time !== undefined && time <= NumCast(anchor[this._props.startTag])) time = undefined; + Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, time, true); if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', time !== undefined ? undefined : '100%', true); } else { anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time; @@ -759,11 +776,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> e, e => { if (!undo) undo = UndoManager.StartBatch('drag anchor'); - this.props.setTime(newTime(e)); + this._props.setTime(newTime(e)); return changeAnchor(anchor, left, newTime(e)); }, action(e => { - this.props.setTime(newTime(e)); + this._props.setTime(newTime(e)); undo?.end(); this.noEvents = false; }), @@ -774,7 +791,9 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> // context menu contextMenuItems = () => { const resetTitle = { - script: ScriptField.MakeFunction(`this.title = this["${this.props.endTag}"] ? "#" + formatToTime(this["${this.props.startTag}"]) + "-" + formatToTime(this["${this.props.endTag}"]) : "#" + formatToTime(this["${this.props.startTag}"])`)!, + script: ScriptField.MakeFunction( + `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])` + )!, icon: 'folder-plus', label: 'Reset Title', }; @@ -785,7 +804,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) { const anchor = observable({ view: undefined as Opt<DocumentView> | null }); const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => { - this.props.playLink(mark, options); + this._props.playLink(mark, options); return undefined; }; return { @@ -793,7 +812,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> view: ( <DocumentView key="view" - {...this.props} + {...this._props} NativeWidth={returnZero} NativeHeight={returnZero} ref={action((r: DocumentView | null) => (anchor.view = r))} @@ -801,11 +820,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> TemplateDataDocument={undefined} docViewPath={returnEmptyDoclist} pointerEvents={this.noEvents ? returnNone : undefined} - styleProvider={this.props.styleProvider} - renderDepth={this.props.renderDepth + 1} + styleProvider={this._props.styleProvider} + renderDepth={this._props.renderDepth + 1} LayoutTemplate={undefined} LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())} - isDocumentActive={this.props.isDocumentActive} + isDocumentActive={this._props.isDocumentActive} PanelWidth={width} PanelHeight={height} layout_fitWidth={returnTrue} @@ -817,7 +836,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} onClick={script} - onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript} + onDoubleClick={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript} ignoreAutoHeight={false} hideResizeHandles={true} bringToFront={emptyFunction} @@ -827,19 +846,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> }; }); - anchorScreenToLocalXf = () => this.props.ScreenToLocalTransform().translate(-this.props.left, -this.props.top); - width = () => this.props.width; - height = () => this.props.height; + anchorScreenToLocalXf = () => this._props.ScreenToLocalTransform().translate(-this._props.left, -this._props.top); + width = () => this._props.width; + height = () => this._props.height; render() { - const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height); + const inner = this.renderInner(this._props.mark, this._props.rangeClickScript, this._props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height); return ( <div style={{ pointerEvents: this.noEvents ? 'none' : undefined }}> {inner.view} {!inner.anchor.view || !inner.anchor.view.SELECTED ? null : ( <> - <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} /> - <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} /> + <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, true)} /> + <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, false)} /> </> )} </div> diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index bc428c22f..984d6eedd 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import * as CSS from 'csstype'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; @@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { copyProps, emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; @@ -31,7 +31,7 @@ import { StyleProp } from '../StyleProvider'; import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; import './CollectionStackingView.scss'; import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; -import { CollectionSubView } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionStackingViewProps = { @@ -64,7 +64,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection @observable _scroll = 0; // used to force the document decoration to update when scrolling // does this mean whether the browser is hidden? Or is chrome something else entirely? @computed get chromeHidden() { - return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); + return this._props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } // it looks like this gets the column headers that Mehek was showing just now @computed get colHeaderData() { @@ -77,18 +77,18 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // filteredChildren is what you want to work with. It's the list of things that you're currently displaying @computed get filteredChildren() { const children = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); - if (this.props.sortFunc) children.sort(this.props.sortFunc); + if (this._props.sortFunc) children.sort(this._props.sortFunc); return children; } // how much margin we give the header @computed get headerMargin() { - return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); + return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin); } @computed get xMargin() { - return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this.props.PanelWidth())); + return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth())); } @computed get yMargin() { - return this.props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this.props.PanelWidth())); + return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); } @computed get gridGap() { @@ -96,7 +96,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } // are we stacking or masonry? @computed get isStackingView() { - return (this.props.type_collection ?? this.layoutDoc._type_collection) === CollectionViewType.Stacking; + return (this._props.type_collection ?? this.layoutDoc._type_collection) === CollectionViewType.Stacking; } // this is the number of StackingViewFieldColumns that we have @computed get numGroupColumns() { @@ -108,15 +108,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } // columnWidth handles the margin on the left and right side of the documents @computed get columnWidth() { - return Math.min(this.props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250)); + return Math.min(this._props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this._props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250)); } @computed get NodeWidth() { - return this.props.PanelWidth() - this.gridGap; + return this._props.PanelWidth() - this.gridGap; } + _prevProps: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionStackingViewProps>>; + _props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionStackingViewProps>>; constructor(props: any) { super(props); + this._props = this._prevProps = props; + makeObservable(this); if (this.colHeaderData === undefined) { // TODO: what is a layout doc? Is it literally how this document is supposed to be layed out? @@ -124,6 +128,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>(); } } + componentDidUpdate() { + copyProps(this); + } // TODO: plj - these are the children children = (docs: Doc[]) => { @@ -203,7 +210,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection componentDidMount() { super.componentDidMount?.(); - this.props.setContentView?.(this); + this._props.setContentView?.(this); // reset section headers when a new filter is inputted this._pivotFieldDisposer = reaction( @@ -214,7 +221,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection () => this.layoutDoc._layout_autoHeight, layout_autoHeight => layout_autoHeight && - this.props.setHeight?.( + this._props.setHeight?.( Math.min( NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + (this.isStackingView ? Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))) : this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0)) @@ -229,20 +236,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection this._layout_autoHeightDisposer?.(); } - isAnyChildContentActive = () => this.props.isAnyChildContentActive(); + isAnyChildContentActive = () => this._props.isAnyChildContentActive(); - @action moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { - return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; + return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; }; createRef = (ele: HTMLDivElement | null) => { this._masonryGridRef = ele; this.createDashEventsTarget(ele!); //so the whole grid is the drop target? }; - onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); + onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick); @computed get onChildDoubleClickHandler() { - return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); + return () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } scrollToBottom = () => { @@ -256,7 +262,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { const top = found.getBoundingClientRect().top; - const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); + const localTop = this._props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { let focusSpeed = options.zoomTime ?? 500; smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); @@ -268,17 +274,16 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => { if (property === StyleProp.Opacity && doc) { - if (this.props.childOpacity) { - return this.props.childOpacity(); + if (this._props.childOpacity) { + return this._props.childOpacity(); } if (this.Document._currentFrame !== undefined) { return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity; } } - return this.props.styleProvider?.(doc, props, property); + return this._props.styleProvider?.(doc, props, property); }; @undoBatch - @action onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { const docView = fieldProps.DocumentView?.(); if (docView && ['Enter'].includes(e.key) && e.ctrlKey) { @@ -296,26 +301,26 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return this.addDocument?.(newDoc); } }; - isContentActive = () => (this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined); + isContentActive = () => (this._props.isContentActive() ? true : this._props.isSelected() === false || this._props.isContentActive() === false ? false : undefined); @observable _renderCount = 5; isChildContentActive = () => - this.props.isContentActive?.() === false + this._props.isContentActive?.() === false ? false - : this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) - ? true - : this.props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false - ? false - : undefined; - isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined); + : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) + ? true + : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false + ? false + : undefined; + isChildButtonContentActive = () => (this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined); @observable docRefs = new ObservableMap<Doc, DocumentView>(); - childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this.props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null)); + childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null)); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list getDisplayDoc(doc: Doc, width: () => number, count: number) { - const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this.props.TemplateDataDocument : undefined; + const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined; const height = () => this.getDocHeight(doc); - const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this.props.PanelHeight())); + const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this._props.PanelHeight())); const panelWidth = () => (this.isStackingView ? width() : this.columnWidth); const stackedDocTransform = () => this.getDocTransform(doc); this._docXfs.push({ stackedDocTransform, width, height }); @@ -324,45 +329,45 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))} Document={doc} TemplateDataDocument={dataDoc ?? (Doc.AreProtosEqual(doc[DocData], doc) ? undefined : doc[DocData])} - renderDepth={this.props.renderDepth + 1} + renderDepth={this._props.renderDepth + 1} PanelWidth={panelWidth} PanelHeight={panelHeight} - pointerEvents={this.props.DocumentView?.().props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView) + pointerEvents={this._props.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView) styleProvider={this.styleProvider} - docViewPath={this.props.docViewPath} + docViewPath={this._props.docViewPath} layout_fitWidth={this.childFitWidth} isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive} onKey={this.onKeyDown} - onBrowseClick={this.props.onBrowseClick} + onBrowseClick={this._props.onBrowseClick} isDocumentActive={this.isContentActive} - LayoutTemplate={this.props.childLayoutTemplate} - LayoutTemplateString={this.props.childLayoutString} - NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox - NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeHeight(doc)) ? height : undefined} - dontCenter={this.props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as any)} - dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to. + LayoutTemplate={this._props.childLayoutTemplate} + LayoutTemplateString={this._props.childLayoutString} + NativeWidth={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox + NativeHeight={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeHeight(doc)) ? height : undefined} + dontCenter={this._props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as any)} + dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this._props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to. rootSelected={this.rootSelected} - layout_showTitle={this.props.childlayout_showTitle} - dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType} + layout_showTitle={this._props.childlayout_showTitle} + dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} ScreenToLocalTransform={stackedDocTransform} focus={this.focusDocument} childFilters={this.childDocFilters} - hideDecorationTitle={this.props.childHideDecorationTitle} - hideResizeHandles={this.props.childHideResizeHandles} + hideDecorationTitle={this._props.childHideDecorationTitle} + hideResizeHandles={this._props.childHideResizeHandles} childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)} - yPadding={NumCast(this.layoutDoc._childYPadding, this.props.childYPadding)} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - removeDocument={this.props.removeDocument} + xPadding={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)} + yPadding={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)} + addDocument={this._props.addDocument} + moveDocument={this._props.moveDocument} + removeDocument={this._props.removeDocument} contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents) as any} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.props.addDocTab} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} + addDocTab={this._props.addDocTab} bringToFront={returnFalse} - pinToPres={this.props.pinToPres} + pinToPres={this._props.pinToPres} /> ); } @@ -372,11 +377,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection this._scroll; // must be referenced for document decorations to update when the text box container is scrolled const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv); // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off - return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); + return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this._props.ScreenToLocalTransform().Scale); } getDocWidth(d?: Doc) { if (!d) return 0; - const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.()); const maxWidth = this.columnWidth / this.numGroupColumns; if (!this.layoutDoc._columnsFill && !this.childFitWidth(childLayoutDoc)) { return Math.min(NumCast(d._width), maxWidth); @@ -385,9 +390,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } getDocHeight(d?: Doc) { if (!d || d.hidden) return 0; - const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); - const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.TemplateDataDocument; - const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); + const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.()); + const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument; + const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._width) : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._height) : 0); if (nw && nh) { @@ -396,7 +401,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return Math.min(maxHeight, (docWid * nh) / nw); } const childHeight = NumCast(childLayoutDoc._height); - const panelHeight = this.childFitWidth(childLayoutDoc) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin; + const panelHeight = this.childFitWidth(childLayoutDoc) ? Number.MAX_SAFE_INTEGER : this._props.PanelHeight() - 2 * this.yMargin; return Math.min(childHeight, maxHeight, panelHeight); } @@ -434,7 +439,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } @undoBatch - @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { // Fairly confident that this is where the swapping of nodes in the various arrays happens const where = [de.x, de.y]; @@ -472,9 +476,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } return true; } - } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) { + } else if (de.complete.linkDragData?.dragDocument.embedContainer === this._props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) { const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' }); - if (!this.props.addDocument?.(source)) e.preventDefault(); + if (!this._props.addDocument?.(source)) e.preventDefault(); de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed e.stopPropagation(); return true; @@ -497,7 +501,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection /// an item from outside of Dash is being dropped onto this stacking view (e.g, a document from the file system) @undoBatch - @action onExternalDrop = async (e: React.DragEvent): Promise<void> => { const where = [e.clientX, e.clientY]; let targInd = -1; @@ -544,8 +547,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection action((entries: any) => { if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', ''))))); - if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { - this.props.setHeight?.(height); + if (!LightboxView.IsLightboxDocView(this._props.docViewPath())) { + this._props.setHeight?.(height); } } }) @@ -556,8 +559,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection addDocument={this.addDocument} chromeHidden={this.chromeHidden} colHeaderData={this.colHeaderData} - Document={this.props.Document} - TemplateDataDocument={this.props.TemplateDataDocument} + Document={this._props.Document} + TemplateDataDocument={this._props.TemplateDataDocument} renderChildren={this.children} columnWidth={this.columnWidth} numGroupColumns={this.numGroupColumns} @@ -571,7 +574,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection yMargin={this.yMargin} type={type} createDropTarget={this.createDashEventsTarget} - screenToLocalTransform={this.props.ScreenToLocalTransform} + screenToLocalTransform={this._props.ScreenToLocalTransform} /> ); }; @@ -584,11 +587,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { type = types[0]; } - const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))))); + const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))))); return ( <CollectionMasonryViewFieldRow showHandle={first} - Document={this.props.Document} + Document={this._props.Document} chromeHidden={this.chromeHidden} pivotField={this.pivotField} unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)} @@ -599,7 +602,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection action((entries: any) => { if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0); - this.props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel + this._props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel } }) ); @@ -615,7 +618,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection parent={this} type={type} createDropTarget={this.createDashEventsTarget} - screenToLocalTransform={this.props.ScreenToLocalTransform} + screenToLocalTransform={this._props.ScreenToLocalTransform} setDocHeight={this.setDocHeight} /> ); @@ -672,43 +675,43 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection Document={menuDoc} isContentActive={this.isContentActive} isDocumentActive={this.isContentActive} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - addDocTab={this.props.addDocTab} - onBrowseClick={this.props.onBrowseClick} + addDocument={this._props.addDocument} + moveDocument={this._props.moveDocument} + addDocTab={this._props.addDocTab} + onBrowseClick={this._props.onBrowseClick} pinToPres={emptyFunction} rootSelected={this.rootSelected} - removeDocument={this.props.removeDocument} + removeDocument={this._props.removeDocument} ScreenToLocalTransform={Transform.Identity} PanelWidth={this.return35} PanelHeight={this.return35} - renderDepth={this.props.renderDepth} + renderDepth={this._props.renderDepth} focus={emptyFunction} - styleProvider={this.props.styleProvider} + styleProvider={this._props.styleProvider} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} - childFilters={this.props.childFilters} - childFiltersByRanges={this.props.childFiltersByRanges} - searchFilterDocs={this.props.searchFilterDocs} + childFilters={this._props.childFilters} + childFiltersByRanges={this._props.childFiltersByRanges} + searchFilterDocs={this._props.searchFilterDocs} /> </div> ); } @computed get nativeWidth() { - return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); + return this._props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); } @computed get nativeHeight() { - return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); + return this._props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); } @computed get scaling() { - return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; + return !this.nativeWidth ? 1 : this._props.PanelHeight() / this.nativeHeight; } @computed get backgroundEvents() { - return this.props.isContentActive() === false ? 'none' : undefined; + return this._props.isContentActive() === false ? 'none' : undefined; } observer: any; render() { @@ -734,8 +737,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection ref={this.createRef} style={{ overflowY: this.isContentActive() ? 'auto' : 'hidden', - background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor), - pointerEvents: (this.props.pointerEvents?.() as any) ?? this.backgroundEvents, + background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor), + pointerEvents: (this._props.pointerEvents?.() as any) ?? this.backgroundEvents, }} onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))} onDrop={this.onExternalDrop.bind(this)} @@ -743,7 +746,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection onWheel={e => this.isContentActive() && e.stopPropagation()}> {this.renderedSections} {!this.showAddAGroup ? null : ( - <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}> + <div key={`${this._props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}> <EditableView {...editableViewProps} /> </div> )} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 3282eb4b4..b8dcd6248 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, untracked } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { RichTextField } from '../../../fields/RichTextField'; @@ -50,15 +50,22 @@ interface CSVFieldColumnProps { @observer export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldColumnProps> { - @observable private _background = 'inherit'; - private dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; private _headerRef: React.RefObject<HTMLDivElement> = React.createRef(); - + @observable private _background = 'inherit'; @observable _paletteOn = false; - @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading; - @observable _color = this.props.headingObject ? this.props.headingObject.color : '#f1efeb'; + @observable _heading = ''; + @observable _color = ''; + + @observable _props!: CSVFieldColumnProps; + constructor(props: CSVFieldColumnProps) { + super(props); + this._props = props; + makeObservable(this); + this._heading = this._props.headingObject ? this._props.headingObject.heading : this._props.heading; + this._color = this._props.headingObject ? this._props.headingObject.color : '#f1efeb'; + } _ele: HTMLElement | null = null; @@ -68,29 +75,33 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC this.dropDisposer?.(); if (ele) { this._ele = ele; - this.props.observeHeight(ele); + this._props.observeHeight(ele); this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this)); } }; + componentDidUpdate() { + // untracked(() => (this._props = this.props)); + } + @action componentDidMount() { this._disposers.collapser = reaction( - () => this.props.headingObject?.collapsed, + () => this._props.headingObject?.collapsed, collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false), { fireImmediately: true } ); } componentWillUnmount() { this._disposers.collapser?.(); - this.props.unobserveHeight(this._ele); + this._props.unobserveHeight(this._ele); } //TODO: what is scripting? I found it in SetInPlace def but don't know what that is @undoBatch columnDrop = action((e: Event, de: DragManager.DropEvent) => { const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) }; - this.props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false)); + this._props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this._props.pivotField, drop.val, false)); return true; }); getValue = (value: string): any => { @@ -105,13 +116,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC headingChanged = (value: string, shiftDown?: boolean) => { const castedValue = this.getValue(value); if (castedValue) { - if (this.props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { + if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { return false; } - this.props.docList.forEach(d => (d[this.props.pivotField] = castedValue)); - if (this.props.headingObject) { - this.props.headingObject.setHeading(castedValue.toString()); - this._heading = this.props.headingObject.heading; + this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue)); + if (this._props.headingObject) { + this._props.headingObject.setHeading(castedValue.toString()); + this._heading = this._props.headingObject.heading; } return true; } @@ -120,7 +131,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action changeColumnColor = (color: string) => { - this.props.headingObject?.setColor(color); + this._props.headingObject?.setColor(color); this._color = color; }; @@ -131,30 +142,30 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { if (!value && !forceEmptyNote) return false; - const key = this.props.pivotField; + const key = this._props.pivotField; const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true }); - newDoc[key] = this.getValue(this.props.heading); - const maxHeading = this.props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0); - const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; + newDoc[key] = this.getValue(this._props.heading); + const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0); + const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; newDoc.heading = heading; FormattedTextBox.SetSelectOnLoad(newDoc); FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' '; - return this.props.addDocument?.(newDoc) || false; + return this._props.addDocument?.(newDoc) || false; }; @action deleteColumn = () => { - this.props.docList.forEach(d => (d[this.props.pivotField] = undefined)); - if (this.props.colHeaderData && this.props.headingObject) { - const index = this.props.colHeaderData.indexOf(this.props.headingObject); - this.props.colHeaderData.splice(index, 1); + this._props.docList.forEach(d => (d[this._props.pivotField] = undefined)); + if (this._props.colHeaderData && this._props.headingObject) { + const index = this._props.colHeaderData.indexOf(this._props.headingObject); + this._props.colHeaderData.splice(index, 1); } }; @action collapseSection = () => { - this.props.headingObject?.setCollapsed(!this.props.headingObject.collapsed); - this.collapsed = BoolCast(this.props.headingObject?.collapsed); + this._props.headingObject?.setCollapsed(!this._props.headingObject.collapsed); + this.collapsed = BoolCast(this._props.headingObject?.collapsed); }; headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); @@ -162,12 +173,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC //TODO: I think this is where I'm supposed to edit stuff startDrag = (e: PointerEvent, down: number[], delta: number[]) => { // is MakeEmbedding a way to make a copy of a doc without rendering it? - const embedding = Doc.MakeEmbedding(this.props.Document); - embedding._width = this.props.columnWidth / (this.props.colHeaderData?.length || 1); + const embedding = Doc.MakeEmbedding(this._props.Document); + embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1); embedding._pivotField = undefined; let value = this.getValue(this._heading); value = typeof value === 'string' ? `"${value}"` : value; - embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name }); + embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this._props.pivotField} === ${value}`, { doc: Doc.name }); if (embedding.viewSpecScript) { DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY); return true; @@ -177,7 +188,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC renderColorPicker = () => { const gray = '#f1efeb'; - const selected = this.props.headingObject ? this.props.headingObject.color : gray; + const selected = this._props.headingObject ? this._props.headingObject.color : gray; const colors = ['pink2', 'purple4', 'bluegreen1', 'yellow4', 'gray', 'red2', 'bluegreen7', 'bluegreen5', 'orange1']; return ( <div className="collectionStackingView-colorPicker"> @@ -211,15 +222,15 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; const docItems: ContextMenuProps[] = []; - const dataDoc = this.props.TemplateDataDocument || this.props.Document; + const dataDoc = this._props.TemplateDataDocument || this._props.Document; const width = this._ele ? Number(getComputedStyle(this._ele).width.replace('px', '')) : 0; const height = this._ele ? Number(getComputedStyle(this._ele).height.replace('px', '')) : 0; DocUtils.addDocumentCreatorMenuItems( doc => { FormattedTextBox.SetSelectOnLoad(doc); - return this.props.addDocument?.(doc); + return this._props.addDocument?.(doc); }, - this.props.addDocument, + this._props.addDocument, 0, 0, true @@ -231,12 +242,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC docItems.push({ description: ':' + fieldKey, event: () => { - const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document)); + const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document)); if (created) { - if (this.props.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this.props.Document); + if (this._props.Document.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this._props.Document); } - return this.props.addDocument?.(created); + return this._props.addDocument?.(created); } }, icon: 'compress-arrows-alt', @@ -250,12 +261,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC event: () => { const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey }); if (created) { - const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document; + const container = this._props.Document.resolvedDataDoc ? Doc.GetProto(this._props.Document) : this._props.Document; if (container.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, container); return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); } - return this.props.addDocument?.(created) || false; + return this._props.addDocument?.(created) || false; } }, icon: 'compress-arrows-alt', @@ -264,16 +275,16 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' }); !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' }); ContextMenu.Instance.setDefaultItem('::', (name: string): void => { - Doc.GetProto(this.props.Document)[name] = ''; + Doc.GetProto(this._props.Document)[name] = ''; const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true }); if (created) { - if (this.props.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this.props.Document); + if (this._props.Document.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this._props.Document); } - this.props.addDocument?.(created); + this._props.addDocument?.(created); } }); - const pt = this.props + const pt = this._props .screenToLocalTransform() .inverse() .transformPoint(width - 30, height); @@ -282,21 +293,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @computed get innards() { TraceMobx(); - const key = this.props.pivotField; - const headings = this.props.headings(); + const key = this._props.pivotField; + const headings = this._props.headings(); const heading = this._heading; - const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin; + const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin; const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); const noValueHeader = `NO ${key.toUpperCase()} VALUE`; - const evContents = heading ? heading : this.props?.type === 'number' ? '0' : noValueHeader; - const headingView = this.props.headingObject ? ( + const evContents = heading ? heading : this._props?.type === 'number' ? '0' : noValueHeader; + const headingView = this._props.headingObject ? ( <div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef} style={{ - marginTop: this.props.yMargin, - width: this.props.columnWidth / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1), + marginTop: this._props.yMargin, + width: this._props.columnWidth / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1), }}> {/* the default bucket (no key value) has a tooltip that describes what it is. Further, it does not have a color and cannot be deleted. */} @@ -326,35 +337,35 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC )} */} </div> <div - className={'collectionStackingView-collapseBar' + (this.props.headingObject.collapsed === true ? ' active' : '')} - style={{ display: this.props.headingObject.collapsed === true ? 'block' : undefined }} + className={'collectionStackingView-collapseBar' + (this._props.headingObject.collapsed === true ? ' active' : '')} + style={{ display: this._props.headingObject.collapsed === true ? 'block' : undefined }} onClick={this.collapseSection} /> </div> ) : null; - const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `; - const type = this.props.Document.type; + const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `; + const type = this._props.Document.type; return ( <> - {this.props.Document._columnsHideIfEmpty ? null : headingView} + {this._props.Document._columnsHideIfEmpty ? null : headingView} {this.collapsed ? null : ( <div> <div key={`${heading}-stack`} className={`collectionStackingView-masonrySingle`} style={{ - padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`, + padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`, margin: 'auto', width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`, height: 'max-content', position: 'relative', - gridGap: this.props.gridGap, + gridGap: this._props.gridGap, gridTemplateColumns: templatecols, gridAutoRows: '0px', }}> - {this.props.renderChildren(this.props.docList)} + {this._props.renderChildren(this._props.docList)} </div> - {!this.props.chromeHidden && type !== DocumentType.PRES ? ( + {!this._props.chromeHidden && type !== DocumentType.PRES ? ( // TODO: this is the "new" button: see what you can work with here // change cursor to pointer for this, and update dragging cursor //TODO: there is a bug that occurs when adding a freeform document and trying to move it around @@ -365,7 +376,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC key={`${heading}-add-document`} onKeyDown={e => e.stopPropagation()} className="collectionStackingView-addDocumentButton" - style={{ width: 'calc(100% - 25px)', maxWidth: this.props.columnWidth / this.props.numGroupColumns - 25, marginBottom: 10 }}> + style={{ width: 'calc(100% - 25px)', maxWidth: this._props.columnWidth / this._props.numGroupColumns - 25, marginBottom: 10 }}> <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} @@ -384,7 +395,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC render() { TraceMobx(); - const headings = this.props.headings(); + const headings = this._props.headings(); const heading = this._heading; const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); return ( @@ -392,7 +403,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC className={'collectionStackingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')} key={heading} style={{ - width: `${100 / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1)}%`, + width: `${100 / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1)}%`, height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined, background: this._background, }} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 40b2f9644..fd53e12da 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, makeObservable, observable, override, runInAction, untracked } from 'mobx'; import * as rp from 'request-promise'; import CursorField from '../../../fields/CursorField'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; @@ -35,6 +35,17 @@ export function CollectionSubView<X>(moreProps?: X) { private dropDisposer?: DragManager.DragDropDisposer; private gestureDisposer?: GestureUtils.GestureEventDisposer; protected _mainCont?: HTMLDivElement; + @override _props: X & SubCollectionViewProps; + + constructor(props: any) { + super(props); + this._props = props; + makeObservable(this); + } + componentDidUpdate() { + // untracked(() => (this._props = this.props)); + } + @observable _focusFilters: Opt<string[]>; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it @observable _focusRangeFilters: Opt<string[]>; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it protected createDashEventsTarget = (ele: HTMLDivElement | null) => { @@ -55,29 +66,29 @@ export function CollectionSubView<X>(moreProps?: X) { this.gestureDisposer?.(); } - @computed get dataDoc() { - return this.props.TemplateDataDocument instanceof Doc && this.props.Document.isTemplateForField - ? Doc.GetProto(this.props.TemplateDataDocument) - : this.props.Document.resolvedDataDoc - ? this.props.Document - : Doc.GetProto(this.props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template + get dataDoc() { + return this._props.TemplateDataDocument instanceof Doc && this._props.Document.isTemplateForField + ? Doc.GetProto(this._props.TemplateDataDocument) + : this._props.Document.resolvedDataDoc + ? this._props.Document + : Doc.GetProto(this._props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template } // this returns whether either the collection is selected, or the template that it is part of is selected - rootSelected = () => this.props.isSelected() || BoolCast(this.props.TemplateDataDocument && this.props.rootSelected?.()); + rootSelected = () => this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.()); - // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument. + // The data field for rendering this collection will be on the this._props.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument. // When a document has a TemplateDataDoc but it's not a template, then it contains its own rendering data, but needs to pass the TemplateDataDoc through // to its children which may be templates. // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey' @computed get dataField() { - return this.dataDoc[this.props.fieldKey]; // this used to be 'layoutDoc', but then template fields will get ignored since the template is not a proto of the layout. hopefully nothing depending on the previous code. + return this.dataDoc[this._props.fieldKey]; // this used to be 'layoutDoc', but then template fields will get ignored since the template is not a proto of the layout. hopefully nothing depending on the previous code. } @computed get childLayoutPairs(): { layout: Doc; data: Doc }[] { - const { Document, TemplateDataDocument } = this.props; + const { Document, TemplateDataDocument } = this._props; const validPairs = this.childDocs - .map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc)) + .map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc)) .filter(pair => { // filter out any documents that have a proto that we don't have permissions to return !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate)); @@ -87,14 +98,14 @@ export function CollectionSubView<X>(moreProps?: X) { @computed get childDocList() { return Cast(this.dataField, listSpec(Doc)); } - collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._childFilters); - collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._childFiltersByRanges, listSpec('string'), []); + collectionFilters = () => this._focusFilters ?? StrListCast(this._props.Document._childFilters); + collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this._props.Document._childFiltersByRanges, listSpec('string'), []); // child filters apply to the descendants of the documents in this collection - childDocFilters = () => [...(this.props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()]; + childDocFilters = () => [...(this._props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()]; // unrecursive filters apply to the documents in the collection, but no their children. See Utils.noRecursionHack - unrecursiveDocFilters = () => [...(this.props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])]; - childDocRangeFilters = () => [...(this.props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()]; - searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs); + unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])]; + childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()]; + searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this._props.Document._searchFilterDocs); @computed.struct get childDocs() { TraceMobx(); let rawdocs: (Doc | Promise<Doc>)[] = []; @@ -108,27 +119,27 @@ export function CollectionSubView<X>(moreProps?: X) { // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc. // For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection. // Since the data field is actually an image, we set the list of documents to the singleton of root document's proto which will be an image. - const templateRoot = this.props.TemplateDataDocument; - rawdocs = templateRoot && !this.props.isAnnotationOverlay ? [Doc.GetProto(templateRoot)] : []; + const templateRoot = this._props.TemplateDataDocument; + rawdocs = templateRoot && !this._props.isAnnotationOverlay ? [Doc.GetProto(templateRoot)] : []; } - const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc); + const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this._props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc); const childDocFilters = this.childDocFilters(); const childFiltersByRanges = this.childDocRangeFilters(); const searchDocs = this.searchFilterDocs(); - if (this.props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) { + if (this._props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) { return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one } const docsforFilter: Doc[] = []; childDocs.forEach(d => { // dragging facets - const dragged = this.props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter)); + const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter)); if (dragged && SnappingManager.GetCanEmbed() && DragManager.docsBeingDragged.includes(d)) return false; - let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.props.Document).length > 0; + let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this._props.Document).length > 0; if (notFiltered) { - notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.props.Document).length > 0; + notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this._props.Document).length > 0; const fieldKey = Doc.LayoutFieldKey(d); const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name); const data = d[annos ? fieldKey + '_annotations' : fieldKey]; @@ -161,7 +172,7 @@ export function CollectionSubView<X>(moreProps?: X) { @action protected async setCursorPosition(position: [number, number]) { let ind; - const doc = this.props.Document; + const doc = this._props.Document; const id = Doc.UserDoc()[Id]; const email = Doc.CurrentUserEmail; const pos = { x: position[0], y: position[1] }; @@ -197,23 +208,23 @@ export function CollectionSubView<X>(moreProps?: X) { const dropAction = this.layoutDoc.dropAction as dropActionType; // if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding. // so we check if our collection has a dropAction set on it and if so, we use that instead. - if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) { + if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this._props.Document && this.childDocs.includes(d))) { de.complete.docDragData.dropAction = dropAction; } e.stopPropagation(); } } - addDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.addDocument?.(doc, annotationKey) || false; - removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.removeDocument?.(doc, annotationKey) || false; - moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this.props.moveDocument?.(doc, targetCollection, addDocument); - @action + addDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.addDocument?.(doc, annotationKey) || false; + removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.removeDocument?.(doc, annotationKey) || false; + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument); + protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { const docDragData = de.complete.docDragData; if (docDragData) { let added = undefined; const dropAction = docDragData.dropAction || docDragData.userDropAction; - const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]); + const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]); const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag)); if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop)); if ((!dropAction || dropAction === 'inSame' || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) { @@ -236,13 +247,13 @@ export function CollectionSubView<X>(moreProps?: X) { added = this.addDocument(docDragData.droppedDocuments); !added && alert('You cannot perform this move'); } - added === false && !this.props.isAnnotationOverlay && e.preventDefault(); + added === false && !this._props.isAnnotationOverlay && e.preventDefault(); added === true && e.stopPropagation(); return added ? true : false; } else if (de.complete.annoDragData) { const dropCreator = de.complete.annoDragData.dropDocCreator; de.complete.annoDragData.dropDocCreator = () => { - const dropped = dropCreator(this.props.isAnnotationOverlay ? this.Document : undefined); + const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined); this.addDocument(dropped); return dropped; }; @@ -252,7 +263,6 @@ export function CollectionSubView<X>(moreProps?: X) { } @undoBatch - @action protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: (docs: Doc[]) => void) { if (e.ctrlKey) { e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl @@ -463,15 +473,15 @@ export function CollectionSubView<X>(moreProps?: X) { } if (generatedDocuments.length) { // Creating a dash document - const isFreeformView = this.props.Document._type_collection === CollectionViewType.Freeform; + const isFreeformView = this._props.Document._type_collection === CollectionViewType.Freeform; const set = !isFreeformView ? generatedDocuments : generatedDocuments.length > 1 - ? generatedDocuments.map(d => { - DocUtils.iconify(d); - return d; - }) - : []; + ? generatedDocuments.map(d => { + DocUtils.iconify(d); + return d; + }) + : []; if (completed) completed(set); else { if (isFreeformView && generatedDocuments.length > 1) { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 5c165fe70..938af002f 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; @@ -7,7 +7,7 @@ import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils'; +import { copyProps, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; @@ -23,7 +23,7 @@ import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; import { CollectionFreeFormView } from './collectionFreeForm'; -import { CollectionSubView } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import './CollectionTreeView.scss'; import { TreeView } from './TreeView'; import * as React from 'react'; @@ -59,27 +59,35 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree private refList: Set<any> = new Set(); // list of tree view items to monitor for height changes private observer: any; // observer for monitoring tree view items. - @computed get doc() { - return this.props.Document; + _prevProps: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>; + @override _props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>; + constructor(props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>) { + super(props); + this._props = this._prevProps = props; + makeObservable(this); } - @computed get dataDoc() { - return this.props.TemplateDataDocument || this.doc; + componentDidUpdate() { + copyProps(this); + } + + get dataDoc() { + return this._props.TemplateDataDocument || this.Document; } @computed get treeViewtruncateTitleWidth() { - return NumCast(this.doc.treeView_TruncateTitleWidth, this.panelWidth()); + return NumCast(this.Document.treeView_TruncateTitleWidth, this.panelWidth()); } @computed get treeChildren() { TraceMobx(); - return this.props.childDocuments || this.childDocs; + return this._props.childDocuments || this.childDocs; } @computed get outlineMode() { - return this.doc.treeView_Type === TreeViewType.outline; + return this.Document.treeView_Type === TreeViewType.outline; } @computed get fileSysMode() { - return this.doc.treeView_Type === TreeViewType.fileSystem; + return this.Document.treeView_Type === TreeViewType.fileSystem; } @computed get dashboardMode() { - return this.doc === Doc.MyDashboards; + return this.Document === Doc.MyDashboards; } @observable _titleHeight = 0; // height of the title bar @@ -88,8 +96,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent @observable _isAnyChildContentActive = false; - whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive))); - isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this.props.isContentActive() ? true : false); + whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive))); + isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this._props.isContentActive() ? true : false); componentWillUnmount() { this._isDisposing = true; @@ -99,7 +107,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } componentDidMount() { - //this.props.setContentView?.(this); + //this._props.setContentView?.(this); this._disposers.autoheight = reaction( () => this.layoutDoc.layout_autoHeight, auto => auto && this.computeHeight(), @@ -112,7 +120,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', '')); const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()) + 6; this.layoutDoc._layout_autoHeightMargins = bodyHeight; - !this.props.dontRegisterView && this.props.setHeight?.(bodyHeight + titleHeight); + !this._props.dontRegisterView && this._props.setHeight?.(bodyHeight + titleHeight); } }; unobserveHeight = (ref: any) => { @@ -135,7 +143,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree }; 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.Document, this.onInternalPreDrop.bind(this)); }; protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent) => { @@ -143,7 +151,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree const dragData = de.complete.docDragData; if (dragData) { const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.Document) ? true : false; - const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d)); + const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d)); if (isAlreadyInTree() !== sameTree) { console.log('WHAAAT'); } @@ -152,22 +160,22 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } }; - screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this._headerHeight); + screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this._headerHeight); @action remove = (doc: Doc | Doc[]): boolean => { const docs = doc instanceof Doc ? [doc] : doc; - const targetDataDoc = this.doc[DocData]; - const value = DocListCast(targetDataDoc[this.props.fieldKey]); + const targetDataDoc = this.Document[DocData]; + const value = DocListCast(targetDataDoc[this._props.fieldKey]); const result = value.filter(v => !docs.includes(v)); if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views().some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll(); if (result.length !== value.length && doc instanceof Doc) { - const ind = DocListCast(targetDataDoc[this.props.fieldKey]).indexOf(doc); - const prev = ind && DocListCast(targetDataDoc[this.props.fieldKey])[ind - 1]; - this.props.removeDocument?.(doc); + const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc); + const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1]; + this._props.removeDocument?.(doc); if (ind > 0 && prev) { FormattedTextBox.SetSelectOnLoad(prev); - DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false); + DocumentManager.Instance.getDocumentView(prev, this._props.DocumentView?.())?.select(false); } return true; } @@ -178,24 +186,28 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => { const doAddDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => { - const res = flg && Doc.AddDocToList(this.doc[DocData], this.props.fieldKey, doc, relativeTo, before); - res && Doc.SetContainer(doc, this.props.Document); + const res = flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before); + res && Doc.SetContainer(doc, this.Document); return res; }, true); - if (this.doc.resolvedDataDoc instanceof Promise) return false; - return relativeTo === undefined ? this.props.addDocument?.(docs) || false : doAddDoc(docs); + if (this.Document.resolvedDataDoc instanceof Promise) return false; + return relativeTo === undefined ? this._props.addDocument?.(docs) || false : doAddDoc(docs); }; 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 (!Doc.noviceMode) { const layoutItems: ContextMenuProps[] = []; - layoutItems.push({ description: 'Make tree state ' + (this.doc.treeView_OpenIsTransient ? 'persistent' : 'transient'), event: () => (this.doc.treeView_OpenIsTransient = !this.doc.treeView_OpenIsTransient), icon: 'paint-brush' }); - layoutItems.push({ description: (this.doc.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.doc.treeView_HideHeaderFields = !this.doc.treeView_HideHeaderFields), icon: 'paint-brush' }); - layoutItems.push({ description: (this.doc.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.doc.treeView_HideTitle = !this.doc.treeView_HideTitle), icon: 'paint-brush' }); + layoutItems.push({ + description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'), + event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient), + icon: 'paint-brush', + }); + layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' }); + layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), 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' }); + onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' }); !existingOnClick && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } }; @@ -213,7 +225,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree height={'auto'} GetValue={() => StrCast(this.dataDoc.title)} SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { - if (enter && this.props.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren); + if (enter && this.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren); this.dataDoc.title = value; return true; })} @@ -231,9 +243,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree get documentTitle() { return ( <FormattedTextBox - {...this.props} + {...this._props} fieldKey="text" - renderDepth={this.props.renderDepth + 1} + renderDepth={this._props.renderDepth + 1} isContentActive={this.isContentActive} isDocumentActive={this.isContentActive} forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not layout_autoHeight @@ -253,52 +265,52 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree ); } childContextMenuItems = () => { - const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []); - const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []); - const icons = StrListCast(this.doc.childContextMenuIcons); - return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); + const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []); + const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []); + const icons = StrListCast(this.Document.childContextMenuIcons); + return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; - headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeView_HideHeaderFields); + headerFields = () => this._props.treeViewHideHeaderFields || BoolCast(this.Document.treeView_HideHeaderFields); @observable _renderCount = 1; @computed get treeViewElements() { TraceMobx(); - const dragAction = StrCast(this.doc.childDragAction) as dropActionType; + const dragAction = StrCast(this.Document.childDragAction) 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) || false; + const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false; if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20)))); return TreeView.GetChildElements( this.treeChildren, this, this, - this.doc, - this.props.TemplateDataDocument, + this.Document, + this._props.TemplateDataDocument, undefined, undefined, addDoc, this.remove, moveDoc, dragAction, - this.props.addDocTab, - this.props.styleProvider, + this._props.addDocTab, + this._props.styleProvider, this.screenToLocalTransform, this.isContentActive, this.panelWidth, - this.props.renderDepth, + this._props.renderDepth, this.headerFields, [], - this.props.onCheckedClick, + this._props.onCheckedClick, this.onChildClick, - this.props.treeViewSkipFields, + this._props.treeViewSkipFields, true, this.whenChildContentsActiveChanged, - this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, 'boolean', null), + this._props.dontRegisterView || Cast(this.Document.childDontRegisterViews, 'boolean', null), this.observeHeight, this.unobserveHeight, this.childContextMenuItems(), //TODO: [AL] add these - this.props.AddToMap, - this.props.RemFromMap, - this.props.hierarchyIndex, + this._props.AddToMap, + this._props.RemFromMap, + this._props.hierarchyIndex, this._renderCount ); } @@ -306,8 +318,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree return this.dataDoc === null ? null : ( <div className="collectionTreeView-titleBar" - ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.props.ScreenToLocalTransform().Scale))} - key={this.doc[Id]} + ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this._props.ScreenToLocalTransform().Scale))} + key={this.Document[Id]} style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}> {this.outlineMode ? this.documentTitle : this.editableTitle} </div> @@ -327,26 +339,26 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree <DocumentView Document={menuDoc} TemplateDataDocument={menuDoc} - isContentActive={this.props.isContentActive} + isContentActive={this._props.isContentActive} isDocumentActive={returnTrue} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - removeDocument={this.props.removeDocument} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} + addDocument={this._props.addDocument} + moveDocument={this._props.moveDocument} + removeDocument={this._props.removeDocument} + addDocTab={this._props.addDocTab} + pinToPres={this._props.pinToPres} rootSelected={this.rootSelected} ScreenToLocalTransform={Transform.Identity} PanelWidth={this.return35} PanelHeight={this.return35} - renderDepth={this.props.renderDepth + 1} + renderDepth={this._props.renderDepth + 1} focus={emptyFunction} - styleProvider={this.props.styleProvider} + styleProvider={this._props.styleProvider} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} - childFilters={this.props.childFilters} - childFiltersByRanges={this.props.childFiltersByRanges} - searchFilterDocs={this.props.searchFilterDocs} + childFilters={this._props.childFilters} + childFiltersByRanges={this._props.childFiltersByRanges} + searchFilterDocs={this._props.searchFilterDocs} /> </div> ); @@ -363,30 +375,30 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @computed get nativeDimScaling() { const nw = this.nativeWidth; const nh = this.nativeHeight; - const hscale = nh ? this.props.PanelHeight() / nh : 1; - const wscale = nw ? this.props.PanelWidth() / nw : 1; + const hscale = nh ? this._props.PanelHeight() / nh : 1; + const wscale = nw ? this._props.PanelWidth() / nw : 1; return wscale < hscale ? wscale : hscale; } - marginX = () => NumCast(this.doc._xMargin); - marginTop = () => NumCast(this.doc._yMargin); - marginBot = () => NumCast(this.doc._yMargin); + marginX = () => NumCast(this.Document._xMargin); + marginTop = () => NumCast(this.Document._yMargin); + marginBot = () => NumCast(this.Document._yMargin); documentTitleWidth = () => Math.min(NumCast(this.layoutDoc?._width), this.panelWidth()); documentTitleHeight = () => NumCast(this.layoutDoc?._height) - NumCast(this.layoutDoc.layout_autoHeightMargins); truncateTitleWidth = () => this.treeViewtruncateTitleWidth; - onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); - panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1)); + onChildClick = () => this._props.onChildClick?.() || ScriptCast(this.Document.onChildClick); + panelWidth = () => Math.max(0, this._props.PanelWidth() - 2 * this.marginX() * (this._props.NativeDimScaling?.() || 1)); - addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this.props.fieldKey}_annotations`) || false; - remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this.props.fieldKey}_annotations`) || false; + addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this._props.fieldKey}_annotations`) || false; + remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this._props.fieldKey}_annotations`) || false; moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => - this.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}_annotations`) || false; + this.moveDocument(doc, targetCollection, addDocument, `${this._props.fieldKey}_annotations`) || false; @observable _headerHeight = 0; @computed get content() { - const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor); - const color = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.Color); - const pointerEvents = () => (this.props.isContentActive() === false ? 'none' : undefined); - const titleBar = this.props.treeViewHideTitle || this.doc.treeView_HideTitle ? null : this.titleBar; + const background = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor); + const color = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.Color); + const pointerEvents = () => (this._props.isContentActive() === false ? 'none' : undefined); + const titleBar = this._props.treeViewHideTitle || this.Document.treeView_HideTitle ? null : this.titleBar; return ( <div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}> {!this.buttonMenu && !this.noviceExplainer ? null : ( @@ -398,7 +410,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree <div className="collectionTreeView-contents" key="tree" - ref={r => !this.doc.treeView_HasOverlay && r && this.createTreeDropTarget(r)} + ref={r => !this.Document.treeView_HasOverlay && r && this.createTreeDropTarget(r)} style={{ ...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}), color: color(), @@ -423,7 +435,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree minHeight: '100%', }} onWheel={e => e.stopPropagation()} - onClick={e => (!this.layoutDoc.forceActive ? this.props.select(false) : SelectionManager.DeselectAll())} + onClick={e => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())} onDrop={this.onTreeDrop}> <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul> </div> @@ -435,27 +447,27 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree render() { TraceMobx(); - const scale = this.props.NativeDimScaling?.() || 1; + const scale = this._props.NativeDimScaling?.() || 1; return ( <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}> - {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeView_HasOverlay ? ( + {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? ( <CollectionFreeFormView - {...this.props} + {...this._props} setContentView={emptyFunction} NativeWidth={returnZero} NativeHeight={returnZero} - pointerEvents={this.props.isContentActive() && SnappingManager.GetIsDragging() ? returnAll : returnNone} + pointerEvents={this._props.isContentActive() && SnappingManager.GetIsDragging() ? returnAll : returnNone} isAnnotationOverlay={true} isAnnotationOverlayScrollable={true} - childDocumentsActive={this.props.isDocumentActive} - fieldKey={this.props.fieldKey + '_annotations'} + childDocumentsActive={this._props.isDocumentActive} + fieldKey={this._props.fieldKey + '_annotations'} dropAction="move" select={emptyFunction} addDocument={this.addAnnotationDocument} removeDocument={this.remAnnotationDocument} moveDocument={this.moveAnnotationDocument} bringToFront={emptyFunction} - renderDepth={this.props.renderDepth + 1}> + renderDepth={this._props.renderDepth + 1}> {this.content} </CollectionFreeFormView> ) : ( diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 2e4c5af6b..e84c12e02 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,4 +1,4 @@ -import { IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; @@ -6,7 +6,7 @@ import { ObjectField } from '../../../fields/ObjectField'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { returnEmptyString } from '../../../Utils'; +import { copyProps, returnEmptyString } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; import { dropActionType } from '../../util/DragManager'; @@ -76,12 +76,19 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab this._safeMode = safeMode; } private reactionDisposer: IReactionDisposer | undefined; - @observable _isContentActive: boolean | undefined; + @observable _isContentActive: boolean | undefined = undefined; + _prevProps: React.PropsWithChildren<ViewBoxAnnotatableProps & CollectionViewProps>; + @override _props: React.PropsWithChildren<ViewBoxAnnotatableProps & CollectionViewProps>; constructor(props: any) { super(props); + this._props = this._prevProps = props; + makeObservable(this); runInAction(() => (this._annotationKeySuffix = returnEmptyString)); } + componentDidUpdate() { + copyProps(this); + } componentDidMount() { // we use a reaction/observable instead of a computed value to reduce invalidations. @@ -89,7 +96,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab // will cause downstream invalidations even if the computed value doesn't change. By making // this a reaction, downstream invalidations only occur when the reaction value actually changes. this.reactionDisposer = reaction( - () => (this.isAnyChildContentActive() ? true : this.props.isContentActive()), + () => (this.isAnyChildContentActive() ? true : this._props.isContentActive()), active => (this._isContentActive = active), { fireImmediately: true } ); @@ -112,7 +119,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab return viewField as any as CollectionViewType; } - screenToLocalTransform = () => (this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth())); + screenToLocalTransform = () => (this._props.renderDepth ? this._props.ScreenToLocalTransform() : this._props.ScreenToLocalTransform().scale(this._props.PanelWidth() / this.bodyPanelWidth())); // prettier-ignore private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => { TraceMobx(); @@ -172,7 +179,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab this.setupViewTypes('Appearance...', vtype => { const newRendition = Doc.MakeEmbedding(this.Document); newRendition._type_collection = vtype; - this.props.addDocTab(newRendition, OpenWhere.addRight); + this._props.addDocTab(newRendition, OpenWhere.addRight); return newRendition; }); @@ -180,10 +187,10 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab const optionItems = options && 'subitems' in options ? options.subitems : []; !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null; if (this.Document.childLayout instanceof Doc) { - optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' }); + optionItems.push({ description: 'View Child Layout', event: () => this._props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } if (this.Document.childClickedOpenTemplateView instanceof Doc) { - optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' }); + optionItems.push({ description: 'View Child Detailed Layout', event: () => this._props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox), icon: 'project-diagram' }); @@ -203,7 +210,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab event: (obj: any) => { const embedding = Doc.MakeEmbedding(this.Document); DocUtils.makeCustomViewClicked(embedding, undefined, func.key); - this.props.addDocTab(embedding, OpenWhere.addRight); + this._props.addDocTab(embedding, OpenWhere.addRight); }, }) ); @@ -226,34 +233,40 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab } }; - bodyPanelWidth = () => this.props.PanelWidth(); + bodyPanelWidth = () => this._props.PanelWidth(); - childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null); + childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null); isContentActive = (outsideReaction?: boolean) => this._isContentActive; + pointerEvents = () => { + const viewPath = this._props.DocumentView?.()?._props.docViewPath(); + return ( + this.layoutDoc._lockedPosition && // + viewPath?.lastElement()?.Document?._type_collection === CollectionViewType.Freeform + ); + }; + render() { TraceMobx(); + const pointerEvents = this.pointerEvents() ? 'none' : undefined; const props: SubCollectionViewProps = { - ...this.props, + ...this._props, addDocument: this.addDocument, moveDocument: this.moveDocument, removeDocument: this.removeDocument, isContentActive: this.isContentActive, isAnyChildContentActive: this.isAnyChildContentActive, PanelWidth: this.bodyPanelWidth, - PanelHeight: this.props.PanelHeight, + PanelHeight: this._props.PanelHeight, ScreenToLocalTransform: this.screenToLocalTransform, childLayoutTemplate: this.childLayoutTemplate, whenChildContentsActiveChanged: this.whenChildContentsActiveChanged, - childLayoutString: StrCast(this.Document.childLayoutString, this.props.childLayoutString), - childHideResizeHandles: this.props.childHideResizeHandles ?? BoolCast(this.Document.childHideResizeHandles), - childHideDecorationTitle: this.props.childHideDecorationTitle ?? BoolCast(this.Document.childHideDecorationTitle), + childLayoutString: StrCast(this.Document.childLayoutString, this._props.childLayoutString), + childHideResizeHandles: this._props.childHideResizeHandles ?? BoolCast(this.Document.childHideResizeHandles), + childHideDecorationTitle: this._props.childHideDecorationTitle ?? BoolCast(this.Document.childHideDecorationTitle), }; return ( - <div - className="collectionView" - onContextMenu={this.onContextMenu} - style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.Document?._type_collection === CollectionViewType.Freeform && this.layoutDoc._lockedPosition ? 'none' : undefined }}> + <div className="collectionView" onContextMenu={this.onContextMenu} style={{ pointerEvents }}> {this.renderSubView(this.collectionViewType, props)} </div> ); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 4a1d702b8..dc7ee206c 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Popup, Type } from 'browndash-components'; import { clamp } from 'lodash'; -import { action, computed, IReactionDisposer, observable, ObservableSet, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom/client'; import { Doc, Opt } from '../../../fields/Doc'; @@ -11,7 +11,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { FieldId } from '../../../fields/RefField'; import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils'; +import { copyProps, DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; @@ -48,6 +48,15 @@ export class TabDocView extends React.Component<TabDocViewProps> { static _allTabs = new ObservableSet<TabDocView>(); _mainCont: HTMLDivElement | null = null; _tabReaction: IReactionDisposer | undefined; + + _prevProps: React.PropsWithChildren<TabDocViewProps>; + @observable _props: React.PropsWithChildren<TabDocViewProps>; + constructor(props: React.PropsWithChildren<TabDocViewProps>) { + super(props); + this._props = this._prevProps = props; + makeObservable(this); + } + @observable _activated: boolean = false; @observable _panelWidth = 0; @observable _panelHeight = 0; @@ -57,21 +66,21 @@ export class TabDocView extends React.Component<TabDocViewProps> { @computed get _isUserActivated() { return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive; } - @computed get _isContentActive() { + get _isContentActive() { return this._isUserActivated || this._hovering; } - @observable _document: Doc | undefined; - @observable _view: DocumentView | undefined; + @observable _document: Doc | undefined = undefined; + @observable _view: DocumentView | undefined = undefined; @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } get stack() { - return (this.props as any).glContainer.parent.parent; + return this._props.glContainer.parent.parent; } get tab() { - return (this.props as any).glContainer.tab; + return this._props.glContainer.tab; } get view() { return this._view; @@ -162,12 +171,12 @@ export class TabDocView extends React.Component<TabDocViewProps> { this._isUserActivated ? 0 : this._hovering - ? 0.25 - : degree === Doc.DocBrushStatus.selfBrushed - ? 0.5 - : degree === Doc.DocBrushStatus.protoBrushed // - ? 0.7 - : 0.9 + ? 0.25 + : degree === Doc.DocBrushStatus.selfBrushed + ? 0.5 + : degree === Doc.DocBrushStatus.protoBrushed // + ? 0.7 + : 0.9 ) .rgb() .toString() @@ -316,7 +325,6 @@ export class TabDocView extends React.Component<TabDocViewProps> { setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs } - @action componentDidMount() { new _global.ResizeObserver( action((entries: any) => { @@ -325,25 +333,25 @@ export class TabDocView extends React.Component<TabDocViewProps> { this._panelHeight = entry.contentRect.height; } }) - ).observe(this.props.glContainer._element[0]); - this.props.glContainer.layoutManager.on('activeContentItemChanged', this.onActiveContentItemChanged); - this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined); + ).observe(this._props.glContainer._element[0]); + this._props.glContainer.layoutManager.on('activeContentItemChanged', this.onActiveContentItemChanged); + this._props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined); // this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }), // ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""), // { fireImmediately: true }); - TabDocView._allTabs.add(this); + runInAction(() => TabDocView._allTabs.add(this)); } componentDidUpdate() { this._view && DocumentManager.Instance.AddView(this._view); + copyProps(this); } - @action componentWillUnmount() { this._tabReaction?.(); this._view && DocumentManager.Instance.RemoveView(this._view); - TabDocView._allTabs.delete(this); + runInAction(() => TabDocView._allTabs.delete(this)); - this.props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged); + this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged); } // Flag indicating that when a tab is activated, it should not select it's document. @@ -420,7 +428,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { ScreenToLocalTransform = () => { this._forceInvalidateScreenToLocal; const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement); - return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity(); + return CollectionDockingView.Instance?._props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity(); }; PanelWidth = () => this._panelWidth; PanelHeight = () => this._panelHeight; @@ -441,8 +449,8 @@ export class TabDocView extends React.Component<TabDocViewProps> { this._lastView = this._view; })} renderDepth={0} - LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined} - hideTitle={this.props.keyValue} + LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined} + hideTitle={this._props.keyValue} Document={this._document} TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined} onBrowseClick={DocumentView.exploreMode} @@ -489,7 +497,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { } this._lastTab = this.tab; (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document); - DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document))); + DocServer.GetRefField(this._props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document))); new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref); } }}> @@ -547,8 +555,15 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { } } }; + + @observable _props: React.PropsWithChildren<TabMinimapViewProps>; + constructor(props: React.PropsWithChildren<TabMinimapViewProps>) { + super(props); + this._props = props; + makeObservable(this); + } @computed get renderBounds() { - const compView = this.props.tabView()?.ComponentView as CollectionFreeFormView; + const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView; const bounds = compView?.freeformData?.(true)?.bounds; if (!bounds) return undefined; const xbounds = bounds.r - bounds.x; @@ -556,10 +571,10 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { const dim = Math.max(xbounds, ybounds); return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim }; } - childLayoutTemplate = () => Cast(this.props.document.childLayoutTemplate, Doc, null); - returnMiniSize = () => NumCast(this.props.document._miniMapSize, 150); + childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null); + returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150); miniDown = (e: React.PointerEvent) => { - const doc = this.props.document; + const doc = this._props.document; const miniSize = this.returnMiniSize(); doc && setupMoveUpEvents( @@ -578,15 +593,15 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { popup = () => { if (!this.renderBounds) return <></>; const renderBounds = this.renderBounds; - const miniWidth = () => (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100; - const miniHeight = () => (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100; - const miniLeft = () => 50 + ((NumCast(this.props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2; - const miniTop = () => 50 + ((NumCast(this.props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2; + const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100; + const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100; + const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2; + const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2; const miniSize = this.returnMiniSize(); return ( - <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}> + <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}> <CollectionFreeFormView - Document={this.props.document} + Document={this._props.document} docViewPath={returnEmptyDoclist} childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. noOverlay={true} // don't render overlay Docs since they won't scale @@ -596,7 +611,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { select={emptyFunction} isSelected={returnFalse} dontRegisterView={true} - fieldKey={Doc.LayoutFieldKey(this.props.document)} + fieldKey={Doc.LayoutFieldKey(this._props.document)} bringToFront={emptyFunction} addDocument={returnFalse} moveDocument={returnFalse} @@ -608,7 +623,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { whenChildContentsActiveChanged={emptyFunction} focus={emptyFunction} styleProvider={TabMinimapView.miniStyleProvider} - addDocTab={this.props.addDocTab} + addDocTab={this._props.addDocTab} pinToPres={TabDocView.PinDoc} childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} @@ -622,7 +637,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { ); }; render() { - return this.props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this.props.document)) || this.props.document?._type_collection !== CollectionViewType.Freeform ? null : ( + return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : ( <div className="miniMap-hidden"> <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement={'top-end'} popup={this.popup} /> </div> diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index ac79e4fef..27ff7166d 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1,7 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconButton, Size } from 'browndash-components'; -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; @@ -12,7 +12,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; +import { copyProps, emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; @@ -44,7 +44,7 @@ export interface TreeViewProps { observeHeight: (ref: any) => void; unobserveHeight: (ref: any) => void; prevSibling?: Doc; - document: Doc; + Document: Doc; dataDoc?: Doc; treeViewParent: Doc; renderDepth: number; @@ -100,57 +100,61 @@ export class TreeView extends React.Component<TreeViewProps> { private _treedropDisposer?: DragManager.DragDropDisposer; get treeViewOpenIsTransient() { - return this.props.treeView.doc.treeView_OpenIsTransient || Doc.IsDataProto(this.doc); + return this.treeView.Document.treeView_OpenIsTransient || Doc.IsDataProto(this.Document); } set treeViewOpen(c: boolean) { if (this.treeViewOpenIsTransient) this._transientOpenState = c; else { - this.doc.treeView_Open = c; + this.Document.treeView_Open = c; this._transientOpenState = false; } } @observable _transientOpenState = false; // override of the treeView_Open field allowing the display state to be independent of the document's state @observable _editTitle: boolean = false; - @observable _dref: DocumentView | undefined | null; + @observable _dref: DocumentView | undefined | null = undefined; get displayName() { - return 'TreeView(' + this.props.document.title + ')'; + return 'TreeView(' + this.Document.title + ')'; } // this makes mobx trace() statements more descriptive get defaultExpandedView() { - return this.doc._type_collection === CollectionViewType.Docking + return this.Document._type_collection === CollectionViewType.Docking ? this.fieldKey - : this.props.treeView.dashboardMode + : this.treeView.dashboardMode ? this.fieldKey - : this.props.treeView.fileSysMode - ? this.doc.isFolder + : this.treeView.fileSysMode + ? this.Document.isFolder ? this.fieldKey : 'data' // file system folders display their contents (data). used to be they displayed their embeddings but now its a tree structure and not a flat list - : this.props.treeView.outlineMode || this.childDocs + : this.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.noviceMode ? 'layout' - : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields'); + : StrCast(this.treeView.Document.treeView_ExpandedView, 'fields'); } - @computed get doc() { - return this.props.document; + @computed get treeView() { + return this._props.treeView; + } + + @computed get Document() { + return this._props.Document; } @computed get treeViewOpen() { - return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, 'treeView_Open', 'boolean', true)) || this._transientOpenState; + return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState; } @computed get treeViewExpandedView() { - return this.validExpandViewTypes.includes(StrCast(this.doc.treeView_ExpandedView)) ? StrCast(this.doc.treeView_ExpandedView) : this.defaultExpandedView; + return this.validExpandViewTypes.includes(StrCast(this.Document.treeView_ExpandedView)) ? StrCast(this.Document.treeView_ExpandedView) : this.defaultExpandedView; } @computed get MAX_EMBED_HEIGHT() { - return NumCast(this.props.treeViewParent.maxEmbedHeight, 200); + return NumCast(this._props.treeViewParent.maxEmbedHeight, 200); } @computed get dataDoc() { - return this.doc[DocData]; + return this.Document[DocData]; } @computed get layoutDoc() { - return Doc.Layout(this.doc); + return Doc.Layout(this.Document); } @computed get fieldKey() { - return StrCast(this.doc._treeView_FieldKey, Doc.LayoutFieldKey(this.doc)); + return StrCast(this.Document._treeView_FieldKey, Doc.LayoutFieldKey(this.Document)); } @computed get childDocs() { return this.childDocList(this.fieldKey); @@ -169,30 +173,30 @@ export class TreeView extends React.Component<TreeViewProps> { } childDocList(field: string) { - const layout = Cast(Doc.LayoutField(this.doc), Doc, null); - return DocListCast(this.props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.doc[field]))); + const layout = Cast(Doc.LayoutField(this.Document), Doc, null); + return DocListCast(this._props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.Document[field]))); } moving: boolean = false; @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - if (this.doc !== target && addDoc !== returnFalse) { - const canAdd1 = (this.props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this.props.parentTreeView?.doc.data)) instanceof ComputedField); + if (this.Document !== target && addDoc !== returnFalse) { + const canAdd1 = (this._props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField); // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse - if (canAdd1 && this.props.removeDoc?.(doc) === true) { - this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = true); + if (canAdd1 && this._props.removeDoc?.(doc) === true) { + this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.moving = true); const res = addDoc(doc); - this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = false); + this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.moving = false); return res; } } return false; }; - @undoBatch @action remove = (doc: Doc | Doc[], key: string) => { - this.props.treeView.props.select(false); + @undoBatch remove = (doc: Doc | Doc[], key: string) => { + this.treeView._props.select(false); const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement()); const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); - res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.props.treeView.props.DocumentView?.())?.select(false); + res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView._props.DocumentView?.())?.select(false); return res; }; @@ -212,17 +216,16 @@ export class TreeView extends React.Component<TreeViewProps> { }; @action openLevel = (docView: DocumentView) => { - if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) { + if (this.Document.isFolder || Doc.IsSystem(this.Document)) { this.treeViewOpen = !this.treeViewOpen; } else { // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding - const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.Document : Doc.BestEmbedding(docView.Document); - this.props.addDocTab(bestEmbedding, OpenWhere.lightbox); + const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document); + this._props.addDocTab(bestEmbedding, OpenWhere.lightbox); } }; @undoBatch - @action recurToggle = (childList: Doc[]) => { if (childList.length > 0) { childList.forEach(child => { @@ -233,7 +236,6 @@ export class TreeView extends React.Component<TreeViewProps> { }; @undoBatch - @action getRunningChildren = (childList: Doc[]) => { if (childList.length === 0) { return []; @@ -253,23 +255,27 @@ export class TreeView extends React.Component<TreeViewProps> { static GetRunningChildren = new Map<Doc, any>(); static ToggleChildrenRun = new Map<Doc, () => void>(); - constructor(props: any) { + _prevProps: TreeViewProps; + @observable _props: TreeViewProps; + constructor(props: TreeViewProps) { super(props); + this._props = this._prevProps = props; + makeObservable(this); if (!TreeView._openLevelScript) { TreeView._openTitleScript = ScriptField.MakeScript('scriptContext.setEditTitle(documentView)', { scriptContext: 'any', documentView: 'any' }); TreeView._openLevelScript = ScriptField.MakeScript(`scriptContext.openLevel(documentView)`, { scriptContext: 'any', documentView: 'any' }); } - this._openScript = Doc.IsSystem(this.props.document) ? undefined : () => TreeView._openLevelScript!; - this._editTitleScript = Doc.IsSystem(this.props.document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!; + this._openScript = Doc.IsSystem(this.Document) ? undefined : () => TreeView._openLevelScript!; + this._editTitleScript = Doc.IsSystem(this.Document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!; // set for child processing highligting this.dataDoc.hasChildren = this.childDocs.length > 0; // this.dataDoc.children = this.childDocs; - TreeView.ToggleChildrenRun.set(this.doc, () => { + TreeView.ToggleChildrenRun.set(this.Document, () => { this.recurToggle(this.childDocs); }); - TreeView.GetRunningChildren.set(this.doc, () => { + TreeView.GetRunningChildren.set(this.Document, () => { return this.getRunningChildren(this.childDocs); }); } @@ -277,31 +283,32 @@ export class TreeView extends React.Component<TreeViewProps> { _treeEle: any; protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); - ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.doc); - if (this._treeEle) this.props.unobserveHeight(this._treeEle); - this.props.observeHeight((this._treeEle = ele)); + ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.Document); + if (this._treeEle) this._props.unobserveHeight(this._treeEle); + this._props.observeHeight((this._treeEle = ele)); }; componentWillUnmount() { this._renderTimer && clearTimeout(this._renderTimer); Object.values(this._disposers).forEach(disposer => disposer?.()); - this._treeEle && this.props.unobserveHeight(this._treeEle); + this._treeEle && this._props.unobserveHeight(this._treeEle); document.removeEventListener('pointermove', this.onDragMove, true); document.removeEventListener('pointermove', this.onDragUp, true); // TODO: [AL] add these - this.props.hierarchyIndex !== undefined && this.props.RemFromMap?.(this.doc, this.props.hierarchyIndex); + this._props.hierarchyIndex !== undefined && this._props.RemFromMap?.(this.Document, this._props.hierarchyIndex); } componentDidUpdate() { + copyProps(this); this._disposers.opening = reaction( () => this.treeViewOpen, open => !open && (this._renderCount = 20) ); - this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex); + this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex); } componentDidMount() { - this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex); + this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex); } onDragUp = (e: PointerEvent) => { @@ -309,8 +316,8 @@ export class TreeView extends React.Component<TreeViewProps> { document.removeEventListener('pointermove', this.onDragMove, true); }; onPointerEnter = (e: React.PointerEvent): void => { - this.props.isContentActive(true) && Doc.BrushDoc(this.dataDoc); - if (e.buttons === 1 && SnappingManager.GetIsDragging() && this.props.isContentActive()) { + this._props.isContentActive(true) && Doc.BrushDoc(this.dataDoc); + if (e.buttons === 1 && SnappingManager.GetIsDragging() && this._props.isContentActive()) { this._header.current!.className = 'treeView-header'; document.removeEventListener('pointermove', this.onDragMove, true); document.removeEventListener('pointerup', this.onDragUp, true); @@ -333,7 +340,7 @@ export class TreeView extends React.Component<TreeViewProps> { const before = pt[1] < rect.top + rect.height / 2; const inside = pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length); this._header.current!.className = 'treeView-header'; - if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.Document) { + if (!this.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.treeView.Document) { 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'; @@ -371,18 +378,18 @@ export class TreeView extends React.Component<TreeViewProps> { makeTextCollection = () => { const bullet = TreeView.makeTextBullet(); TreeView._editTitleOnLoad = { id: bullet[Id], parent: this }; - return this.props.addDocument(bullet); + return this._props.addDocument(bullet); }; makeFolder = () => { const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }); - TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView }; - return this.props.addDocument(folder); + TreeView._editTitleOnLoad = { id: folder[Id], parent: this._props.parentTreeView }; + return this._props.addDocument(folder); }; preTreeDrop = (e: Event, de: DragManager.DropEvent) => { const dragData = de.complete.docDragData; - dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction); + dragData && (dragData.dropAction = this.treeView.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction); }; @undoBatch @@ -391,17 +398,17 @@ export class TreeView extends React.Component<TreeViewProps> { if (!this._header.current) return false; const rect = this._header.current.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); + const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); - const destDoc = this.doc; + const destDoc = this.Document; DocUtils.MakeLink(sourceDoc, destDoc, { link_relationship: 'tree link' }); e.stopPropagation(); return true; } const docDragData = de.complete.docDragData; if (docDragData && pt[0] < rect.left + rect.width) { - if (docDragData.draggedDocuments[0] === this.doc) return true; + if (docDragData.draggedDocuments[0] === this.Document) return true; const added = this.dropDocuments( docDragData.droppedDocuments, // before, @@ -409,7 +416,7 @@ export class TreeView extends React.Component<TreeViewProps> { docDragData.dropAction, docDragData.removeDocument, docDragData.moveDocument, - docDragData.treeViewDoc === this.props.treeView.props.Document + docDragData.treeViewDoc === this.treeView.Document ); e.stopPropagation(); !added && e.preventDefault(); @@ -420,40 +427,40 @@ export class TreeView extends React.Component<TreeViewProps> { dropping: boolean = false; dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) { - const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, undefined, before); + const parentAddDoc = (doc: Doc | Doc[]) => this._props.addDocument(doc, undefined, undefined, before); const localAdd = (doc: Doc | Doc[]) => { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer)); + dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); }; const addDoc = inside ? localAdd : parentAddDoc; const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument; - const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd; - if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this.props.parentTreeView?.doc))) { - this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true); + const canAdd = (!this.treeView.outlineMode && !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd; + if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) { + this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = true); const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false); - this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = false); + this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = false); return res; } return false; } refTransform = (ref: HTMLDivElement | undefined | null) => { - if (!ref) return this.props.ScreenToLocalTransform(); + if (!ref) return this._props.ScreenToLocalTransform(); const { scale, translateX, translateY } = Utils.GetScreenTransform(ref); - const outerXf = Utils.GetScreenTransform(this.props.treeView.MainEle()); - const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); + const outerXf = Utils.GetScreenTransform(this.treeView.MainEle()); + const offset = this._props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); + return this._props.ScreenToLocalTransform().translate(offset[0], offset[1]); }; docTransform = () => this.refTransform(this._dref?.ContentRef?.current); getTransform = () => this.refTransform(this._tref.current); - embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1); + embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1); embeddedPanelHeight = () => { - const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.Document))(this.treeView._props.childLayoutTemplate?.()) || this.layoutDoc; return Math.min( NumCast(layoutDoc._height), this.MAX_EMBED_HEIGHT, @@ -463,7 +470,7 @@ export class TreeView extends React.Component<TreeViewProps> { return layoutDoc._layout_fitWidth ? !Doc.NativeHeight(layoutDoc) ? NumCast(layoutDoc._height) - : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height))) + : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this._props.treeViewParent._height))) : (this.embeddedPanelWidth() * NumCast(layoutDoc._height)) / NumCast(layoutDoc._width); })() ); @@ -471,16 +478,16 @@ export class TreeView extends React.Component<TreeViewProps> { @computed get expandedField() { const ids: { [key: string]: string } = {}; const rows: JSX.Element[] = []; - const doc = this.doc; + const doc = this.Document; doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); for (const key of Object.keys(ids).slice().sort()) { - if (this.props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue; + if (this._props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue; const contents = doc[key]; let contentElement: (JSX.Element | null)[] | JSX.Element = []; let leftOffset = observable({ width: 0 }); - const expandedWidth = () => this.props.panelWidth() - leftOffset.width; + const expandedWidth = () => this._props.panelWidth() - leftOffset.width; 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 moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc); @@ -488,44 +495,44 @@ export class TreeView extends React.Component<TreeViewProps> { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer)); + dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); }; contentElement = TreeView.GetChildElements( contents instanceof Doc ? [contents] : DocListCast(contents), - this.props.treeView, + this.treeView, this, doc, undefined, - this.props.treeViewParent, - this.props.prevSibling, + this._props.treeViewParent, + this._props.prevSibling, addDoc, remDoc, moveDoc, - this.props.dragAction, - this.props.addDocTab, + this._props.dragAction, + this._props.addDocTab, this.titleStyleProvider, - this.props.ScreenToLocalTransform, - this.props.isContentActive, + this._props.ScreenToLocalTransform, + this._props.isContentActive, expandedWidth, - this.props.renderDepth, - this.props.treeViewHideHeaderFields, - [...this.props.renderedIds, doc[Id]], - this.props.onCheckedClick, - this.props.onChildClick, - this.props.skipFields, + this._props.renderDepth, + this._props.treeViewHideHeaderFields, + [...this._props.renderedIds, doc[Id]], + this._props.onCheckedClick, + this._props.onChildClick, + this._props.skipFields, false, - this.props.whenChildContentsActiveChanged, - this.props.dontRegisterView, + this._props.whenChildContentsActiveChanged, + this._props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(), // TODO: [AL] Add these - this.props.AddToMap, - this.props.RemFromMap, - this.props.hierarchyIndex + this._props.AddToMap, + this._props.RemFromMap, + this._props.hierarchyIndex ); } else { contentElement = ( @@ -573,9 +580,9 @@ export class TreeView extends React.Component<TreeViewProps> { @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; - const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {}; + const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {}; if (['links', 'annotations', 'embeddings', this.fieldKey].includes(expandKey)) { - const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded); + const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded); const sortKeys = Object.keys(sortings); const curSortIndex = Math.max( 0, @@ -587,7 +594,7 @@ export class TreeView extends React.Component<TreeViewProps> { const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { // if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't), // then the modification would be done here - const ordering = StrCast(this.doc.treeView_SortCriterion); + const ordering = StrCast(this.Document.treeView_SortCriterion); if (ordering === TreeSort.Zindex) { const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering); doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000; @@ -596,7 +603,7 @@ export class TreeView extends React.Component<TreeViewProps> { } const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - !dataIsComputed && added && Doc.SetContainer(doc, this.doc); + !dataIsComputed && added && Doc.SetContainer(doc, this.Document); return added; }; @@ -614,12 +621,12 @@ export class TreeView extends React.Component<TreeViewProps> { } return ( <div> - {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : ( + {!docs?.length || this._props.AddToMap /* hack to identify pres box trees */ ? null : ( <div className="treeView-sorting"> <IconButton color={sortings[sorting]?.color} size={Size.XSMALL} - tooltip={`Sorted by : ${this.doc.treeView_SortCriterion}. click to cycle`} + tooltip={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`} icon={sortings[sorting]?.icon} onPointerDown={e => { downX = e.clientX; @@ -627,8 +634,8 @@ export class TreeView extends React.Component<TreeViewProps> { e.stopPropagation(); }} onClick={undoable(e => { - if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { - !this.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); + if (this._props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { + !this.treeView.outlineMode && (this.Document.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); e.stopPropagation(); } }, 'sort order')} @@ -638,7 +645,7 @@ export class TreeView extends React.Component<TreeViewProps> { <ul style={{ cursor: 'inherit' }} key={expandKey + 'more'} - title={`Sorted by : ${this.doc.treeView_SortCriterion}. click to cycle`} + title={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`} className="" //this.doc.treeView_HideTitle ? 'no-indent' : ''} onPointerDown={e => { downX = e.clientX; @@ -646,8 +653,8 @@ export class TreeView extends React.Component<TreeViewProps> { e.stopPropagation(); }} onClick={undoable(e => { - if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { - !this.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); + if (this._props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { + !this.treeView.outlineMode && (this.Document.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); e.stopPropagation(); } }, 'sort order')}> @@ -655,37 +662,37 @@ export class TreeView extends React.Component<TreeViewProps> { ? null : TreeView.GetChildElements( docs, - this.props.treeView, + this.treeView, this, this.layoutDoc, this.dataDoc, - this.props.treeViewParent, - this.props.prevSibling, + this._props.treeViewParent, + this._props.prevSibling, addDoc, remDoc, moveDoc, - StrCast(this.doc.childDragAction, this.props.dragAction) as dropActionType, - this.props.addDocTab, + StrCast(this.Document.childDragAction, this._props.dragAction) as dropActionType, + this._props.addDocTab, this.titleStyleProvider, - this.props.ScreenToLocalTransform, - this.props.isContentActive, - this.props.panelWidth, - this.props.renderDepth, - this.props.treeViewHideHeaderFields, - [...this.props.renderedIds, this.doc[Id]], - this.props.onCheckedClick, - this.props.onChildClick, - this.props.skipFields, + this._props.ScreenToLocalTransform, + this._props.isContentActive, + this._props.panelWidth, + this._props.renderDepth, + this._props.treeViewHideHeaderFields, + [...this._props.renderedIds, this.Document[Id]], + this._props.onCheckedClick, + this._props.onChildClick, + this._props.skipFields, false, - this.props.whenChildContentsActiveChanged, - this.props.dontRegisterView, + this._props.whenChildContentsActiveChanged, + this._props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(), // TODO: [AL] add these - this.props.AddToMap, - this.props.RemFromMap, - this.props.hierarchyIndex, + this._props.AddToMap, + this._props.RemFromMap, + this._props.hierarchyIndex, this._renderCount )} </ul> @@ -693,7 +700,7 @@ export class TreeView extends React.Component<TreeViewProps> { ); } else if (this.treeViewExpandedView === 'fields') { return ( - <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit' }}> + <ul key={this.Document[Id] + this.Document.title} style={{ cursor: 'inherit' }}> <div>{this.expandedField}</div> </ul> ); @@ -705,13 +712,13 @@ export class TreeView extends React.Component<TreeViewProps> { e.preventDefault(); e.stopPropagation(); }}> - {this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)} + {this.renderEmbeddedDocument(false, this.treeView._props.childDocumentsActive ?? returnFalse)} </ul> ); // "layout" } get onCheckedClick() { - return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); + return this.Document.type === DocumentType.COL ? undefined : this._props.onCheckedClick?.() ?? ScriptCast(this.Document.onCheckedClick); } @action @@ -719,10 +726,10 @@ export class TreeView extends React.Component<TreeViewProps> { if (this.onCheckedClick) { this.onCheckedClick?.script.run( { - this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, - heading: this.props.treeViewParent.title, - checked: this.doc.treeView_Checked === 'check' ? 'x' : this.doc.treeView_Checked === 'x' ? 'remove' : 'check', - containingTreeView: this.props.treeView.props.Document, + this: this.Document.isTemplateForField && this._props.dataDoc ? this._props.dataDoc : this.Document, + heading: this._props.treeViewParent.title, + checked: this.Document.treeView_Checked === 'check' ? 'x' : this.Document.treeView_Checked === 'x' ? 'remove' : 'check', + containingTreeView: this.treeView.Document, }, console.log ); @@ -734,28 +741,28 @@ export class TreeView extends React.Component<TreeViewProps> { @computed get renderBullet() { TraceMobx(); - const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question'; + const iconType = this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question'; const color = SettingsManager.userColor; - const checked = this.onCheckedClick ? this.doc.treeView_Checked ?? 'unchecked' : undefined; + const checked = this.onCheckedClick ? this.Document.treeView_Checked ?? 'unchecked' : undefined; return ( <div - className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`} + className={`bullet${this.treeView.outlineMode ? '-outline' : ''}`} key="bullet" - title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.props.document.type} content`} + title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.Document.type} content`} onClick={this.bulletClick} style={ - this.props.treeView.outlineMode + this.treeView.outlineMode ? { - opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity), + opacity: this.titleStyleProvider?.(this.Document, this.treeView._props, StyleProp.Opacity), } : { - pointerEvents: this.props.isContentActive() ? 'all' : undefined, + pointerEvents: this._props.isContentActive() ? 'all' : undefined, opacity: checked === 'unchecked' || typeof iconType !== 'string' ? undefined : 0.4, color: checked === 'unchecked' ? SettingsManager.userColor : 'inherit', } }> - {this.props.treeView.outlineMode ? ( - !(this.doc.text as RichTextField)?.Text ? null : ( + {this.treeView.outlineMode ? ( + !(this.Document.text as RichTextField)?.Text ? null : ( <IconButton color={color} icon={<FontAwesomeIcon icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />} size={Size.XSMALL} /> ) ) : ( @@ -775,28 +782,28 @@ export class TreeView extends React.Component<TreeViewProps> { } @computed get validExpandViewTypes() { - const annos = () => (DocListCast(this.doc[this.fieldKey + '_annotations']).length && !this.props.treeView.dashboardMode ? 'annotations' : ''); - const links = () => (LinkManager.Links(this.doc).length && !this.props.treeView.dashboardMode ? 'links' : ''); - const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : ''); - const embeddings = () => (this.props.treeView.dashboardMode ? '' : 'embeddings'); + const annos = () => (DocListCast(this.Document[this.fieldKey + '_annotations']).length && !this.treeView.dashboardMode ? 'annotations' : ''); + const links = () => (LinkManager.Links(this.Document).length && !this.treeView.dashboardMode ? 'links' : ''); + const data = () => (this.childDocs || this.treeView.dashboardMode ? this.fieldKey : ''); + const embeddings = () => (this.treeView.dashboardMode ? '' : 'embeddings'); const fields = () => (Doc.noviceMode ? '' : 'fields'); - const layout = Doc.noviceMode || this.doc._type_collection === CollectionViewType.Docking ? [] : ['layout']; - return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m); + const layout = Doc.noviceMode || this.Document._type_collection === CollectionViewType.Docking ? [] : ['layout']; + return [data(), ...layout, ...(this.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m); } @action expandNextviewType = () => { - if (this.treeViewOpen && !this.doc.isFolder && !this.props.treeView.outlineMode && !this.doc.treeView_ExpandedViewLock) { + if (this.treeViewOpen && !this.Document.isFolder && !this.treeView.outlineMode && !this.Document.treeView_ExpandedViewLock) { const next = (modes: any[]) => modes[(modes.indexOf(StrCast(this.treeViewExpandedView)) + 1) % modes.length]; - this.doc.treeView_ExpandedView = next(this.validExpandViewTypes); + this.Document.treeView_ExpandedView = next(this.validExpandViewTypes); } this.treeViewOpen = true; }; @observable headerEleWidth = 0; @computed get titleButtons() { - const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations); + const customHeaderButtons = this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.Decorations); const color = SettingsManager.userColor; - return this.props.treeViewHideHeaderFields() || this.doc.treeView_HideHeaderFields ? null : ( + return this._props.treeViewHideHeaderFields() || this.Document.treeView_HideHeaderFields ? null : ( <> {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */} <IconButton @@ -808,7 +815,7 @@ export class TreeView extends React.Component<TreeViewProps> { e.stopPropagation(); }} /> - {Doc.noviceMode ? null : this.doc.treeView_ExpandedViewLock || Doc.IsSystem(this.doc) ? null : ( + {Doc.noviceMode ? null : this.Document.treeView_ExpandedViewLock || Doc.IsSystem(this.Document) ? null : ( <span className="collectionTreeView-keyHeader" title="type of expanded data" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}> {this.treeViewExpandedView} </span> @@ -829,50 +836,50 @@ export class TreeView extends React.Component<TreeViewProps> { const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Focus or Open' }; const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Reopen' }; return [ - ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)), - ...(this.doc.isFolder + ...(this._props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.Document })?.result)), + ...(this.Document.isFolder ? folderOp - : Doc.IsSystem(this.doc) + : Doc.IsSystem(this.Document) ? [] - : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) + : this.treeView.fileSysMode && this.Document === Doc.GetProto(this.Document) ? [openEmbedding, makeFolder] - : this.doc._type_collection === CollectionViewType.Docking + : this.Document._type_collection === CollectionViewType.Docking ? [] - : this.props.treeView.Document === Doc.MyRecentlyClosed + : this.treeView.Document === Doc.MyRecentlyClosed ? [reopenDoc] : [openEmbedding, focusDoc]), ]; }; childContextMenuItems = () => { - const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []); - const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []); - const icons = StrListCast(this.doc.childContextMenuIcons); - return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); + const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []); + const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []); + const icons = StrListCast(this.Document.childContextMenuIcons); + return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; - onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!); + onChildClick = () => this._props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!); - onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeView_ChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null); + onChildDoubleClick = () => ScriptCast(this.treeView.Document.treeView_ChildDoubleClick, !this.treeView.outlineMode ? this._openScript?.() : null); - refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document, {}); + refocus = () => this.treeView._props.focus(this.treeView.Document, {}); ignoreEvent = (e: any) => { - if (this.props.isContentActive(true)) { + if (this._props.isContentActive(true)) { e.stopPropagation(); e.preventDefault(); } }; titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { - if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView + if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView - const treeView = this.props.treeView; + const treeView = this.treeView; // prettier-ignore switch (property.split(':')[0]) { - case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1; + case StyleProp.Opacity: return this.treeView.outlineMode ? undefined : 1; case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); - case StyleProp.Highlighting: if (this.props.treeView.outlineMode) return undefined; + case StyleProp.Highlighting: if (this.treeView.outlineMode) return undefined; case StyleProp.BoxShadow: return undefined; case StyleProp.DocContents: - const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc); + const highlightIndex = this.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc); const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; return treeView.outlineMode ? null : ( <div @@ -882,32 +889,32 @@ export class TreeView extends React.Component<TreeViewProps> { maxWidth: props?.PanelWidth() || undefined, background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor), outline: SnappingManager.GetIsDragging() ? undefined: `solid ${highlightColor} ${highlightIndex}px`, - paddingLeft: NumCast(treeView.Document.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)), - paddingRight: NumCast(treeView.Document.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)), - paddingTop: treeView.props.childYPadding, - paddingBottom: treeView.props.childYPadding, + paddingLeft: NumCast(treeView.Document.childXPadding, NumCast(treeView._props.childXPadding, Doc.IsComicStyle(doc)?20:0)), + paddingRight: NumCast(treeView.Document.childXPadding, NumCast(treeView._props.childXPadding, Doc.IsComicStyle(doc)?20:0)), + paddingTop: treeView._props.childYPadding, + paddingBottom: treeView._props.childYPadding, }}> {StrCast(doc?.title)} </div> ); } - return treeView.props.styleProvider?.(doc, props, property); + return treeView._props.styleProvider?.(doc, props, property); }; embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { if (property.startsWith(StyleProp.Decorations)) return null; - return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView + return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView }; onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { - if (this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) { + if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) { switch (e.key) { case 'Tab': e.stopPropagation?.(); e.preventDefault?.(); setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); - UndoManager.RunInBatch(() => (e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true)), 'tab'); + UndoManager.RunInBatch(() => (e.shiftKey ? this._props.outdentDocument?.(true) : this._props.indentDocument?.(true)), 'tab'); return true; case 'Backspace': - if (!(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) { + if (!(this.Document.text as RichTextField)?.Text && this._props.removeDoc?.(this.Document)) { e.stopPropagation?.(); e.preventDefault?.(); return true; @@ -921,7 +928,7 @@ export class TreeView extends React.Component<TreeViewProps> { } return false; }; - titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth(); + titleWidth = () => Math.max(20, Math.min(this.treeView.truncateTitleWidth(), this._props.panelWidth())) / (this.treeView._props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth(); /** * Renders the EditableView title element for placement into the tree. @@ -936,21 +943,21 @@ export class TreeView extends React.Component<TreeViewProps> { display={'inline-block'} editing={this._editTitle} background={'#7089bb'} - contents={StrCast(this.doc.title)} + contents={StrCast(this.Document.title)} height={12} sizeToContent={true} fontSize={12} isEditingCallback={action(e => (this._editTitle = e))} - GetValue={() => StrCast(this.doc.title)} + GetValue={() => StrCast(this.Document.title)} OnTab={undoBatch((shift?: boolean) => { - if (!shift) this.props.indentDocument?.(true); - else this.props.outdentDocument?.(true); + if (!shift) this._props.indentDocument?.(true); + else this._props.outdentDocument?.(true); })} - OnEmpty={undoBatch(() => this.props.treeView.outlineMode && this.props.removeDoc?.(this.doc))} - OnFillDown={val => this.props.treeView.fileSysMode && this.makeFolder()} + OnEmpty={undoBatch(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document))} + OnFillDown={val => this.treeView.fileSysMode && this.makeFolder()} SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { - Doc.SetInPlace(this.doc, 'title', value, false); - this.props.treeView.outlineMode && enterKey && this.makeTextCollection(); + Doc.SetInPlace(this.Document, 'title', value, false); + this.treeView.outlineMode && enterKey && this.makeTextCollection(); })} /> ) : ( @@ -958,29 +965,29 @@ export class TreeView extends React.Component<TreeViewProps> { key="title" ref={action((r: any) => { this._docRef = r ? r : undefined; - if (this._docRef && TreeView._editTitleOnLoad?.id === this.props.document[Id] && TreeView._editTitleOnLoad.parent === this.props.parentTreeView) { + if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) { this._docRef.select(false); this.setEditTitle(this._docRef); TreeView._editTitleOnLoad = undefined; } })} - Document={this.doc} + Document={this.Document} layout_fitWidth={returnTrue} scriptContext={this} - hideDecorationTitle={this.props.treeView.outlineMode} - hideResizeHandles={this.props.treeView.outlineMode} + hideDecorationTitle={this.treeView.outlineMode} + hideResizeHandles={this.treeView.outlineMode} styleProvider={this.titleStyleProvider} onClickScriptDisable="never" // tree docViews have a script to show fields, etc. - docViewPath={this.props.treeView.props.docViewPath} - treeViewDoc={this.props.treeView.props.Document} + docViewPath={this.treeView._props.docViewPath} + treeViewDoc={this.treeView.Document} addDocument={undefined} - addDocTab={this.props.addDocTab} - pinToPres={this.props.treeView.props.pinToPres} + addDocTab={this._props.addDocTab} + pinToPres={this.treeView._props.pinToPres} onClick={this.onChildClick} onDoubleClick={this.onChildDoubleClick} - dragAction={this.props.dragAction} + dragAction={this._props.dragAction} moveDocument={this.move} - removeDocument={this.props.removeDoc} + removeDocument={this._props.removeDoc} ScreenToLocalTransform={this.getTransform} NativeHeight={returnZero} NativeWidth={returnZero} @@ -988,16 +995,16 @@ export class TreeView extends React.Component<TreeViewProps> { PanelHeight={return18} contextMenuItems={this.contextMenuItems} renderDepth={1} - isContentActive={emptyFunction} //this.props.isContentActive} - isDocumentActive={this.props.isContentActive} + isContentActive={emptyFunction} //this._props.isContentActive} + isDocumentActive={this._props.isContentActive} focus={this.refocus} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} bringToFront={emptyFunction} - disableBrushing={this.props.treeView.props.disableBrushing} - hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} - dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} - xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)} - yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)} + disableBrushing={this.treeView._props.disableBrushing} + hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)} + dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)} + xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} + yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} @@ -1006,16 +1013,16 @@ export class TreeView extends React.Component<TreeViewProps> { return ( <> <div - className={`docContainer${Doc.IsSystem(this.props.document) || this.props.document.isFolder ? '-system' : ''}`} + className={`docContainer${Doc.IsSystem(this.Document) || this.Document.isFolder ? '-system' : ''}`} ref={this._tref} title="click to edit title. Double Click or Drag to Open" style={{ - backgroundColor: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? SettingsManager.userVariantColor : undefined, - color: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? lightOrDark(SettingsManager.userVariantColor) : undefined, - fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? 'bold' : undefined, - textDecoration: Doc.GetT(this.doc, 'title', 'string', true) ? 'underline' : undefined, - outline: this.doc === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined, - pointerEvents: !this.props.isContentActive() ? 'none' : undefined, + backgroundColor: Doc.IsSystem(this.Document) || this.Document.isFolder ? SettingsManager.userVariantColor : undefined, + color: Doc.IsSystem(this.Document) || this.Document.isFolder ? lightOrDark(SettingsManager.userVariantColor) : undefined, + fontWeight: Doc.IsSearchMatch(this.Document) !== undefined ? 'bold' : undefined, + textDecoration: Doc.GetT(this.Document, 'title', 'string', true) ? 'underline' : undefined, + outline: this.Document === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined, + pointerEvents: !this._props.isContentActive() ? 'none' : undefined, }}> {view} </div> @@ -1055,42 +1062,42 @@ export class TreeView extends React.Component<TreeViewProps> { return ( <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}> <DocumentView - key={this.doc[Id]} + key={this.Document[Id]} ref={action((r: DocumentView | null) => (this._dref = r))} - Document={this.doc} + Document={this.Document} layout_fitWidth={this.fitWidthFilter} PanelWidth={this.embeddedPanelWidth} PanelHeight={this.embeddedPanelHeight} LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} - LayoutTemplate={this.props.treeView.props.childLayoutTemplate} + LayoutTemplate={this.treeView._props.childLayoutTemplate} isContentActive={isActive} isDocumentActive={isActive} styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} hideTitle={asText} fitContentsToBox={returnTrue} - hideDecorationTitle={this.props.treeView.outlineMode} - hideResizeHandles={this.props.treeView.outlineMode} + hideDecorationTitle={this.treeView.outlineMode} + hideResizeHandles={this.treeView.outlineMode} onClick={this.onChildClick} focus={this.refocus} onKey={this.onKeyDown} - hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} - dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} + hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)} + dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)} ScreenToLocalTransform={this.docTransform} - renderDepth={this.props.renderDepth + 1} - treeViewDoc={this.props.treeView?.props.Document} - docViewPath={this.props.treeView.props.docViewPath} + renderDepth={this._props.renderDepth + 1} + treeViewDoc={this.treeView?.Document} + docViewPath={this.treeView._props.docViewPath} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - addDocument={this.props.addDocument} + addDocument={this._props.addDocument} moveDocument={this.move} - removeDocument={this.props.removeDoc} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)} - yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)} - addDocTab={this.props.addDocTab} - pinToPres={this.props.treeView.props.pinToPres} - disableBrushing={this.props.treeView.props.disableBrushing} + removeDocument={this._props.removeDoc} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} + xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} + yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} + addDocTab={this._props.addDocTab} + pinToPres={this.treeView._props.pinToPres} + disableBrushing={this.treeView._props.disableBrushing} bringToFront={returnFalse} scriptContext={this} /> @@ -1100,7 +1107,7 @@ export class TreeView extends React.Component<TreeViewProps> { // renders the text version of a document as the header. This is used in the file system mode and in other vanilla tree views. @computed get renderTitleAsHeader() { - return this.props.treeView.Document.treeView_HideUnrendered && this.doc.layout_unrendered && !this.doc.treeView_FieldKey ? ( + return this.treeView.Document.treeView_HideUnrendered && this.Document.layout_unrendered && !this.Document.treeView_FieldKey ? ( <div></div> ) : ( <> @@ -1115,16 +1122,16 @@ export class TreeView extends React.Component<TreeViewProps> { return ( <> {this.renderBullet} - {this.renderEmbeddedDocument(asText, this.props.isContentActive)} + {this.renderEmbeddedDocument(asText, this._props.isContentActive)} </> ); }; @computed get renderBorder() { - const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded); - const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } }; + const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded); + const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } }; return ( - <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}> + <div className={`treeView-border${this.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}> {!this.treeViewOpen ? null : this.renderContent} </div> ); @@ -1134,28 +1141,28 @@ export class TreeView extends React.Component<TreeViewProps> { const pt = [de.clientX, de.clientY]; const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); + const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); - const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false)); + const docs = this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false)); }; render() { TraceMobx(); - const hideTitle = this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode; - return this.props.renderedIds?.indexOf(this.doc[Id]) !== -1 ? ( - '<' + this.doc.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles + const hideTitle = this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode; + return this._props.renderedIds?.indexOf(this.Document[Id]) !== -1 ? ( + '<' + this.Document.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles ) : ( <div - className={`treeView-container${this.props.isContentActive() ? '-active' : ''}`} + className={`treeView-container${this._props.isContentActive() ? '-active' : ''}`} ref={this.createTreeDropTarget} onDrop={this.onTreeDrop} - //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document + //onPointerDown={e => this._props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document // onKeyDown={this.onKeyDown} > <li className="collection-child"> - {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader" + {hideTitle && this.Document.type !== DocumentType.RTF && !this.Document.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader" ? this.renderEmbeddedDocument(false, returnFalse) - : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeView_RenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)} + : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.Document.treeView_RenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)} </li> </div> ); @@ -1231,7 +1238,7 @@ export class TreeView extends React.Component<TreeViewProps> { } const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.WhenAdded)); - const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1); + const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1); const treeView_Refs = new Map<Doc, TreeView | undefined>(); return docs .filter(child => child instanceof Doc) @@ -1243,7 +1250,7 @@ export class TreeView extends React.Component<TreeViewProps> { } const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => { - if (parent instanceof TreeView && parent.props.treeView.fileSysMode && !newParent.isFolder) return; + if (parent instanceof TreeView && parent._props.treeView.fileSysMode && !newParent.isFolder) return; const fieldKey = Doc.LayoutFieldKey(newParent); if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) { remove(child); @@ -1255,7 +1262,7 @@ export class TreeView extends React.Component<TreeViewProps> { } }; const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeView_Refs.get(docs[i - 1])); - const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined); + const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView._props.parentTreeView : undefined); const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); const childLayout = Doc.Layout(pair.layout); const rowHeight = () => { @@ -1266,7 +1273,7 @@ export class TreeView extends React.Component<TreeViewProps> { <TreeView key={child[Id]} ref={r => treeView_Refs.set(child, r ? r : undefined)} - document={pair.layout} + Document={pair.layout} dataDoc={pair.data} treeViewParent={treeView_Parent} prevSibling={docs[i]} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 3ba7aedef..dd7e12e41 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -1,6 +1,7 @@ -import { IReactionDisposer, reaction } from 'mobx'; +import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { copyProps } from '../../../../Utils'; import './CollectionFreeFormView.scss'; /** @@ -49,8 +50,16 @@ export interface CollectionFreeFormInfoStateProps { export class CollectionFreeFormInfoState extends React.Component<CollectionFreeFormInfoStateProps> { _disposers: IReactionDisposer[] = []; + _prevProps: React.PropsWithChildren<CollectionFreeFormInfoStateProps>; + @observable _props: React.PropsWithChildren<CollectionFreeFormInfoStateProps>; + constructor(props: React.PropsWithChildren<CollectionFreeFormInfoStateProps>) { + super(props); + this._props = this._prevProps = props; + makeObservable(this); + } + get State() { - return this.props.infoState; + return this._props.infoState; } get Arcs() { return Object.keys(this.State).map(key => this.State[key]); @@ -65,7 +74,7 @@ export class CollectionFreeFormInfoState extends React.Component<CollectionFreeF res => { if (res) { const next = arc.act(res); - this.props.next(next); + this._props.next(next); } }, { fireImmediately: true } @@ -76,6 +85,7 @@ export class CollectionFreeFormInfoState extends React.Component<CollectionFreeF this.initState(); } componentDidUpdate() { + copyProps(this); this.clearState(); this.initState(); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index 1265dc2de..f0a052c1d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -1,4 +1,4 @@ -import { IReactionDisposer, computed, observable, reaction, action, runInAction } from 'mobx'; +import { IReactionDisposer, computed, observable, reaction, action, runInAction, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../../fields/Doc'; import { ScriptField } from '../../../../fields/ScriptField'; @@ -10,11 +10,12 @@ import { NumCast } from '../../../../fields/Types'; import { LinkManager } from '../../../util/LinkManager'; import { InkTool } from '../../../../fields/InkField'; import { LinkDocPreview } from '../../nodes/LinkDocPreview'; -import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; +import { DocumentLinksButton, DocButtonState } from '../../nodes/DocumentLinksButton'; import { DocumentManager } from '../../../util/DocumentManager'; import { CollectionFreeFormInfoState, infoState, StateMessage, infoArc, StateEntryFunc, InfoState } from './CollectionFreeFormInfoState'; import { string32 } from 'pdfjs-dist/types/src/shared/util'; import { any } from 'bluebird'; +import { copyProps } from '../../../../Utils'; export interface CollectionFreeFormInfoUIProps { Document: Doc; @@ -25,10 +26,23 @@ export interface CollectionFreeFormInfoUIProps { export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeFormInfoUIProps> { private _disposers: { [name: string]: IReactionDisposer } = {}; - @observable currState!: infoState; - constructor(props: any) { + @observable _currState: infoState | undefined = undefined; + get currState() { + return this._currState!; + } + set currState(val) { + this._currState = val; + } + _prevProps: React.PropsWithChildren<CollectionFreeFormInfoUIProps>; + @observable _props: React.PropsWithChildren<CollectionFreeFormInfoUIProps>; + constructor(props: React.PropsWithChildren<CollectionFreeFormInfoUIProps>) { super(props); - this.setCurrState(this.setupStates()); + this._props = this._prevProps = this.props; + makeObservable(this); + this.currState = this.setupStates(); + } + componentDidUpdate() { + copyProps(this); } setCurrState = (state: infoState) => { @@ -40,12 +54,12 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm setupStates = () => { // state entry functions - const setBackground = (col: string) => () => (this.props.Freeform.layoutDoc.backgroundColor = col); + const setBackground = (col: string) => () => (this._props.Freeform.layoutDoc.backgroundColor = col); // arc transition trigger conditions - const firstDoc = () => this.props.Freeform.childDocs.lastElement(); - const numDocs = () => this.props.Freeform.childDocs.length; + const firstDoc = () => this._props.Freeform.childDocs.lastElement(); + const numDocs = () => this._props.Freeform.childDocs.length; const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length; - const linkMenuOpen = () => DocumentLinksButton.LinkEditorDocView; + const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView; // set of states. const start = InfoState('Click to create Object', { @@ -78,7 +92,7 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm /* componentDidMount(): void { this._disposers.reaction1 = reaction( - () => this.props.Freeform.childDocs.slice(), + () => this._props.Freeform.childDocs.slice(), docs => { if (docs.length === 1) { this.firstDoc = docs[0]; @@ -162,8 +176,8 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm // this._disposers.reaction1(); @observable message = 'Click anywhere and begin typing to create your first document!'; - @observable firstDoc: Doc | undefined; - @observable secondDoc: Doc | undefined; + @observable firstDoc: Doc | undefined = undefined; + @observable secondDoc: Doc | undefined = undefined; */ firstDocPos = { x: 0, y: 0 }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 4995925c8..5cbea4783 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -171,8 +171,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo @action toggleProperties = () => { - if ((SettingsManager.propertiesWidth ?? 0) < 100) { - SettingsManager.propertiesWidth = 250; + if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) { + SettingsManager.Instance.propertiesWidth = 250; } }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 03d302f39..154c914ad 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,6 +1,6 @@ import { Bezier } from 'bezier-js'; import { Colors } from 'browndash-components'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, toJS } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; @@ -16,7 +16,7 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../ import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, copyProps, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -43,7 +43,7 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../../nodes/trails/PresBox'; import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; -import { CollectionSubView } from '../CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI'; @@ -69,9 +69,20 @@ export type collectionFreeformViewProps = { @observer export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() { public get displayName() { - return 'CollectionFreeFormView(' + this.props.Document.title?.toString() + ')'; + return 'CollectionFreeFormView(' + this._props.Document.title?.toString() + ')'; } // this makes mobx trace() statements more descriptive + _prevProps: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionFreeformViewProps>>; + @override _props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionFreeformViewProps>>; + constructor(props: any) { + super(props); + this._props = this._prevProps = props; + makeObservable(this); + } + componentDidUpdate() { + copyProps(this); + } + @observable public static ShowPresPaths = false; @@ -90,19 +101,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection private _brushtimer1: any; public get isAnnotationOverlay() { - return this.props.isAnnotationOverlay; + return this._props.isAnnotationOverlay; } public get scaleFieldKey() { - return (this.props.viewField ?? '') + '_freeform_scale'; + return (this._props.viewField ?? '') + '_freeform_scale'; } private get panXFieldKey() { - return (this.props.viewField ?? '') + '_freeform_panX'; + return (this._props.viewField ?? '') + '_freeform_panX'; } private get panYFieldKey() { - return (this.props.viewField ?? '') + '_freeform_panY'; + return (this._props.viewField ?? '') + '_freeform_panY'; } private get autoResetFieldKey() { - return (this.props.viewField ?? '') + '_freeform_autoReset'; + return (this._props.viewField ?? '') + '_freeform_autoReset'; } @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables @@ -113,7 +124,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _deleteList: DocumentView[] = []; @observable _timelineRef = React.createRef<Timeline>(); @observable _marqueeViewRef = React.createRef<MarqueeView>(); - @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region + @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined = undefined; // highlighted region of freeform canvas used by presentations to indicate a region @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. @computed get contentViews() { @@ -130,11 +141,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection scale: (!this.childDocs.length || !Number.isFinite(hgt) || !Number.isFinite(wid) ? 1 // - : Math.min(this.props.PanelHeight() / hgt, this.props.PanelWidth() / wid)) / (this.props.NativeDimScaling?.() || 1), + : Math.min(this._props.PanelHeight() / hgt, this._props.PanelWidth() / wid)) / (this._props.NativeDimScaling?.() || 1), }; } @computed get fitContentsToBox() { - return (this.props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay; + return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay; } @computed get contentBounds() { const cb = Cast(this.dataDoc.contentBounds, listSpec('number')); @@ -147,30 +158,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); } @computed get nativeWidth() { - return this.props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); + return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } @computed get nativeHeight() { - return this.props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); + return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } @computed get cachedCenteringShiftX(): number { const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling; - return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections + return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get cachedCenteringShiftY(): number { - const dv = this.props.DocumentView?.(); - const fitWidth = this.props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth; + const dv = this._props.DocumentView?.(); + const fitWidth = this._props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth; const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling; // if freeform has a native aspect, then the panel height needs to be adjusted to match it - const aspect = dv?.nativeWidth && dv?.nativeHeight && !fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth(); - return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections + const aspect = dv?.nativeWidth && dv?.nativeHeight && !fitWidth ? dv.nativeHeight / dv.nativeWidth : this._props.PanelHeight() / this._props.PanelWidth(); + return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : (aspect * this._props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get panZoomXf() { return new Transform(this.panX(), this.panY(), 1 / this.zoomScaling()); } @computed get screenToLocalXf() { - return this.props + return this._props .ScreenToLocalTransform() - .scale(this.props.isAnnotationOverlay ? 1 : 1) + .scale(this._props.isAnnotationOverlay ? 1 : 1) .translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY) .transform(this.panZoomXf); } @@ -219,9 +230,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _keyframeEditing = false; @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set); getKeyFrameEditing = () => this._keyframeEditing; - onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); - onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); - onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); + onBrowseClickHandler = () => this._props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); + onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick); + onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; fitContentOnce = () => { const vals = this.fitToContentVals; @@ -236,27 +247,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1)); zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1)); PanZoomCenterXf = () => - this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; + this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; ScreenToLocalXf = () => this.screenToLocalXf.copy(); getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); - isAnyChildContentActive = () => this.props.isAnyChildContentActive(); + isAnyChildContentActive = () => this._props.isAnyChildContentActive(); addLiveTextBox = (newDoc: Doc) => { FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed this.addDocument(newDoc); }; selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); - docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true)); + docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true)); }; addDocument = (newBox: Doc | Doc[]) => { let retVal = false; if (newBox instanceof Doc) { - if ((retVal = this.props.addDocument?.(newBox) || false)) { + if ((retVal = this._props.addDocument?.(newBox) || false)) { this.bringToFront(newBox); this.updateCluster(newBox); } } else { - retVal = this.props.addDocument?.(newBox) || false; + retVal = this._props.addDocument?.(newBox) || false; // bcz: deal with clusters } if (retVal) { @@ -270,7 +281,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection CollectionFreeFormDocumentView.animFields.forEach((field, i) => field.key !== 'opacity' && (newBox[field.key] = vals[i])); } } - if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) { + if (this.Document._currentFrame !== undefined && !this._props.isAnnotationOverlay) { CollectionFreeFormDocumentView.setupKeyframes(newBoxes, NumCast(this.Document._currentFrame), true); } } @@ -286,7 +297,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection groupFocus = (anchor: Doc, options: DocFocusOptions) => { options.docTransform = new Transform(-NumCast(this.layoutDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.layoutDoc[this.panYFieldKey]) + NumCast(anchor.y), 1); - const res = this.props.focus(this.Document, options); + const res = this._props.focus(this.Document, options); options.docTransform = undefined; return res; }; @@ -324,11 +335,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection findDoc(dv => res(dv)); }); - @action internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) { if (!super.onInternalDrop(e, de)) return false; const refDoc = docDragData.droppedDocuments[0]; - const [xpo, ypo] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); + const [xpo, ypo] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y); const z = NumCast(refDoc.z); const x = (z ? xpo : xp) - docDragData.offset[0]; const y = (z ? ypo : yp) - docDragData.offset[1]; @@ -343,7 +353,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection for (let i = 0; i < docDragData.droppedDocuments.length; i++) { const d = docDragData.droppedDocuments[i]; const layoutDoc = Doc.Layout(d); - const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this.props.ScreenToLocalTransform().Rotate); + const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this._props.ScreenToLocalTransform().Rotate); if (this.Document._currentFrame !== undefined) { CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false); const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position @@ -405,7 +415,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @undoBatch internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) { - if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) { + if (linkDragData.linkDragView.props.docViewPath().includes(this._props.docViewPath().lastElement())) { let added = false; // do nothing if link is dropped into any freeform view parent of dragged document const source = @@ -426,7 +436,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection _xPadding: 0, onClick: FollowLinkScript(), }); - added = this.props.addDocument?.(source) ? true : false; + added = this._props.addDocument?.(source) ? true : false; de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed e.stopPropagation(); @@ -463,7 +473,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return this.childLayoutPairs .map(pair => pair.layout) .reduce((cluster, cd) => { - const grouping = this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1); + const grouping = this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1); if (grouping !== -1) { const layoutDoc = Doc.Layout(cd); const cx = NumCast(cd.x) - this._clusterDistance / 2; @@ -480,11 +490,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (cluster !== -1) { const ptsParent = e; if (ptsParent) { - const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster); - const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!); + const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster); + const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this._props.DocumentView?.())!); const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 }; const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined); - de.moveDocument = this.props.moveDocument; + de.moveDocument = this._props.moveDocument; de.offset = this.screenToLocalXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top); DragManager.StartDocumentDrag( clusterDocs.map(v => v.ContentDiv!), @@ -502,7 +512,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action updateClusters(_freeform_useClusters: boolean) { - this.props.Document._freeform_useClusters = _freeform_useClusters; + this._props.Document._freeform_useClusters = _freeform_useClusters; this._clusterSets.length = 0; this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c)); } @@ -510,7 +520,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action updateClusterDocs(docs: Doc[]) { const childLayouts = this.childLayoutPairs.map(pair => pair.layout); - if (this.props.Document._freeform_useClusters) { + if (this._props.Document._freeform_useClusters) { const docFirst = docs[0]; docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1))); const preferredInd = NumCast(docFirst.layout_cluster); @@ -553,7 +563,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action updateCluster = (doc: Doc) => { const childLayouts = this.childLayoutPairs.map(pair => pair.layout); - if (this.props.Document._freeform_useClusters) { + if (this._props.Document._freeform_useClusters) { this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); const preferredInd = NumCast(doc.layout_cluster); doc.layout_cluster = -1; @@ -583,7 +593,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => { - let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 + let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 if (doc && this.childDocList?.includes(doc)) switch (property) { case StyleProp.BackgroundColor: @@ -612,7 +622,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection trySelectCluster = (addToSel: boolean) => { if (this._hitCluster !== -1) { !addToSel && SelectionManager.DeselectAll(); - const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster); + const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster); this.selectDocuments(eles); return true; } @@ -625,7 +635,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._downY = this._lastY = e.pageY; this._downTime = Date.now(); const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode; - if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) { + if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this._props.isContentActive(true)) { if (!this.Document.isGroup) { // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag // prettier-ignore @@ -638,7 +648,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction); break; case InkTool.None: - if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) { + if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) { this._hitCluster = this.pickCluster(this.screenToLocalXf.transformPoint(e.clientX, e.clientY)); setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false); } @@ -661,7 +671,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection case GestureUtils.Gestures.Stroke: const points = ge.points; const B = this.screenToLocalXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height); - const inkWidth = ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale; + const inkWidth = ActiveInkWidth() * this._props.ScreenToLocalTransform().Scale; const inkDoc = Docs.Create.InkDocument( points, { title: ge.gesture.toString(), @@ -710,7 +720,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (this._lightboxDoc) this._lightboxDoc = undefined; if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) { if (this.onBrowseClickHandler()) { - this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY }); + this.onBrowseClickHandler().script.run({ documentView: this._props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY }); e.stopPropagation(); e.preventDefault(); } else if (this.isContentActive() && e.shiftKey) { @@ -733,9 +743,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const ctrlKey = e.ctrlKey && !e.shiftKey; const shiftKey = e.shiftKey && !e.ctrlKey; PresBox.Instance?.pauseAutoPres(); - this.props.DocumentView?.().clearViewTransition(); + this._props.DocumentView?.().clearViewTransition(); const [dxi, dyi] = this.screenToLocalXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); - const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.props.ScreenToLocalTransform().Rotate); + const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this._props.ScreenToLocalTransform().Rotate); this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true); this._lastX = e.clientX; this._lastY = e.clientY; @@ -788,7 +798,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return true; } // pan the view if this is a regular collection, or it's an overlay and the overlay is zoomed (otherwise, there's nothing to pan) - if (!this.props.isAnnotationOverlay || 1 - NumCast(this.layoutDoc._freeform_scale_min, 1) / this.zoomScaling()) { + if (!this._props.isAnnotationOverlay || 1 - NumCast(this.layoutDoc._freeform_scale_min, 1) / this.zoomScaling()) { this.pan(e); e.stopPropagation(); // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning } @@ -804,7 +814,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) }; return this.childDocs - .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())) + .map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! })) .filter( @@ -901,7 +911,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.childDocs .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect) .forEach(doc => { - const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke; + const otherInk = DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())?.ComponentView as InkingStroke; const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] }; const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point)); const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt)); @@ -933,7 +943,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action zoom = (pointX: number, pointY: number, deltaY: number): void => { - if (this.Document.isGroup || this.Document[(this.props.viewField ?? '_') + 'freeform_noZoom']) return; + if (this.Document.isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return; let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05; if (deltaScale < 0) deltaScale = -deltaScale; const [x, y] = this.screenToLocalXf.transformPoint(pointX, pointY); @@ -955,8 +965,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const localTransform = invTransform.scaleAbout(deltaScale, x, y); if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) { const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20); - this.props.Document[this.scaleFieldKey] = Math.abs(safeScale); - this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale); + this._props.Document[this.scaleFieldKey] = Math.abs(safeScale); + this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this._props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale); } }; @@ -964,10 +974,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onPointerWheel = (e: React.WheelEvent): void => { if (this.Document.isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom PresBox.Instance?.pauseAutoPres(); - if (this.layoutDoc._Transform || this.props.Document.treeView_OutlineMode === TreeViewType.outline) return; + if (this.layoutDoc._Transform || this._props.Document.treeView_OutlineMode === TreeViewType.outline) return; e.stopPropagation(); const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight); - const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling + 1e-4; + const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling + 1e-4; switch ( !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?// Doc.UserDoc().freeformScrollMode : // no modifiers, do assigned mode @@ -975,7 +985,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore ) { case freeformScrollMode.Pan: - if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this.props.isContentActive(true)) { + if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this._props.isContentActive(true)) { const deltaX = e.shiftKey ? e.deltaX : e.ctrlKey ? 0 : e.deltaX; const deltaY = e.shiftKey ? 0 : e.ctrlKey ? e.deltaY : e.deltaY; this.scrollPan({ deltaX: -deltaX * this.screenToLocalXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToLocalXf.Scale }); @@ -983,8 +993,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } default: case freeformScrollMode.Zoom: - if ((e.ctrlKey || !scrollable) && this.props.isContentActive(true)) { - this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? + if ((e.ctrlKey || !scrollable) && this._props.isContentActive(true)) { + this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? e.preventDefault(); } break; @@ -1013,19 +1023,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) }, }), { - xrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE }, - yrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE }, + xrange: { min: this._props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE }, + yrange: { min: this._props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE }, } ); - const scaling = this.zoomScaling() * (this.props.NativeDimScaling?.() || 1); - const panelWidMax = (this.props.PanelWidth() / scaling) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1); - const panelWidMin = (this.props.PanelWidth() / scaling) * (this.props.originTopLeft ? 0 : 1); - const panelHgtMax = (this.props.PanelHeight() / scaling) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1); - const panelHgtMin = (this.props.PanelHeight() / scaling) * (this.props.originTopLeft ? 0 : 1); - if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2); - else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2); - if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2); - else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2); + const scaling = this.zoomScaling() * (this._props.NativeDimScaling?.() || 1); + const panelWidMax = (this._props.PanelWidth() / scaling) * (this._props.originTopLeft ? 2 / this.nativeDimScaling : 1); + const panelWidMin = (this._props.PanelWidth() / scaling) * (this._props.originTopLeft ? 0 : 1); + const panelHgtMax = (this._props.PanelHeight() / scaling) * (this._props.originTopLeft ? 2 / this.nativeDimScaling : 1); + const panelHgtMin = (this._props.PanelHeight() / scaling) * (this._props.originTopLeft ? 0 : 1); + if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this._props.originTopLeft ? 0 : panelWidMax / 2); + else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this._props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2); + if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this._props.originTopLeft ? 0 : panelHgtMax / 2); + else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this._props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2); } } if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) { @@ -1036,13 +1046,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const minPanY = NumCast(this.dataDoc._freeform_panY_min, 0); const maxPanX = NumCast(this.dataDoc._freeform_panX_max, this.nativeWidth); const newPanX = Math.min(minPanX + scale * maxPanX, Math.max(minPanX, panX)); - const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale; - const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight; - const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight(); + const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this._props.PanelWidth() - this._props.PanelHeight()) * this._props.ScreenToLocalTransform().Scale) / minScale; + const nativeHeight = (this._props.PanelHeight() / this._props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight; + const maxScrollTop = this.nativeHeight / this._props.ScreenToLocalTransform().Scale - this._props.PanelHeight(); const maxPanY = minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth scale * NumCast(this.dataDoc._panY_max, nativeHeight) + - (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning + (!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning let newPanY = Math.max(minPanY, Math.min(maxPanY, panY)); if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) { const relTop = NumCast(this.layoutDoc.layout_scrollTop) / maxScrollTop; @@ -1061,11 +1071,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action nudge = (x: number, y: number, nudgeTime: number = 500) => { - const collectionDoc = this.props.docViewPath().lastElement().Document; + const collectionDoc = this._props.docViewPath().lastElement().Document; if (collectionDoc?._type_collection !== CollectionViewType.Freeform) { this.setPan( - NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale - NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), + NumCast(this.layoutDoc[this.panXFieldKey]) + ((this._props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale + NumCast(this.layoutDoc[this.panYFieldKey]) + ((this._props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true ); @@ -1132,20 +1142,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const newScale = scale === 0 ? NumCast(this.layoutDoc[this.scaleFieldKey]) - : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height))); + : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this._props.PanelWidth() / Math.abs(bounds.width), this._props.PanelHeight() / Math.abs(bounds.height))); return { - panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2, - panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2, + panX: this._props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2, + panY: this._props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2, scale: newScale, }; } - const panelWidth = this.props.isAnnotationOverlay ? this.nativeWidth : this.props.PanelWidth(); - const panelHeight = this.props.isAnnotationOverlay ? this.nativeHeight : this.props.PanelHeight(); + const panelWidth = this._props.isAnnotationOverlay ? this.nativeWidth : this._props.PanelWidth(); + const panelHeight = this._props.isAnnotationOverlay ? this.nativeHeight : this._props.PanelHeight(); const pw = panelWidth / NumCast(this.layoutDoc._freeform_scale, 1); const ph = panelHeight / NumCast(this.layoutDoc._freeform_scale, 1); - const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this.props.isAnnotationOverlay ? pw / 2 : 0); - const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this.props.isAnnotationOverlay ? ph / 2 : 0); + const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this._props.isAnnotationOverlay ? pw / 2 : 0); + const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this._props.isAnnotationOverlay ? ph / 2 : 0); const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 }; const maxYShift = Math.max(0, screen.bot - screen.top - (bounds.bot - bounds.top)); const phborder = bounds.top < screen.top || bounds.bot > screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0; @@ -1153,19 +1163,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return { panX: (bounds.left + bounds.right) / 2, panY: (bounds.top + bounds.bot) / 2, - scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1, + scale: Math.min(this._props.PanelHeight() / (bounds.bot - bounds.top), this._props.PanelWidth() / (bounds.right - bounds.left)) / 1.1, }; } return { - panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right), - panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot), + panX: (this._props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right), + panY: (this._props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot), }; }; - isContentActive = () => this.props.isContentActive(); + isContentActive = () => this._props.isContentActive(); @undoBatch - @action onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { const docView = fieldProps.DocumentView?.(); if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) { @@ -1186,21 +1195,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } }; @computed get childPointerEvents() { - const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); + const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine); const pointerevents = DocumentView.Interacting ? 'none' - : this.props.childPointerEvents?.() ?? - (this.props.viewDefDivClick || // - (engine === computePassLayout.name && !this.props.isSelected()) || + : this._props.childPointerEvents?.() ?? + (this._props.viewDefDivClick || // + (engine === computePassLayout.name && !this._props.isSelected()) || this.isContentActive() === false ? 'none' - : this.props.pointerEvents?.()); + : this._props.pointerEvents?.()); return pointerevents; } - @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined; + @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined = undefined; childPointerEventsFunc = () => this._childPointerEvents; - childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)(); + childContentsActive = () => (this._props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)(); getChildDocView(entry: PoolData) { const childLayout = entry.pair.layout; const childData = entry.pair.data; @@ -1212,50 +1221,50 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection TemplateDataDocument={childData} dragStarting={this.dragStarting} dragEnding={this.dragEnding} - isGroupActive={this.props.isGroupActive} - renderDepth={this.props.renderDepth + 1} + isGroupActive={this._props.isGroupActive} + renderDepth={this._props.renderDepth + 1} hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)} suppressSetHeight={this.layoutEngine ? true : false} RenderCutoffProvider={this.renderCutoffProvider} CollectionFreeFormView={this} - LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate} - LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString} + LayoutTemplate={childLayout.z ? undefined : this._props.childLayoutTemplate} + LayoutTemplateString={childLayout.z ? undefined : this._props.childLayoutString} rootSelected={childData ? this.rootSelected : returnFalse} - waitForDoubleClickToClick={this.props.waitForDoubleClickToClick} + waitForDoubleClickToClick={this._props.waitForDoubleClickToClick} onClick={this.onChildClickHandler} onKey={this.onKeyDown} onDoubleClick={this.onChildDoubleClickHandler} onBrowseClick={this.onBrowseClickHandler} - ScreenToLocalTransform={childLayout.z ? this.props.ScreenToLocalTransform : this.ScreenToLocalXf} + ScreenToLocalTransform={childLayout.z ? this._props.ScreenToLocalTransform : this.ScreenToLocalXf} PanelWidth={childLayout[Width]} PanelHeight={childLayout[Height]} childFilters={this.childDocFilters} childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} + isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive} isContentActive={this.childContentsActive} - focus={this.Document.isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus} + focus={this.Document.isGroup ? this.groupFocus : this.isAnnotationOverlay ? this._props.focus : this.focus} addDocTab={this.addDocTab} - addDocument={this.props.addDocument} - removeDocument={this.props.removeDocument} - moveDocument={this.props.moveDocument} - pinToPres={this.props.pinToPres} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - docViewPath={this.props.docViewPath} + addDocument={this._props.addDocument} + removeDocument={this._props.removeDocument} + moveDocument={this._props.moveDocument} + pinToPres={this._props.pinToPres} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} + docViewPath={this._props.docViewPath} styleProvider={this.clusterStyleProvider} - dragAction={(this.Document.childDragAction ?? this.props.childDragAction) as dropActionType} + dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType} bringToFront={this.bringToFront} - layout_showTitle={this.props.childlayout_showTitle} - dontRegisterView={this.props.dontRegisterView} + layout_showTitle={this._props.childlayout_showTitle} + dontRegisterView={this._props.dontRegisterView} pointerEvents={this.childPointerEventsFunc} /> ); } addDocTab = action((doc: Doc, where: OpenWhere) => { - if (this.props.isAnnotationOverlay) return this.props.addDocTab(doc, where); + if (this._props.isAnnotationOverlay) return this._props.addDocTab(doc, where); switch (where) { case OpenWhere.inParent: - return this.props.addDocument?.(doc) || false; + return this._props.addDocument?.(doc) || false; case OpenWhere.inParentFromScreen: const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.embedContainer); return ( @@ -1267,7 +1276,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return doc; }) ) && - (!docContext || this.props.removeDocument?.(docContext))) || + (!docContext || this._props.removeDocument?.(docContext))) || false ); case undefined: @@ -1281,9 +1290,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return true; } } - return this.props.addDocTab(doc, where); + return this._props.addDocTab(doc, where); }); - @observable _lightboxDoc: Opt<Doc>; + @observable _lightboxDoc: Opt<Doc> = undefined; getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData { const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min); @@ -1295,7 +1304,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber); const { x, y, autoDim, _width, _height, opacity, _rotation } = layoutFrameNumber === undefined // -1 for width/height means width/height should be PanelWidth/PanelHeight (prevents collectionfreeformdocumentview width/height from getting out of synch with panelWIdth/Height which causes detailView to re-render and lose focus because HTMLtag scaling gets set to a bad intermediate value) - ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } + ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this._props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber); // prettier-ignore const rotation = Cast(_rotation,'number', @@ -1307,9 +1316,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection z: Cast(z, 'number'), autoDim, rotation, - color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color), - backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this.props, StyleProp.BackgroundColor), - opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity), + color: Cast(color, 'string') ? StrCast(color) : this._props.styleProvider?.(childDoc, this._props, StyleProp.Color), + backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this._props, StyleProp.BackgroundColor), + opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._props.styleProvider?.(childDoc, this._props, StyleProp.Opacity), zIndex: Cast(zIndex, 'number'), width: _width, height: _height, @@ -1321,7 +1330,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } onViewDefDivClick = (e: React.MouseEvent, payload: any) => { - (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload }); + (this._props.viewDefDivClick || ScriptCast(this._props.Document.onViewDefDivClick))?.script.run({ this: this._props.Document, payload }); e.stopPropagation(); }; @@ -1376,7 +1385,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection poolData: Map<string, PoolData>, engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[] ) { - return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps); + return engine(poolData, this._props.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps); } doFreeformLayout(poolData: Map<string, PoolData>) { @@ -1385,7 +1394,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } @computed get layoutEngine() { - return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); + return this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); } @computed get doInternalLayoutComputation() { TraceMobx(); @@ -1423,10 +1432,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document); if (addAsAnnotation) { - if (Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) { - Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor); + if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) { + Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor); } else { - this.dataDoc[this.props.fieldKey + '_annotations'] = new List<Doc>([anchor]); + this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]); } } return anchor; @@ -1435,7 +1444,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection infoUI = () => <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} />; componentDidMount() { - this.props.setContentView?.(this); + this._props.setContentView?.(this); super.componentDidMount?.(); setTimeout( action(() => { @@ -1473,15 +1482,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._disposers.pointerevents = reaction( () => { - const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); + const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine); return DocumentView.Interacting ? 'none' - : this.props.childPointerEvents?.() ?? - (this.props.viewDefDivClick || // - (engine === computePassLayout.name && !this.props.isSelected()) || + : this._props.childPointerEvents?.() ?? + (this._props.viewDefDivClick || // + (engine === computePassLayout.name && !this._props.isSelected()) || this.isContentActive() === false ? 'none' - : this.props.pointerEvents?.()); + : this._props.pointerEvents?.()); }, pointerevents => (this._childPointerEvents = pointerevents as any), { fireImmediately: true } @@ -1536,11 +1545,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection updateIcon = () => CollectionFreeFormView.UpdateIcon( this.layoutDoc[Id] + '-icon' + new Date().getTime(), - this.props.docViewPath().lastElement().ContentDiv!, + this._props.docViewPath().lastElement().ContentDiv!, NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._height), - this.props.PanelWidth(), - this.props.PanelHeight(), + this._props.PanelWidth(), + this._props.PanelHeight(), 0, 1, false, @@ -1600,7 +1609,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection doc.x = scr?.[0]; doc.y = scr?.[1]; }); - this.props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen); + this._props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen); }; @undoBatch @@ -1643,28 +1652,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; onContextMenu = (e: React.MouseEvent) => { - if (this.props.isAnnotationOverlay || !ContextMenu.Instance) return; + if (this._props.isAnnotationOverlay || !ContextMenu.Instance) return; const appearance = ContextMenu.Instance.findByDescription('Appearance...'); const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; - !this.props.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' }); - !this.props.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' }); + !this._props.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' }); + !this._props.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' }); !Doc.noviceMode && appearanceItems.push({ description: 'Toggle auto arrange', event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange), icon: 'compress-arrows-alt', }); - if (this.props.setContentView === emptyFunction) { + if (this._props.setContentView === emptyFunction) { !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); return; } !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' }); - appearanceItems.push({ description: `Pin View`, event: () => this.props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' }); + appearanceItems.push({ description: `Pin View`, event: () => this._props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this._props.PanelWidth(), this._props.PanelHeight()) }), icon: 'map-pin' }); !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' }); - this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); + this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); - this.props.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' }); + this._props.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' }); !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null; !Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null; @@ -1672,11 +1681,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const options = ContextMenu.Instance.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; - !this.props.isAnnotationOverlay && + !this._props.isAnnotationOverlay && !Doc.noviceMode && optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' }); - this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' }); - this.props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' }); + this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' }); + this._props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' }); if (!Doc.noviceMode) { optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' }); } @@ -1687,10 +1696,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; @undoBatch - @action transcribeStrokes = () => { - if (this.props.Document.isGroup && this.props.Document.transcription) { - const text = StrCast(this.props.Document.transcription); + if (this._props.Document.isGroup && this._props.Document.transcription) { + const text = StrCast(this._props.Document.transcription); const lines = text.split('\n'); const height = 30 + 15 * lines.length; @@ -1709,7 +1717,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection visited.add(this.Document); showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document.isGroup)); const activeDocs = this.getActiveDocuments(); - const size = this.screenToLocalXf.transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); + const size = this.screenToLocalXf.transformDirection(this._props.PanelWidth(), this._props.PanelHeight()); const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }); const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect); @@ -1738,7 +1746,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0; incrementalRender = action(() => { - if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) { + if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath())) { const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); const loadIncrement = 5; for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) { @@ -1752,28 +1760,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // which will be a DocumentView. In this sitation, this freeform views acts as an annotation overlay for // the underlying DocumentView and will pan and scoll with the underlying Documen tView. @computed get underlayViews() { - return this.props.children ? [this.props.children] : []; + return this._props.children ? [toJS(this._props.children)] : []; } @computed get placeholder() { return ( - <div className="collectionfreeformview-placeholder" style={{ background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor) }}> - <span className="collectionfreeformview-placeholderSpan">{this.props.Document.annotationOn ? '' : this.props.Document.title?.toString()}</span> + <div className="collectionfreeformview-placeholder" style={{ background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) }}> + <span className="collectionfreeformview-placeholderSpan">{this._props.Document.annotationOn ? '' : this._props.Document.title?.toString()}</span> </div> ); } brushedView = () => this._brushedView; gridColor = () => - DashColor(lightOrDark(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor))) + DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor))) .fade(0.5) .toString(); @computed get backgroundGrid() { return ( <div> <CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render when taking snapshot of a dashboard and the background grid is on!!? - PanelWidth={this.props.PanelWidth} - PanelHeight={this.props.PanelHeight} + PanelWidth={this._props.PanelWidth} + PanelHeight={this._props.PanelHeight} panX={this.panX} panY={this.panY} color={this.gridColor} @@ -1795,11 +1803,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection brushedView={this.brushedView} isAnnotationOverlay={this.isAnnotationOverlay} transform={this.PanZoomCenterXf} - transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.Document._viewTransition, 'string', null))} - viewDefDivClick={this.props.viewDefDivClick}> + transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this._props.DocumentView?.()?.Document._viewTransition, 'string', null))} + viewDefDivClick={this._props.viewDefDivClick}> {this.underlayViews} {this.contentViews} - <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> + <CollectionFreeFormRemoteCursors {...this._props} key="remoteCursors" /> </CollectionFreeFormPannableContents> ); } @@ -1807,10 +1815,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection TraceMobx(); return ( <MarqueeView - {...this.props} + {...this._props} ref={this._marqueeViewRef} ungroup={this.Document.isGroup ? this.promoteCollection : undefined} - nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge} + nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge} addDocTab={this.addDocTab} slowLoadDocuments={this.slowLoadDocuments} trySelectCluster={this.trySelectCluster} @@ -1818,25 +1826,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection selectDocuments={this.selectDocuments} addDocument={this.addDocument} addLiveTextDocument={this.addLiveTextBox} - getContainerTransform={this.props.ScreenToLocalTransform} + getContainerTransform={this._props.ScreenToLocalTransform} getTransform={this.ScreenToLocalXf} panXFieldKey={this.panXFieldKey} panYFieldKey={this.panYFieldKey} isAnnotationOverlay={this.isAnnotationOverlay}> {this.layoutDoc._freeform_backgroundGrid ? this.backgroundGrid : null} {this.pannableContents} - {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : null} + {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this._props} /> : null} </MarqueeView> ); } @computed get nativeDimScaling() { - if (this._firstRender || (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent)) return 0; + if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 0; const nw = this.nativeWidth; const nh = this.nativeHeight; - const hscale = nh ? this.props.PanelHeight() / nh : 1; - const wscale = nw ? this.props.PanelWidth() / nw : 1; - return wscale < hscale || (this.props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale; + const hscale = nh ? this._props.PanelHeight() / nh : 1; + const wscale = nw ? this._props.PanelWidth() / nw : 1; + return wscale < hscale || (this._props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale; } nativeDim = () => this.nativeDimScaling; @@ -1853,13 +1861,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection transTime + 1 ); }; - lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30); - lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30); - lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15); + lightboxPanelWidth = () => Math.max(0, this._props.PanelWidth() - 30); + lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30); + lightboxScreenToLocal = () => this._props.ScreenToLocalTransform().translate(-15, -15); onPassiveWheel = (e: WheelEvent) => { const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight); - const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling; - this.props.isSelected() && !scrollable && e.preventDefault(); + const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling; + this._props.isSelected() && !scrollable && e.preventDefault(); }; _oldWheel: any; render() { @@ -1882,16 +1890,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onDragOver={e => e.preventDefault()} onContextMenu={this.onContextMenu} style={{ - pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : (this.props.pointerEvents?.() as any), + pointerEvents: this._props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : (this._props.pointerEvents?.() as any), textAlign: this.isAnnotationOverlay ? 'initial' : undefined, transform: `scale(${this.nativeDimScaling || 1})`, width: `${100 / (this.nativeDimScaling || 1)}%`, - height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`, + height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`, }}> {this._lightboxDoc ? ( <div style={{ padding: 15, width: '100%', height: '100%' }}> <DocumentView - {...this.props} + {...this._props} Document={this._lightboxDoc} TemplateDataDocument={undefined} PanelWidth={this.lightboxPanelWidth} @@ -1905,8 +1913,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection childFilters={this.childDocFilters} childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} - isContentActive={this.props.childContentsActive ?? emptyFunction} + isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive} + isContentActive={this._props.childContentsActive ?? emptyFunction} addDocTab={this.addDocTab} ScreenToLocalTransform={this.lightboxScreenToLocal} fitContentsToBox={undefined} @@ -1916,7 +1924,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ) : ( <> {this._firstRender ? this.placeholder : this.marqueeView} - {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />} + {this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />} {!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />} </> )} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 330c13e97..9f316bef3 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols'; @@ -57,6 +57,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque return { left: NumCast(pinDoc._freeform_panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._freeform_panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps }; } + @observable _props: React.PropsWithChildren<SubCollectionViewProps & MarqueeViewProps>; + constructor(props: React.PropsWithChildren<SubCollectionViewProps & MarqueeViewProps>) { + super(props); + this._props = props; + makeObservable(this); + } + private _commandExecuted = false; @observable _lastX: number = 0; @observable _lastY: number = 0; @@ -67,7 +74,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @observable _lassoFreehand: boolean = false; @computed get Transform() { - return this.props.getTransform(); + return this._props.getTransform(); } @computed get Bounds() { // nda - ternary argument to transformPoint is returning the lower of the downX/Y and lastX/Y and passing in as args x,y @@ -79,7 +86,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } componentDidMount() { - this.props.setPreviewCursor?.(this.setPreviewCursor); + this._props.setPreviewCursor?.(this.setPreviewCursor); } @action @@ -101,20 +108,20 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const cm = ContextMenu.Instance; const [x, y] = this.Transform.transformPoint(this._downX, this._downY); if (e.key === '?') { - cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight)); + cm.setDefaultItem('?', (str: string) => this._props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight)); cm.displayMenu(this._downX, this._downY, undefined, true); e.stopPropagation(); - } else if (e.key === 'u' && this.props.ungroup) { + } else if (e.key === 'u' && this._props.ungroup) { e.stopPropagation(); - this.props.ungroup(); + this._props.ungroup(); } else if (e.key === ':') { - DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument || returnFalse, x, y); + DocUtils.addDocumentCreatorMenuItems(this._props.addLiveTextDocument, this._props.addDocument || returnFalse, x, y); cm.displayMenu(this._downX, this._downY, undefined, true); e.stopPropagation(); } else if (e.key === 'a' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.props.selectDocuments(this.props.activeDocuments()); + this._props.selectDocuments(this._props.activeDocuments()); e.stopPropagation(); } else if (e.key === 'q' && e.ctrlKey) { e.preventDefault(); @@ -136,7 +143,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque ns.map(line => { const indent = line.search(/\S|$/); const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line }); - this.props.addDocument?.(newBox); + this._props.addDocument?.(newBox); ypos += 40 * this.Transform.Scale; }); })(); @@ -147,8 +154,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque pasteImageBitmap((data: any, error: any) => { error && console.log(error); data && - Utils.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => { - this.props.Document['thumb-frozen'] = new ImageField(returnedfilename); + Utils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => { + this._props.Document['thumb-frozen'] = new ImageField(returnedfilename); }); }) ); @@ -159,7 +166,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque slide.y = y; FormattedTextBox.SelectOnLoad = slide[Id]; TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined }; - this.props.addDocument?.(slide); + this._props.addDocument?.(slide); e.stopPropagation(); }*/ else if (e.key === 'p' && e.ctrlKey) { e.preventDefault(); @@ -170,9 +177,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque })(); e.stopPropagation(); } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) { - FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : ''; + FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : ''; FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note'); - this.props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this.props.xPadding === 0)); + this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this._props.xPadding === 0)); e.stopPropagation(); } }; @@ -203,7 +210,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const file = new File([blob], 'droppedTable', options); const loading = Docs.Create.LoadingDocument(file, options); DocUtils.uploadFileToDoc(file, {}, loading); - this.props.addDocument?.(loading); + this._props.addDocument?.(loading); } @action @@ -214,9 +221,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode; // allow marquee if right drag/meta drag, or pan mode if (e.button === 2 || e.metaKey || scrollMode === freeformScrollMode.Pan) { - this.setPreviewCursor(e.clientX, e.clientY, true, false, this.props.Document); + this.setPreviewCursor(e.clientX, e.clientY, true, false, this._props.Document); e.preventDefault(); - } else PreviewCursor.Visible = false; + } else PreviewCursor.Instance.Visible = false; }; @action @@ -243,10 +250,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque if (this._visible) { const mselect = this.marqueeSelect(); if (!e.shiftKey) { - SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document); + SelectionManager.DeselectAll(mselect.length ? undefined : this._props.Document); } - const docs = mselect.length ? mselect : [this.props.Document]; - this.props.selectDocuments(docs); + const docs = mselect.length ? mselect : [this._props.Document]; + this._props.selectDocuments(docs); } const hideMarquee = () => { this.hideMarquee(); @@ -285,14 +292,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this._downX = this._lastX = x; this._downY = this._lastY = y; this._commandExecuted = false; - PreviewCursor.Visible = false; - PreviewCursor.Doc = undefined; + PreviewCursor.Instance.Visible = false; + PreviewCursor.Instance.Doc = undefined; } else if (drag) { this._downX = this._lastX = x; this._downY = this._lastY = y; this._commandExecuted = false; - PreviewCursor.Visible = false; - PreviewCursor.Doc = undefined; + PreviewCursor.Instance.Visible = false; + PreviewCursor.Instance.Doc = undefined; this.cleanupInteractions(true); document.addEventListener('pointermove', this.onPointerMove, true); document.addEventListener('pointerup', this.onPointerUp, true); @@ -300,10 +307,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } else { this._downX = x; this._downY = y; - const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]); + const effectiveAcl = GetEffectiveAcl(this._props.Document[DocData]); if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) { - PreviewCursor.Doc = doc; - PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge, this.props.slowLoadDocuments); + PreviewCursor.Instance.Doc = doc; + PreviewCursor.Show(x, y, this.onKeyPress, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments); } this.clearSelection(); } @@ -311,11 +318,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @action onClick = (e: React.MouseEvent): void => { - if (this.props.pointerEvents?.() === 'none') return; + if (this._props.pointerEvents?.() === 'none') return; if (Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) { if (Doc.ActiveTool === InkTool.None) { - if (!this.props.trySelectCluster(e.shiftKey)) { - !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document); + if (!this._props.trySelectCluster(e.shiftKey)) { + !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document); } else e.stopPropagation(); } // let the DocumentView stopPropagation of this event when it selects this document @@ -332,16 +339,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque hideMarquee = () => (this._visible = false); @undoBatch - @action - delete = (e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => { + delete = action((e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => { const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); - selected.forEach(doc => (hide ? (doc.hidden = true) : this.props.removeDocument?.(doc))); + selected.forEach(doc => (hide ? (doc.hidden = true) : this._props.removeDocument?.(doc))); this.cleanupInteractions(false); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); - }; + }); getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, makeGroup: Opt<boolean>) => { const newCollection = creator @@ -366,17 +372,16 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque }); @undoBatch - @action - pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => { + pileup = action((e: KeyboardEvent | React.PointerEvent | undefined) => { const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); - selected.forEach(d => this.props.removeDocument?.(d)); + selected.forEach(d => this._props.removeDocument?.(d)); const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!; - this.props.addDocument?.(newCollection); - this.props.selectDocuments([newCollection]); + this._props.addDocument?.(newCollection); + this._props.selectDocuments([newCollection]); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); - }; + }); /** * This triggers the TabDocView.PinDoc method which is the universal method @@ -385,35 +390,32 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque * This one is unique in that it includes the bounds associated with marquee view. */ @undoBatch - @action - pinWithView = () => { - this.props.pinToPres(this.props.Document, { pinViewport: this.Bounds }); + pinWithView = action(() => { + this._props.pinToPres(this._props.Document, { pinViewport: this.Bounds }); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); - }; + }); @undoBatch - @action - collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => { + collection = action((e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => { const selected = selection ?? this.marqueeSelect(false); const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined); if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) { - this.props.removeDocument?.(selected); + this._props.removeDocument?.(selected); } const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === 't' ? Docs.Create.StackingDocument : undefined, group); newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2; newCollection._freeform_panY = this.Bounds.top + this.Bounds.height / 2; newCollection._currentFrame = activeFrame; - this.props.addDocument?.(newCollection); - this.props.selectDocuments([newCollection]); + this._props.addDocument?.(newCollection); + this._props.selectDocuments([newCollection]); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); - }; + }); @undoBatch - @action - syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => { + syntaxHighlight = action((e: KeyboardEvent | React.PointerEvent | undefined) => { const selected = this.marqueeSelect(false); if (e instanceof KeyboardEvent ? e.key === 'i' : true) { const inks = selected.filter(s => s.type === DocumentType.INK); @@ -472,15 +474,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque // } const lines = results.filter((r: any) => r.category === 'line'); const text = lines.map((l: any) => l.recognizedText).join('\r\n'); - this.props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text })); + this._props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text })); }); } - }; + }); @undoBatch summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => { const selected = this.marqueeSelect(false).map(d => { - this.props.removeDocument?.(d); + this._props.removeDocument?.(d); d.x = NumCast(d.x) - this.Bounds.left; d.y = NumCast(d.y) - this.Bounds.top; return d; @@ -500,8 +502,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' }); portal.hidden = true; - this.props.addDocument?.(portal); - this.props.addLiveTextDocument(summary); + this._props.addDocument?.(portal); + this._props.addLiveTextDocument(summary); MarqueeOptionsMenu.Instance.fadeOut(true); }); @@ -582,17 +584,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque (this.touchesLine(bounds) || this.boundingShape(bounds)) && selection.push(doc); } }; - this.props + this._props .activeDocuments() .filter(doc => !doc.z && !doc._lockedPosition) .map(selectFunc); if (!selection.length && selectBackgrounds) - this.props + this._props .activeDocuments() .filter(doc => doc.z === undefined) .map(selectFunc); if (!selection.length) - this.props + this._props .activeDocuments() .filter(doc => doc.z !== undefined) .map(selectFunc); @@ -601,8 +603,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @computed get marqueeDiv() { const cpt = this._lassoFreehand || !this._visible ? [0, 0] : [this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY]; - const p = this.props.getContainerTransform().transformPoint(cpt[0], cpt[1]); - const v = this._lassoFreehand ? [0, 0] : this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + const p = this._props.getContainerTransform().transformPoint(cpt[0], cpt[1]); + const v = this._lassoFreehand ? [0, 0] : this._props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); return ( <div className="marquee" @@ -610,8 +612,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque transform: `translate(${p[0]}px, ${p[1]}px)`, width: Math.abs(v[0]), height: Math.abs(v[1]), - color: lightOrDark(this.props.Document?.backgroundColor ?? 'white'), - borderColor: lightOrDark(this.props.Document?.backgroundColor ?? 'white'), + color: lightOrDark(this._props.Document?.backgroundColor ?? 'white'), + borderColor: lightOrDark(this._props.Document?.backgroundColor ?? 'white'), zIndex: 2000, }}> {' '} @@ -620,7 +622,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque <polyline // points={this._lassoPts.reduce((s, pt) => s + pt[0] + ',' + pt[1] + ' ', '')} fill="none" - stroke={lightOrDark(this.props.Document?.backgroundColor ?? 'white')} + stroke={lightOrDark(this._props.Document?.backgroundColor ?? 'white')} strokeWidth="1" strokeDasharray="3" /> @@ -635,19 +637,19 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @action onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => { - if ((e as any).handlePan || this.props.isAnnotationOverlay) return; + if ((e as any).handlePan || this._props.isAnnotationOverlay) return; (e as any).handlePan = true; const bounds = this.MarqueeRef?.getBoundingClientRect(); - if (!this.props.Document._freeform_noAutoPan && !this.props.renderDepth && bounds) { + if (!this._props.Document._freeform_noAutoPan && !this._props.renderDepth && bounds) { const dragX = e.detail.clientX; const dragY = e.detail.clientY; const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0; const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0; if (deltaX !== 0 || deltaY !== 0) { - this.props.Document[this.props.panYFieldKey] = NumCast(this.props.Document[this.props.panYFieldKey]) + deltaY / 2; - this.props.Document[this.props.panXFieldKey] = NumCast(this.props.Document[this.props.panXFieldKey]) + deltaX / 2; + this._props.Document[this._props.panYFieldKey] = NumCast(this._props.Document[this._props.panYFieldKey]) + deltaY / 2; + this._props.Document[this._props.panXFieldKey] = NumCast(this._props.Document[this._props.panXFieldKey]) + deltaX / 2; } } e.stopPropagation(); @@ -661,7 +663,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this.MarqueeRef = r; }} style={{ - overflow: StrCast(this.props.Document._overflow), + overflow: StrCast(this._props.Document._overflow), cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer', }} onDragOver={e => e.preventDefault()} diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 8daed9746..697a11ccc 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -277,7 +277,6 @@ export class CollectionGridView extends CollectionSubView() { /** * Handles internal drop of Dash documents. */ - @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { const savedLayouts = this.savedLayoutList; const dropped = de.complete.docDragData?.droppedDocuments; @@ -292,7 +291,6 @@ export class CollectionGridView extends CollectionSubView() { /** * Handles external drop of images/PDFs etc from outside Dash. */ - @action onExternalDrop = async (e: React.DragEvent): Promise<void> => { this.dropLocation = this.screenToCell(e.clientX, e.clientY); super.onExternalDrop(e, {}); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 0ee5f3b40..a9782f699 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -1,14 +1,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { Toggle, ToggleType, Type } from 'browndash-components'; -import { action, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, Opt } from '../../../../fields/Doc'; import { Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils'; +import { copyProps, emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { BranchingTrailManager } from '../../../util/BranchingTrailManager'; import { DocumentManager } from '../../../util/DocumentManager'; @@ -20,7 +20,7 @@ import { DocumentView } from '../../nodes/DocumentView'; import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup'; import { UndoStack } from '../../UndoStack'; import { CollectionStackedTimeline } from '../CollectionStackedTimeline'; -import { CollectionSubView } from '../CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import './CollectionLinearView.scss'; /** @@ -39,6 +39,18 @@ export class CollectionLinearView extends CollectionSubView() { private _widthDisposer?: IReactionDisposer; private _selectedDisposer?: IReactionDisposer; + _prevProps: SubCollectionViewProps; + @override _props: SubCollectionViewProps; + constructor(props: SubCollectionViewProps) { + super(props); + this._props = this._prevProps = props; + makeObservable(this); + } + + componentDidUpdate() { + copyProps(this); + } + componentWillUnmount() { this._dropDisposer?.(); this._widthDisposer?.(); @@ -170,28 +182,28 @@ export class CollectionLinearView extends CollectionSubView() { }}> <DocumentView Document={doc} - isContentActive={this.props.isContentActive} + isContentActive={this._props.isContentActive} isDocumentActive={returnTrue} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - addDocTab={this.props.addDocTab} + addDocument={this._props.addDocument} + moveDocument={this._props.moveDocument} + addDocTab={this._props.addDocTab} pinToPres={emptyFunction} - dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType} + dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType} rootSelected={this.rootSelected} - removeDocument={this.props.removeDocument} + removeDocument={this._props.removeDocument} ScreenToLocalTransform={docXf} PanelWidth={doc[Width]} PanelHeight={nested || doc._height ? doc[Height] : this.dimension} - renderDepth={this.props.renderDepth + 1} + renderDepth={this._props.renderDepth + 1} dontRegisterView={BoolCast(this.Document.childDontRegisterViews)} focus={emptyFunction} - styleProvider={this.props.styleProvider} + styleProvider={this._props.styleProvider} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} - childFilters={this.props.childFilters} - childFiltersByRanges={this.props.childFiltersByRanges} - searchFilterDocs={this.props.searchFilterDocs} + childFilters={this._props.childFilters} + childFiltersByRanges={this._props.childFiltersByRanges} + searchFilterDocs={this._props.searchFilterDocs} hideResizeHandles={true} /> </div> @@ -205,8 +217,8 @@ export class CollectionLinearView extends CollectionSubView() { const menuOpener = ( <Toggle - text={Cast(this.props.Document.icon, 'string', null)} - icon={Cast(this.props.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon color={SettingsManager.userColor} icon={isExpanded ? 'minus' : 'plus'} />} + text={Cast(this.Document.icon, 'string', null)} + icon={Cast(this.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon color={SettingsManager.userColor} icon={isExpanded ? 'minus' : 'plus'} />} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index e7af91390..037786abf 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -192,7 +192,6 @@ export class CollectionMulticolumnView extends CollectionSubView() { }; @undoBatch - @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { let dropInd = -1; if (de.complete.docDragData && this._mainCont) { diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index ef66e8ce5..bbf3481dd 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -188,7 +188,6 @@ export class CollectionMultirowView extends CollectionSubView() { }; @undoBatch - @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { let dropInd = -1; if (de.complete.docDragData && this._mainCont) { diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 029d39f88..9a00595f9 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -68,16 +68,16 @@ export class CollectionSchemaView extends CollectionSubView() { @observable _menuKeys: string[] = []; @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>(); @observable _colEles: HTMLDivElement[] = []; - @observable _displayColumnWidths: number[] | undefined; - @observable _columnMenuIndex: number | undefined; + @observable _displayColumnWidths: number[] | undefined = undefined; + @observable _columnMenuIndex: number | undefined = undefined; @observable _newFieldWarning: string = ''; @observable _makeNewField: boolean = false; @observable _newFieldDefault: any = 0; @observable _newFieldType: ColumnType = ColumnType.Number; @observable _menuValue: string = ''; - @observable _filterColumnIndex: number | undefined; + @observable _filterColumnIndex: number | undefined = undefined; @observable _filterSearchValue: string = ''; - @observable _selectedCell: [Doc, number] | undefined; + @observable _selectedCell: [Doc, number] | undefined = undefined; // target HTMLelement portal for showing a popup menu to edit cell values. public get MenuTarget() { @@ -437,7 +437,6 @@ export class CollectionSchemaView extends CollectionSubView() { setDropIndex = (index: number) => (this._closestDropIndex = index); - @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.columnDragData) { const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0]; @@ -473,7 +472,6 @@ export class CollectionSchemaView extends CollectionSubView() { return false; }; - @action onExternalDrop = async (e: React.DragEvent): Promise<void> => { super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc))))); }; diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 0e37198bb..04443b4a7 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -26,7 +26,7 @@ export interface SchemaColumnHeaderProps { export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> { @observable _ref: HTMLDivElement | null = null; - @computed get fieldKey() { + get fieldKey() { return this.props.columnKeys[this.props.columnIndex]; } diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 53f4a4d51..8d2671a6e 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -34,11 +34,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo bounds = () => this._ref?.getBoundingClientRect(); @computed get schemaView() { - return this.props.DocumentView?.().props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView; + return this.props.DocumentView?.()._props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView; } @computed get schemaDoc() { - return this.props.DocumentView?.().props.docViewPath().lastElement()?.Document; + return this.props.DocumentView?.()._props.docViewPath().lastElement()?.Document; } @computed get rowIndex() { diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index bd6d3bad6..af0fe73c3 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import Select, { MenuPlacement } from 'react-select'; -import { action, computed, observable } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; import DatePicker from 'react-datepicker'; @@ -95,13 +95,19 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { return { color, textDecoration, fieldProps, cursor, pointerEvents }; } + @observable _props: React.PropsWithChildren<SchemaTableCellProps>; + constructor(props: React.PropsWithChildren<SchemaTableCellProps>) { + super(props); + this._props = props; + } + @computed get selected() { - const selected: [Doc, number] | undefined = this.props.selectedCell(); - return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col; + const selected: [Doc, number] | undefined = this._props.selectedCell(); + return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; } @computed get defaultCellContent() { - const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this.props); + const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this._props); return ( <div @@ -113,17 +119,17 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { pointerEvents, }}> <EditableView - oneLine={this.props.oneLine} - allowCRs={this.props.allowCRs} + oneLine={this._props.oneLine} + allowCRs={this._props.allowCRs} contents={<FieldView {...fieldProps} />} editing={this.selected ? undefined : false} - GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)} + GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)} SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { - this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value); + this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value); } - const ret = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value); - this.props.finishEdit?.(); + const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value); + this._props.finishEdit?.(); return ret; }, 'edit schema cell')} /> @@ -132,8 +138,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { } get getCellType() { - const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType; - const cellValue = this.props.Document[this.props.fieldKey]; + const columnTypeStr = this._props.getFinfo(this._props.fieldKey)?.fieldType; + const cellValue = this._props.Document[this._props.fieldKey]; if (cellValue instanceof ImageField) return ColumnType.Image; if (cellValue instanceof DateField) return ColumnType.Date; if (cellValue instanceof RichTextField) return ColumnType.RTF; @@ -152,11 +158,11 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { const cellType: ColumnType = this.getCellType; // prettier-ignore switch (cellType) { - case ColumnType.Image: return <SchemaImageCell {...this.props} />; - case ColumnType.Boolean: return <SchemaBoolCell {...this.props} />; - case ColumnType.RTF: return <SchemaRTFCell {...this.props} />; - case ColumnType.Enumeration: return <SchemaEnumerationCell {...this.props} options={this.props.getFinfo(this.props.fieldKey)?.values?.map(val => val.toString())} />; - case ColumnType.Date: // return <SchemaDateCell {...this.props} />; + case ColumnType.Image: return <SchemaImageCell {...this._props} />; + case ColumnType.Boolean: return <SchemaBoolCell {...this._props} />; + case ColumnType.RTF: return <SchemaRTFCell {...this._props} />; + case ColumnType.Enumeration: return <SchemaEnumerationCell {...this._props} options={this._props.getFinfo(this._props.fieldKey)?.values?.map(val => val.toString())} />; + case ColumnType.Date: // return <SchemaDateCell {...this._props} />; default: return this.defaultCellContent; } } @@ -165,8 +171,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { return ( <div className="schema-table-cell" - onPointerDown={action(e => !this.selected && this.props.selectCell(this.props.Document, this.props.col))} - style={{ padding: this.props.padding, maxWidth: this.props.maxWidth?.(), width: this.props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}> + onPointerDown={action(e => !this.selected && this._props.selectCell(this._props.Document, this._props.col))} + style={{ padding: this._props.padding, maxWidth: this._props.maxWidth?.(), width: this._props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}> {this.content} </div> ); @@ -176,7 +182,14 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { // mj: most of this is adapted from old schema code so I'm not sure what it does tbh @observer export class SchemaImageCell extends React.Component<SchemaTableCellProps> { - @observable _previewRef: HTMLImageElement | undefined; + @observable _previewRef: HTMLImageElement | undefined = undefined; + + _props: React.PropsWithChildren<SchemaTableCellProps>; + constructor(props: React.PropsWithChildren<SchemaTableCellProps>) { + super(props); + this._props = props; + makeObservable(this); + } choosePath(url: URL) { if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href @@ -188,8 +201,8 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> { } get url() { - const field = Cast(this.props.Document[this.props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc - const alts = DocListCast(this.props.Document[this.props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images + const field = Cast(this._props.Document[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc + const alts = DocListCast(this._props.Document[this._props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images const altpaths = alts .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url) .filter(url => url) @@ -226,10 +239,10 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> { }; render() { - const aspect = Doc.NativeAspect(this.props.Document); // aspect ratio - // let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px + const aspect = Doc.NativeAspect(this._props.Document); // aspect ratio + // let width = Math.max(75, this._props.columnWidth); // get a with that is no smaller than 75px // const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px - const height = this.props.rowHeight() ? this.props.rowHeight() - (this.props.padding || 6) * 2 : undefined; + const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined; const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality return <img src={this.url} width={width ? width : undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />; @@ -240,19 +253,26 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> { export class SchemaDateCell extends React.Component<SchemaTableCellProps> { @observable _pickingDate: boolean = false; + @observable _props: React.PropsWithChildren<SchemaTableCellProps>; + constructor(props: React.PropsWithChildren<SchemaTableCellProps>) { + super(props); + this._props = props; + makeObservable(this); + } + @computed get date(): DateField { // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined. - return DateCast(this.props.Document[this.props.fieldKey]); + return DateCast(this._props.Document[this._props.fieldKey]); } @action handleChange = (date: any) => { // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } }); // if (script.compiled) { - // this.applyToDoc(this._document, this.props.row, this.props.col, script.run); + // this.applyToDoc(this._document, this._props.row, this._props.col, script.run); // } else { // ^ DateCast is always undefined for some reason, but that is what the field should be set to - this.props.Document[this.props.fieldKey] = new DateField(date as Date); + this._props.Document[this._props.fieldKey] = new DateField(date as Date); //} }; @@ -262,13 +282,20 @@ export class SchemaDateCell extends React.Component<SchemaTableCellProps> { } @observer export class SchemaRTFCell extends React.Component<SchemaTableCellProps> { + @observable _props: React.PropsWithChildren<SchemaTableCellProps>; + constructor(props: React.PropsWithChildren<SchemaTableCellProps>) { + super(props); + this._props = props; + makeObservable(this); + } + @computed get selected() { - const selected: [Doc, number] | undefined = this.props.selectedCell(); - return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col; + const selected: [Doc, number] | undefined = this._props.selectedCell(); + return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; } selectedFunc = () => this.selected; render() { - const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props); + const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); fieldProps.isContentActive = this.selectedFunc; return ( <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}> @@ -279,35 +306,42 @@ export class SchemaRTFCell extends React.Component<SchemaTableCellProps> { } @observer export class SchemaBoolCell extends React.Component<SchemaTableCellProps> { + @observable _props: React.PropsWithChildren<SchemaTableCellProps>; + constructor(props: React.PropsWithChildren<SchemaTableCellProps>) { + super(props); + this._props = props; + makeObservable(this); + } + @computed get selected() { - const selected: [Doc, number] | undefined = this.props.selectedCell(); - return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col; + const selected: [Doc, number] | undefined = this._props.selectedCell(); + return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; } render() { - const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props); + const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); return ( <div className="schemaBoolCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}> <input style={{ marginRight: 4 }} type="checkbox" - checked={BoolCast(this.props.Document[this.props.fieldKey])} + checked={BoolCast(this._props.Document[this._props.fieldKey])} onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => { if ((value?.nativeEvent as any).shiftKey) { - this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); + this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); } - KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); + KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); })} /> <EditableView contents={<FieldView {...fieldProps} />} editing={this.selected ? undefined : false} - GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)} + GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)} SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { - this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value); + this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value); } - const set = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value); - this.props.finishEdit?.(); + const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value); + this._props.finishEdit?.(); return set; })} /> @@ -317,13 +351,20 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> { } @observer export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps> { + @observable _props: React.PropsWithChildren<SchemaTableCellProps>; + constructor(props: React.PropsWithChildren<SchemaTableCellProps>) { + super(props); + this._props = props; + makeObservable(this); + } + @computed get selected() { - const selected: [Doc, number] | undefined = this.props.selectedCell(); - return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col; + const selected: [Doc, number] | undefined = this._props.selectedCell(); + return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; } render() { - const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props); - const options = this.props.options?.map(facet => ({ value: facet, label: facet })); + const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); + const options = this._props.options?.map(facet => ({ value: facet, label: facet })); return ( <div className="schemaSelectionCell" style={{ color, textDecoration, cursor, pointerEvents }}> <div style={{ width: '100%' }}> @@ -357,17 +398,17 @@ export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps> ...base, left: 0, top: 0, - transform: `translate(${this.props.transform().TranslateX}px, ${this.props.transform().TranslateY}px)`, - width: Number(base.width) * this.props.transform().Scale, + transform: `translate(${this._props.transform().TranslateX}px, ${this._props.transform().TranslateY}px)`, + width: Number(base.width) * this._props.transform().Scale, zIndex: 9999, }), }} - menuPortalTarget={this.props.menuTarget} + menuPortalTarget={this._props.menuTarget} menuPosition={'absolute'} - placeholder={StrCast(this.props.Document[this.props.fieldKey], 'select...')} + placeholder={StrCast(this._props.Document[this._props.fieldKey], 'select...')} options={options} isMulti={false} - onChange={val => KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)} + onChange={val => KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)} /> </div> </div> |
