diff options
| author | bobzel <zzzman@gmail.com> | 2023-12-21 14:55:48 -0500 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2023-12-21 14:55:48 -0500 |
| commit | 1caba64ee0f32ee8af79263cd4ef2a8bc5d5146e (patch) | |
| tree | 0fa0e957d1f342fdc6ed4a4b43f5dddfddb1298a /src/client/views/collections | |
| parent | 02eb7da95df283606d4275a22d9451cef371c3b5 (diff) | |
| parent | 2691b951d96f2ce7652acbea9e340b61737b3b57 (diff) | |
Merge branch 'moreUpgrading' into dataViz-annotations
Diffstat (limited to 'src/client/views/collections')
54 files changed, 2633 insertions, 2752 deletions
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx new file mode 100644 index 000000000..2cec29669 --- /dev/null +++ b/src/client/views/collections/CollectionCalendarView.tsx @@ -0,0 +1,107 @@ +import * as React from 'react'; +import { CollectionSubView } from './CollectionSubView'; +import { observer } from 'mobx-react'; +import { computed, makeObservable, observable } from 'mobx'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { CollectionStackingView } from './CollectionStackingView'; +import { CollectionViewType } from '../../documents/DocumentTypes'; +import { dateRangeStrToDates, emptyFunction, returnAll, returnEmptyDoclist, returnNone, returnOne, returnTrue } from '../../../Utils'; +import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { TbRuler } from 'react-icons/tb'; +import { Transform } from '../../util/Transform'; +import { DocData } from '../../../fields/DocSymbols'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { StyleProp } from '../StyleProvider'; +import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; + +@observer +export class CollectionCalendarView extends CollectionSubView() { + constructor(props: any) { + super(props); + makeObservable(this); + } + + componentDidMount(): void {} + + componentWillUnmount(): void {} + + @computed get allCalendars() { + return this.childDocs; // returns a list of docs (i.e. calendars) + } + + removeCalendar = () => {}; + + addCalendar = (doc: Doc | Doc[], annotationKey?: string | undefined): boolean => { + // bring up calendar modal with option to create a calendar + return true; + }; + + _stackRef = React.createRef<CollectionStackingView>(); + + panelHeight = () => { + return this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title + }; + + // most recent calendar should come first + sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => { + const aDateRangeStr = StrCast(DocListCast(calendarA.data).lastElement()?.date_range); + const bDateRangeStr = StrCast(DocListCast(calendarB.data).lastElement()?.date_range); + + const [aFromDate, aToDate] = dateRangeStrToDates(aDateRangeStr); + const [bFromDate, bToDate] = dateRangeStrToDates(bDateRangeStr); + + if (aFromDate > bFromDate) { + return -1; // a comes first + } else if (aFromDate < bFromDate) { + return 1; // b comes first + } else { + // start dates are the same + if (aToDate > bToDate) { + return -1; // a comes first + } else if (aToDate < bToDate) { + return 1; // b comes first + } else { + return 0; // same start and end dates + } + } + }; + + screenToLocalTransform = () => + this._props + .ScreenToLocalTransform() + .translate(Doc.NativeWidth(this._props.Document), 0) + .scale(this._props.NativeDimScaling?.() || 1); + + get calendarsKey() { + return this._props.fieldKey; + } + + render() { + return ( + <div className="collectionCalendarView"> + <CollectionStackingView + {...this._props} + setContentView={emptyFunction} + ref={this._stackRef} + PanelHeight={this.panelHeight} + PanelWidth={this._props.PanelWidth} + // childFilters={this.childFilters} DO I NEED THIS? + sortFunc={this.sortByMostRecentDate} + setHeight={undefined} + isAnnotationOverlay={false} + // select={emptyFunction} What does this mean? + isAnyChildContentActive={returnTrue} // ?? + // childDocumentsActive={} + // whenChildContentsActiveChanged={} + childHideDecorationTitle={false} + removeDocument={this.removeDocument} // will calendar automatically be removed from myCalendars + moveDocument={this.moveDocument} + addDocument={this.addCalendar} + ScreenToLocalTransform={this.screenToLocalTransform} + renderDepth={this._props.renderDepth + 1} + fieldKey={this.calendarsKey} + /> + </div> + ); + } +} diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss index 8319f19ca..a556d0fa7 100644 --- a/src/client/views/collections/CollectionCarousel3DView.scss +++ b/src/client/views/collections/CollectionCarousel3DView.scss @@ -1,8 +1,9 @@ -@import '../global/globalCssVariables'; +@import '../global/globalCssVariables.module.scss'; .collectionCarousel3DView-outer { height: 100%; position: relative; background-color: white; + overflow: hidden; } .carousel-wrapper { @@ -16,7 +17,9 @@ .collectionCarousel3DView-item, .collectionCarousel3DView-item-active { flex: 1; - transition: opacity 0.3s linear, transform 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955); + transition: + opacity 0.3s linear, + transform 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955); pointer-events: none; opacity: 0.5; z-index: 1; diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index a8d080953..a11c53d4d 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -1,25 +1,28 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { computed } from 'mobx'; +import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { Utils, returnFalse, returnZero } from '../../../Utils'; import { Doc, DocListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { returnFalse, returnZero, Utils } from '../../../Utils'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; -import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss'; -import { DocFocusOptions, DocumentView } from '../nodes/DocumentView'; import { StyleProp } from '../StyleProvider'; +import { DocFocusOptions, DocumentView } from '../nodes/DocumentView'; import './CollectionCarousel3DView.scss'; import { CollectionSubView } from './CollectionSubView'; - +const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore @observer export class CollectionCarousel3DView extends CollectionSubView() { @computed get scrollSpeed() { return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed } + constructor(props: any) { + super(props); + makeObservable(this); + } private _dropDisposer?: DragManager.DragDropDisposer; @@ -61,15 +64,16 @@ export class CollectionCarousel3DView extends CollectionSubView() { return ( <DocumentView {...this.props} + Document={childPair.layout} + TemplateDataDocument={childPair.data} + //suppressSetHeight={true} NativeWidth={returnZero} NativeHeight={returnZero} - //suppressSetHeight={true} + layout_fitWidth={undefined} onDoubleClick={this.onChildDoubleClick} renderDepth={this.props.renderDepth + 1} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} - Document={childPair.layout} - DataDoc={childPair.data} focus={this.focus} ScreenToLocalTransform={this.childScreenToLocal} isContentActive={this.isChildContentActive} diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 33a92d406..5d09f14ef 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -1,14 +1,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { computed } from 'mobx'; +import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { StopEvent, emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { NumCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, returnFalse, returnOne, returnZero, StopEvent } from '../../../Utils'; +import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; +import { StyleProp } from '../StyleProvider'; import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; -import { StyleProp } from '../StyleProvider'; import './CollectionCarouselView.scss'; import { CollectionSubView } from './CollectionSubView'; @@ -16,6 +16,11 @@ import { CollectionSubView } from './CollectionSubView'; export class CollectionCarouselView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; + constructor(props: any) { + super(props); + makeObservable(this); + } + componentWillUnmount() { this._dropDisposer?.(); } @@ -51,7 +56,7 @@ export class CollectionCarouselView extends CollectionSubView() { const index = NumCast(this.layoutDoc._carousel_index); const curDoc = this.childLayoutPairs?.[index]; const captionProps = { ...this.props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined }; - const show_captions = StrCast(this.layoutDoc._layout_showCaption); + const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption); return !(curDoc?.layout instanceof Doc) ? null : ( <> <div className="collectionCarouselView-image" key="image"> @@ -59,34 +64,36 @@ export class CollectionCarouselView extends CollectionSubView() { {...this.props} NativeWidth={returnZero} NativeHeight={returnZero} + layout_fitWidth={undefined} setContentView={undefined} onDoubleClick={this.onContentDoubleClick} onClick={this.onContentClick} isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.props.isContentActive} isContentActive={this.props.childContentsActive ?? this.props.isContentActive() === false ? returnFalse : emptyFunction} - hideCaptions={show_captions ? true : false} + hideCaptions={!!carouselShowsCaptions} // hide captions if the carousel is configured to show the captions renderDepth={this.props.renderDepth + 1} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} Document={curDoc.layout} - DataDoc={curDoc.layout.resolvedDataDoc as Doc} + TemplateDataDocument={DocCast(curDoc.layout.resolvedDataDoc)} PanelHeight={this.panelHeight} bringToFront={returnFalse} /> </div> - <div - className="collectionCarouselView-caption" - key="caption" - onWheel={StopEvent} - style={{ - display: show_captions ? undefined : 'none', - borderRadius: this.props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding), - marginRight: this.marginX, - marginLeft: this.marginX, - width: `calc(100% - ${this.marginX * 2}px)`, - }}> - <FormattedTextBox key={index} {...captionProps} fieldKey={show_captions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} DataDoc={undefined} /> - </div> + {!carouselShowsCaptions ? null : ( + <div + className="collectionCarouselView-caption" + key="caption" + onWheel={StopEvent} + style={{ + borderRadius: this.props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding), + marginRight: this.marginX, + marginLeft: this.marginX, + width: `calc(100% - ${this.marginX * 2}px)`, + }}> + <FormattedTextBox key={index} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} TemplateDataDocument={undefined} /> + </div> + )} </> ); } diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 333ba9f32..a747ef45f 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -1,4 +1,4 @@ -@import '../global/globalCssVariables.scss'; +@import '../global/globalCssVariables.module.scss'; .lm_root { position: relative; @@ -28,10 +28,6 @@ position: relative; z-index: 20; } -.lm_splitter:hover, -.lm_splitter.lm_dragging { - background: orange; -} .lm_splitter.lm_vertical .lm_drag_handle { width: 100%; position: absolute; @@ -276,7 +272,7 @@ z-index: 20; } /*# sourceMappingURL=goldenlayout-base.css.map */ -@import '../../../../node_modules/golden-layout/src/css/goldenlayout-dark-theme.css'; +@import './goldenLayoutTheme.css'; .lm_title { -webkit-appearance: none; @@ -681,11 +677,6 @@ ul.lm_tabs::before { height: 8px; } - .flexlayout__tab_button:hover .flexlayout__tab_button_trailing, - .flexlayout__tab_button--selected .flexlayout__tab_button_trailing { - background: transparent url('../../../../node_modules/flexlayout-react/images/close_white.png') no-repeat center; - } - .flexlayout__tab_button_overflow { float: left; width: 20px; @@ -696,7 +687,6 @@ ul.lm_tabs::before { font-size: 10px; color: lightgray; font-family: Arial, sans-serif; - background: transparent url('../../../../node_modules/flexlayout-react/images/more.png') no-repeat left; } .flexlayout__tabset_header { @@ -751,7 +741,6 @@ ul.lm_tabs::before { height: 20px; border: none; outline-width: 0; - background: transparent url('../../../../node_modules/flexlayout-react/images/maximize.png') no-repeat center; } .flexlayout__tab_toolbar_button-max { @@ -759,7 +748,6 @@ ul.lm_tabs::before { height: 20px; border: none; outline-width: 0; - background: transparent url('../../../../node_modules/flexlayout-react/images/restore.png') no-repeat center; } .flexlayout__popup_menu_item { @@ -877,11 +865,6 @@ ul.lm_tabs::before { height: 8px; } - .flexlayout__border_button:hover .flexlayout__border_button_trailing, - .flexlayout__border_button--selected .flexlayout__border_button_trailing { - background: transparent url('../../../../node_modules/flexlayout-react/images/close_white.png') no-repeat center; - } - .flexlayout__border_toolbar_left { position: absolute; display: flex; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index f155e64b5..a9474fe93 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,5 +1,6 @@ -import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import * as GoldenLayout from '../../../client/goldenLayout'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; @@ -19,7 +20,8 @@ import { DragManager } from '../../util/DragManager'; import { InteractionUtils } from '../../util/InteractionUtils'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; -import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { SettingsManager } from '../../util/SettingsManager'; +import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import { DashboardView } from '../DashboardView'; import { LightboxView } from '../LightboxView'; import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; @@ -30,13 +32,11 @@ import './CollectionDockingView.scss'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; -import React = require('react'); -import { SettingsManager } from '../../util/SettingsManager'; 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,14 +62,16 @@ export class CollectionDockingView extends CollectionSubView() { } private _goldenLayout: any = null; static _highlightStyleSheet: any = addStyleSheet(); - constructor(props: SubCollectionViewProps) { + + constructor(props: any) { super(props); - if (this.props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this)); + makeObservable(this); + if (this._props.renderDepth < 0) CollectionDockingView.Instance = this; //Why is this here? (window as any).React = React; (window as any).ReactDOM = ReactDOM; DragManager.StartWindowDrag = this.StartOtherDrag; - this.rootDoc.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... + 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... } /** @@ -274,8 +276,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 +320,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 +330,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,14 +377,14 @@ 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; this.layoutDoc.data = new List<Doc>(docs); } else { - Doc.SetInPlace(this.rootDoc, 'dockingConfig', json, true); - Doc.SetInPlace(this.rootDoc, 'data', new List<Doc>(docs), true); + Doc.SetInPlace(this.Document, 'dockingConfig', json, true); + Doc.SetInPlace(this.Document, 'data', new List<Doc>(docs), true); } } this._flush?.end(); @@ -425,7 +427,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', '')); @@ -465,6 +467,7 @@ export class CollectionDockingView extends CollectionSubView() { }); const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); DashboardView.SetupDashboardTrails(copy); + DashboardView.SetupDashboardCalendars(copy); // Zaul TODO: needed? return DashboardView.openDashboard(copy); } @@ -472,16 +475,16 @@ 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; }; tabDestroyed = (tab: any) => { this._flush = this._flush ?? UndoManager.StartBatch('tab movement'); if (tab.DashDoc && ![DocumentType.PRES].includes(tab.DashDoc?.type) && !tab.contentItem.config.props.keyValue) { - Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc); + Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc, undefined, undefined, true); // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed - if (tab.DashDoc.embedContainer === this.rootDoc) tab.DashDoc.embedContainer = undefined; + if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined; if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); Doc.RemoveDocFromList(Doc.GetProto(tab.DashDoc), 'proto_embeddings', tab.DashDoc); } @@ -506,34 +509,37 @@ 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'])}`, }); - Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd); - inheritParentAcls(this.rootDoc, docToAdd, false); + Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); + inheritParentAcls(this.Document, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }); - let addNewDoc = action(() => { - const dashboard = Doc.ActiveDashboard; - if (dashboard) { - dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1; - const docToAdd = Docs.Create.FreeformDocument([], { - _width: this.props.PanelWidth(), - _height: this.props.PanelHeight(), - _layout_fitWidth: true, - _freeform_backgroundGrid: true, - title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, - }); - Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd); - inheritParentAcls(this.dataDoc, docToAdd, false); - CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); - } - }); + let addNewDoc = undoable( + action(() => { + const dashboard = Doc.ActiveDashboard; + if (dashboard) { + dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1; + const docToAdd = Docs.Create.FreeformDocument([], { + _width: this._props.PanelWidth(), + _height: this._props.PanelHeight(), + _layout_fitWidth: true, + _freeform_backgroundGrid: true, + title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, + }); + Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); + inheritParentAcls(this.dataDoc, docToAdd, false); + CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); + } + }), + 'add new tab' + ); stack.header?.controlsContainer .find('.lm_close') //get the close icon @@ -569,15 +575,15 @@ export class CollectionDockingView extends CollectionSubView() { }; render() { - const href = ImageCast(this.rootDoc.thumb)?.url?.href; - return this.props.renderDepth > -1 ? ( + const href = ImageCast(this.Document.thumb)?.url?.href; + 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 06522b85e..8627ba650 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -1,13 +1,12 @@ -import React = require('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 * as React from 'react'; +import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils'; 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'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { CompileScript } from '../../util/Scripting'; @@ -15,12 +14,10 @@ import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; +import { ObservableReactComponent } from '../ObservableReactComponent'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { CollectionStackingView } from './CollectionStackingView'; import './CollectionStackingView.scss'; -const higflyout = require('@hig/flyout'); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; interface CMVFieldRowProps { rows: () => number; @@ -42,7 +39,12 @@ interface CMVFieldRowProps { } @observer -export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowProps> { +export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVFieldRowProps> { + constructor(props: any) { + super(props); + makeObservable(this); + } + @observable private _background = 'inherit'; @observable private _createEmbeddingSelected: boolean = false; @observable private heading: string = ''; @@ -50,13 +52,13 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @observable private collapsed: boolean = false; @observable private _paletteOn = false; 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 +70,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 +99,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 +121,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; } @@ -140,7 +142,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr this._color = color; }; - pointerEnteredRow = action(() => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4')); + pointerEnteredRow = action(() => SnappingManager.IsDragging && (this._background = '#b4b4b4')); @action pointerLeaveRow = () => { @@ -152,24 +154,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.SelectOnLoad = newDoc[Id]; + 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 +184,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 +200,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 +209,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; @@ -246,37 +248,25 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr toggleEmbedding = action(() => (this._createEmbeddingSelected = true)); toggleVisibility = () => (this._collapsed = !this.collapsed); - renderMenu = () => { - const selected = this._createEmbeddingSelected; - return ( - <div className="collectionStackingView-optionPicker"> - <div className="optionOptions"> - <div className={'optionPicker' + (selected === true ? ' active' : '')} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}> - Delete - </div> - </div> - </div> - ); - }; @action textCallback = (char: string) => { return this.addDocument('', false); }; @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 +277,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" @@ -338,11 +328,9 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr )} {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : ( <div className="collectionStackingView-sectionOptions" onPointerDown={e => e.stopPropagation()}> - <Flyout anchorPoint={anchorPoints.RIGHT_TOP} content={this.renderMenu()}> - <button className="collectionStackingView-sectionOptionButton"> - <FontAwesomeIcon icon="ellipsis-v" size="lg" /> - </button> - </Flyout> + <button className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}> + <FontAwesomeIcon icon="trash" size="lg" /> + </button> </div> )} </div> @@ -352,7 +340,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.scss b/src/client/views/collections/CollectionMenu.scss index 6eeccc94e..5d46649b2 100644 --- a/src/client/views/collections/CollectionMenu.scss +++ b/src/client/views/collections/CollectionMenu.scss @@ -1,4 +1,4 @@ -@import "../global/globalCssVariables"; +@import '../global/globalCssVariables.module.scss'; .collectionMenu-container { display: flex; @@ -19,4 +19,4 @@ display: flex; flex-direction: row; } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index cf154be8d..443aa3f17 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1,43 +1,26 @@ -import React = require('react'); -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import * as React from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@mui/material'; import { Toggle, ToggleType, Type } from 'browndash-components'; -import { action, computed, Lambda, observable, reaction, runInAction } from 'mobx'; +import { action, computed, Lambda, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { ColorState } from 'react-color'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { Document } from '../../../fields/documentSchemas'; -import { Id } from '../../../fields/FieldSymbols'; -import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { RichTextField } from '../../../fields/RichTextField'; -import { listSpec } from '../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils'; -import { Docs } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; -import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { SettingsManager } from '../../util/SettingsManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { AntimodeMenu } from '../AntimodeMenu'; import { EditableView } from '../EditableView'; -import { GestureOverlay } from '../GestureOverlay'; -import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke'; -import { LightboxView } from '../LightboxView'; import { MainView } from '../MainView'; -import { media_state } from '../nodes/AudioBox'; -import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; -import { DocumentView, DocumentViewInternal, OpenWhereMod } from '../nodes/DocumentView'; -import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; +import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { DefaultStyleProvider } from '../StyleProvider'; -import { CollectionDockingView } from './CollectionDockingView'; -import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionLinearView } from './collectionLinear'; import './CollectionMenu.scss'; @@ -50,23 +33,24 @@ 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>(); constructor(props: any) { super(props); + makeObservable(this); 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.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true); this.jumpTo(300, 300); } componentDidMount() { reaction( - () => SelectionManager.Views().length && SelectionManager.Views()[0], + () => SelectionManager.Views.lastElement(), view => view && this.SetSelection(view) ); } @@ -86,19 +70,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; } }; @@ -111,15 +95,13 @@ 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} - DataDoc={undefined} fieldKey="data" dropAction="embed" setHeight={returnFalse} styleProvider={DefaultStyleProvider} - rootSelected={returnTrue} bringToFront={emptyFunction} select={emptyFunction} isContentActive={returnTrue} @@ -132,8 +114,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} @@ -146,10 +128,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`}> @@ -158,7 +140,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} /> @@ -167,14 +149,13 @@ 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} /> </div> ); - // NEW BUTTONS //dash col linear view buttons const contMenuButtons = ( <div @@ -189,21 +170,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { ); return contMenuButtons; - - // const button = <Tooltip title={<div className="dash-tooltip">Pin Menu</div>} key="pin menu" placement="bottom"> - // <button className="antimodeMenu-button" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}> - // <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} /> - // </button> - // </Tooltip>; - - // //OLD BUTTONS - // return this.getElement(!this.SelectedCollection ? [/*button*/] : - // [<CollectionViewBaseChrome key="chrome" - // docView={this.SelectedCollection} - // fieldKey={this.SelectedCollection.LayoutFieldKey} - // type={StrCast(this.SelectedCollection?.props.Document._type_collection, CollectionViewType.Invalid) as CollectionViewType} />, - // prop, - // /*button*/]); } } @@ -220,7 +186,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; @@ -228,7 +194,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu _templateCommand = { params: ['target', 'source'], title: 'item view', - script: 'self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])', + script: 'this.target.childLayoutTemplate = getDocTemplate(this.source?.[0])', immediate: undoBatch((source: Doc[]) => { let formatStr = source.length && Cast(source[0].text, RichTextField, null)?.Text; try { @@ -250,24 +216,24 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu _narrativeCommand = { params: ['target', 'source'], title: 'child click view', - script: 'self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])', + script: 'this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])', immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))), initialize: emptyFunction, }; _contentCommand = { params: ['target', 'source'], title: 'set content', - script: 'getProto(self.target).data = copyField(self.source);', + script: 'getProto(this.target).data = copyField(this.source);', immediate: undoBatch((source: Doc[]) => (Doc.GetProto(this.target).data = new List<Doc>(source))), initialize: emptyFunction, }; _onClickCommand = { params: ['target', 'proxy'], title: 'copy onClick', - script: `{ if (self.proxy?.[0]) { - getProto(self.proxy[0]).onClick = copyField(self.target.onClick); - getProto(self.proxy[0]).target = self.target.target; - getProto(self.proxy[0]).source = copyField(self.target.source); + script: `{ if (this.proxy?.[0]) { + getProto(this.proxy[0]).onClick = copyField(this.target.onClick); + getProto(this.proxy[0]).target = this.target.target; + getProto(this.proxy[0]).source = copyField(this.target.source); }}`, immediate: undoBatch((source: Doc[]) => {}), initialize: emptyFunction, @@ -275,7 +241,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu _viewCommand = { params: ['target'], title: 'bookmark view', - script: "self.target._freeform_panX = self['target-freeform_panX']; self.target._freeform_panY = self['target-freeform_panY']; self.target._freeform_scale = self['target_freeform_scale']; gotoFrame(self.target, self['target-currentFrame']);", + script: "this.target._freeform_panX = self['target-freeform_panX']; this.target._freeform_panY = this['target-freeform_panY']; this.target._freeform_scale = this['target_freeform_scale']; gotoFrame(this.target, this['target-currentFrame']);", immediate: undoBatch((source: Doc[]) => { this.target._freeform_panX = 0; this.target._freeform_panY = 0; @@ -292,22 +258,22 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu _clusterCommand = { params: ['target'], title: 'fit content', - script: 'self.target._freeform_fitContentsToBox = !self.target._freeform_fitContentsToBox;', + script: 'this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;', immediate: undoBatch((source: Doc[]) => (this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox)), initialize: emptyFunction, }; _fitContentCommand = { params: ['target'], title: 'toggle clusters', - script: 'self.target._freeform_useClusters = !self.target._freeform_useClusters;', + script: 'this.target._freeform_useClusters = !this.target._freeform_useClusters;', immediate: undoBatch((source: Doc[]) => (this.target._freeform_useClusters = !this.target._freeform_useClusters)), initialize: emptyFunction, }; _saveFilterCommand = { params: ['target'], title: 'save filter', - script: `self.target._childFilters = compareLists(self['target-childFilters'],self.target._childFilters) ? undefined : copyField(self['target-childFilters']); - self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`, + script: `this.target._childFilters = compareLists(this['target-childFilters'],this.target._childFilters) ? undefined : copyField(this['target-childFilters']); + this.target._searchFilterDocs = compareLists(this['target-searchFilterDocs'],this.target._searchFilterDocs) ? undefined: copyField(this['target-searchFilterDocs']);`, immediate: undoBatch((source: Doc[]) => { this.target._childFilters = undefined; this.target._searchFilterDocs = undefined; @@ -390,57 +356,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu this.document._facetWidth = 0; }; - @computed get subChrome() { - switch ( - this.props.docView.props.LayoutTemplateString ? CollectionViewType.Freeform : this.props.type // bcz: ARgh! hack to get menu for tree view outline items - ) { - default: - return this.otherSubChrome; - case CollectionViewType.Invalid: - case CollectionViewType.Freeform: - return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />; - case CollectionViewType.Stacking: - return <CollectionStackingViewChrome key="collchrome" {...this.props} />; - case CollectionViewType.NoteTaking: - return <CollectionNoteTakingViewChrome key="collchrome" {...this.props} />; - case CollectionViewType.Schema: - return <CollectionSchemaViewChrome key="collchrome" {...this.props} />; - case CollectionViewType.Tree: - return <CollectionTreeViewChrome key="collchrome" {...this.props} />; - case CollectionViewType.Masonry: - return <CollectionStackingViewChrome key="collchrome" {...this.props} />; - case CollectionViewType.Carousel: - case CollectionViewType.Carousel3D: - return <Collection3DCarouselViewChrome key="collchrome" {...this.props} />; - case CollectionViewType.Grid: - return <CollectionGridViewChrome key="collchrome" {...this.props} />; - case CollectionViewType.Docking: - return <CollectionDockingChrome key="collchrome" {...this.props} />; - } - } - - @computed get otherSubChrome() { - const docType = this.props.docView.Document.type; - switch (docType) { - default: - return null; - case DocumentType.IMG: - return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />; - case DocumentType.PDF: - return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />; - case DocumentType.INK: - return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />; - case DocumentType.WEB: - return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />; - case DocumentType.VID: - return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />; - case DocumentType.RTF: - return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} isDoc={true} />; - case DocumentType.MAP: - return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />; - } - } - private dropDisposer?: DragManager.DragDropDisposer; protected createDropTarget = (ele: HTMLDivElement) => { this.dropDisposer?.(); @@ -517,593 +432,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu </div> ); } - - @computed get viewModes() { - const excludedViewTypes = [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear]; - const isPres: boolean = this.document && this.document.type === DocumentType.PRES; - return isPres ? null : ( - <div className="collectionViewBaseChrome-viewModes"> - <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom"> - <div className="commandEntry-outerDiv" ref={this._viewRef} onPointerDown={this.dragViewDown}> - <button className={'antimodeMenu-button'}> - <FontAwesomeIcon icon="bullseye" size="lg" /> - </button> - <select className="collectionViewBaseChrome-viewPicker" onPointerDown={stopPropagation} onChange={this.viewChanged} value={StrCast(this.props.type)}> - {Object.values(CollectionViewType) - .filter(type => !excludedViewTypes.includes(type)) - .map(type => ( - <option key={Utils.GenerateGuid()} className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value={type}> - {type[0].toUpperCase() + type.substring(1)} - </option> - ))} - </select> - </div> - </Tooltip> - </div> - ); - } - - @computed get selectedDocumentView() { - return SelectionManager.Views().lastElement(); - } - @computed get selectedDoc() { - return SelectionManager.Docs().lastElement(); - } - @computed get notACollection() { - if (this.selectedDoc) { - const layoutField = Doc.LayoutField(this.selectedDoc); - return this.props.type === CollectionViewType.Docking || (typeof layoutField === 'string' && !layoutField?.includes('CollectionView')); - } else return false; - } - - @undoBatch - @action - startRecording = () => { - const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _layout_fitWidth: true, _width: 400, _height: 200, mediaState: media_state.PendingRecording }); - CollectionDockingView.AddSplit(doc, OpenWhereMod.right); - }; - - @computed - get recordButton() { - const targetDoc = this.selectedDoc; - return ( - <Tooltip key="record" title={<div className="dash-tooltip">{'Capture screen'}</div>} placement="top"> - <button className="antimodeMenu-button" onClick={e => this.startRecording()}> - <div className="recordButtonOutline" style={{}}> - <div className="recordButtonInner" style={{}}></div> - </div> - </button> - </Tooltip> - ); - } - - @undoBatch - onEmbed = () => { - if (this.selectedDoc && this.selectedDocumentView) { - // const copy = Doc.MakeCopy(this.selectedDocumentView.props.Document, true); - // copy.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width); - // copy.y = NumCast(this.selectedDoc.y) + 30; - // this.selectedDocumentView.props.addDocument?.(copy); - const embedding = Doc.MakeEmbedding(this.selectedDoc); - embedding.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width); - embedding.y = NumCast(this.selectedDoc.y) + 30; - this.selectedDocumentView.props.addDocument?.(embedding); - } - }; - onEmbedButtonDown = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, this.onEmbedButtonMoved, emptyFunction, emptyFunction); - }; - - @undoBatch - onEmbedButtonMoved = (e: PointerEvent) => { - const contentDiv = this.selectedDocumentView?.ContentDiv; - if (contentDiv && this.selectedDoc) { - const dragData = new DragManager.DocumentDragData([this.selectedDoc]); - const offset = [e.clientX - contentDiv.getBoundingClientRect().x, e.clientY - contentDiv.getBoundingClientRect().y]; - dragData.defaultDropAction = 'embed'; - dragData.canEmbed = true; - DragManager.StartDocumentDrag([contentDiv], dragData, e.clientX, e.clientY, { - offsetX: offset[0], - offsetY: offset[1], - hideSource: false, - }); - return true; - } - return false; - }; - - @computed - get embedButton() { - const targetDoc = this.selectedDoc; - return !targetDoc || targetDoc.type === DocumentType.PRES ? null : ( - <Tooltip title={<div className="dash-tooltip">{'Tap or Drag to create an embedding'}</div>} placement="top"> - <button className="antimodeMenu-button" onPointerDown={this.onEmbedButtonDown} onClick={this.onEmbed} style={{ cursor: 'drag' }}> - <FontAwesomeIcon className="colMenu-icon" icon="copy" size="lg" /> - </button> - </Tooltip> - ); - } - - @computed get lightboxButton() { - const targetDoc = this.selectedDoc; - return !targetDoc ? null : ( - <Tooltip title={<div className="dash-tooltip">{'View in Lightbox'}</div>} placement="top"> - <button - className="antimodeMenu-button" - onPointerDown={() => { - const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); - LightboxView.Instance.SetLightboxDoc(targetDoc, undefined, docs); - }}> - <FontAwesomeIcon className="colMenu-icon" icon="desktop" size="lg" /> - </button> - </Tooltip> - ); - } - - @computed get toggleOverlayButton() { - return ( - <> - <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom"> - <button - className={'antimodeMenu-button'} - key="float" - style={{ - backgroundColor: this.props.docView.layoutDoc.z ? '121212' : undefined, - pointerEvents: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._type_collection !== CollectionViewType.Freeform ? 'none' : undefined, - color: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._type_collection !== CollectionViewType.Freeform ? 'dimgrey' : undefined, - }} - onClick={undoBatch(() => this.props.docView.CollectionFreeFormDocumentView?.float())}> - <FontAwesomeIcon icon={['fab', 'buffer']} size={'lg'} /> - </button> - </Tooltip> - </> - ); - } - render() { return ( <div className="collectionMenu-cont"> <div className="collectionMenu"> - <div className="collectionViewBaseChrome"> - {this.notACollection || this.props.type === CollectionViewType.Invalid ? null : this.viewModes} - <div className="collectionMenu-divider" key="divider1"></div> - {this.embedButton} - {/* {this.pinButton} */} - {this.toggleOverlayButton} - <div className="collectionMenu-divider" key="divider2"></div> - {this.subChrome} - <div className="collectionMenu-divider" key="divider3"></div> - {this.lightboxButton} - {this.recordButton} - {!this._buttonizableCommands ? null : this.templateChrome} - </div> - </div> - </div> - ); - } -} - -@observer -export class CollectionDockingChrome extends React.Component<CollectionViewMenuProps> { - render() { - return null; - } -} - -@observer -export class CollectionFreeFormViewChrome extends React.Component<CollectionViewMenuProps & { isOverlay: boolean; isDoc?: boolean }> { - public static Instance: CollectionFreeFormViewChrome; - constructor(props: any) { - super(props); - CollectionFreeFormViewChrome.Instance = this; - } - get document() { - return this.props.docView.props.Document; - } - @computed get dataField() { - return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? '_annotations' : '')]; - } - @computed get childDocs() { - return DocListCast(this.dataField); - } - @computed get selectedDocumentView() { - return SelectionManager.Views().lastElement(); - } - @computed get selectedDoc() { - return SelectionManager.Docs().lastElement(); - } - @computed get isText() { - return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false; - } - - public static gotoKeyFrame(doc: Doc, newFrame: number) { - if (doc) { - const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]); - const currentFrame = Cast(doc._currentFrame, 'number', null); - if (currentFrame === undefined) { - doc._currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); - } - CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0); - doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame); - } - } - - _keyTimer: NodeJS.Timeout | undefined; - @undoBatch - @action - nextKeyframe = (): void => { - const currentFrame = Cast(this.document._currentFrame, 'number', null); - if (currentFrame === undefined) { - this.document._currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); - } - this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.document], currentFrame || 0); - this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1); - this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame)); - }; - @undoBatch - @action - prevKeyframe = (): void => { - const currentFrame = Cast(this.document._currentFrame, 'number', null); - if (currentFrame === undefined) { - this.document._currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); - } - this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.document], 1000); - this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1); - }; - - private _palette = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '']; - private _width = ['1', '5', '10', '100']; - private _dotsize = [10, 20, 30, 40]; - private _draw = ['∿', '=', '⎯', '→', '↔︎', 'ãƒ', 'O']; - private _head = ['', '', '', '', 'arrow', '', '']; - private _end = ['', '', '', 'arrow', 'arrow', '', '']; - private _shapePrims = ['', '', 'line', 'line', 'line', 'rectangle', 'circle'] as GestureUtils.Gestures[]; - private _title = ['pen', 'highlighter', 'line', 'line with arrow', 'line with double arrows', 'square', 'circle']; - private _faName = ['pen-fancy', 'highlighter', 'minus', 'long-arrow-alt-right', 'arrows-alt-h', 'square', 'circle']; - @observable _selectedPrimitive = this._shapePrims.length; - @observable _keepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode - @observable _colorBtn = false; - @observable _widthBtn = false; - @observable _fillBtn = false; - - @action clearKeepPrimitiveMode() { - this._selectedPrimitive = this._shapePrims.length; - } - @action primCreated() { - if (!this._keepPrimitiveMode) { - //get out of ink mode after each stroke= - if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor); - Doc.ActiveTool = InkTool.None; - this._selectedPrimitive = this._shapePrims.length; - SetActiveArrowStart('none'); - SetActiveArrowEnd('none'); - } - } - - @action - changeColor = (color: string, type: string) => { - const col: ColorState = { - hex: color, - hsl: { a: 0, h: 0, s: 0, l: 0, source: '' }, - hsv: { a: 0, h: 0, s: 0, v: 0, source: '' }, - rgb: { a: 0, r: 0, b: 0, g: 0, source: '' }, - oldHue: 0, - source: '', - }; - if (type === 'color') { - SetActiveInkColor(Utils.colorString(col)); - } else if (type === 'fill') { - SetActiveFillColor(Utils.colorString(col)); - } - }; - - @action - editProperties = (value: any, field: string) => { - SelectionManager.Views().forEach( - action((element: DocumentView) => { - const doc = Document(element.rootDoc); - if (doc.type === DocumentType.INK) { - switch (field) { - case 'width': - doc.stroke_width = Number(value); - break; - case 'color': - doc.color = String(value); - break; - case 'fill': - doc.fillColor = String(value); - break; - case 'dash': - doc.stroke_dash = value; - } - } - }) - ); - }; - - @computed get drawButtons() { - const func = action((e: React.MouseEvent | React.PointerEvent, i: number, keep: boolean) => { - this._keepPrimitiveMode = keep; - // these are for shapes - if (this._selectedPrimitive !== i) { - this._selectedPrimitive = i; - if (this._title[i] === 'highlighter') { - Doc.ActiveTool = InkTool.Highlighter; - GestureOverlay.Instance.SavedColor = ActiveInkColor(); - SetActiveInkColor('rgba(245, 230, 95, 0.75)'); - } else { - Doc.ActiveTool = InkTool.Pen; - } - SetActiveArrowStart(this._head[i]); - SetActiveArrowEnd(this._end[i]); - SetActiveBezierApprox('300'); - - if (GestureOverlay.Instance) GestureOverlay.Instance.InkShape = this._shapePrims[i]; - } else { - this._selectedPrimitive = this._shapePrims.length; - Doc.ActiveTool = InkTool.None; - SetActiveArrowStart(''); - SetActiveArrowEnd(''); - if (GestureOverlay.Instance) GestureOverlay.Instance.InkShape = undefined; - SetActiveBezierApprox('0'); - } - e.stopPropagation(); - }); - return ( - <div className="btn-draw" key="draw"> - {this._draw.map((icon, i) => ( - <Tooltip key={icon} title={<div className="dash-tooltip">{this._title[i]}</div>} placement="bottom"> - <button className="antimodeMenu-button" onPointerDown={e => func(e, i, false)} onDoubleClick={e => func(e, i, true)} style={{ backgroundColor: i === this._selectedPrimitive ? '525252' : '', fontSize: '20' }}> - <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" /> - </button> - </Tooltip> - ))} - </div> - ); - } - - toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps['icon'], ele: JSX.Element | null) => { - return ( - <Tooltip title={<div className="dash-tooltip">{key}</div>} placement="bottom"> - <button className="antimodeMenu-button" key={key} onPointerDown={action(e => setter())} style={{ backgroundColor: value ? '121212' : '' }}> - <FontAwesomeIcon icon={icon} size="lg" /> - {ele} - </button> - </Tooltip> - ); - }; - - @computed get widthPicker() { - const widthPicker = this.toggleButton('stroke width', this._widthBtn, () => (this._widthBtn = !this._widthBtn), 'bars', null); - return !this._widthBtn ? ( - widthPicker - ) : ( - <div className="btn2-group" key="width"> - {widthPicker} - {this._width.map((wid, i) => ( - <Tooltip title={<div className="dash-tooltip">change width</div>} placement="bottom"> - <button - className="antimodeMenu-button" - key={wid} - onPointerDown={action(() => { - SetActiveInkWidth(wid); - this._widthBtn = false; - this.editProperties(wid, 'width'); - })} - style={{ backgroundColor: this._widthBtn ? '121212' : '', zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: 'center' }}> - • - </button> - </Tooltip> - ))} - </div> - ); - } - - @computed get colorPicker() { - const colorPicker = this.toggleButton('stroke color', this._colorBtn, () => (this._colorBtn = !this._colorBtn), 'pen-nib', <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? '121212' }} />); - return !this._colorBtn ? ( - colorPicker - ) : ( - <div className="btn-group" key="color"> - {colorPicker} - {this._palette.map(color => ( - <button - className="antimodeMenu-button" - key={color} - onPointerDown={action(() => { - this.changeColor(color, 'color'); - this._colorBtn = false; - this.editProperties(color, 'color'); - })} - style={{ backgroundColor: this._colorBtn ? '121212' : '', zIndex: 1001 }}> - {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */} - <div className="color-previewII" style={{ backgroundColor: color }}> - {color === '' ? <p style={{ fontSize: 40, color: 'red', marginTop: -10, marginLeft: -5, position: 'fixed' }}>☒</p> : ''} - </div> - </button> - ))} - </div> - ); - } - @computed get fillPicker() { - const fillPicker = this.toggleButton('shape fill color', this._fillBtn, () => (this._fillBtn = !this._fillBtn), 'fill-drip', <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? '121212' }} />); - return !this._fillBtn ? ( - fillPicker - ) : ( - <div className="btn-group" key="fill"> - {fillPicker} - {this._palette.map(color => ( - <button - className="antimodeMenu-button" - key={color} - onPointerDown={action(() => { - this.changeColor(color, 'fill'); - this._fillBtn = false; - this.editProperties(color, 'fill'); - })} - style={{ backgroundColor: this._fillBtn ? '121212' : '', zIndex: 1001 }}> - <div className="color-previewII" style={{ backgroundColor: color }}> - {color === '' ? <p style={{ fontSize: 40, color: 'red', marginTop: -10, marginLeft: -5, position: 'fixed' }}>☒</p> : ''} - </div> - </button> - ))} - </div> - ); - } - - render() { - return !this.props.docView.layoutDoc ? null : ( - <div className="collectionFreeFormMenu-cont"> - <RichTextMenu key="rich" /> - {!this.isText ? ( - <> - {this.drawButtons} - {this.widthPicker} - {this.colorPicker} - {this.fillPicker} - {Doc.noviceMode || this.props.isDoc ? null : ( - <> - <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom"> - <div className="backKeyframe" onClick={this.prevKeyframe}> - <FontAwesomeIcon icon={'caret-left'} size={'lg'} /> - </div> - </Tooltip> - <Tooltip key="num" title={<div className="dash-tooltip">Frame number</div>} placement="bottom"> - <div - className="numKeyframe" - style={{ color: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? 'white' : 'black', backgroundColor: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? '#5B9FDD' : '#AEDDF8' }} - onClick={action(() => this.props.docView.ComponentView?.setKeyFrameEditing?.(!this.props.docView.ComponentView?.getKeyFrameEditing?.()))}> - {NumCast(this.document._currentFrame)} - </div> - </Tooltip> - <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom"> - <div className="fwdKeyframe" onClick={this.nextKeyframe}> - <FontAwesomeIcon icon={'caret-right'} size={'lg'} /> - </div> - </Tooltip> - </> - )} - </> - ) : null} - {!this.selectedDocumentView?.ComponentView?.menuControls ? null : this.selectedDocumentView?.ComponentView?.menuControls?.()} - </div> - ); - } -} -@observer -export class CollectionStackingViewChrome extends React.Component<CollectionViewMenuProps> { - @observable private _currentKey: string = ''; - @observable private suggestions: string[] = []; - - get document() { - return this.props.docView.props.Document; - } - - @computed private get descending() { - return StrCast(this.document._columnsSort) === 'descending'; - } - @computed get pivotField() { - return StrCast(this.document._pivotField); - } - - getKeySuggestions = async (value: string): Promise<string[]> => { - const val = value.toLowerCase(); - const docs = DocListCast(this.document[this.props.fieldKey]); - - if (Doc.noviceMode) { - if (docs instanceof Doc) { - const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_')); - return keys.filter(key => key.toLowerCase().indexOf(val) > -1); - } - const keys = new Set<string>(); - docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); - const noviceKeys = Array.from(keys).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')); - return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); - } - - if (docs instanceof Doc) { - return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1); - } else { - const keys = new Set<string>(); - docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); - return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1); - } - }; - - @action - onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { - this._currentKey = newValue; - }; - - getSuggestionValue = (suggestion: string) => suggestion; - - renderSuggestion = (suggestion: string) => { - return <p>{suggestion}</p>; - }; - - onSuggestionFetch = async ({ value }: { value: string }) => { - const sugg = await this.getKeySuggestions(value); - runInAction(() => { - this.suggestions = sugg; - }); - }; - - @action - onSuggestionClear = () => { - this.suggestions = []; - }; - - @action - setValue = (value: string) => { - this.document._pivotField = value; - return true; - }; - - @action toggleSort = () => { - this.document._columnsSort = this.document._columnsSort === 'descending' ? 'ascending' : this.document._columnsSort === 'ascending' ? undefined : 'descending'; - }; - @action resetValue = () => { - this._currentKey = this.pivotField; - }; - - render() { - const doctype = this.props.docView.Document.type; - const isPres: boolean = doctype === DocumentType.PRES; - return isPres ? null : ( - <div className="collectionStackingViewChrome-cont"> - <div className="collectionStackingViewChrome-pivotField-cont"> - <div className="collectionStackingViewChrome-pivotField-label">GROUP BY:</div> - <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? '180' : '0'}deg)` }}> - <FontAwesomeIcon icon="caret-up" size="2x" color="white" /> - </div> - <div className="collectionStackingViewChrome-pivotField"> - <EditableView - GetValue={() => this.pivotField} - autosuggestProps={{ - resetValue: this.resetValue, - value: this._currentKey, - onChange: this.onKeyChange, - autosuggestProps: { - inputProps: { - value: this._currentKey, - onChange: this.onKeyChange, - }, - getSuggestionValue: this.getSuggestionValue, - suggestions: this.suggestions, - alwaysRenderSuggestions: true, - renderSuggestion: this.renderSuggestion, - onSuggestionsFetchRequested: this.onSuggestionFetch, - onSuggestionsClearRequested: this.onSuggestionClear, - }, - }} - oneLine - SetValue={this.setValue} - contents={this.pivotField ? this.pivotField : 'N/A'} - /> - </div> + <div className="collectionViewBaseChrome">{!this._buttonizableCommands ? null : this.templateChrome}</div> </div> </div> ); @@ -1209,6 +542,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi autosuggestProps: { inputProps: { value: this._currentKey, + // @ts-ignore onChange: this.onKeyChange, }, getSuggestionValue: this.getSuggestionValue, @@ -1230,123 +564,6 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi } } -@observer -export class CollectionSchemaViewChrome extends React.Component<CollectionViewMenuProps> { - // private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0; - get document() { - return this.props.docView.props.Document; - } - - @undoBatch - togglePreview = () => { - const dividerWidth = 4; - const borderWidth = 0; - const panelWidth = this.props.docView.props.PanelWidth(); - const previewWidth = NumCast(this.document.schema_previewWidth); - const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth; - this.document.schema_previewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0; - }; - - @undoBatch - @action - toggleTextwrap = async () => { - const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []); - if (textwrappedRows.length) { - this.document.textwrappedSchemaRows = new List<string>([]); - } else { - const docs = DocListCast(this.document[this.props.fieldKey]); - const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]); - this.document.textwrappedSchemaRows = new List<string>(allRows); - } - }; - - render() { - const previewWidth = NumCast(this.document.schema_previewWidth); - const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []).length > 0; - - return ( - <div className="collectionSchemaViewChrome-cont"> - <div className="collectionSchemaViewChrome-toggle"> - <div className="collectionSchemaViewChrome-label">Show Preview: </div> - <div className="collectionSchemaViewChrome-toggler" onClick={this.togglePreview}> - <div className={'collectionSchemaViewChrome-togglerButton' + (previewWidth !== 0 ? ' on' : ' off')}>{previewWidth !== 0 ? 'on' : 'off'}</div> - </div> - </div> - </div> - ); - } -} - -@observer -export class CollectionTreeViewChrome extends React.Component<CollectionViewMenuProps> { - get document() { - return this.props.docView.props.Document; - } - get sortAscending() { - return this.document[this.props.fieldKey + '-sortAscending']; - } - set sortAscending(value) { - this.document[this.props.fieldKey + '-sortAscending'] = value; - } - @computed private get ascending() { - return Cast(this.sortAscending, 'boolean', null); - } - - @action toggleSort = () => { - if (this.sortAscending) this.sortAscending = undefined; - else if (this.sortAscending === undefined) this.sortAscending = false; - else this.sortAscending = true; - }; - - render() { - return ( - <div className="collectionTreeViewChrome-cont"> - <button className="collectionTreeViewChrome-sort" onClick={this.toggleSort}> - <div className="collectionTreeViewChrome-sortLabel">Sort</div> - <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.ascending === undefined ? '90' : this.ascending ? '180' : '0'}deg)` }}> - <FontAwesomeIcon icon="caret-up" size="2x" color="white" /> - </div> - </button> - </div> - ); - } -} - -// Enter scroll speed for 3D Carousel -@observer -export class Collection3DCarouselViewChrome extends React.Component<CollectionViewMenuProps> { - get document() { - return this.props.docView.props.Document; - } - @computed get scrollSpeed() { - return this.document._autoScrollSpeed; - } - - @action - setValue = (value: string) => { - const numValue = Number(StrCast(value)); - if (numValue > 0) { - this.document._autoScrollSpeed = numValue; - return true; - } - return false; - }; - - render() { - return ( - <div className="collection3DCarouselViewChrome-cont"> - <div className="collection3DCarouselViewChrome-scrollSpeed-cont"> - {/* {FormattedTextBox.Focused ? <RichTextMenu /> : null} */} - <div className="collectionStackingViewChrome-scrollSpeed-label">AUTOSCROLL SPEED:</div> - <div className="collection3DCarouselViewChrome-scrollSpeed"> - <EditableView GetValue={() => StrCast(this.scrollSpeed)} oneLine SetValue={this.setValue} contents={this.scrollSpeed ? this.scrollSpeed : 1000} /> - </div> - </div> - </div> - ); - } -} - /** * Chrome for grid view. */ @@ -1361,13 +578,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() { @@ -1492,12 +714,6 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu <input className="collectionGridViewChrome-columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" /> <input className="collectionGridViewChrome-columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" /> </span> - {/* <span className="grid-control"> - <span className="grid-icon"> - <FontAwesomeIcon icon="text-height" size="1x" /> - </span> - <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} /> - </span> */} <span className="grid-control" style={{ width: this.resize ? '12%' : '20%' }}> <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.document.gridPreventCollision} /> <label className="flexLabel">{this.resize ? 'Coll' : 'Collisions'}</label> @@ -1526,6 +742,3 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu ); } } -ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) { - CollectionFreeFormViewChrome.gotoKeyFrame(doc, newFrame); -}); diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss index be1800d81..91a82d40f 100644 --- a/src/client/views/collections/CollectionNoteTakingView.scss +++ b/src/client/views/collections/CollectionNoteTakingView.scss @@ -1,4 +1,4 @@ -@import '../global/globalCssVariables'; +@import '../global/globalCssVariables.module.scss'; .collectionNoteTakingView-DocumentButtons { display: none; diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index ac916fef3..f9d258490 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -1,6 +1,5 @@ -import React = require('react'); -import { CursorProperty } from 'csstype'; -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import * as React from 'react'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Field, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; @@ -44,8 +43,13 @@ export class CollectionNoteTakingView extends CollectionSubView() { notetakingCategoryField = 'NotetakingCategory'; public DividerWidth = 16; @observable docsDraggedRowCol: number[] = []; - @observable _cursor: CursorProperty = 'grab'; @observable _scroll = 0; + + constructor(props: any) { + super(props); + makeObservable(this); + } + @computed get chromeHidden() { return BoolCast(this.layoutDoc.chromeHidden) || this.props.onBrowseClick?.() ? true : false; } @@ -221,7 +225,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { blockPointerEventsWhenDragging = () => (this.docsDraggedRowCol.length ? 'none' : undefined); // getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView) getDisplayDoc(doc: Doc, width: () => number) { - const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc; + const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.TemplateDataDocument; const height = () => this.getDocHeight(doc); let dref: Opt<DocumentView>; const noteTakingDocTransform = () => this.getDocTransform(doc, dref); @@ -229,8 +233,8 @@ export class CollectionNoteTakingView extends CollectionSubView() { <DocumentView ref={r => (dref = r || undefined)} Document={doc} + TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)} pointerEvents={this.blockPointerEventsWhenDragging} - DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)} renderDepth={this.props.renderDepth + 1} PanelWidth={width} PanelHeight={height} @@ -257,9 +261,8 @@ export class CollectionNoteTakingView extends CollectionSubView() { ScreenToLocalTransform={noteTakingDocTransform} focus={this.focusDocument} childFilters={this.childDocFilters} - hideDecorationTitle={this.props.childHideDecorationTitle?.()} - hideResizeHandles={this.props.childHideResizeHandles?.()} - hideTitle={this.props.childHideTitle?.()} + hideDecorationTitle={this.props.childHideDecorationTitle} + hideResizeHandles={this.props.childHideResizeHandles} childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} addDocument={this.props.addDocument} @@ -297,7 +300,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { 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.DataDoc; + 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) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0); @@ -336,7 +339,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { // onPointerMove is used to preview where a document will drop in a column once a drag is complete. @action onPointerMove = (force: boolean, ex: number, ey: number) => { - if (this.childDocList?.includes(DragManager.DocDragData?.draggedDocuments?.lastElement() as any) || force || this.isContentActive()) { + if (this.childDocList?.includes(DragManager.DocDragData?.draggedDocuments?.lastElement() as any) || force || SnappingManager.CanEmbed) { // get the current docs for the column based on the mouse's x coordinate const xCoord = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[0] - 2 * this.gridGap; const colDocs = this.getDocsFromXCoord(xCoord); @@ -407,11 +410,11 @@ export class CollectionNoteTakingView extends CollectionSubView() { @action onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { const docView = fieldProps.DocumentView?.(); - if (docView && (e.ctrlKey || docView.rootDoc._createDocOnCR) && ['Enter'].includes(e.key)) { + if (docView && (e.ctrlKey || docView.Document._createDocOnCR) && ['Enter'].includes(e.key)) { e.stopPropagation?.(); - const newDoc = Doc.MakeCopy(docView.rootDoc, true); + const newDoc = Doc.MakeCopy(docView.Document, true); Doc.GetProto(newDoc).text = undefined; - FormattedTextBox.SelectOnLoad = newDoc[Id]; + FormattedTextBox.SetSelectOnLoad(newDoc); return this.addDocument?.(newDoc); } }; @@ -419,7 +422,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)) { @@ -458,7 +460,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { @undoBatch internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) { const dropCreator = annoDragData.dropDocCreator; - annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => dropCreator(annotationOn) || this.rootDoc; + annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => dropCreator(annotationOn) || this.Document; return true; } @@ -509,7 +511,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { this.refList.push(ref); this.observer = new _global.ResizeObserver( action((entries: any) => { - if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { + if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) { 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); @@ -526,7 +528,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { chromeHidden={this.chromeHidden} colHeaderData={this.colHeaderData} Document={this.props.Document} - DataDoc={this.props.DataDoc} + TemplateDataDocument={this.props.TemplateDataDocument} resizeColumns={this.resizeColumns} renderChildren={this.children} numGroupColumns={this.numGroupColumns} @@ -635,7 +637,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { key="notes" style={{ overflowY: this.props.isContentActive() ? 'auto' : 'hidden', - background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor), + background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor), pointerEvents: this.backgroundEvents, }} onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))} diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 52cc21903..bb01a1782 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, trace } from 'mobx'; import { observer } from 'mobx-react'; @@ -25,7 +25,7 @@ import './CollectionNoteTakingView.scss'; interface CSVFieldColumnProps { Document: Doc; - DataDoc: Opt<Doc>; + TemplateDataDocument: Opt<Doc>; docList: Doc[]; heading: string; pivotField: string; @@ -121,7 +121,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu return false; }; - @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4'); + @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4'); @action pointerLeave = () => (this._background = 'inherit'); @undoBatch addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true); @@ -134,7 +134,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true }); const colValue = this.getValue(this.props.heading); newDoc[key] = colValue; - FormattedTextBox.SelectOnLoad = newDoc[Id]; + FormattedTextBox.SetSelectOnLoad(newDoc); FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' '; return this.props.addDocument?.(newDoc) || false; }; @@ -157,14 +157,14 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; const docItems: ContextMenuProps[] = []; - const dataDoc = this.props.DataDoc || this.props.Document; + const dataDoc = this.props.TemplateDataDocument || this.props.Document; const pivotValue = this.getValue(this.props.heading); DocUtils.addDocumentCreatorMenuItems( doc => { const key = this.props.pivotField; doc[key] = this.getValue(this.props.heading); - FormattedTextBox.SelectOnLoad = doc[Id]; + FormattedTextBox.SetSelectOnLoad(doc); return this.props.addDocument?.(doc); }, this.props.addDocument, diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index abb12a8ab..170b1f1ec 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast } from '../../../fields/Doc'; import { ScriptField } from '../../../fields/ScriptField'; @@ -12,13 +12,18 @@ import { computePassLayout, computeStarburstLayout } from './collectionFreeForm' import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import './CollectionPileView.scss'; import { CollectionSubView } from './CollectionSubView'; -import React = require('react'); +import * as React from 'react'; @observer export class CollectionPileView extends CollectionSubView() { _originalChrome: any = ''; _disposers: { [name: string]: IReactionDisposer } = {}; + constructor(props: any) { + super(props); + makeObservable(this); + } + componentDidMount() { if (this.layoutEngine() !== computePassLayout.name && this.layoutEngine() !== computeStarburstLayout.name) { this.Document._freeform_pileEngine = computePassLayout.name; @@ -43,7 +48,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.rootDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.rootDoc); + if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.()._props.removeDocument?.(this.Document); return ret; }; @@ -78,12 +83,12 @@ export class CollectionPileView extends CollectionSubView() { toggleStarburst = action(() => { this.layoutDoc._freeform_scale = undefined; if (this.layoutEngine() === computeStarburstLayout.name) { - if (NumCast(this.rootDoc._width) !== NumCast(this.rootDoc._starburstDiameter, 500)) { - this.rootDoc._starburstDiameter = NumCast(this.rootDoc._width); + if (NumCast(this.layoutDoc._width) !== NumCast(this.Document._starburstDiameter, 500)) { + this.Document._starburstDiameter = NumCast(this.layoutDoc._width); } const defaultSize = 110; - this.rootDoc.x = NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2; - this.rootDoc.y = NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2; + this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2; + this.Document.y = NumCast(this.Document.y) + NumCast(this.layoutDoc._height) / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2; this.layoutDoc._width = NumCast(this.layoutDoc._freeform_pileWidth, defaultSize); this.layoutDoc._height = NumCast(this.layoutDoc._freeform_pileHeight, defaultSize); DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false); @@ -91,9 +96,9 @@ export class CollectionPileView extends CollectionSubView() { this.layoutDoc._freeform_panY = -10; this.props.Document._freeform_pileEngine = computePassLayout.name; } else { - const defaultSize = NumCast(this.rootDoc._starburstDiameter, 400); - this.rootDoc.x = NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2; - this.rootDoc.y = NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) / 2 - defaultSize / 2; + const defaultSize = NumCast(this.Document._starburstDiameter, 400); + this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2; + this.Document.y = NumCast(this.Document.y) + NumCast(this.layoutDoc._height) / 2 - defaultSize / 2; this.layoutDoc._freeform_pileWidth = NumCast(this.layoutDoc._width); this.layoutDoc._freeform_pileHeight = NumCast(this.layoutDoc._height); this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0; diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index a19d8e696..0ced3f9e3 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -1,4 +1,4 @@ -@import '../global/globalCssVariables.scss'; +@import '../global/globalCssVariables.module.scss'; .collectionStackedTimeline-timelineContainer { height: 100%; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 3351ca48e..fb8bc4da2 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -1,7 +1,7 @@ -import React = require('react'); -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, 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'; @@ -17,16 +17,16 @@ import { DragManager } from '../../util/DragManager'; import { FollowLinkScript, IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; -import { SelectionManager } from '../../util/SelectionManager'; 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'; +import { ObservableReactComponent } from '../ObservableReactComponent'; import './CollectionStackedTimeline.scss'; export type CollectionStackedTimelineProps = { @@ -35,6 +35,7 @@ export type CollectionStackedTimelineProps = { playLink: (linkDoc: Doc, options: DocFocusOptions) => void; playFrom: (seekTimeInSeconds: number, endTime?: number) => void; playing: () => boolean; + thumbnails?: () => string[]; setTime: (time: number) => void; startTag: string; endTag: string; @@ -43,7 +44,6 @@ export type CollectionStackedTimelineProps = { rawDuration: number; dataFieldKey: string; fieldKey: string; - thumbnails?: () => string[]; }; // trimming state: shows full clip, current trim bounds, or not trimming @@ -57,6 +57,10 @@ export enum TrimScope { export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() { @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; @observable public static CurrentlyPlaying: DocumentView[]; + constructor(props: any) { + super(props); + makeObservable(this); + } static LabelScript: ScriptField; static LabelPlayScript: ScriptField; @@ -64,7 +68,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack 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 @@ -74,14 +78,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() { @@ -101,7 +105,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() { @@ -116,11 +120,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)); } } @@ -150,8 +153,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) => { @@ -178,13 +181,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) { @@ -192,7 +195,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack CollectionStackedTimeline.SelectingRegion = this; } else { this._markerEnd = this.currentTime; - CollectionStackedTimeline.createAnchor(this.rootDoc, 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; } @@ -205,11 +208,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; } @@ -219,7 +222,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; @@ -234,9 +237,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, @@ -258,7 +261,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._markerEnd = tmp; } if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) { - const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, 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); @@ -266,17 +269,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.rootDoc, 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)); } } ); @@ -291,7 +294,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; } @@ -306,7 +309,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; @@ -326,7 +329,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; @@ -349,8 +352,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; @@ -364,15 +367,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; @@ -387,22 +390,21 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // creates marker on timeline @undoBatch - @action - static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) { - if (anchorStartTime === undefined) return rootDoc; + 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'; const endTag = '_timecodeToHide'; const anchor = docAnchor ?? Docs.Create.LabelDocument({ - title: ComputedField.MakeFunction(`self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])`) as any, + title: ComputedField.MakeFunction(`this["${endTag}"] ? "#" + formatToTime(this["${startTag}"]) + "-" + formatToTime(this["${endTag}"]) : "#" + formatToTime(this["${startTag}"])`) as any, _label_minFontSize: 12, _label_maxFontSize: 24, _dragOnlyWithinContainer: true, backgroundColor: 'rgba(128, 128, 128, 0.5)', layout_hideLinkButton: true, onClick: FollowLinkScript(), - annotationOn: rootDoc, + annotationOn: doc, _isTimelineLabel: true, layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined, }); @@ -423,20 +425,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); } } @@ -451,17 +453,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 }; @@ -491,18 +493,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; @@ -521,7 +523,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: { @@ -538,8 +540,8 @@ 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' : '' }} - onWheel={e => e.stopPropagation()} + style={{ width: this._props.PanelWidth(), cursor: SnappingManager.IsDragging ? 'grab' : '' }} + onWheel={e => this.isContentActive() && e.stopPropagation()} onScroll={this.setScroll} onMouseMove={e => this.isContentActive() && this.onHover(e)} ref={wrapper => (this._timelineWrapper = wrapper)}> @@ -554,11 +556,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]} @@ -570,7 +572,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack pointerEvents: 'none', }}> <StackedTimelineAnchor - {...this.props} + {...this._props} mark={d.anchor} rangeClickScript={this.rangeClickScript} rangePlayScript={this.rangePlayScript} @@ -591,18 +593,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} /> )} @@ -688,41 +691,42 @@ interface StackedTimelineAnchorProps { } @observer -class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> { +class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnchorProps> { _lastTimecode: number; _disposer: IReactionDisposer | undefined; constructor(props: any) { super(props); - this._lastTimecode = this.props.currentTimecode(); + makeObservable(this); + this._lastTimecode = this._props.currentTimecode(); } // 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; } @@ -737,16 +741,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; @@ -760,11 +764,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; }), @@ -775,7 +779,9 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> // context menu contextMenuItems = () => { const resetTitle = { - script: ScriptField.MakeFunction(`self.title = self["${this.props.endTag}"] ? "#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"]) : "#" + formatToTime(self["${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', }; @@ -786,7 +792,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 { @@ -794,19 +800,19 @@ 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))} Document={mark} - DataDoc={undefined} + 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,9 +823,8 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> searchFilterDocs={returnEmptyDoclist} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} - rootSelected={returnFalse} onClick={script} - onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript} + onDoubleClick={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript} ignoreAutoHeight={false} hideResizeHandles={true} bringToFront={emptyFunction} @@ -829,19 +834,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.scss b/src/client/views/collections/CollectionStackingView.scss index dddb3ec71..6225cc52a 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -1,4 +1,4 @@ -@import '../global/globalCssVariables'; +@import '../global/globalCssVariables.module.scss'; .collectionMasonryView { display: inline; @@ -142,7 +142,7 @@ transform-origin: top left; grid-column-end: span 1; height: 100%; - margin: auto; + //margin: auto; display: inline-grid; } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index da00093dd..16adf4b96 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,15 +1,15 @@ -import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { CursorProperty } from 'csstype'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import * as CSS from 'csstype'; +import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +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 { Docs, DocUtils } from '../../documents/Documents'; @@ -59,12 +59,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // map of node headers to their heights. Used in Masonry @observable _heightMap = new Map<string, number>(); // Assuming that this is the current css cursor style - @observable _cursor: CursorProperty = 'ew-resize'; + @observable _cursor: CSS.Property.Cursor = 'ew-resize'; // gets reset whenever we scroll. Not sure what it is @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,16 +108,16 @@ 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; } constructor(props: any) { super(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? // here we're making an empty list of column headers (again, what Mehek showed us) @@ -138,7 +138,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); // just getting the style - const style = this.isStackingView ? { margin: this.rootDoc._stacking_alignCenter ? 'auto' : undefined, width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` }; + const style = this.isStackingView ? { margin: this.Document._stacking_alignCenter ? 'auto' : undefined, width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` }; // So we're choosing whether we're going to render a column or a masonry doc return ( <div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}> @@ -203,7 +203,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 +214,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 +229,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 +255,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,100 +267,100 @@ 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) { e.stopPropagation?.(); const below = !e.altKey && e.key !== 'Tab'; const layout_fieldKey = StrCast(docView.LayoutFieldKey); - const newDoc = Doc.MakeCopy(docView.rootDoc, true); - const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; + const newDoc = Doc.MakeCopy(docView.Document, true); + const dataField = docView.Document[Doc.LayoutFieldKey(newDoc)]; newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined; - if (layout_fieldKey !== 'layout' && docView.rootDoc[layout_fieldKey] instanceof Doc) { - newDoc[layout_fieldKey] = docView.rootDoc[layout_fieldKey]; + if (layout_fieldKey !== 'layout' && docView.Document[layout_fieldKey] instanceof Doc) { + newDoc[layout_fieldKey] = docView.Document[layout_fieldKey]; } Doc.GetProto(newDoc).text = undefined; - FormattedTextBox.SelectOnLoad = newDoc[Id]; + FormattedTextBox.SetSelectOnLoad(newDoc); 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 - ? false - : this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) - ? true - : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false + this._props.isContentActive?.() === false ? false - : undefined; - isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.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)); // 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 ? undefined : this.props.DataDoc; + 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 panelWidth = () => (this.isStackingView ? width() : this.columnWidth); const stackedDocTransform = () => this.getDocTransform(doc); this._docXfs.push({ stackedDocTransform, width, height }); return count > this._renderCount ? null : ( <DocumentView ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))} Document={doc} - DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)} - renderDepth={this.props.renderDepth + 1} - PanelWidth={width} - PanelHeight={height} - 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) + TemplateDataDocument={dataDoc ?? (Doc.AreProtosEqual(doc[DocData], doc) ? undefined : doc[DocData])} + 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) styleProvider={this.styleProvider} - docViewPath={this.props.docViewPath} - layout_fitWidth={this.props.childLayoutFitWidth} + 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) || (doc._layout_fitWidth && !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) || (doc._layout_fitWidth && !Doc.NativeHeight(doc)) ? height : undefined} - dontCenter={this.props.childIgnoreNativeSize ? 'xy' : undefined} - 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?.()} - hideTitle={this.props.childHideTitle?.()} + 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} /> ); } @@ -371,31 +370,31 @@ 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 && !(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d))) { + if (!this.layoutDoc._columnsFill && !this.childFitWidth(childLayoutDoc)) { return Math.min(NumCast(d._width), maxWidth); } return maxWidth; } 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.DataDoc; - const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); - const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0); - const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 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 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) { const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid); return Math.min(maxHeight, (docWid * nh) / nw); } const childHeight = NumCast(childLayoutDoc._height); - const panelHeight = childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d) ? 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); } @@ -433,7 +432,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]; @@ -471,9 +469,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; @@ -489,14 +487,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const dropCreator = annoDragData.dropDocCreator; annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => { const dropDoc = dropCreator(annotationOn); - return dropDoc || this.rootDoc; + return dropDoc || this.Document; }; return true; } /// 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; @@ -541,10 +538,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection this.refList.push(ref); this.observer = new _global.ResizeObserver( action((entries: any) => { - if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { + if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) { 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); } } }) @@ -555,8 +552,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection addDocument={this.addDocument} chromeHidden={this.chromeHidden} colHeaderData={this.colHeaderData} - Document={this.props.Document} - DataDoc={this.props.DataDoc} + Document={this._props.Document} + TemplateDataDocument={this._props.TemplateDataDocument} renderChildren={this.children} columnWidth={this.columnWidth} numGroupColumns={this.numGroupColumns} @@ -570,7 +567,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection yMargin={this.yMargin} type={type} createDropTarget={this.createDashEventsTarget} - screenToLocalTransform={this.props.ScreenToLocalTransform} + screenToLocalTransform={this._props.ScreenToLocalTransform} /> ); }; @@ -583,11 +580,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)} @@ -596,9 +593,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection this.refList.push(ref); this.observer = new _global.ResizeObserver( action((entries: any) => { - if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { + if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) { 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 } }) ); @@ -614,7 +611,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} /> ); @@ -664,56 +661,50 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return35 = () => 35; @computed get buttonMenu() { - const menuDoc: Doc = Cast(this.rootDoc.layout_headerButton, Doc, null); - // TODO:glr Allow support for multiple buttons - if (menuDoc) { - const width: number = NumCast(menuDoc._width, 30); - const height: number = NumCast(menuDoc._height, 30); - return ( - <div className="buttonMenu-docBtn" style={{ width: width, height: height }}> - <DocumentView - Document={menuDoc} - DataDoc={menuDoc} - isContentActive={this.isContentActive} - isDocumentActive={this.isContentActive} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - addDocTab={this.props.addDocTab} - onBrowseClick={this.props.onBrowseClick} - pinToPres={emptyFunction} - rootSelected={this.props.isSelected} - removeDocument={this.props.removeDocument} - ScreenToLocalTransform={Transform.Identity} - PanelWidth={this.return35} - PanelHeight={this.return35} - renderDepth={this.props.renderDepth} - focus={emptyFunction} - styleProvider={this.props.styleProvider} - docViewPath={returnEmptyDoclist} - whenChildContentsActiveChanged={emptyFunction} - bringToFront={emptyFunction} - childFilters={this.props.childFilters} - childFiltersByRanges={this.props.childFiltersByRanges} - searchFilterDocs={this.props.searchFilterDocs} - /> - </div> - ); - } + const menuDoc = DocCast(this.layoutDoc.layout_headerButton); + return !menuDoc ? null : ( + <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}> + <DocumentView + Document={menuDoc} + isContentActive={this.isContentActive} + isDocumentActive={this.isContentActive} + 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} + ScreenToLocalTransform={Transform.Identity} + PanelWidth={this.return35} + PanelHeight={this.return35} + renderDepth={this._props.renderDepth} + focus={emptyFunction} + styleProvider={this._props.styleProvider} + docViewPath={returnEmptyDoclist} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + 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() { @@ -723,8 +714,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection SetValue: this.addGroup, contents: '+ ADD A GROUP', }; - const buttonMenu = this.rootDoc.layout_headerButton; - const noviceExplainer = this.rootDoc.layout_explainer; + const buttonMenu = this.layoutDoc.layout_headerButton; + const noviceExplainer = this.layoutDoc.layout_explainer; return ( <> {buttonMenu || noviceExplainer ? ( @@ -739,8 +730,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection ref={this.createRef} style={{ overflowY: this.isContentActive() ? 'auto' : 'hidden', - background: this.props.styleProvider?.(this.rootDoc, 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)} @@ -748,7 +739,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 3598d548a..f9d575da2 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -1,15 +1,15 @@ -import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { RichTextField } from '../../../fields/RichTextField'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { BoolCast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, setupMoveUpEvents, returnFalse, returnEmptyString } from '../../../Utils'; +import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; @@ -19,14 +19,14 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; -import './CollectionStackingView.scss'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; -import { Id } from '../../../fields/FieldSymbols'; +import './CollectionStackingView.scss'; +import { ObservableReactComponent } from '../ObservableReactComponent'; // So this is how we are storing a column interface CSVFieldColumnProps { Document: Doc; - DataDoc: Opt<Doc>; + TemplateDataDocument: Opt<Doc>; docList: Doc[]; heading: string; pivotField: string; @@ -49,16 +49,21 @@ interface CSVFieldColumnProps { } @observer -export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldColumnProps> { - @observable private _background = 'inherit'; - +export class CollectionStackingViewFieldColumn extends ObservableReactComponent<CSVFieldColumnProps> { 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 = ''; + + constructor(props: any) { + super(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,7 +73,7 @@ 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)); } }; @@ -76,21 +81,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @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 +110,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,41 +125,41 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action changeColumnColor = (color: string) => { - this.props.headingObject?.setColor(color); + this._props.headingObject?.setColor(color); this._color = color; }; - @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4'); + @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4'); @action pointerLeave = () => (this._background = 'inherit'); @undoBatch typedNote = (char: string) => this.addNewTextDoc('-typed text-', false, true); @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.SelectOnLoad = newDoc[Id]; + 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 +167,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 +182,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 +216,15 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; const docItems: ContextMenuProps[] = []; - const dataDoc = this.props.DataDoc || 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.SelectOnLoad = doc[Id]; - return this.props.addDocument?.(doc); + FormattedTextBox.SetSelectOnLoad(doc); + return this._props.addDocument?.(doc); }, - this.props.addDocument, + this._props.addDocument, 0, 0, true @@ -231,12 +236,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 +255,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 +269,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 +287,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 +331,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 +370,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,15 +389,15 @@ 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 ( <div - className={'collectionStackingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')} + className={'collectionStackingViewFieldColumn' + (SnappingManager.IsDragging ? '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 328b060c4..b56973dc6 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,41 +1,46 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; +import * as React from 'react'; import * as rp from 'request-promise'; +import { Utils, returnFalse } from '../../../Utils'; import CursorField from '../../../fields/CursorField'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; import { AclPrivate } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; -import { Cast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { returnFalse, Utils } from '../../../Utils'; import { DocServer } from '../../DocServer'; -import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents'; -import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; +import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents'; import { DragManager, dropActionType } from '../../util/DragManager'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; -import { InteractionUtils } from '../../util/InteractionUtils'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; -import { undoBatch, UndoManager } from '../../util/UndoManager'; -import { DocComponent } from '../DocComponent'; +import { UndoManager, undoBatch } from '../../util/UndoManager'; +import { ViewBoxBaseComponent } from '../DocComponent'; +import { LoadingBox } from '../nodes/LoadingBox'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { CollectionView, CollectionViewProps } from './CollectionView'; -import React = require('react'); -import { LoadingBox } from '../nodes/LoadingBox'; export interface SubCollectionViewProps extends CollectionViewProps { isAnyChildContentActive: () => boolean; } export function CollectionSubView<X>(moreProps?: X) { - class CollectionSubView extends DocComponent<X & SubCollectionViewProps>() { + class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() { private dropDisposer?: DragManager.DragDropDisposer; private gestureDisposer?: GestureUtils.GestureEventDisposer; protected _mainCont?: HTMLDivElement; + + constructor(props: any) { + super(props); + makeObservable(this); + } + @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) => { @@ -56,24 +61,29 @@ export function CollectionSubView<X>(moreProps?: X) { this.gestureDisposer?.(); } - @computed get dataDoc() { - return this.props.DataDoc instanceof Doc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : 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 } - rootSelected = () => this.props.isSelected() || (this.rootDoc && this.props.rootSelected()); + // 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?.()); - // 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.DataDoc. - // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through + // 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.layoutDoc[this.props.fieldKey]; + 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, DataDoc } = this.props; + const { Document, TemplateDataDocument } = this._props; const validPairs = this.childDocs - .map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : 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)); @@ -83,14 +93,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>)[] = []; @@ -100,31 +110,31 @@ export function CollectionSubView<X>(moreProps?: X) { } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it. rawdocs = Cast(this.dataField, listSpec(Doc), null); - } else { + } else if (this.dataField) { // 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 rootDoc = Cast(this.props.Document.rootDocument, Doc, null); - rawdocs = rootDoc && !this.props.isAnnotationOverlay ? [Doc.GetProto(rootDoc)] : []; + 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)); - 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; + const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter)); + if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return false; + 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]; @@ -157,7 +167,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] }; @@ -193,23 +203,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) || false; + 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) { @@ -217,28 +227,28 @@ export function CollectionSubView<X>(moreProps?: X) { const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d); if (movedDocs.length) { const canAdd = - (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.rootDoc)); - const moved = docDragData.moveDocument(movedDocs, this.rootDoc, canAdd ? this.addDocument : returnFalse); + (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.Document)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.Document)); + const moved = docDragData.moveDocument(movedDocs, this.Document, canAdd ? this.addDocument : returnFalse); added = canAdd || moved ? moved : undefined; } else if (addedDocs.length) { added = this.addDocument(addedDocs); } - if (!added && ScriptCast(this.rootDoc.dropConverter)) { - ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData }); + if (!added && ScriptCast(this.Document.dropConverter)) { + ScriptCast(this.Document.dropConverter)?.script.run({ dragData: docDragData }); added = addedDocs.length ? this.addDocument(addedDocs) : true; } } else { - ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData }); + ScriptCast(this.Document.dropConverter)?.script.run({ dragData: docDragData }); 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.rootDoc : undefined); + const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined); this.addDocument(dropped); return dropped; }; @@ -248,7 +258,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 @@ -330,7 +339,7 @@ export function CollectionSubView<X>(moreProps?: X) { } }); } else { - const srcWeb = SelectionManager.Views().lastElement(); + const srcWeb = SelectionManager.Views.lastElement(); const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0]; const reg = new RegExp(Utils.prepend(''), 'g'); const modHtml = srcUrl ? html.replace(reg, srcUrl) : html; @@ -339,7 +348,7 @@ export function CollectionSubView<X>(moreProps?: X) { Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text; addDocument(htmlDoc); if (srcWeb) { - const iframe = SelectionManager.Views()[0].ContentDiv?.getElementsByTagName('iframe')?.[0]; + const iframe = SelectionManager.Views[0].ContentDiv?.getElementsByTagName('iframe')?.[0]; const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any; if (focusNode) { const anchor = srcWeb?.ComponentView?.getAnchor?.(true); @@ -459,15 +468,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/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index a8f5345b7..6033b897d 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,5 +1,5 @@ import { toUpper } from 'lodash'; -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt, StrListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; @@ -21,7 +21,7 @@ import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './coll import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTimeView.scss'; -import React = require('react'); +import * as React from 'react'; @observer export class CollectionTimeView extends CollectionSubView() { @@ -32,10 +32,15 @@ export class CollectionTimeView extends CollectionSubView() { @observable _viewDefDivClick: Opt<ScriptField>; @observable _focusPivotField: Opt<string>; - async componentDidMount() { - this.props.setContentView?.(this); + constructor(props: any) { + super(props); + makeObservable(this); + } + + componentDidMount() { + this._props.setContentView?.(this); runInAction(() => { - this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name }); + this._childClickedScript = ScriptField.MakeScript('openInLightbox(this)', { this: Doc.name }); this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' }); }); } @@ -47,16 +52,16 @@ export class CollectionTimeView extends CollectionSubView() { getAnchor = (addAsAnnotation: boolean) => { const anchor = Docs.Create.HTMLMarkerDocument([], { title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, - annotationOn: this.rootDoc, + annotationOn: this.Document, }); - PresBox.pinDocView(anchor, { pinData: { type_collection: true, pivot: true, filters: true } }, this.rootDoc); + PresBox.pinDocView(anchor, { pinData: { type_collection: true, pivot: true, filters: true } }, this.Document); if (addAsAnnotation) { // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered - 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; @@ -79,10 +84,10 @@ export class CollectionTimeView extends CollectionSubView() { this, e, action((e: PointerEvent, down: number[], delta: number[]) => { - const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0)); - const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10)); - this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); - this.props.Document[this.props.fieldKey + '-timelineSpan'] = undefined; + const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0)); + const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10)); + this.Document[this._props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this._props.PanelWidth(); + this.Document[this._props.fieldKey + '-timelineSpan'] = undefined; return false; }), returnFalse, @@ -95,9 +100,9 @@ export class CollectionTimeView extends CollectionSubView() { this, e, action((e: PointerEvent, down: number[], delta: number[]) => { - const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0)); - const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10)); - this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); + const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0)); + const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10)); + this.Document[this._props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this._props.PanelWidth(); return false; }), returnFalse, @@ -110,10 +115,10 @@ export class CollectionTimeView extends CollectionSubView() { this, e, action((e: PointerEvent, down: number[], delta: number[]) => { - const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0)); - const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10)); - this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); - this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth(); + const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0)); + const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10)); + this.Document[this._props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this._props.PanelWidth(); + this.Document[this._props.fieldKey + '-timelineMaxReq'] = maxReq - ((maxReq - minReq) * delta[0]) / this._props.PanelWidth(); return false; }), returnFalse, @@ -140,9 +145,9 @@ export class CollectionTimeView extends CollectionSubView() { @computed get contents() { return ( - <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}> + <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}> <CollectionFreeFormView - {...this.props} + {...this._props} engineProps={{ pivotField: this.pivotField, childFilters: this.childDocFilters, childFiltersByRanges: this.childDocRangeFilters }} fitContentsToBox={returnTrue} childClickScript={this._childClickedScript} @@ -189,7 +194,7 @@ export class CollectionTimeView extends CollectionSubView() { @computed get _allFacets() { const facets = new Set<string>(); this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); - Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key))); + Doc.AreProtosEqual(this.dataDoc, this.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key))); return Array.from(facets); } menuCallback = (x: number, y: number) => { @@ -206,7 +211,7 @@ export class CollectionTimeView extends CollectionSubView() { Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' }); ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' }); - const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y); + const pt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y); ContextMenu.Instance.displayMenu(x, y, ':'); }; @@ -222,7 +227,7 @@ export class CollectionTimeView extends CollectionSubView() { } return false; }} - background={'#f1efeb'} // this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + background={'#f1efeb'} // this._props.headingObject ? this._props.headingObject.color : "#f1efeb"; contents={':' + StrCast(this.layoutDoc._pivotField)} showMenuOnLoad={true} display={'inline'} @@ -241,7 +246,7 @@ export class CollectionTimeView extends CollectionSubView() { } }); const forceLayout = StrCast(this.layoutDoc._forceRenderEngine); - const doTimeline = forceLayout ? forceLayout === computeTimelineLayout.name : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6; + const doTimeline = forceLayout ? forceLayout === computeTimelineLayout.name : nonNumbers / this.childDocs.length < 0.1 && this._props.PanelWidth() / this._props.PanelHeight() > 6; if (doTimeline !== (this._layoutEngine === computeTimelineLayout.name)) { if (!this._changing) { this._changing = true; @@ -256,10 +261,10 @@ export class CollectionTimeView extends CollectionSubView() { } return ( - <div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this.props.PanelWidth(), height: '100%' }}> + <div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this._props.PanelWidth(), height: '100%' }}> {this.pivotKeyUI} {this.contents} - {!this.props.isSelected() || !doTimeline ? null : ( + {!this._props.isSelected() || !doTimeline ? null : ( <> <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} /> <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} /> diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 21efeba44..bbbef78b4 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,4 +1,4 @@ -@import '../global/globalCssVariables'; +@import '../global/globalCssVariables.module.scss'; .collectionTreeView-container { transform-origin: top left; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 761192a22..7f8d42088 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,5 +1,6 @@ -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -26,7 +27,6 @@ import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTreeView.scss'; import { TreeView } from './TreeView'; -import React = require('react'); const _global = (window /* browser */ || global) /* node */ as any; export type collectionTreeViewProps = { @@ -59,27 +59,29 @@ 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; + constructor(props: any) { + super(props); + makeObservable(this); } - @computed get dataDoc() { - return this.props.DataDoc || this.doc; + + 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 +90,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,9 +101,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } componentDidMount() { - //this.props.setContentView?.(this); + //this._props.setContentView?.(this); this._disposers.autoheight = reaction( - () => this.rootDoc.layout_autoHeight, + () => this.layoutDoc.layout_autoHeight, auto => auto && this.computeHeight(), { fireImmediately: true } ); @@ -112,38 +114,38 @@ 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) => { this.refList.delete(ref); - this.rootDoc.layout_autoHeight && this.computeHeight(); + this.layoutDoc.layout_autoHeight && this.computeHeight(); }; observeHeight = (ref: any) => { if (ref) { this.refList.add(ref); this.observer = new _global.ResizeObserver( action((entries: any) => { - if (this.rootDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) { + if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) { this.computeHeight(); } }) ); - this.rootDoc.layout_autoHeight && this.computeHeight(); + this.layoutDoc.layout_autoHeight && this.computeHeight(); this.observer.observe(ref); } }; 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) => { const dropAction = this.layoutDoc.dropAction as dropActionType; const dragData = de.complete.docDragData; if (dragData) { - const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.rootDoc) ? true : false; - const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d)); + const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.Document) ? true : false; + const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d)); if (isAlreadyInTree() !== sameTree) { console.log('WHAAAT'); } @@ -152,22 +154,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.rootDoc, doc)))) SelectionManager.DeselectAll(); + 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.SelectOnLoad = prev[Id]; - DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false); + FormattedTextBox.SetSelectOnLoad(prev); + DocumentManager.Instance.getDocumentView(prev, this._props.DocumentView?.())?.select(false); } return true; } @@ -178,24 +180,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 +219,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,12 +237,11 @@ 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} - rootSelected={returnTrue} forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not layout_autoHeight PanelWidth={this.documentTitleWidth} PanelHeight={this.documentTitleHeight} @@ -254,52 +259,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.DataDoc, + 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 ); } @@ -307,8 +312,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> @@ -316,38 +321,38 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } @computed get noviceExplainer() { - return !Doc.noviceMode || !this.rootDoc.layout_explainer ? null : <div className="documentExplanation"> {StrCast(this.rootDoc.layout_explainer)} </div>; + return !Doc.noviceMode || !this.layoutDoc.layout_explainer ? null : <div className="documentExplanation"> {StrCast(this.layoutDoc.layout_explainer)} </div>; } return35 = () => 35; @computed get buttonMenu() { - const menuDoc = Cast(this.rootDoc.layout_headerButton, Doc, null); + const menuDoc = Cast(this.layoutDoc.layout_headerButton, Doc, null); // To create a multibutton menu add a CollectionLinearView return !menuDoc ? null : ( <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}> <DocumentView Document={menuDoc} - DataDoc={menuDoc} - isContentActive={this.props.isContentActive} + TemplateDataDocument={menuDoc} + isContentActive={this._props.isContentActive} isDocumentActive={returnTrue} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - removeDocument={this.props.removeDocument} - addDocTab={this.props.addDocTab} - pinToPres={emptyFunction} - rootSelected={this.props.isSelected} + 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> ); @@ -364,30 +369,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 : ( @@ -399,7 +404,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(), @@ -424,7 +429,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> @@ -436,27 +441,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.IsDragging ? 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.scss b/src/client/views/collections/CollectionView.scss index 5db489c0a..de53a2c62 100644 --- a/src/client/views/collections/CollectionView.scss +++ b/src/client/views/collections/CollectionView.scss @@ -1,4 +1,4 @@ -@import "../global/globalCssVariables"; +@import '../global/globalCssVariables.module.scss'; .collectionView { border-width: 0; @@ -9,7 +9,7 @@ border-radius: inherit; width: 100%; height: 100%; - overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying... + //overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying... .collectionView-filterDragger { background-color: rgb(140, 139, 139); @@ -54,8 +54,8 @@ } } - >div, - >div>div { + > div, + > div > div { width: 100%; height: 100%; } @@ -80,4 +80,4 @@ border-radius: 50%; padding: 3px; background: white; -}
\ No newline at end of file +} diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 389a9a534..0237ec95e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,17 +1,16 @@ -import { IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { returnEmptyString } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; 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 { DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; +import { DocUtils } from '../../documents/Documents'; import { dropActionType } from '../../util/DragManager'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; -import { InteractionUtils } from '../../util/InteractionUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; @@ -20,19 +19,20 @@ import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; import { CollectionDockingView } from './CollectionDockingView'; -import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; -import { CollectionGridView } from './collectionGrid/CollectionGridView'; -import { CollectionLinearView } from './collectionLinear'; -import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; -import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionNoteTakingView } from './CollectionNoteTakingView'; import { CollectionPileView } from './CollectionPileView'; -import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; import { CollectionStackingView } from './CollectionStackingView'; import { SubCollectionViewProps } from './CollectionSubView'; import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from './CollectionTreeView'; import './CollectionView.scss'; +import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; +import { CollectionGridView } from './collectionGrid/CollectionGridView'; +import { CollectionCalendarView} from './CollectionCalendarView'; +import { CollectionLinearView } from './collectionLinear'; +import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; +import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; +import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; const path = require('path'); interface CollectionViewProps_ extends FieldViewProps { @@ -50,10 +50,9 @@ interface CollectionViewProps_ extends FieldViewProps { childlayout_showTitle?: () => string; childOpacity?: () => number; childContextMenuItems?: () => { script: ScriptField; label: string }[]; - childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children - childHideDecorationTitle?: () => boolean; - childHideResizeHandles?: () => boolean; childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection + childHideDecorationTitle?: boolean; + childHideResizeHandles?: boolean; childDragAction?: dropActionType; childXPadding?: number; childYPadding?: number; @@ -78,11 +77,12 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab this._safeMode = safeMode; } private reactionDisposer: IReactionDisposer | undefined; - @observable _isContentActive: boolean | undefined; + @observable _isContentActive: boolean | undefined = undefined; constructor(props: any) { super(props); - runInAction(() => (this._annotationKeySuffix = returnEmptyString)); + makeObservable(this); + this._annotationKeySuffix = returnEmptyString; } componentDidMount() { @@ -91,7 +91,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 } ); @@ -114,7 +114,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(); @@ -122,8 +122,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab switch (type) { default: case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />; - case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />; case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />; + case CollectionViewType.Calendar: return <CollectionCalendarView key="collview" {...props} />; case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />; case CollectionViewType.Tree: return <CollectionTreeView key="collview" {...props} />; case CollectionViewType.Multicolumn: return <CollectionMulticolumnView key="collview" {...props} />; @@ -141,13 +141,14 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab }; setupViewTypes(category: string, func: (type_collection: CollectionViewType) => Doc) { - if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._type_collection !== CollectionViewType.Docking && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) { + if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking && !this.dataDoc.isGroup && !this.Document.annotationOn) { // prettier-ignore const subItems: ContextMenuProps[] = [ { description: 'Freeform', event: () => func(CollectionViewType.Freeform), icon: 'signature' }, { description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' }, { description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' }, { description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._layout_autoHeight = true), icon: 'ellipsis-v' }, + { description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns'}, { description: 'Notetaking', event: () => (func(CollectionViewType.NoteTaking)._layout_autoHeight = true), icon: 'ellipsis-v' }, { description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' }, { description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' }, @@ -172,26 +173,26 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 !Doc.noviceMode && this.setupViewTypes('Appearance...', vtype => { - const newRendition = Doc.MakeEmbedding(this.rootDoc); + const newRendition = Doc.MakeEmbedding(this.Document); newRendition._type_collection = vtype; - this.props.addDocTab(newRendition, OpenWhere.addRight); + this._props.addDocTab(newRendition, OpenWhere.addRight); return newRendition; }); const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; - !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.rootDoc.forceActive = !this.rootDoc.forceActive), icon: 'project-diagram' }) : null; - if (this.rootDoc.childLayout instanceof Doc) { - optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' }); + !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' }); } - if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) { - optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView 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' }); } - !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.rootDoc._isLightbox = !this.rootDoc._isLightbox), 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' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' }); - if (!Doc.noviceMode && !this.rootDoc.annotationOn) { + if (!Doc.noviceMode && !this.Document.annotationOn) { const existingOnClick = cm.findByDescription('OnClick...'); const onClicks = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; const funcs = [ @@ -203,9 +204,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab description: `Edit ${func.name} script`, icon: 'edit', event: (obj: any) => { - const embedding = Doc.MakeEmbedding(this.rootDoc); + const embedding = Doc.MakeEmbedding(this.Document); DocUtils.makeCustomViewClicked(embedding, undefined, func.key); - this.props.addDocTab(embedding, OpenWhere.addRight); + this._props.addDocTab(embedding, OpenWhere.addRight); }, }) ); @@ -213,51 +214,55 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab onClicks.push({ description: `Set child ${childClick.title}`, icon: 'edit', - event: () => (Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))), + event: () => (this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))), }) ); - !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); + !Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } if (!Doc.noviceMode) { const more = cm.findByDescription('More...'); const moreItems = more && 'subitems' in more ? more.subitems : []; - moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) }); + moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.Document) }); !more && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'hand-point-right' }); } } }; - bodyPanelWidth = () => this.props.PanelWidth(); + bodyPanelWidth = () => this._props.PanelWidth(); - childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles); - childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle); - childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.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, - whenChildContentsActiveChanged: this.whenChildContentsActiveChanged, PanelWidth: this.bodyPanelWidth, - PanelHeight: this.props.PanelHeight, + PanelHeight: this._props.PanelHeight, ScreenToLocalTransform: this.screenToLocalTransform, childLayoutTemplate: this.childLayoutTemplate, - childLayoutString: StrCast(this.rootDoc.childLayoutString, this.props.childLayoutString), - childHideResizeHandles: this.childHideResizeHandles, - childHideDecorationTitle: this.childHideDecorationTitle, + 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), }; return ( - <div - className="collectionView" - onContextMenu={this.onContextMenu} - style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.rootDoc?._type_collection === CollectionViewType.Freeform && this.rootDoc._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.scss b/src/client/views/collections/TabDocView.scss index d447991a1..dd4c0b881 100644 --- a/src/client/views/collections/TabDocView.scss +++ b/src/client/views/collections/TabDocView.scss @@ -1,4 +1,4 @@ -@import '../global/globalCssVariables.scss'; +@import '../global/globalCssVariables.module.scss'; .tabDocView-content { height: 100%; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 6e1e6cf8d..783817b06 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -2,38 +2,41 @@ 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 { IReactionDisposer, ObservableSet, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; +import { DashColor, Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { FieldId } from '../../../fields/RefField'; +import { ComputedField } from '../../../fields/ScriptField'; import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { SettingsManager } from '../../util/SettingsManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoable, UndoManager } from '../../util/UndoManager'; +import { UndoManager, undoable } from '../../util/UndoManager'; import { DashboardView } from '../DashboardView'; -import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; +import { ObservableReactComponent } from '../ObservableReactComponent'; +import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; +import { Colors } from '../global/globalEnums'; import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; -import { DashFieldView } from '../nodes/formattedText/DashFieldView'; import { KeyValueBox } from '../nodes/KeyValueBox'; +import { DashFieldView } from '../nodes/formattedText/DashFieldView'; import { PinProps, PresBox, PresMovement } from '../nodes/trails'; -import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; import { CollectionDockingView } from './CollectionDockingView'; -import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionView } from './CollectionView'; import './TabDocView.scss'; -import React = require('react'); +import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; const _global = (window /* browser */ || global) /* node */ as any; interface TabDocViewProps { @@ -42,10 +45,16 @@ interface TabDocViewProps { glContainer: any; } @observer -export class TabDocView extends React.Component<TabDocViewProps> { +export class TabDocView extends ObservableReactComponent<TabDocViewProps> { static _allTabs = new ObservableSet<TabDocView>(); _mainCont: HTMLDivElement | null = null; _tabReaction: IReactionDisposer | undefined; + + constructor(props: any) { + super(props); + makeObservable(this); + } + @observable _activated: boolean = false; @observable _panelWidth = 0; @observable _panelHeight = 0; @@ -55,21 +64,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; @@ -160,12 +169,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() @@ -176,7 +185,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { } // shifts the focus to this tab when another tab is dragged over it tab.element[0].onmouseenter = (e: MouseEvent) => { - if (SnappingManager.GetIsDragging() && tab.contentItem !== tab.header.parent.getActiveContentItem()) { + if (SnappingManager.IsDragging && tab.contentItem !== tab.header.parent.getActiveContentItem()) { tab.header.parent.setActiveContentItem(tab.contentItem); tab.setActive(true); } @@ -259,10 +268,10 @@ export class TabDocView extends React.Component<TabDocViewProps> { return; } const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps); - const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Doc.MakeDelegate(anchorDoc && anchorDoc !== doc ? anchorDoc : doc); - pinDoc.presentation_targetDoc = anchorDoc ?? doc; + const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({}); + const targDoc = (pinDoc.presentation_targetDoc = anchorDoc ?? doc); pinDoc.title = doc.title + ' - Slide'; - pinDoc.data = new List<Doc>(); // the children of the embedding's layout are the presentation slide children. the embedding's data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data + pinDoc.data = targDoc.type === DocumentType.PRES ? ComputedField.MakeFunction('copyField(this.presentation_targetDoc.data') : new List<Doc>(); // the children of the embedding's layout are the presentation slide children. the embedding's data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data pinDoc.presentation_movement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; pinDoc.presentation_duration = pinDoc.presentation_duration ?? 1000; pinDoc.presentation_groupWithUp = false; @@ -271,7 +280,6 @@ export class TabDocView extends React.Component<TabDocViewProps> { pinDoc.treeView = ''; // not really needed, but makes key value pane look better pinDoc.treeView_RenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area pinDoc.treeView_HeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling. - pinDoc.treeView_ChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc. pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header @@ -315,7 +323,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) => { @@ -324,25 +331,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() { + componentDidUpdate(prevProps: Readonly<TabDocViewProps>) { + super.componentDidUpdate(prevProps); this._view && DocumentManager.Instance.AddView(this._view); } - @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. @@ -377,9 +384,9 @@ export class TabDocView extends React.Component<TabDocViewProps> { case undefined: case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) { const lightboxView = !doc.annotationOn && DocCast(doc.embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.embedContainer)) : undefined; - const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)]; + const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.Document)]; if (lightboxView && (!data || data instanceof List)) { - lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)] = new List<Doc>([doc]); + lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>([doc]); return true; } } @@ -419,7 +426,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; @@ -440,10 +447,10 @@ 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} - DataDoc={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined} + TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined} onBrowseClick={DocumentView.exploreMode} waitForDoubleClickToClick={this.waitForDoubleClick} isContentActive={this.isContentActive} @@ -459,7 +466,6 @@ export class TabDocView extends React.Component<TabDocViewProps> { addDocTab={this.addDocTab} ScreenToLocalTransform={this.ScreenToLocalTransform} dontCenter={'y'} - rootSelected={returnTrue} whenChildContentsActiveChanged={this.whenChildContentActiveChanges} focus={this.focusFunc} docViewPath={returnEmptyDoclist} @@ -489,7 +495,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); } }}> @@ -513,14 +519,14 @@ interface TabMiniThumbProps { miniTop: () => number; miniLeft: () => number; } -@observer + class TabMiniThumb extends React.Component<TabMiniThumbProps> { render() { return <div className="miniThumb" style={{ width: `${this.props.miniWidth()}% `, height: `${this.props.miniHeight()}% `, left: `${this.props.miniLeft()}% `, top: `${this.props.miniTop()}% ` }} />; } } @observer -export class TabMinimapView extends React.Component<TabMinimapViewProps> { +export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> { static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => { if (doc) { switch (property.split(':')[0]) { @@ -547,8 +553,9 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { } } }; + @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 +563,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 +585,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,9 +603,8 @@ 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} - rootSelected={returnTrue} addDocument={returnFalse} moveDocument={returnFalse} removeDocument={returnFalse} @@ -609,7 +615,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} @@ -623,7 +629,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.scss b/src/client/views/collections/TreeView.scss index cbcc7c710..0a1946f09 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -1,4 +1,4 @@ -@import '../global/globalCssVariables'; +@import '../global/globalCssVariables.module.scss'; .treeView-label { max-height: 1.5em; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 004857ed1..a7705ea7e 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1,8 +1,10 @@ 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 { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Utils, emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick } from '../../../Utils'; import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -12,31 +14,29 @@ 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 { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { DocUtils, Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; -import { SelectionManager } from '../../util/SelectionManager'; import { SettingsManager } from '../../util/SettingsManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; +import { UndoManager, undoBatch, undoable } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; -import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; +import { ObservableReactComponent } from '../ObservableReactComponent'; +import { StyleProp } from '../StyleProvider'; import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; +import { KeyValueBox } from '../nodes/KeyValueBox'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { KeyValueBox } from '../nodes/KeyValueBox'; -import { StyleProp } from '../StyleProvider'; import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView } from './CollectionView'; import { TreeSort } from './TreeSort'; import './TreeView.scss'; -import React = require('react'); +const { default: { TREE_BULLET_WIDTH } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore export interface TreeViewProps { treeView: CollectionTreeView; @@ -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; @@ -87,7 +87,7 @@ const treeBulletWidth = function () { * treeView_ExpandedView : name of field whose contents are being displayed as the document's subtree */ @observer -export class TreeView extends React.Component<TreeViewProps> { +export class TreeView extends ObservableReactComponent<TreeViewProps> { static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>; static _openTitleScript: Opt<ScriptField | undefined>; static _openLevelScript: Opt<ScriptField | undefined>; @@ -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 - ? this.fieldKey - : this.props.treeView.dashboardMode + return this.Document._type_collection === CollectionViewType.Docking ? this.fieldKey - : this.props.treeView.fileSysMode - ? this.doc.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.fieldKey - : Doc.noviceMode - ? 'layout' - : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields'); + : this.treeView.dashboardMode + ? this.fieldKey + : 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.treeView.outlineMode || this.childDocs + ? this.fieldKey + : Doc.noviceMode + ? 'layout' + : StrCast(this.treeView.Document.treeView_ExpandedView, 'fields'); + } + + @computed get treeView() { + return this._props.treeView; } - @computed get doc() { - return this.props.document; + @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.props.document.treeView_ChildrenOnRoot ? this.doc : 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; }; @@ -200,10 +204,10 @@ export class TreeView extends React.Component<TreeViewProps> { this._disposers.selection?.(); if (!docView) { this._editTitle = false; - } else if (docView.isSelected()) { + } else if (docView.SELECTED) { this._editTitle = true; this._disposers.selection = reaction( - () => docView.isSelected(), + () => docView.SELECTED, isSel => !isSel && this.setEditTitle(undefined) ); } else { @@ -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.rootDoc.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.rootDoc : Doc.BestEmbedding(docView.rootDoc); - 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,24 @@ export class TreeView extends React.Component<TreeViewProps> { static GetRunningChildren = new Map<Doc, any>(); static ToggleChildrenRun = new Map<Doc, () => void>(); - constructor(props: any) { + constructor(props: TreeViewProps) { super(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 +280,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() { + componentDidUpdate(prevProps: Readonly<TreeViewProps>) { + super.componentDidUpdate(prevProps); 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 +313,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.IsDragging && this._props.isContentActive()) { this._header.current!.className = 'treeView-header'; document.removeEventListener('pointermove', this.onDragMove, true); document.removeEventListener('pointerup', this.onDragUp, true); @@ -333,7 +337,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.rootDoc) { + 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'; @@ -361,7 +365,7 @@ export class TreeView extends React.Component<TreeViewProps> { _width: 1000, _height: 10, }); - Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); + Doc.GetProto(bullet).title = ComputedField.MakeFunction('this.text?.Text'); Doc.GetProto(bullet).data = new List<Doc>([]); DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.()); @@ -371,18 +375,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 +395,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 +413,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 +424,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 +467,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 +475,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 +492,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 +577,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 +591,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 +600,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 +618,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 +631,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 +642,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 +650,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 +659,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 +697,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 +709,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 +723,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 +738,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 +779,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 +812,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> @@ -825,54 +829,54 @@ export class TreeView extends React.Component<TreeViewProps> { contextMenuItems = () => { const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' }; const folderOp = this.childDocs?.length ? [makeFolder] : []; - const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(self), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' }; - const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' }; - const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Reopen' }; + const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(this), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' }; + 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) - ? [] - : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) - ? [openEmbedding, makeFolder] - : this.doc._type_collection === CollectionViewType.Docking - ? [] - : this.props.treeView.rootDoc === Doc.MyRecentlyClosed - ? [reopenDoc] - : [openEmbedding, focusDoc]), + : Doc.IsSystem(this.Document) + ? [] + : this.treeView.fileSysMode && this.Document === Doc.GetProto(this.Document) + ? [openEmbedding, makeFolder] + : this.Document._type_collection === CollectionViewType.Docking + ? [] + : 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(self)`)!); + 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 @@ -881,33 +885,33 @@ export class TreeView extends React.Component<TreeViewProps> { // just render a title for a tree view label (identified by treeViewDoc being set in 'props') maxWidth: props?.PanelWidth() || undefined, background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor), - outline: SnappingManager.GetIsDragging() ? undefined: `solid ${highlightColor} ${highlightIndex}px`, - paddingLeft: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)), - paddingRight: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)), - paddingTop: treeView.props.childYPadding, - paddingBottom: treeView.props.childYPadding, + outline: SnappingManager.IsDragging ? 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, }}> {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 +925,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 +940,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,49 +962,46 @@ 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} - DataDoc={undefined} // or this.dataDoc? + 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} - rootSelected={returnTrue} - pinToPres={emptyFunction} + 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={return18} + NativeHeight={returnZero} NativeWidth={returnZero} - shouldNotScale={returnTrue} PanelWidth={this.titleWidth} 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} @@ -1009,16 +1010,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> @@ -1058,44 +1059,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} - DataDoc={undefined} + 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} + fitContentsToBox={returnTrue} + 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} - rootSelected={returnTrue} - 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} /> @@ -1105,7 +1104,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> ) : ( <> @@ -1120,16 +1119,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> ); @@ -1139,28 +1138,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> ); @@ -1236,7 +1235,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) @@ -1248,11 +1247,11 @@ 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); - FormattedTextBox.SelectOnLoad = child[Id]; + FormattedTextBox.SetSelectOnLoad(child); TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined; Doc.AddDocToList(newParent, fieldKey, child, addAfter, false); newParent.treeView_Open = true; @@ -1260,7 +1259,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 = () => { @@ -1271,7 +1270,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/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx index 00505dbe3..99ee5ef4e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx @@ -2,7 +2,7 @@ import { observer } from 'mobx-react'; import { Doc } from '../../../../fields/Doc'; import { NumCast } from '../../../../fields/Types'; import './CollectionFreeFormView.scss'; -import React = require('react'); +import * as React from 'react'; export interface CollectionFreeFormViewBackgroundGridProps { panX: () => number; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx new file mode 100644 index 000000000..d9f7ca6ea --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -0,0 +1,113 @@ +import { IconButton, Size, Type } from 'browndash-components'; +import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { SettingsManager } from '../../../util/SettingsManager'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; +import './CollectionFreeFormView.scss'; +// import assets from './assets/link.png'; + +/** + * An Fsa Arc. The first array element is a test condition function that will be observed. + * The second array element is a function that will be invoked when the first test function + * returns a truthy value + */ +export type infoArc = [() => any, (res?: any) => infoState]; + +export const StateMessage = Symbol('StateMessage'); +export const StateEntryFunc = Symbol('StateEntryFunc'); +export const StateMessageGIF = Symbol('StateMessageGIF'); +export class infoState { + [StateMessage]: string = ''; + [StateEntryFunc]?: () => any; + [StateMessageGIF]?: string = ''; + [key: string]: infoArc; + constructor(message: string, arcs: { [key: string]: infoArc }, entryFunc?: () => any, messageGif?: string) { + this[StateMessage] = message; + Object.assign(this, arcs); + this[StateEntryFunc] = entryFunc; + this[StateMessageGIF] = messageGif; + } +} + +/** + * Create an FSA state. + * @param msg the message displayed when in this state + * @param arcs an object with fields containing @infoArcs (an object with field names indicating the arc transition and + * field values being a tuple of an arc transition trigger function (that returns a truthy value when the arc should fire), + * and an arc transition action function (that sets the next state) + * @param entryFunc a function to call when entering the state + * @returns an FSA state + */ +export function InfoState( + msg: string, // + arcs: { [key: string]: infoArc }, + entryFunc?: () => any, + gif?: string +) { + return new infoState(msg, arcs, entryFunc, gif); +} + +export interface CollectionFreeFormInfoStateProps { + infoState: infoState; + next: (state: infoState) => any; +} + +@observer +export class CollectionFreeFormInfoState extends ObservableReactComponent<CollectionFreeFormInfoStateProps> { + _disposers: IReactionDisposer[] = []; + @observable _hide = false; + + constructor(props: any) { + super(props); + makeObservable(this); + } + + get State() { + return this._props.infoState; + } + get Arcs() { + return Object.keys(this.State ?? []).map(key => this.State?.[key]); + } + + clearState = () => this._disposers.map(disposer => disposer()); + initState = () => + (this._disposers = this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(arc => { + return reaction( + // + arc.test, + res => { + if (res) { + const next = arc.act(res); + this._props.next(next); + } + }, + { fireImmediately: true } + ); + })); + + componentDidMount(): void { + this.initState(); + } + componentDidUpdate(prevProps: Readonly<CollectionFreeFormInfoStateProps>) { + super.componentDidUpdate(prevProps); + this.clearState(); + this.initState(); + } + componentWillUnmount(): void { + this.clearState(); + } + render() { + return ( + <div className="collectionFreeform-infoUI" style={{display:this._hide ? 'none':undefined}}> + <div className="msg">{this.State?.[StateMessage]}</div> + <div className="gif-container" style={{ display: this.State?.[StateMessageGIF] ? undefined : 'none' }}> + <img className="gif" src={this.State?.[StateMessageGIF]} alt="state message gif"></img> + </div> + <div style={{position:"absolute", top:-10, left:-10}}> + <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this._hide = true)} /> + </div> + </div> + ); + } +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx new file mode 100644 index 000000000..181f325f3 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -0,0 +1,230 @@ +import { IReactionDisposer, makeObservable, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc, DocListCast, Field, FieldResult } from '../../../../fields/Doc'; +import { InkTool } from '../../../../fields/InkField'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { LinkManager } from '../../../util/LinkManager'; +import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { CollectionFreeFormInfoState, InfoState, infoState, StateEntryFunc } from './CollectionFreeFormInfoState'; +import { CollectionFreeFormView } from './CollectionFreeFormView'; +import './CollectionFreeFormView.scss'; + +export interface CollectionFreeFormInfoUIProps { + Document: Doc; + Freeform: CollectionFreeFormView; +} + +@observer +export class CollectionFreeFormInfoUI extends ObservableReactComponent<CollectionFreeFormInfoUIProps> { + _firstDocPos = { x: 0, y: 0 }; + + constructor(props: any) { + super(props); + makeObservable(this); + this.currState = this.setupStates(); + } + + @observable _currState: infoState | undefined = undefined; + get currState() { return this._currState!; } // prettier-ignore + set currState(val) { runInAction(() => (this._currState = val)); } // prettier-ignore + + setCurrState = (state: infoState) => { + if (state) { + this.currState = state; + this.currState[StateEntryFunc]?.(); + } + }; + + setupStates = () => { + // state entry functions + const setBackground = (colour: string) => () => (this._props.Freeform.layoutDoc.backgroundColor = colour); + const setOpacity = (opacity: number) => () => (this._props.Freeform.layoutDoc.opacity = opacity); + // arc transition trigger conditions + const firstDoc = () => (this._props.Freeform.childDocs.length ? this._props.Freeform.childDocs[0] : undefined); + const numDocs = () => this._props.Freeform.childDocs.length; + + let docX: FieldResult<Field>; + let docY: FieldResult<Field>; + + const docNewX = () => firstDoc()?.x; + const docNewY = () => firstDoc()?.y; + + const linkStart = () => DocumentLinksButton.StartLink; + + const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length; + const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView; + + const activeTool = () => Doc.ActiveTool; + + const pin = () => DocListCast(Doc.ActivePresentation?.data); + + let trail: number; + + const trailView = () => DocumentManager.Instance.DocumentViews.find(view => view.Document === Doc.MyTrails); + const presentationMode = () => Doc.ActivePresentation?.presentation_status; + + // set of states + const start = InfoState('Click anywhere and begin typing to create your first text document.', { + docCreated: [() => numDocs(), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return oneDoc; + }], + }, setBackground("blue")); // prettier-ignore + + const oneDoc = InfoState('Hello world! You can drag and drop to move your document around.', { + // docCreated: [() => numDocs() > 1, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc1; + }], + }, setBackground("red")); // prettier-ignore + + const movedDoc1 = InfoState('Great moves. Try creating a second document.', { + docCreated: [() => numDocs() == 2, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc2; + }], + }, setBackground("yellow")); // prettier-ignore + + const movedDoc2 = InfoState('Slick moves. Try creating a second document.', { + docCreated: [() => numDocs() == 2, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc3; + }], + }, setBackground("pink")); // prettier-ignore + + const movedDoc3 = InfoState('Groovy moves. Try creating a second document.', { + docCreated: [() => numDocs() == 2, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc1; + }], + }, setBackground("green")); // prettier-ignore + + const multipleDocs = InfoState('Let\'s create a new link. Click the link icon on one of your documents.', { + linkStarted: [() => linkStart(), () => startedLink], + docRemoved: [() => numDocs() < 2, () => oneDoc], + }, setBackground("purple")); // prettier-ignore + + const startedLink = InfoState('Now click the highlighted link icon on your other document.', { + linkCreated: [() => numDocLinks(), () => madeLink], + docRemoved: [() => numDocs() < 2, () => oneDoc], + }, setBackground("orange")); // prettier-ignore + + const madeLink = InfoState('You made your first link! You can view your links by selecting the blue dot.', { + linkCreated: [() => !numDocLinks(), () => multipleDocs], + linkViewed: [() => linkMenuOpen(), () => { + alert(numDocLinks() + " cheer for " + numDocLinks() + " link!"); + return viewedLink; + }], + }, setBackground("blue")); // prettier-ignore + + const viewedLink = InfoState('Great work. You are now ready to create your own hypermedia world.', { + linkDeleted: [() => !numDocLinks(), () => multipleDocs], + docRemoved: [() => numDocs() < 2, () => oneDoc], + docCreated: [() => numDocs() == 3, () => { + trail = pin().length; + return presentDocs; + }], + activePen: [() => activeTool() === InkTool.Pen, () => penMode], + }, setBackground("black")); // prettier-ignore + + const presentDocs = InfoState('Another document! You could make a presentation. Click the pin icon in the top left corner.', { + docPinned: [() => pin().length > trail, () => { + trail = pin().length; + return pinnedDoc1; + }], + docRemoved: [() => numDocs() < 3, () => viewedLink], + }, setBackground("black"), "/assets/dash-pin-with-view.gif"); + + const penMode = InfoState('You\'re in pen mode. Click and drag to draw your first masterpiece.', { + // activePen: [() => activeTool() === InkTool.Eraser, () => eraserMode], + activePen: [() => activeTool() !== InkTool.Pen, () => viewedLink], + }); // prettier-ignore + + // const eraserMode = InfoState('You\'re in eraser mode. Say goodbye to your first masterpiece.', { + // docsRemoved: [() => numDocs() == 3, () => demos], + // }); // prettier-ignore + + const pinnedDoc1 = InfoState('You just pinned your doc.', { + docPinned: [() => pin().length > trail, () => { + trail = pin().length; + return pinnedDoc2; + }], + // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], + // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], + autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], + docRemoved: [() => numDocs() < 3, () => viewedLink], + }); + + const pinnedDoc2 = InfoState(`You pinned another doc.`, { + docPinned: [() => pin().length > trail, () => { + trail = pin().length; + return pinnedDoc3; + }], + // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], + // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], + autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], + docRemoved: [() => numDocs() < 3, () => viewedLink], + }); + + const pinnedDoc3 = InfoState(`You pinned yet another doc.`, { + docPinned: [() => pin().length > trail, () => { + trail = pin().length; + return pinnedDoc2; + }], + // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], + // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], + autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], + docRemoved: [() => numDocs() < 3, () => viewedLink], + }); + + // const openedTrail = InfoState('This is your trails tab.', { + // trailView: [() => presentationMode() === 'edit', () => editPresentationMode], + // }); + + // const editPresentationMode = InfoState('You are editing your presentation.', { + // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], + // autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], + // docRemoved: [() => numDocs() < 3, () => demos], + // docCreated: [() => numDocs() == 4, () => completed], + // }); + + const manualPresentationMode = InfoState('You\'re in manual presentation mode.', { + // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], + autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], + docRemoved: [() => numDocs() < 3, () => viewedLink], + docCreated: [() => numDocs() == 4, () => completed], + }); + + const autoPresentationMode = InfoState('You\'re in auto presentation mode.', { + // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], + manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], + docRemoved: [() => numDocs() < 3, () => viewedLink], + docCreated: [() => numDocs() == 4, () => completed], + }); + + const completed = InfoState('Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', { + docRemoved: [() => numDocs() == 1, () => oneDoc], + }, setBackground("white")); // prettier-ignore + + return start; + }; + + render() { + return <CollectionFreeFormInfoState next={this.setCurrState} infoState={this.currState} />; + } +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 403fba67b..b8c0967c1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -5,7 +5,7 @@ import { RefField } from '../../../../fields/RefField'; import { listSpec } from '../../../../fields/Schema'; import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { aggregateBounds } from '../../../../Utils'; -import React = require('react'); +import * as React from 'react'; export interface ViewDefBounds { type: string; @@ -37,6 +37,7 @@ export interface PoolData { zIndex?: number; width?: number; height?: number; + autoDim?: number; // 1 for set to Panel dims, 0 for use width/height as entered backgroundColor?: string; color?: string; opacity?: number; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index aca6df3c9..6337c8d34 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,5 +1,6 @@ -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import { Doc, Field } from '../../../../fields/Doc'; import { Brushed, DocCss } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; @@ -12,8 +13,8 @@ import { SettingsManager } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../../nodes/DocumentView'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; import './CollectionFreeFormLinkView.scss'; -import React = require('react'); export interface CollectionFreeFormLinkViewProps { A: DocumentView; @@ -24,24 +25,29 @@ export interface CollectionFreeFormLinkViewProps { // props.screentolocatransform @observer -export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> { +export class CollectionFreeFormLinkView extends ObservableReactComponent<CollectionFreeFormLinkViewProps> { @observable _opacity: number = 0; @observable _start = 0; _anchorDisposer: IReactionDisposer | undefined; _timeout: NodeJS.Timeout | undefined; + constructor(props: any) { + super(props); + makeObservable(this); + } + componentWillUnmount() { this._anchorDisposer?.(); } - @action timeout = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25))); + @action timeout: any = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25))); componentDidMount() { this._anchorDisposer = reaction( () => [ - this.props.A.props.ScreenToLocalTransform(), - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop, - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss], - this.props.B.props.ScreenToLocalTransform(), - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop, - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss], + this._props.A._props.ScreenToLocalTransform(), + Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop, + Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss], + this._props.B._props.ScreenToLocalTransform(), + Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop, + Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss], ], action(() => { this._start = Date.now(); @@ -53,9 +59,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo ); } placeAnchors = () => { - const { A, B, LinkDocs } = this.props; + const { A, B, LinkDocs } = this._props; const linkDoc = LinkDocs[0]; - if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return; + if (SnappingManager.IsDragging || !A.ContentDiv || !B.ContentDiv) return; setTimeout( action(() => (this._opacity = 0.75)), 0 @@ -85,9 +91,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo } } else { const m = targetAhyperlink.getBoundingClientRect(); - const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5); - const mpx = mp[0] / A.props.PanelWidth(); - const mpy = mp[1] / A.props.PanelHeight(); + const mp = A._props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5); + const mpx = mp[0] / A._props.PanelWidth(); + const mpy = mp[1] / A._props.PanelHeight(); if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_1_x = mpx * 100; if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_1_y = mpy * 100; if (getComputedStyle(targetAhyperlink).fontSize === '0px') linkDoc.opacity = 0; @@ -100,9 +106,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo } } else { const m = targetBhyperlink.getBoundingClientRect(); - const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5); - const mpx = mp[0] / B.props.PanelWidth(); - const mpy = mp[1] / B.props.PanelHeight(); + const mp = B._props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5); + const mpx = mp[0] / B._props.PanelWidth(); + const mpy = mp[1] / B._props.PanelHeight(); if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_2_x = mpx * 100; if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_2_y = mpy * 100; if (getComputedStyle(targetBhyperlink).fontSize === '0px') linkDoc.opacity = 0; @@ -115,18 +121,18 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo this, e, (e, down, delta) => { - this.props.LinkDocs[0].link_relationship_OffsetX = NumCast(this.props.LinkDocs[0].link_relationship_OffsetX) + delta[0]; - this.props.LinkDocs[0].link_relationship_OffsetY = NumCast(this.props.LinkDocs[0].link_relationship_OffsetY) + delta[1]; + this._props.LinkDocs[0].link_relationship_OffsetX = NumCast(this._props.LinkDocs[0].link_relationship_OffsetX) + delta[0]; + this._props.LinkDocs[0].link_relationship_OffsetY = NumCast(this._props.LinkDocs[0].link_relationship_OffsetY) + delta[1]; return false; }, emptyFunction, action(() => { SelectionManager.DeselectAll(); - SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true); - LinkManager.currentLink = this.props.LinkDocs[0]; + SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true); + LinkManager.currentLink = this._props.LinkDocs[0]; this.toggleProperties(); // OverlayView.Instance.addElement( - // <LinkEditor sourceDoc={this.props.A.props.Document} linkDoc={this.props.LinkDocs[0]} + // <LinkEditor sourceDoc={this._props.A._props.Document} linkDoc={this._props.LinkDocs[0]} // showLinks={action(() => { })} // />, { x: 300, y: 300 }); }) @@ -171,23 +177,23 @@ 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; } }; @action onClickLine = () => { SelectionManager.DeselectAll(); - SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true); - LinkManager.currentLink = this.props.LinkDocs[0]; + SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true); + LinkManager.currentLink = this._props.LinkDocs[0]; this.toggleProperties(); }; @computed.struct get renderData() { this._start; - SnappingManager.GetIsDragging(); - const { A, B, LinkDocs } = this.props; + SnappingManager.IsDragging; + const { A, B, LinkDocs } = this._props; if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined; const acont = A.ContentDiv.getElementsByClassName('linkAnchorBox-cont'); const bcont = B.ContentDiv.getElementsByClassName('linkAnchorBox-cont'); @@ -200,8 +206,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const atop = this.visibleY(adiv); const btop = this.visibleY(bdiv); if (!a.width || !b.width) return undefined; - const aDocBounds = (A.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 }; - const bDocBounds = (B.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 }; + const aDocBounds = (A._props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 }; + const bDocBounds = (B._props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 }; const aleft = this.visibleX(adiv); const bleft = this.visibleX(bdiv); const aclipped = aleft !== a.left || atop !== a.top; @@ -223,12 +229,12 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1; const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen]; const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen]; - const aActive = A.isSelected() || A.rootDoc[Brushed]; - const bActive = B.isSelected() || B.rootDoc[Brushed]; + const aActive = A.SELECTED || A.Document[Brushed]; + const bActive = B.SELECTED || B.Document[Brushed]; const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX); const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY); - const link = this.props.LinkDocs[0]; + const link = this._props.LinkDocs[0]; return { a, b, @@ -250,7 +256,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo render() { if (!this.renderData) return null; - const link = this.props.LinkDocs[0]; + const link = this._props.LinkDocs[0]; const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData; const linkRelationship = Field.toString(link?.link_relationship as any as Field); //get string representing relationship const linkRelationshipList = Doc.UserDoc().link_relationshipList as List<string>; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 420e6a318..779a0bf96 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -5,7 +5,7 @@ import { DocumentManager } from '../../../util/DocumentManager'; import { LightboxView } from '../../LightboxView'; import './CollectionFreeFormLinksView.scss'; import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView'; -import React = require('react'); +import * as React from 'react'; @observer export class CollectionFreeFormLinksView extends React.Component { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx index 856e195a3..f54726a00 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx @@ -4,11 +4,11 @@ import { Doc } from '../../../../fields/Doc'; import { ScriptField } from '../../../../fields/ScriptField'; import { PresBox } from '../../nodes/trails/PresBox'; import './CollectionFreeFormView.scss'; -import React = require('react'); +import * as React from 'react'; import { CollectionFreeFormView } from './CollectionFreeFormView'; export interface CollectionFreeFormPannableContentsProps { - rootDoc: Doc; + Document: Doc; viewDefDivClick?: ScriptField; children?: React.ReactNode | undefined; transition?: string; @@ -20,7 +20,7 @@ export interface CollectionFreeFormPannableContentsProps { @observer export class CollectionFreeFormPannableContents extends React.Component<CollectionFreeFormPannableContentsProps> { @computed get presPaths() { - return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.rootDoc) : null; + return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.Document) : null; } // rectangle highlight used when following trail/link to a region of a collection that isn't a document showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) => diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss index 5fa01b102..7951aff65 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss @@ -1,14 +1,12 @@ -@import "global/globalCssVariables"; +@import 'global/globalCssVariables.module.scss'; .collectionFreeFormRemoteCursors-cont { - - position:absolute; + position: absolute; z-index: $remoteCursors-zindex; transform-origin: 'center center'; } .collectionFreeFormRemoteCursors-canvas { - - position:absolute; + position: absolute; width: 20px; height: 20px; opacity: 0.5; @@ -21,4 +19,4 @@ // fontStyle: "italic", margin-left: -12; margin-top: 4; -}
\ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 9e8d92d7d..45e24bbb2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -9,8 +9,8 @@ import { listSpec } from '../../../../fields/Schema'; import { Cast } from '../../../../fields/Types'; import { CollectionViewProps } from '../CollectionView'; import './CollectionFreeFormView.scss'; -import React = require('react'); -import v5 = require('uuid/v5'); +import * as React from 'react'; +import * as uuid from 'uuid'; @observer export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> { @@ -42,7 +42,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV if (el) { const ctx = el.getContext('2d'); if (ctx) { - ctx.fillStyle = '#' + v5(metadata.id, v5.URL).substring(0, 6).toUpperCase() + '22'; + ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22'; ctx.fillRect(0, 0, 20, 20); ctx.fillStyle = 'black'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 250760bd5..7d3acaea7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -1,4 +1,4 @@ -@import '../../global/globalCssVariables'; +@import '../../global/globalCssVariables.module.scss'; .collectionfreeformview-none { position: inherit; @@ -255,3 +255,49 @@ background-color: rgba($color: #000000, $alpha: 0.4); position: absolute; } + +.collectionFreeform-infoUI { + position: absolute; + display: block; + top: 0; + + color: white; + background-color: #5075ef; + border-radius: 5px; + box-shadow: 2px 2px 5px black; + + margin: 15px; + padding: 10px; + + .msg { + position: relative; + // display: block; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; + + } + + .gif-container { + position: relative; + margin-top: 5px; + // display: block; + + justify-content: center; + align-items: center; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; + + + .gif { + background-color: transparent; + height: 300px; + } + } + +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d53049e04..1f4688729 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,8 +1,9 @@ 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, reaction, runInAction, toJS } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; +import * as React from 'react'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; import { DocData, Height, Width } from '../../../../fields/DocSymbols'; @@ -45,13 +46,12 @@ import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; +import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI'; import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents'; import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; -import React = require('react'); -import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp'; export type collectionFreeformViewProps = { NativeWidth?: () => number; @@ -69,9 +69,13 @@ 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 + constructor(props: any) { + super(props); + makeObservable(this); + } @observable public static ShowPresPaths = false; @@ -90,19 +94,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 +117,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() { @@ -123,19 +127,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return renderableEles; } @computed get fitToContentVals() { + const hgt = this.contentBounds.b - this.contentBounds.y; + const wid = this.contentBounds.r - this.contentBounds.x; return { - bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 }, + bounds: { ...this.contentBounds, cx: this.contentBounds.x + wid / 2, cy: this.contentBounds.y + hgt / 2 }, scale: - !this.childDocs.length || !Number.isFinite(this.contentBounds.b - this.contentBounds.y) || !Number.isFinite(this.contentBounds.r - this.contentBounds.x) - ? 1 - : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)), + (!this.childDocs.length || !Number.isFinite(hgt) || !Number.isFinite(wid) + ? 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.rootDoc.contentBounds, listSpec('number')); + const cb = Cast(this.dataDoc.contentBounds, listSpec('number')); return cb ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] } : aggregateBounds( @@ -145,29 +151,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); } @computed get nativeWidth() { - return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : 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?.() || (this.fitContentsToBox ? 0 : 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.fitContentsToBox || !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 + 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 } @computed get cachedCenteringShiftY(): number { - const dv = this.props.DocumentView?.(); - const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling; + 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 && !dv.layoutDoc.layout_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); } @@ -182,15 +189,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const timecode = Math.round(time); docs.forEach(doc => { CollectionFreeFormDocumentView.animFields.forEach(val => { - const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null); + const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null); findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); }); CollectionFreeFormDocumentView.animStringFields.forEach(val => { - const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null); + const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null); findexed?.length <= timecode + 1 && findexed.push(undefined as any as string); }); CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => { - const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null); + const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null); findexed?.length <= timecode + 1 && findexed.push(undefined as any); }); }); @@ -216,46 +223,44 @@ 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 = () => { - if (this.props.DocumentView?.().nativeWidth) return; const vals = this.fitToContentVals; this.layoutDoc._freeform_panX = vals.bounds.cx; this.layoutDoc._freeform_panY = vals.bounds.cy; this.layoutDoc._freeform_scale = vals.scale; }; freeformData = (force?: boolean) => (!this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined); - ignoreNativeDimScaling = () => (this.fitContentsToBox ? true : false); // freeform_panx, freeform_pany, freeform_scale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document. // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1)); 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(); - addLiveTextBox = (newBox: Doc) => { - FormattedTextBox.SelectOnLoad = newBox[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed - this.addDocument(newBox); + 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) { @@ -263,13 +268,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection for (const newBox of newBoxes) { if (newBox.activeFrame !== undefined) { const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field.key]); - CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field.key}-indexed`]); + CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field.key}_indexed`]); CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field.key]); delete newBox.activeFrame; 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); } } @@ -284,15 +289,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } groupFocus = (anchor: Doc, options: DocFocusOptions) => { - options.docTransform = new Transform(-NumCast(this.rootDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.rootDoc[this.panYFieldKey]) + NumCast(anchor.y), 1); - const res = this.props.focus(this.rootDoc, options); + 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); options.docTransform = undefined; return res; }; focus = (anchor: Doc, options: DocFocusOptions) => { if (this._lightboxDoc) return; - if (anchor === this.rootDoc) { + if (anchor === this.Document) { if (options.willZoomCentered && options.zoomScale) { this.fitContentOnce(); options.didMove = true; @@ -301,7 +306,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return; const xfToCollection = options?.docTransform ?? Transform.Identity(); const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; - const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); + const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale ?? 0.75 : undefined); // focus on the document in the collection @@ -323,11 +328,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]; @@ -342,7 +346,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 @@ -397,18 +401,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection dropDoc.y = yp - annoDragData.offset[1]; this.bringToFront(dropDoc); } - return dropDoc || this.rootDoc; + return dropDoc || this.Document; }; return true; } @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 = - !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.rootDoc + !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.Document ? Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' }) : Docs.Create.FontIconDocument({ title: 'anchor', @@ -425,7 +429,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(); @@ -462,7 +466,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; @@ -479,11 +483,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!), @@ -501,7 +505,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)); } @@ -509,7 +513,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); @@ -552,7 +556,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; @@ -582,35 +586,36 @@ 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 - switch (property) { - case StyleProp.BackgroundColor: - const cluster = NumCast(doc?.layout_cluster); - if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) { - if (this._clusterSets.length <= cluster) { - setTimeout(() => doc && this.updateCluster(doc)); - } else { - // choose a cluster color from a palette - const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; - styleProp = colors[cluster % colors.length]; - const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); - // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.map(s => (styleProp = StrCast(s.backgroundColor))); + 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: + const cluster = NumCast(doc?.layout_cluster); + if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) { + if (this._clusterSets.length <= cluster) { + setTimeout(() => doc && this.updateCluster(doc)); + } else { + // choose a cluster color from a palette + const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; + styleProp = colors[cluster % colors.length]; + const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); + // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document + set?.map(s => (styleProp = StrCast(s.backgroundColor))); + } } - } - break; - case StyleProp.FillColor: - if (doc && this.Document._currentFrame !== undefined) { - return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor; - } - } + break; + case StyleProp.FillColor: + if (doc && this.Document._currentFrame !== undefined) { + return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor; + } + } return styleProp; }; 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; } @@ -623,8 +628,8 @@ 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 (!this.props.Document._isGroup) { + 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 switch (Doc.ActiveTool) { @@ -636,7 +641,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); } @@ -659,7 +664,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(), @@ -698,7 +703,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; @action onEraserUp = (e: PointerEvent): void => { - this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc)); + this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.Document)); this._deleteList = []; this._batch?.end(); }; @@ -708,7 +713,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) { @@ -731,9 +736,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; @@ -753,8 +758,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => { if (!this._deleteList.includes(intersect.inkView)) { this._deleteList.push(intersect.inkView); - SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.stroke_width?.toString()) || '1'); - SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black'); + SetActiveInkWidth(StrCast(intersect.inkView.Document.stroke_width?.toString()) || '1'); + SetActiveInkColor(StrCast(intersect.inkView.Document.color?.toString()) || 'black'); // create a new curve by appending all curves of the current segment together in order to render a single new stroke. if (!e.shiftKey) { this._eraserLock++; @@ -786,7 +791,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.rootDoc._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 } @@ -802,7 +807,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( @@ -813,23 +818,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection eraserMax.X >= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top ) - .reduce((intersections, { inkStroke, inkView }) => { - const { inkData } = inkStroke.inkScaledData(); - // Convert from screen space to ink space for the intersection. - const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); - const currPointInkSpace = inkStroke.ptFromScreen(currPoint); - for (var i = 0; i < inkData.length - 3; i += 4) { - const rawIntersects = InkField.Segment(inkData, i).intersects({ - // compute all unique intersections - p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, - p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }, - }); - const intersects = Array.from(new Set(rawIntersects as (number | string)[])); // convert to more manageable union array type - // return tuples of the inkingStroke intersected, and the t value of the intersection - intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve - } - return intersections; - }, [] as { t: number; inkView: DocumentView }[]); + .reduce( + (intersections, { inkStroke, inkView }) => { + const { inkData } = inkStroke.inkScaledData(); + // Convert from screen space to ink space for the intersection. + const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); + const currPointInkSpace = inkStroke.ptFromScreen(currPoint); + for (var i = 0; i < inkData.length - 3; i += 4) { + const rawIntersects = InkField.Segment(inkData, i).intersects({ + // compute all unique intersections + p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, + p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }, + }); + const intersects = Array.from(new Set(rawIntersects as (number | string)[])); // convert to more manageable union array type + // return tuples of the inkingStroke intersected, and the t value of the intersection + intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve + } + return intersections; + }, + [] as { t: number; inkView: DocumentView }[] + ); }; /** @@ -896,7 +904,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)); @@ -928,7 +936,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); @@ -936,33 +944,33 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (deltaScale * invTransform.Scale > 20) { deltaScale = 20 / invTransform.Scale; } - if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc[this.scaleFieldKey + '_min'])) { + if (deltaScale < 1 && invTransform.Scale <= NumCast(this.Document[this.scaleFieldKey + '_min'])) { this.setPan(0, 0); return; } - if (deltaScale * invTransform.Scale > NumCast(this.rootDoc[this.scaleFieldKey + '_max'], Number.MAX_VALUE)) { - deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_max'], 1) / invTransform.Scale; + if (deltaScale * invTransform.Scale > NumCast(this.Document[this.scaleFieldKey + '_max'], Number.MAX_VALUE)) { + deltaScale = NumCast(this.Document[this.scaleFieldKey + '_max'], 1) / invTransform.Scale; } - if (deltaScale * invTransform.Scale < NumCast(this.rootDoc[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0)) { - deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_min'], 1) / invTransform.Scale; + if (deltaScale * invTransform.Scale < NumCast(this.Document[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0)) { + deltaScale = NumCast(this.Document[this.scaleFieldKey + '_min'], 1) / invTransform.Scale; } 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); } }; @action onPointerWheel = (e: React.WheelEvent): void => { - if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom + 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.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight); - const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling + 1e-4; + 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; switch ( !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?// Doc.UserDoc().freeformScrollMode : // no modifiers, do assigned mode @@ -970,7 +978,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 }); @@ -978,8 +986,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; @@ -995,7 +1003,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc); const measuredDocs = docs - .map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc.width), height: NumCast(doc.height) } })) + .map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc._width), height: NumCast(doc._height) } })) .filter(({ pos, size }) => pos && size) .map(({ pos, size }) => ({ pos: pos!, size: size! })); if (measuredDocs.length) { @@ -1008,45 +1016,45 @@ 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) { this.setPanZoomTransition(panTime); - const minScale = NumCast(this.rootDoc._freeform_scale_min, 1); + const minScale = NumCast(this.dataDoc._freeform_scale_min, 1); const scale = 1 - minScale / this.zoomScaling(); - const minPanX = NumCast(this.rootDoc._freeform_panX_min, 0); - const minPanY = NumCast(this.rootDoc._freeform_panY_min, 0); - const maxPanX = NumCast(this.rootDoc._freeform_panX_max, this.nativeWidth); + const minPanX = NumCast(this.dataDoc._freeform_panX_min, 0); + 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.rootDoc._panY_max, nativeHeight) + - (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning + scale * NumCast(this.dataDoc._panY_max, nativeHeight) + + (!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.rootDoc.layout_scrollTop) && NumCast(this.rootDoc._freeform_scale, minScale) !== minScale) { - const relTop = NumCast(this.rootDoc.layout_scrollTop) / maxScrollTop; - this.rootDoc.layout_scrollTop = undefined; + if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) { + const relTop = NumCast(this.layoutDoc.layout_scrollTop) / maxScrollTop; + this.layoutDoc.layout_scrollTop = undefined; newPanY = minPanY + relTop * (maxPanY - minPanY); - } else if (fitYscroll > 2 && this.rootDoc.layout_scrollTop === undefined && NumCast(this.rootDoc._freeform_scale, minScale) === minScale) { + } else if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) { const maxPanY = minPanY + fitYscroll; const relTop = (panY - minPanY) / (maxPanY - minPanY); - setTimeout(() => (this.rootDoc.layout_scrollTop = relTop * maxScrollTop), 10); + setTimeout(() => (this.layoutDoc.layout_scrollTop = relTop * maxScrollTop), 10); newPanY = minPanY; } !this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX); @@ -1056,11 +1064,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action nudge = (x: number, y: number, nudgeTime: number = 500) => { - const collectionDoc = this.props.docViewPath().lastElement().rootDoc; - if (collectionDoc?._type_collection !== CollectionViewType.Freeform || collectionDoc._freeform_ !== undefined) { + 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 ); @@ -1105,7 +1113,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) { - if (this.Document._isGroup) return; + if (this.Document.isGroup) return; this.setPanZoomTransition(transitionTime); const screenXY = this.screenToLocalXf.inverse().transformPoint(docpt[0], docpt[1]); this.layoutDoc[this.scaleFieldKey] = scale; @@ -1127,20 +1135,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; @@ -1148,54 +1156,53 @@ 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.rootDoc._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) { + if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) { e.stopPropagation?.(); const below = !e.altKey && e.key !== 'Tab'; const layout_fieldKey = StrCast(docView.LayoutFieldKey); - const newDoc = Doc.MakeCopy(docView.rootDoc, true); - const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; + const newDoc = Doc.MakeCopy(docView.Document, true); + const dataField = docView.Document[Doc.LayoutFieldKey(newDoc)]; newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined; - if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10; - else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10; - if (layout_fieldKey !== 'layout' && docView.rootDoc[layout_fieldKey] instanceof Doc) { - newDoc[layout_fieldKey] = docView.rootDoc[layout_fieldKey]; + if (below) newDoc.y = NumCast(docView.Document.y) + NumCast(docView.Document._height) + 10; + else newDoc.x = NumCast(docView.Document.x) + NumCast(docView.Document._width) + 10; + if (layout_fieldKey !== 'layout' && docView.Document[layout_fieldKey] instanceof Doc) { + newDoc[layout_fieldKey] = docView.Document[layout_fieldKey]; } Doc.GetProto(newDoc).text = undefined; - FormattedTextBox.SelectOnLoad = newDoc[Id]; + FormattedTextBox.SetSelectOnLoad(newDoc); return this.addDocument?.(newDoc); } }; @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; @@ -1203,55 +1210,54 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection <CollectionFreeFormDocumentViewWrapper {...OmitKeys(entry, ['replica', 'pair']).omit} key={childLayout[Id] + (entry.replica || '')} - DataDoc={childData} Document={childLayout} + 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.rootDoc.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} - //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeView_FreezeChildDimensions)} // bcz: check this /> ); } 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 ( @@ -1263,7 +1269,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return doc; }) ) && - (!docContext || this.props.removeDocument?.(docContext))) || + (!docContext || this._props.removeDocument?.(docContext))) || false ); case undefined: @@ -1277,9 +1283,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); @@ -1289,9 +1295,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed const { z, zIndex } = childDoc; const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber); - const { x, y, _width, _height, opacity, _rotation } = - layoutFrameNumber === undefined - ? { _width: Cast(childDocLayout._width, 'number'), _height: Cast(childDocLayout._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } + 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?.() } : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber); // prettier-ignore const rotation = Cast(_rotation,'number', @@ -1301,22 +1307,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x), y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y), 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, transition: StrCast(childDocLayout.dataTransition), - pair, pointerEvents: Cast(childDoc.pointerEvents, 'string', null), + pair, replica: '', }; } 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(); }; @@ -1371,7 +1378,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>) { @@ -1380,7 +1387,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(); @@ -1414,28 +1421,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type) - const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.rootDoc }); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, type_collection: true, filters: true } }, this.rootDoc); + const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.Document }); + 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; }; + infoUI = () => (this.Document.annotationOn ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} />); + componentDidMount() { - this.props.setContentView?.(this); + this._props.setContentView?.(this); super.componentDidMount?.(); setTimeout( action(() => { this._firstRender = false; this._disposers.groupBounds = reaction( () => { - if (this.Document._isGroup && this.childDocs.length === this.childDocList?.length) { + if (this.Document.isGroup && this.childDocs.length === this.childDocList?.length) { const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: NumCast(cd._width), height: NumCast(cd._height) })); return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding)); } @@ -1466,15 +1475,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 } @@ -1482,7 +1491,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._disposers.active = reaction( () => this.isContentActive(), // if autoreset is on, then whenever the view is selected, it will be restored to it default pan/zoom positions - active => !SnappingManager.GetIsDragging() && this.rootDoc[this.autoResetFieldKey] && active && this.resetView() + active => !SnappingManager.IsDragging && this.dataDoc[this.autoResetFieldKey] && active && this.resetView() ); }) ); @@ -1529,11 +1538,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, @@ -1576,7 +1585,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } componentWillUnmount() { - this.rootDoc[this.autoResetFieldKey] && this.resetView(); + this.dataDoc[this.autoResetFieldKey] && this.resetView(); Object.values(this._disposers).forEach(disposer => disposer?.()); } @@ -1593,7 +1602,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 @@ -1617,9 +1626,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection /// @undoBatch resetView = () => { - this.rootDoc[this.panXFieldKey] = NumCast(this.rootDoc[this.panXFieldKey + '_reset']); - this.props.Document[this.panYFieldKey] = NumCast(this.rootDoc[this.panYFieldKey + '_reset']); - this.rootDoc[this.scaleFieldKey] = NumCast(this.rootDoc[this.scaleFieldKey + '_reset'], 1); + this.layoutDoc[this.panXFieldKey] = NumCast(this.dataDoc[this.panXFieldKey + '_reset']); + this.layoutDoc[this.panYFieldKey] = NumCast(this.dataDoc[this.panYFieldKey + '_reset']); + this.layoutDoc[this.scaleFieldKey] = NumCast(this.dataDoc[this.scaleFieldKey + '_reset'], 1); }; /// /// resetView restores a freeform collection to unit scale and centered at (0,0) UNLESS @@ -1627,37 +1636,37 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection /// @undoBatch toggleResetView = () => { - this.rootDoc[this.autoResetFieldKey] = !this.rootDoc[this.autoResetFieldKey]; - if (this.rootDoc[this.autoResetFieldKey]) { - this.rootDoc[this.panXFieldKey + '_reset'] = this.rootDoc[this.panXFieldKey]; - this.rootDoc[this.panYFieldKey + '_reset'] = this.rootDoc[this.panYFieldKey]; - this.rootDoc[this.scaleFieldKey + '_reset'] = this.rootDoc[this.scaleFieldKey]; + this.dataDoc[this.autoResetFieldKey] = !this.dataDoc[this.autoResetFieldKey]; + if (this.dataDoc[this.autoResetFieldKey]) { + this.dataDoc[this.panXFieldKey + '_reset'] = this.dataDoc[this.panXFieldKey]; + this.dataDoc[this.panYFieldKey + '_reset'] = this.dataDoc[this.panYFieldKey]; + this.dataDoc[this.scaleFieldKey + '_reset'] = this.dataDoc[this.scaleFieldKey]; } }; 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.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, 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; @@ -1665,11 +1674,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' }); } @@ -1680,10 +1689,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; @@ -1698,25 +1706,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; @action dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean = true, visited = new Set<Doc>()) => { - if (visited.has(this.rootDoc)) return; - visited.add(this.rootDoc); - showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document._isGroup)); + if (visited.has(this.Document)) return; + 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); const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to activeDocs - .filter(doc => doc._isGroup && SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc)) + .filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc)) .forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited)); const horizLines: number[] = []; const vertLines: number[] = []; const invXf = this.screenToLocalXf.inverse(); snappableDocs - .filter(doc => !doc._isGroup && (snapToDraggedDoc || (SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc)))) + .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc)))) .forEach(doc => { const { left, top, width, height } = docDims(doc); const topLeftInScreen = invXf.transformPoint(left, top); @@ -1731,7 +1739,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++) { @@ -1745,28 +1753,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} @@ -1784,15 +1792,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.incrementalRender(); return ( <CollectionFreeFormPannableContents - rootDoc={this.rootDoc} + Document={this.Document} 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?.()?.rootDoc._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> ); } @@ -1800,10 +1808,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection TraceMobx(); return ( <MarqueeView - {...this.props} + {...this._props} ref={this._marqueeViewRef} - ungroup={this.rootDoc._isGroup ? this.promoteCollection : undefined} - nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge} + ungroup={this.Document.isGroup ? this.promoteCollection : undefined} + nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge} addDocTab={this.addDocTab} slowLoadDocuments={this.slowLoadDocuments} trySelectCluster={this.trySelectCluster} @@ -1811,25 +1819,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.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; @@ -1846,13 +1854,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.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_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 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(); }; _oldWheel: any; render() { @@ -1875,18 +1883,18 @@ 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.IsDragging ? '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} - DataDoc={undefined} + TemplateDataDocument={undefined} PanelWidth={this.lightboxPanelWidth} PanelHeight={this.lightboxPanelHeight} NativeWidth={returnZero} @@ -1898,8 +1906,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} @@ -1909,7 +1917,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" />} </> )} @@ -1929,16 +1937,16 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY const browseTransitionTime = 500; SelectionManager.DeselectAll(); dv && - DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => { + DocumentManager.Instance.showDocument(dv.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => { if (!focused) { - const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; + const selfFfview = !dv.Document.isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; let containers = dv.props.docViewPath(); let parFfview = dv.CollectionFreeFormView; for (var cont of containers) { parFfview = parFfview ?? cont.CollectionFreeFormView; } - while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView; - const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview + while (parFfview?.Document.isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView; + const ffview = selfFfview && selfFfview.layoutDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview ffview?.zoomSmoothlyAboutPt(ffview.screenToLocalXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime); Doc.linkFollowHighlight(dv?.props.Document, false); } @@ -1946,68 +1954,31 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY } ScriptingGlobals.add(CollectionBrowseClick); ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { - !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); + !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); }); ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { - !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); + !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) { - const selView = SelectionManager.Views(); + const selView = SelectionManager.Views; if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent'; runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.())); }); ScriptingGlobals.add(function pinWithView(pinContent: boolean) { - SelectionManager.Views().forEach(view => - view.props.pinToPres(view.rootDoc, { - currentFrame: Cast(view.rootDoc.currentFrame, 'number', null), + SelectionManager.Views.forEach(view => + view.props.pinToPres(view.Document, { + currentFrame: Cast(view.Document.currentFrame, 'number', null), pinData: { poslayoutview: pinContent, dataview: pinContent, }, - pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()), + pinViewport: MarqueeView.CurViewBounds(view.Document, view.props.PanelWidth(), view.props.PanelHeight()), }) ); }); ScriptingGlobals.add(function bringToFront() { - SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc)); + SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document)); }); ScriptingGlobals.add(function sendToBack(doc: Doc) { - SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc, true)); + SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document, true)); }); -ScriptingGlobals.add(function datavizFromSchema(doc: Doc) { - SelectionManager.Views().forEach(view => { - if (!view.layoutDoc.schema_columnKeys){ - view.layoutDoc.schema_columnKeys = new List<string>(['title', 'type', 'author', 'author_date']) - } - const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key!="text"); - if (!keys) return; - - const children = DocListCast(view.rootDoc[Doc.LayoutFieldKey(view.rootDoc)]); - let csvRows = []; - csvRows.push(keys.join(',')); - for (let i=0; i < children.length; i++) { - let eachRow = []; - for (let j=0; j<keys.length; j++){ - var cell = children[i][keys[j]]; - if (cell && cell as string) cell = cell.toString().replace(/\,/g, ''); - eachRow.push(cell); - } - csvRows.push(eachRow); - } - const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' }); - const options = { x:0, y:-300, title: 'schemaTable', _width: 300, _height: 100, type: 'text/csv' }; - const file = new File([blob], 'schemaTable', options); - const loading = Docs.Create.LoadingDocument(file, options); - loading.presentation_openInLightbox = true; - DocUtils.uploadFileToDoc(file, {}, loading); - - if (view.ComponentView?.addDocument) { - // loading.dataViz_fromSchema = true; - loading.dataViz_asSchema = view.layoutDoc; - SchemaCSVPopUp.Instance.setView(view); - SchemaCSVPopUp.Instance.setTarget(view.layoutDoc); - SchemaCSVPopUp.Instance.setDataVizDoc(loading); - SchemaCSVPopUp.Instance.setVisible(true); - } - }) -});
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 607f9fb95..79cc534dc 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -1,14 +1,11 @@ -import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { IconButton } from 'browndash-components'; +import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import { unimplementedFunction } from '../../../../Utils'; -import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; -import { IconButton } from 'browndash-components'; -import { StrCast } from '../../../../fields/Types'; -import { Doc } from '../../../../fields/Doc'; -import { computed } from 'mobx'; import { SettingsManager } from '../../../util/SettingsManager'; +import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; @observer export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -21,9 +18,9 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { public hideMarquee: () => void = unimplementedFunction; public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public isShown = () => this._opacity > 0; - constructor(props: Readonly<{}>) { + constructor(props: any) { super(props); - + makeObservable(this); MarqueeOptionsMenu.Instance = this; } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 2092ecb7a..2c65726b2 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,5 +1,7 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Utils, intersectRect, lightOrDark, returnFalse } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; @@ -7,25 +9,24 @@ import { InkData, InkField, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { RichTextField } from '../../../../fields/RichTextField'; import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; -import { ImageField, nullAudio } from '../../../../fields/URLField'; +import { ImageField } from '../../../../fields/URLField'; import { GetEffectiveAcl } from '../../../../fields/util'; -import { intersectRect, lightOrDark, returnFalse, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; -import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; import { DocumentType } from '../../../documents/DocumentTypes'; +import { DocUtils, Docs, DocumentOptions } from '../../../documents/Documents'; import { SelectionManager } from '../../../util/SelectionManager'; +import { freeformScrollMode } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; -import { undoBatch, UndoManager } from '../../../util/UndoManager'; +import { UndoManager, undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { PreviewCursor } from '../../PreviewCursor'; import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; -import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; -import { PreviewCursor } from '../../PreviewCursor'; +import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { SubCollectionViewProps } from '../CollectionSubView'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; import './MarqueeView.scss'; -import React = require('react'); -import { freeformScrollMode } from '../../../util/SettingsManager'; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -51,12 +52,17 @@ export interface MarqueeViewBounds { } @observer -export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> { +export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps & MarqueeViewProps> { public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) { const ps = NumCast(pinDoc._freeform_scale, 1); return { left: NumCast(pinDoc._freeform_panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._freeform_panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps }; } + constructor(props: any) { + super(props); + makeObservable(this); + } + private _commandExecuted = false; @observable _lastX: number = 0; @observable _lastY: number = 0; @@ -67,7 +73,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 +85,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } componentDidMount() { - this.props.setPreviewCursor?.(this.setPreviewCursor); + this._props.setPreviewCursor?.(this.setPreviewCursor); } @action @@ -101,20 +107,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 +142,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 +153,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 +165,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(); @@ -169,10 +175,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this.pasteTable(ns, x, y); })(); e.stopPropagation(); - } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) { - FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : ''; + } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views.length < 2) { + 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 +209,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 +220,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 +249,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 +291,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 +306,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 +317,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,22 +338,22 @@ 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 ? creator(selected, { title: 'nested stack' }) : ((doc: Doc) => { Doc.GetProto(doc).data = new List<Doc>(selected); + Doc.GetProto(doc).isGroup = makeGroup; Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform'; doc._freeform_panX = doc._freeform_panY = 0; return doc; @@ -355,7 +361,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque newCollection.isSystem = undefined; newCollection._width = this.Bounds.width; newCollection._height = this.Bounds.height; - newCollection._isGroup = makeGroup; newCollection._dragWhenActive = makeGroup; newCollection.x = this.Bounds.left; newCollection.y = this.Bounds.top; @@ -366,17 +371,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 +389,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 +473,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 +501,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 +583,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 +602,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 +611,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 +621,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 +636,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 +662,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 274012000..1e19964d7 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -1,4 +1,4 @@ -import { action, computed, Lambda, observable, reaction } from 'mobx'; +import { action, computed, Lambda, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, Opt } from '../../../../fields/Doc'; @@ -27,6 +27,11 @@ export class CollectionGridView extends CollectionSubView() { @observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll private dropLocation: object = {}; // sets the drop location for external drops + constructor(props: any) { + super(props); + makeObservable(this); + } + onChildClickHandler = () => ScriptCast(this.Document.onChildClick); @computed get numCols() { @@ -174,7 +179,7 @@ export class CollectionGridView extends CollectionSubView() { } isContentActive = () => this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : undefined); + isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined); /** * * @param layout @@ -191,7 +196,7 @@ export class CollectionGridView extends CollectionSubView() { NativeHeight={returnZero} setContentView={emptyFunction} Document={layout} - DataDoc={layout.resolvedDataDoc as Doc} + TemplateDataDocument={layout.resolvedDataDoc as Doc} isContentActive={this.isChildContentActive} PanelWidth={width} PanelHeight={height} @@ -199,7 +204,7 @@ export class CollectionGridView extends CollectionSubView() { whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} onClick={this.onChildClickHandler} renderDepth={this.props.renderDepth + 1} - dontCenter={this.props.Document.centerY ? undefined : 'y'} + dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy' /> ); } @@ -277,7 +282,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 +296,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, {}); @@ -350,7 +353,7 @@ export class CollectionGridView extends CollectionSubView() { undoBatch( action(() => { const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 }); - FormattedTextBox.SelectOnLoad = text[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed + FormattedTextBox.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed Doc.AddDocToList(this.props.Document, this.props.fieldKey, text); this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY)))); }) diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss index d0c14a21d..b8ceec139 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss @@ -1,4 +1,4 @@ -@import '../../global/globalCssVariables'; +@import '../../global/globalCssVariables.module.scss'; @import '../../_nodeModuleOverrides'; .collectionLinearView { @@ -18,9 +18,9 @@ user-select: none; } - > input:not(:checked) ~ &.true { - background-color: transparent; - } + // > input:not(:checked) ~ &.true { + // background-color: transparent; + // } .collectionLinearView { display: flex; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 4267a9059..f1fb68003 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -1,24 +1,24 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { Tooltip } from '@mui/material'; import { Toggle, ToggleType, Type } from 'browndash-components'; -import { action, IReactionDisposer, observable, reaction } from 'mobx'; +import { IReactionDisposer, action, makeObservable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { Utils, emptyFunction, returnEmptyDoclist, returnTrue } from '../../../../Utils'; 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 { CollectionViewType } from '../../../documents/DocumentTypes'; import { BranchingTrailManager } from '../../../util/BranchingTrailManager'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; +import { UndoStack } from '../../UndoStack'; import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { DocumentView } from '../../nodes/DocumentView'; import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup'; -import { UndoStack } from '../../UndoStack'; import { CollectionStackedTimeline } from '../CollectionStackedTimeline'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionLinearView.scss'; @@ -33,12 +33,15 @@ import './CollectionLinearView.scss'; */ @observer export class CollectionLinearView extends CollectionSubView() { - @observable public addMenuToggle = React.createRef<HTMLInputElement>(); - @observable private _selectedIndex = -1; private _dropDisposer?: DragManager.DragDropDisposer; private _widthDisposer?: IReactionDisposer; private _selectedDisposer?: IReactionDisposer; + constructor(props: any) { + super(props); + makeObservable(this); + } + componentWillUnmount() { this._dropDisposer?.(); this._widthDisposer?.(); @@ -48,7 +51,7 @@ export class CollectionLinearView extends CollectionSubView() { componentDidMount() { this._widthDisposer = reaction( - () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0), + () => 5 + NumCast(this.dataDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0), width => this.childDocs.length && (this.layoutDoc._width = width), { fireImmediately: true } ); @@ -58,7 +61,7 @@ export class CollectionLinearView extends CollectionSubView() { if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); }; - dimension = () => NumCast(this.rootDoc._height); + dimension = () => NumCast(this.layoutDoc._height); getTransform = (ele: Opt<HTMLDivElement>) => { if (!ele) return Transform.Identity(); const { scale, translateX, translateY } = Utils.GetScreenTransform(ele); @@ -126,8 +129,8 @@ export class CollectionLinearView extends CollectionSubView() { Currently playing: {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( <> - <span className="audio-title" onPointerDown={() => DocumentManager.Instance.showDocument(clip.rootDoc, { willZoomCentered: true })}> - {clip.rootDoc.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? ' ' : ',')} + <span className="audio-title" onPointerDown={() => DocumentManager.Instance.showDocument(clip.Document, { willZoomCentered: true })}> + {clip.Document.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? ' ' : ',')} </span> <FontAwesomeIcon icon={!clip.ComponentView?.IsPlaying?.() ? 'play' : 'pause'} size="lg" onPointerDown={() => clip.ComponentView?.TogglePause?.()} />{' '} <FontAwesomeIcon icon="times" size="lg" onPointerDown={() => clip.ComponentView?.Pause?.()} />{' '} @@ -170,28 +173,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} - rootSelected={this.props.isSelected} - removeDocument={this.props.removeDocument} + dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType} + rootSelected={this.rootSelected} + removeDocument={this._props.removeDocument} ScreenToLocalTransform={docXf} PanelWidth={doc[Width]} PanelHeight={nested || doc._height ? doc[Height] : this.dimension} - renderDepth={this.props.renderDepth + 1} - dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)} + 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 +208,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} @@ -215,7 +218,7 @@ export class CollectionLinearView extends CollectionSubView() { toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)} onClick={() => { this.layoutDoc.linearView_IsOpen = !isExpanded; - ScriptCast(this.rootDoc.onClick)?.script.run({ this: this.rootDoc }, console.log); + ScriptCast(this.Document.onClick)?.script.run({ this: this.Document }, console.log); }} tooltip={isExpanded ? 'Close' : 'Open'} fillWidth={true} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss index cb0d5e03f..f983fd815 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss @@ -1,6 +1,6 @@ .collectionMulticolumnView_contents { display: flex; - overflow: hidden; + //overflow: hidden; // bcz: turned of to allow highlighting to appear when there is no border (e.g, for a component of the slide template) width: 100%; height: 100%; @@ -17,7 +17,7 @@ } .contentFittingDocumentView { - width: unset; + margin: auto; } .label-wrapper { diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index c2586fb4b..e12bcd8b0 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -1,7 +1,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { Button } from 'browndash-components'; -import { action, computed } from 'mobx'; +import { action, computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../../fields/Doc'; @@ -37,6 +37,11 @@ const resizerWidth = 8; @observer export class CollectionMulticolumnView extends CollectionSubView() { + constructor(props: any) { + super(props); + makeObservable(this); + } + /** * @returns the list of layout documents whose width unit is * *, denoting that it will be displayed with a ratio, not fixed pixel, value @@ -122,7 +127,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { private get totalRatioAllocation(): number | undefined { const layoutInfoLen = this.resolvedLayoutInformation.widthSpecifiers.length; if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) { - return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._xMargin); + return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._xMargin); } } @@ -184,7 +189,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { let offset = 0; for (const { layout: candidate } of this.childLayoutPairs) { if (candidate === layout) { - return this.props.ScreenToLocalTransform().translate(-offset / (this.props.NativeDimScaling?.() || 1), 0); + return this._props.ScreenToLocalTransform().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0); } offset += this.lookupPixels(candidate) + resizerWidth; } @@ -192,7 +197,6 @@ export class CollectionMulticolumnView extends CollectionSubView() { }; @undoBatch - @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { let dropInd = -1; if (de.complete.docDragData && this._mainCont) { @@ -219,8 +223,8 @@ export class CollectionMulticolumnView extends CollectionSubView() { if (this.childDocs.includes(d)) { if (dropInd > this.childDocs.indexOf(d)) dropInd--; } - Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d); - Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1); + Doc.RemoveDocFromList(this.dataDoc, this._props.fieldKey, d); + Doc.AddDocToList(this.dataDoc, this._props.fieldKey, d, DocListCast(this.dataDoc[this._props.fieldKey])[dropInd], undefined, dropInd === -1); } }) ); @@ -233,51 +237,58 @@ export class CollectionMulticolumnView extends CollectionSubView() { onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); - isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); + isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); isChildContentActive = () => { - const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive; - return this.props.isContentActive?.() === false || childDocsActive === false + const childDocsActive = this._props.childDocumentsActive?.() ?? this.Document.childDocumentsActive; + return this._props.isContentActive?.() === false || childDocsActive === false ? false // - : this.props.isDocumentActive?.() && childDocsActive - ? true - : undefined; + : this._props.isDocumentActive?.() && childDocsActive + ? true + : undefined; }; - getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number, shouldNotScale: () => boolean) => { + getDisplayDoc = (childLayout: Doc) => { + const width = () => this.lookupPixels(childLayout); + const height = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0); + const dxf = () => + this.lookupIndividualTransform(childLayout) + .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin)) + .scale(this._props.NativeDimScaling?.() || 1); + const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(childLayout.freeform_fitContentsToBox); return ( <DocumentView - Document={layout} - DataDoc={layout.resolvedDataDoc as Doc} - styleProvider={this.props.styleProvider} - docViewPath={this.props.docViewPath} - LayoutTemplate={this.props.childLayoutTemplate} - LayoutTemplateString={this.props.childLayoutString} - renderDepth={this.props.renderDepth + 1} + Document={childLayout} + TemplateDataDocument={childLayout.resolvedDataDoc as Doc} + styleProvider={this._props.styleProvider} + docViewPath={this._props.docViewPath} + LayoutTemplate={this._props.childLayoutTemplate} + LayoutTemplateString={this._props.childLayoutString} + renderDepth={this._props.renderDepth + 1} PanelWidth={width} PanelHeight={height} - shouldNotScale={shouldNotScale} rootSelected={this.rootSelected} - dragAction={(this.props.Document.childDragAction ?? this.props.childDragAction) as dropActionType} + dragAction={(this._props.Document.childDragAction ?? this._props.childDragAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} suppressSetHeight={true} ScreenToLocalTransform={dxf} isContentActive={this.isChildContentActive} - isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive} - hideResizeHandles={this.props.childHideResizeHandles?.()} - hideDecorationTitle={this.props.childHideDecorationTitle?.()} - fitContentsToBox={this.props.fitContentsToBox} - focus={this.props.focus} + isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} + hideResizeHandles={childLayout.layout_fitWidth || this._props.childHideResizeHandles ? true : false} + hideDecorationTitle={this._props.childHideDecorationTitle} + fitContentsToBox={this._props.fitContentsToBox} + focus={this._props.focus} childFilters={this.childDocFilters} childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - dontRegisterView={this.props.dontRegisterView} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - removeDocument={this.props.removeDocument} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} + dontRegisterView={this._props.dontRegisterView} + addDocument={this._props.addDocument} + moveDocument={this._props.moveDocument} + removeDocument={this._props.removeDocument} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} + addDocTab={this._props.addDocTab} + pinToPres={this._props.pinToPres} bringToFront={returnFalse} + dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy' /> ); }; @@ -288,34 +299,23 @@ export class CollectionMulticolumnView extends CollectionSubView() { @computed private get contents(): JSX.Element[] | null { const { childLayoutPairs } = this; - const { Document, PanelHeight } = this.props; const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; - const aspect = Doc.NativeAspect(layout, undefined, true); - const width = () => this.lookupPixels(layout); - const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); - const docwidth = () => (layout._layout_reflowHorizontal ? width() : Math.min(height() * aspect, width())); - const docheight = () => Math.min(docwidth() / aspect, height()); - const dxf = () => - this.lookupIndividualTransform(layout) - .translate(-NumCast(Document._xMargin) - (width() - docwidth()) / 2, -NumCast(Document._yMargin)) - .scale(this.props.NativeDimScaling?.() || 1); - const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox); collector.push( - <Tooltip title={'Tab: ' + StrCast(layout.title)}> - <div className="document-wrapper" key={'wrapper' + i} style={{ width: width() }}> - {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)} - <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this.props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} /> - <WidthLabel layout={layout} collectionDoc={Document} /> + <Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}> + <div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}> + {this.getDisplayDoc(layout)} + <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} /> + <WidthLabel layout={layout} collectionDoc={this.Document} /> </div> </Tooltip>, <ResizeBar width={resizerWidth} key={'resizer' + i} - styleProvider={this.props.styleProvider} - isContentActive={this.props.isContentActive} - select={this.props.select} + styleProvider={this._props.styleProvider} + isContentActive={this._props.isContentActive} + select={this._props.select} columnUnitLength={this.getColumnUnitLength} toLeft={layout} toRight={childLayoutPairs[i + 1]?.layout} @@ -326,18 +326,18 @@ export class CollectionMulticolumnView extends CollectionSubView() { return collector; } - render(): JSX.Element { + render() { return ( <div - className={'collectionMulticolumnView_contents'} + className="collectionMulticolumnView_contents" ref={this.createDashEventsTarget} style={{ - width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`, - height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`, - marginLeft: NumCast(this.props.Document._xMargin), - marginRight: NumCast(this.props.Document._xMargin), - marginTop: NumCast(this.props.Document._yMargin), - marginBottom: NumCast(this.props.Document._yMargin), + width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`, + height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`, + marginLeft: NumCast(this.Document._xMargin), + marginRight: NumCast(this.Document._xMargin), + marginTop: NumCast(this.Document._yMargin), + marginBottom: NumCast(this.Document._yMargin), }}> {this.contents} </div> diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss index ec7200a03..f44eacb2a 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss @@ -1,6 +1,6 @@ .collectionMultirowView_contents { display: flex; - overflow: hidden; + //overflow: hidden; // bcz: turned of to allow highlighting to appear when there is no border (e.g, for a component of the slide template) width: 100%; height: 100%; flex-direction: column; @@ -10,11 +10,6 @@ flex-direction: row; height: 100%; align-items: center; - margin: auto; - - .contentFittingDocumentView { - height: unset; - } .label-wrapper { display: flex; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 249c3551b..7dbc18e60 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -1,4 +1,4 @@ -import { action, computed } from 'mobx'; +import { action, computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../../fields/Doc'; @@ -33,6 +33,11 @@ const resizerHeight = 8; @observer export class CollectionMultirowView extends CollectionSubView() { + constructor(props: any) { + super(props); + makeObservable(this); + } + /** * @returns the list of layout documents whose width unit is * *, denoting that it will be displayed with a ratio, not fixed pixel, value @@ -118,7 +123,7 @@ export class CollectionMultirowView extends CollectionSubView() { private get totalRatioAllocation(): number | undefined { const layoutInfoLen = this.resolvedLayoutInformation.heightSpecifiers.length; if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) { - return this.props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._yMargin); + return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._yMargin); } } @@ -180,7 +185,7 @@ export class CollectionMultirowView extends CollectionSubView() { let offset = 0; for (const { layout: candidate } of this.childLayoutPairs) { if (candidate === layout) { - return this.props.ScreenToLocalTransform().translate(0, -offset / (this.props.NativeDimScaling?.() || 1)); + return this._props.ScreenToLocalTransform().translate(0, -offset / (this._props.NativeDimScaling?.() || 1)); } offset += this.lookupPixels(candidate) + resizerHeight; } @@ -188,7 +193,6 @@ export class CollectionMultirowView extends CollectionSubView() { }; @undoBatch - @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { let dropInd = -1; if (de.complete.docDragData && this._mainCont) { @@ -215,8 +219,8 @@ export class CollectionMultirowView extends CollectionSubView() { if (this.childDocs.includes(d)) { if (dropInd > this.childDocs.indexOf(d)) dropInd--; } - Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d); - Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1); + Doc.RemoveDocFromList(this.dataDoc, this._props.fieldKey, d); + Doc.AddDocToList(this.dataDoc, this._props.fieldKey, d, DocListCast(this.dataDoc[this._props.fieldKey])[dropInd], undefined, dropInd === -1); } }) ); @@ -229,51 +233,58 @@ export class CollectionMultirowView extends CollectionSubView() { onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); - isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); + isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); isChildContentActive = () => { - const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive; - return this.props.isContentActive?.() === false || childDocsActive === false + const childDocsActive = this._props.childDocumentsActive?.() ?? this.Document.childDocumentsActive; + return this._props.isContentActive?.() === false || childDocsActive === false ? false // - : this.props.isDocumentActive?.() && childDocsActive - ? true - : undefined; + : this._props.isDocumentActive?.() && childDocsActive + ? true + : undefined; }; - getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number, shouldNotScale: () => boolean) => { + getDisplayDoc = (layout: Doc) => { + const height = () => this.lookupPixels(layout); + const width = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0); + const dxf = () => + this.lookupIndividualTransform(layout) + .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin)) + .scale(this._props.NativeDimScaling?.() || 1); + const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox); return ( <DocumentView Document={layout} - DataDoc={layout.resolvedDataDoc as Doc} - styleProvider={this.props.styleProvider} - docViewPath={this.props.docViewPath} - LayoutTemplate={this.props.childLayoutTemplate} - LayoutTemplateString={this.props.childLayoutString} - renderDepth={this.props.renderDepth + 1} + TemplateDataDocument={layout.resolvedDataDoc as Doc} + styleProvider={this._props.styleProvider} + docViewPath={this._props.docViewPath} + LayoutTemplate={this._props.childLayoutTemplate} + LayoutTemplateString={this._props.childLayoutString} + renderDepth={this._props.renderDepth + 1} PanelWidth={width} PanelHeight={height} - shouldNotScale={shouldNotScale} rootSelected={this.rootSelected} - dropAction={StrCast(this.rootDoc.childDragAction) as dropActionType} + dropAction={StrCast(this.Document.childDragAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} ScreenToLocalTransform={dxf} isContentActive={this.isChildContentActive} - isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive} - hideResizeHandles={this.props.childHideResizeHandles?.()} - hideDecorationTitle={this.props.childHideDecorationTitle?.()} - fitContentsToBox={this.props.fitContentsToBox} - dragAction={this.props.childDragAction} - focus={this.props.focus} + isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} + hideResizeHandles={layout.layout_fitWidth || this._props.childHideResizeHandles ? true : false} + hideDecorationTitle={this._props.childHideDecorationTitle} + fitContentsToBox={this._props.fitContentsToBox} + dragAction={this._props.childDragAction} + focus={this._props.focus} childFilters={this.childDocFilters} childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - dontRegisterView={this.props.dontRegisterView} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - removeDocument={this.props.removeDocument} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} + dontRegisterView={this._props.dontRegisterView} + addDocument={this._props.addDocument} + moveDocument={this._props.moveDocument} + removeDocument={this._props.removeDocument} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} + addDocTab={this._props.addDocTab} + pinToPres={this._props.pinToPres} bringToFront={returnFalse} + dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy' /> ); }; @@ -284,29 +295,18 @@ export class CollectionMultirowView extends CollectionSubView() { @computed private get contents(): JSX.Element[] | null { const { childLayoutPairs } = this; - const { Document, PanelWidth } = this.props; const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; - const aspect = Doc.NativeAspect(layout, undefined, true); - const height = () => this.lookupPixels(layout); - const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); - const docheight = () => Math.min(width() / aspect, height()); - const docwidth = () => (layout._layout_reflowHorizontal ? width() : Math.min(width(), docheight() * aspect)); - const dxf = () => - this.lookupIndividualTransform(layout) - .translate(-NumCast(Document._xMargin) - (width() - docwidth()) / 2, -NumCast(Document._yMargin) - (height() - docheight()) / 2) - .scale(this.props.NativeDimScaling?.() || 1); - const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox); collector.push( - <div className="document-wrapper" style={{ height: height() }} key={'wrapper' + i}> - {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)} - <HeightLabel layout={layout} collectionDoc={Document} /> + <div className="document-wrapper" style={{ flexDirection: 'row', height: this.lookupPixels(layout) }} key={'wrapper' + i}> + {this.getDisplayDoc(layout)} + <HeightLabel layout={layout} collectionDoc={this.Document} /> </div>, <ResizeBar height={resizerHeight} - styleProvider={this.props.styleProvider} - isContentActive={this.props.isContentActive} + styleProvider={this._props.styleProvider} + isContentActive={this._props.isContentActive} key={'resizer' + i} columnUnitLength={this.getRowUnitLength} toTop={layout} @@ -318,17 +318,17 @@ export class CollectionMultirowView extends CollectionSubView() { return collector; } - render(): JSX.Element { + render() { return ( <div - className={'collectionMultirowView_contents'} + className="collectionMultirowView_contents" style={{ - width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`, - height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`, - marginLeft: NumCast(this.props.Document._xMargin), - marginRight: NumCast(this.props.Document._xMargin), - marginTop: NumCast(this.props.Document._yMargin), - marginBottom: NumCast(this.props.Document._yMargin), + width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`, + height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`, + marginLeft: NumCast(this.Document._xMargin), + marginRight: NumCast(this.Document._xMargin), + marginTop: NumCast(this.Document._yMargin), + marginBottom: NumCast(this.Document._yMargin), }} ref={this.createDashEventsTarget}> {this.contents} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 76bd392a5..02131ae22 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -1,4 +1,4 @@ -@import '../../global/globalCssVariables.scss'; +@import '../../global/globalCssVariables.module.scss'; .collectionSchemaView { cursor: default; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 190e4ff2a..2546f5b02 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,7 +1,7 @@ -import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, ObservableMap, observe } from 'mobx'; +import { action, computed, makeObservable, observable, ObservableMap, observe } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; @@ -18,6 +18,7 @@ import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; import { DocFocusOptions, DocumentView, DocumentViewProps } from '../../nodes/DocumentView'; import { KeyValueBox } from '../../nodes/KeyValueBox'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DefaultStyleProvider, StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; @@ -57,6 +58,11 @@ export class CollectionSchemaView extends CollectionSubView() { private _tableContentRef: HTMLDivElement | null = null; private _menuTarget = React.createRef<HTMLDivElement>(); + constructor(props: any) { + super(props); + makeObservable(this); + } + static _rowHeight: number = 50; static _rowSingleLineHeight: number = 32; public static _minColWidth: number = 25; @@ -68,16 +74,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() { @@ -85,12 +91,12 @@ export class CollectionSchemaView extends CollectionSubView() { } @computed get _selectedDocs() { - const selected = SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.rootDoc)); + const selected = SelectionManager.Docs.filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document)); if (!selected.length) { - for (const sel of SelectionManager.Docs()) { + for (const sel of SelectionManager.Docs) { const contextPath = DocumentManager.GetContextPath(sel, true); - if (contextPath.includes(this.rootDoc)) { - const parentInd = contextPath.indexOf(this.rootDoc); + if (contextPath.includes(this.Document)) { + const parentInd = contextPath.indexOf(this.Document); return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : []; } } @@ -107,7 +113,7 @@ export class CollectionSchemaView extends CollectionSubView() { } @computed get tableWidth() { - return this.props.PanelWidth() - this.previewWidth - (this.previewWidth === 0 ? 0 : CollectionSchemaView._previewDividerWidth); + return this._props.PanelWidth() - this.previewWidth - (this.previewWidth === 0 ? 0 : CollectionSchemaView._previewDividerWidth); } @computed get columnKeys() { @@ -139,14 +145,13 @@ export class CollectionSchemaView extends CollectionSubView() { return BoolCast(this.layoutDoc.sortDesc); } - @action componentDidMount() { - this.props.setContentView?.(this); + this._props.setContentView?.(this); document.addEventListener('keydown', this.onKeyDown); Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1])); this._keysDisposer = observe( - this.rootDoc[this.fieldKey ?? 'data'] as List<Doc>, + this.dataDoc[this.fieldKey ?? 'data'] as List<Doc>, change => { switch (change.type as any) { case 'splice': @@ -154,7 +159,7 @@ export class CollectionSchemaView extends CollectionSubView() { (change as any).added.forEach((doc: Doc) => // for each document added Doc.GetAllPrototypes(doc.value as Doc).forEach(proto => // for all of its prototypes (and itself) Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them - !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo('')))))); + !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(key, key === 'author')))))); break; case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list } @@ -239,7 +244,6 @@ export class CollectionSchemaView extends CollectionSubView() { }; @undoBatch - @action setColumnSort = (field: string | undefined, desc: boolean = false) => { this.layoutDoc.sortField = field; this.layoutDoc.sortDesc = desc; @@ -248,7 +252,6 @@ export class CollectionSchemaView extends CollectionSubView() { addRow = (doc: Doc | Doc[]) => this.addDocument(doc); @undoBatch - @action changeColumnKey = (index: number, newKey: string, defaultVal?: any) => { if (!this.documentKeys.includes(newKey)) { this.addNewKey(newKey, defaultVal); @@ -260,7 +263,6 @@ export class CollectionSchemaView extends CollectionSubView() { }; @undoBatch - @action addColumn = (key: string, defaultVal?: any) => { if (!this.documentKeys.includes(key)) { this.addNewKey(key, defaultVal); @@ -281,7 +283,6 @@ export class CollectionSchemaView extends CollectionSubView() { addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal)); @undoBatch - @action removeColumn = (index: number) => { if (this.columnKeys.length === 1) return; const currWidths = this.storedColumnWidths.slice(); @@ -320,8 +321,8 @@ export class CollectionSchemaView extends CollectionSubView() { change = this._displayColumnWidths[shrinking] - CollectionSchemaView._minColWidth; } - this._displayColumnWidths[shrinking] -= change * this.props.ScreenToLocalTransform().Scale; - this._displayColumnWidths[growing] += change * this.props.ScreenToLocalTransform().Scale; + this._displayColumnWidths[shrinking] -= change * this._props.ScreenToLocalTransform().Scale; + this._displayColumnWidths[growing] += change * this._props.ScreenToLocalTransform().Scale; return false; } @@ -335,7 +336,6 @@ export class CollectionSchemaView extends CollectionSubView() { }; @undoBatch - @action moveColumn = (fromIndex: number, toIndex: number) => { let currKeys = this.columnKeys.slice(); currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]); @@ -379,7 +379,7 @@ export class CollectionSchemaView extends CollectionSubView() { @action highlightDropColumn = (e: PointerEvent) => { e.stopPropagation(); - const mouseX = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0]; + const mouseX = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0]; const index = this.findDropIndex(mouseX); this._colEles.forEach((colRef, i) => { let leftStyle = ''; @@ -416,8 +416,8 @@ export class CollectionSchemaView extends CollectionSubView() { @action clearSelection = () => SelectionManager.DeselectAll(); - selectRows = (rootDoc: Doc, lastSelected: Doc) => { - const index = this.rowIndex(rootDoc); + selectRows = (doc: Doc, lastSelected: Doc) => { + const index = this.rowIndex(doc); const lastSelectedRow = this.rowIndex(lastSelected); const startRow = Math.min(lastSelectedRow, index); const endRow = Math.max(lastSelectedRow, index); @@ -437,10 +437,9 @@ 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]; + const mouseX = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0]; const index = this.findDropIndex(mouseX); this.moveColumn(de.complete.columnDragData.colIndex, index ?? de.complete.columnDragData.colIndex); @@ -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))))); }; @@ -485,7 +483,7 @@ export class CollectionSchemaView extends CollectionSubView() { const nativeWidth = this._previewRef!.getBoundingClientRect(); const minWidth = 40; const maxWidth = 1000; - const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0]; + const movedWidth = this._props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0]; const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth; this.layoutDoc.schema_previewWidth = width; return false; @@ -509,8 +507,8 @@ export class CollectionSchemaView extends CollectionSubView() { const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { const rect = found.getBoundingClientRect(); - const localRect = this.props.ScreenToLocalTransform().transformBounds(rect.left, rect.top, rect.width, rect.height); - if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this.props.PanelHeight()) { + const localRect = this._props.ScreenToLocalTransform().transformBounds(rect.left, rect.top, rect.width, rect.height); + if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this._props.PanelHeight()) { let focusSpeed = options.zoomTime ?? 50; smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc); return focusSpeed; @@ -757,7 +755,7 @@ export class CollectionSchemaView extends CollectionSubView() { @computed get renderFilterOptions() { const keyOptions: string[] = []; const columnKey = this.columnKeys[this._filterColumnIndex!]; - const allDocs = DocListCast(this.dataDoc[this.props.fieldKey]); + const allDocs = DocListCast(this.dataDoc[this._props.fieldKey]); allDocs.forEach(doc => { const value = StrCast(doc[columnKey]); if (!keyOptions.includes(value) && value !== '' && (this._filterSearchValue === '' || value.includes(this._filterSearchValue))) { @@ -781,9 +779,9 @@ export class CollectionSchemaView extends CollectionSubView() { onClick={e => e.stopPropagation()} onChange={action(e => { if (e.target.checked) { - Doc.setDocFilter(this.props.Document, columnKey, key, 'check'); + Doc.setDocFilter(this.Document, columnKey, key, 'check'); } else { - Doc.setDocFilter(this.props.Document, columnKey, key, 'remove'); + Doc.setDocFilter(this.Document, columnKey, key, 'remove'); } })} checked={bool} @@ -830,8 +828,8 @@ export class CollectionSchemaView extends CollectionSubView() { } rowHeightFunc = () => (BoolCast(this.layoutDoc._schema_singleLine) ? CollectionSchemaView._rowSingleLineHeight : CollectionSchemaView._rowHeight); sortedDocsFunc = () => this.sortedDocs; - isContentActive = () => this.props.isSelected() || this.props.isContentActive(); - screenToLocal = () => this.props.ScreenToLocalTransform().translate(-this.tableWidth, 0); + isContentActive = () => this._props.isSelected() || this._props.isContentActive(); + screenToLocal = () => this._props.ScreenToLocalTransform().translate(-this.tableWidth, 0); previewWidthFunc = () => this.previewWidth; render() { return ( @@ -840,7 +838,7 @@ export class CollectionSchemaView extends CollectionSubView() { <div className="schema-table" style={{ width: `calc(100% - ${this.previewWidth}px)` }} - onWheel={e => this.props.isContentActive() && e.stopPropagation()} + onWheel={e => this._props.isContentActive() && e.stopPropagation()} ref={r => { // prevent wheel events from passively propagating up through containers r?.addEventListener('wheel', (e: WheelEvent) => {}, { passive: false }); @@ -866,7 +864,7 @@ export class CollectionSchemaView extends CollectionSubView() { openContextMenu={this.openContextMenu} dragColumn={this.dragColumn} setColRef={this.setColRef} - isContentActive={this.props.isContentActive} + isContentActive={this._props.isContentActive} /> ))} </div> @@ -892,16 +890,15 @@ export class CollectionSchemaView extends CollectionSubView() { {Array.from(this._selectedDocs).lastElement() && ( <DocumentView Document={Array.from(this._selectedDocs).lastElement()} - DataDoc={undefined} fitContentsToBox={returnTrue} dontCenter={'y'} onClickScriptDisable="always" focus={emptyFunction} defaultDoubleClick={returnIgnore} - renderDepth={this.props.renderDepth + 1} + renderDepth={this._props.renderDepth + 1} rootSelected={this.rootSelected} PanelWidth={this.previewWidthFunc} - PanelHeight={this.props.PanelHeight} + PanelHeight={this._props.PanelHeight} isContentActive={returnTrue} isDocumentActive={returnFalse} ScreenToLocalTransform={this.screenToLocal} @@ -910,12 +907,12 @@ export class CollectionSchemaView extends CollectionSubView() { searchFilterDocs={this.searchFilterDocs} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} - moveDocument={this.props.moveDocument} + moveDocument={this._props.moveDocument} addDocument={this.addRow} - removeDocument={this.props.removeDocument} + removeDocument={this._props.removeDocument} whenChildContentsActiveChanged={returnFalse} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} + addDocTab={this._props.addDocTab} + pinToPres={this._props.pinToPres} bringToFront={returnFalse} /> )} @@ -939,7 +936,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP return ( <div className="schema-table-content" ref={this.props.setRef} style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + this.props.rowHeight()}px)` }}> {this.props.childDocs().docs.map((doc: Doc, index: number) => ( - <div className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}> + <div key={doc[Id]} className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}> <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} /> </div> ))} @@ -956,9 +953,14 @@ interface CollectionSchemaViewDocProps { } @observer -class CollectionSchemaViewDoc extends React.Component<CollectionSchemaViewDocProps> { - tableWidthFunc = () => this.props.schema.tableWidth; - screenToLocalXf = () => this.props.schema.props.ScreenToLocalTransform().translate(0, -this.props.rowHeight() - this.props.index * this.props.rowHeight()); +class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaViewDocProps> { + constructor(props: any) { + super(props); + makeObservable(this); + } + + tableWidthFunc = () => this._props.schema.tableWidth; + screenToLocalXf = () => this._props.schema._props.ScreenToLocalTransform().translate(0, -this._props.rowHeight() - this._props.index * this._props.rowHeight()); noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => { if (property === StyleProp.Opacity) return 1; return DefaultStyleProvider(doc, props, property); @@ -966,31 +968,30 @@ class CollectionSchemaViewDoc extends React.Component<CollectionSchemaViewDocPro render() { return ( <DocumentView - key={this.props.doc[Id]} - {...this.props.schema.props} - LayoutTemplate={this.props.schema.props.childLayoutTemplate} - LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey, this.props.index)} - Document={this.props.doc} - DataDoc={undefined} - renderDepth={this.props.schema.props.renderDepth + 1} + key={this._props.doc[Id]} + {...this._props.schema._props} + LayoutTemplate={this._props.schema._props.childLayoutTemplate} + LayoutTemplateString={SchemaRowBox.LayoutString(this._props.schema._props.fieldKey, this._props.index)} + Document={this._props.doc} + renderDepth={this._props.schema._props.renderDepth + 1} PanelWidth={this.tableWidthFunc} - PanelHeight={this.props.rowHeight} + PanelHeight={this._props.rowHeight} styleProvider={this.noOpacityStyleProvider} waitForDoubleClickToClick={returnNever} defaultDoubleClick={returnIgnore} dragAction="move" onClickScriptDisable="always" - focus={this.props.schema.focusDocument} - childFilters={this.props.schema.childDocFilters} - childFiltersByRanges={this.props.schema.childDocRangeFilters} - searchFilterDocs={this.props.schema.searchFilterDocs} - rootSelected={this.props.schema.rootSelected} + focus={this._props.schema.focusDocument} + childFilters={this._props.schema.childDocFilters} + childFiltersByRanges={this._props.schema.childDocRangeFilters} + searchFilterDocs={this._props.schema.searchFilterDocs} + rootSelected={this._props.schema.rootSelected} ScreenToLocalTransform={this.screenToLocalXf} bringToFront={emptyFunction} dragWhenActive={true} - isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive} + isDocumentActive={this._props.schema._props.childDocumentsActive?.() ? this._props.schema._props.isDocumentActive : this._props.schema.isContentActive} isContentActive={emptyFunction} - whenChildContentsActiveChanged={this.props.schema.props.whenChildContentsActiveChanged} + whenChildContentsActiveChanged={this._props.schema._props.whenChildContentsActiveChanged} hideDecorations={true} hideTitle={true} hideDocumentButtonBar={true} diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 65e47f441..04443b4a7 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -1,4 +1,4 @@ -import React = require('react'); +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; @@ -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 7346c4f12..5a3be826b 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -1,13 +1,13 @@ -import React = require('react'); import { IconButton, Size } from 'browndash-components'; -import { computed } from 'mobx'; +import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; +import * as React from 'react'; import { CgClose } from 'react-icons/cg'; import { FaExternalLinkAlt } from 'react-icons/fa'; +import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils'; import { Doc } from '../../../../fields/Doc'; import { BoolCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils'; import { DragManager } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; @@ -28,38 +28,42 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo public static LayoutString(fieldKey: string, rowIndex: number) { return FieldView.LayoutString(SchemaRowBox, fieldKey).replace('fieldKey', `rowIndex={${rowIndex}} fieldKey`); } - private _ref: HTMLDivElement | null = null; + constructor(props: any) { + super(props); + makeObservable(this); + } + 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()?.rootDoc; + return this._props.DocumentView?.()._props.docViewPath().lastElement()?.Document; } @computed get rowIndex() { - return this.schemaView?.rowIndex(this.rootDoc) ?? -1; + return this.schemaView?.rowIndex(this.Document) ?? -1; } componentDidMount(): void { - this.props.setContentView?.(this); + this._props.setContentView?.(this); } select = (ctrlKey: boolean, shiftKey: boolean) => { if (!this.schemaView) return; const lastSelected = Array.from(this.schemaView._selectedDocs).lastElement(); - if (shiftKey && lastSelected) this.schemaView.selectRows(this.rootDoc, lastSelected); + if (shiftKey && lastSelected) this.schemaView.selectRows(this.Document, lastSelected); else { - this.props.select?.(ctrlKey); + this._props.select?.(ctrlKey); } }; onPointerEnter = (e: any) => { - if (SnappingManager.GetIsDragging() && this.props.isContentActive()) { + if (SnappingManager.IsDragging && this._props.isContentActive()) { document.removeEventListener('pointermove', this.onPointerMove); document.addEventListener('pointermove', this.onPointerMove); } @@ -103,18 +107,18 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo return ( <div className="schema-row" - style={{ height: this.props.PanelHeight(), backgroundColor: this.props.isSelected() ? Colors.LIGHT_BLUE : undefined }} + style={{ height: this._props.PanelHeight(), backgroundColor: this._props.isSelected() ? Colors.LIGHT_BLUE : undefined }} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} ref={(row: HTMLDivElement | null) => { - row && this.schemaView?.addRowRef?.(this.rootDoc, row); + row && this.schemaView?.addRowRef?.(this.Document, row); this._ref = row; }}> <div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth, - pointerEvents: !this.props.isContentActive() ? 'none' : undefined, + pointerEvents: !this._props.isContentActive() ? 'none' : undefined, }}> <IconButton tooltip="close" @@ -128,7 +132,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo emptyFunction, undoable(e => { e.stopPropagation(); - this.props.removeDocument?.(this.rootDoc); + this._props.removeDocument?.(this.Document); }, 'Delete Row') ) } @@ -145,7 +149,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo emptyFunction, undoable(e => { e.stopPropagation(); - this.props.addDocTab(this.rootDoc, OpenWhere.addRight); + this._props.addDocTab(this.Document, OpenWhere.addRight); }, 'Open schema Doc preview') ) } @@ -155,13 +159,13 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo {this.schemaView?.columnKeys?.map((key, index) => ( <SchemaTableCell key={key} - Document={this.rootDoc} + Document={this.Document} col={index} fieldKey={key} allowCRs={false} // to enter text with new lines, must use \n columnWidth={this.columnWidth(index)} rowHeight={this.schemaView.rowHeightFunc} - isRowActive={this.props.isContentActive} + isRowActive={this._props.isContentActive} getFinfo={this.getFinfo} selectCell={this.selectCell} deselectCell={this.deselectCell} @@ -172,7 +176,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo transform={() => { const ind = index === this.schemaView.columnKeys.length - 1 ? this.schemaView.columnKeys.length - 3 : index; const x = this.schemaView?.displayColumnWidths.reduce((p, c, i) => (i <= ind ? p + c : p), 0); - const y = (this.props.rowIndex ?? 0) * this.props.PanelHeight(); + const y = (this._props.rowIndex ?? 0) * this._props.PanelHeight(); return new Transform(x + CollectionSchemaView._rowMenuWidth, y, 1); }} /> diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 9d5b533d1..85269028b 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -1,27 +1,28 @@ -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 * as React from 'react'; import DatePicker from 'react-datepicker'; +import Select from 'react-select'; +import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field } from '../../../../fields/Doc'; import { RichTextField } from '../../../../fields/RichTextField'; import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils'; import { FInfo } from '../../../documents/Documents'; import { DocFocusOrOpen } from '../../../util/DocumentManager'; import { Transform } from '../../../util/Transform'; -import { undoable, undoBatch } from '../../../util/UndoManager'; +import { undoBatch, undoable } from '../../../util/UndoManager'; import { EditableView } from '../../EditableView'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { DefaultStyleProvider } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; import { OpenWhere } from '../../nodes/DocumentView'; -import { FieldView, FieldViewProps } from '../../nodes/FieldView'; -import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; +import { FieldViewProps } from '../../nodes/FieldView'; import { KeyValueBox } from '../../nodes/KeyValueBox'; -import { DefaultStyleProvider } from '../../StyleProvider'; -import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView'; +import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; +import { ColumnType, FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; export interface SchemaTableCellProps { @@ -47,7 +48,12 @@ export interface SchemaTableCellProps { } @observer -export class SchemaTableCell extends React.Component<SchemaTableCellProps> { +export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellProps> { + constructor(props: any) { + super(props); + makeObservable(this); + } + static addFieldDoc = (doc: Doc, where: OpenWhere) => { DocFocusOrOpen(doc); return true; @@ -72,7 +78,6 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { searchFilterDocs: returnEmptyDoclist, styleProvider: DefaultStyleProvider, docViewPath: returnEmptyDoclist, - rootSelected: returnFalse, isSelected: returnFalse, setHeight: returnFalse, select: emptyFunction, @@ -97,12 +102,12 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { } @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 } = SchemaTableCell.renderProps(this.props); + const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this._props); return ( <div @@ -111,19 +116,21 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { color, textDecoration, width: '100%', + pointerEvents, }}> <EditableView - oneLine={this.props.oneLine} - allowCRs={this.props.allowCRs} - contents={<FieldView {...fieldProps} />} + oneLine={this._props.oneLine} + allowCRs={this._props.allowCRs} + contents={undefined} + fieldContents={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 +139,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 +159,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 +172,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> ); @@ -175,8 +182,13 @@ 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; +export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellProps> { + constructor(props: any) { + super(props); + makeObservable(this); + } + + @observable _previewRef: HTMLImageElement | undefined = undefined; choosePath(url: URL) { if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href @@ -188,8 +200,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 +238,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} />; @@ -237,22 +249,26 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> { } @observer -export class SchemaDateCell extends React.Component<SchemaTableCellProps> { - @observable _pickingDate: boolean = false; +export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProps> { + constructor(props: any) { + super(props); + makeObservable(this); + } + @observable _pickingDate: boolean = false; @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); //} }; @@ -261,53 +277,64 @@ export class SchemaDateCell extends React.Component<SchemaTableCellProps> { } } @observer -export class SchemaRTFCell extends React.Component<SchemaTableCellProps> { +export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps> { + constructor(props: any) { + super(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 }}> - {this.selected ? <FormattedTextBox {...fieldProps} DataDoc={this.props.Document} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} + {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} </div> ); } } @observer -export class SchemaBoolCell extends React.Component<SchemaTableCellProps> { +export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProps> { + constructor(props: any) { + super(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} />} + contents={undefined} + fieldContents={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; })} /> @@ -316,14 +343,19 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> { } } @observer -export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps> { +export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableCellProps> { + constructor(props: any) { + super(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 +389,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> diff --git a/src/client/views/collections/goldenLayoutTheme.css b/src/client/views/collections/goldenLayoutTheme.css new file mode 100644 index 000000000..cf577d6b1 --- /dev/null +++ b/src/client/views/collections/goldenLayoutTheme.css @@ -0,0 +1,132 @@ +.lm_goldenlayout { + background: #000000; +} +.lm_content { + background: #222222; +} +.lm_dragProxy .lm_content { + box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9); +} +.lm_dropTargetIndicator { + box-shadow: inset 0 0 30px #000000; + outline: 1px dashed #cccccc; + transition: all 200ms ease; +} +.lm_dropTargetIndicator .lm_inner { + background: #000000; + opacity: 0.2; +} +.lm_splitter { + background: #000000; + opacity: 0.001; + transition: opacity 200ms ease; +} +.lm_splitter:hover, +.lm_splitter.lm_dragging { + background: #444444; + opacity: 1; +} +.lm_header { + height: 20px; + user-select: none; +} +.lm_header.lm_selectable { + cursor: pointer; +} +.lm_header .lm_tab { + font-family: Arial, sans-serif; + font-size: 12px; + color: #999999; + background: #111111; + box-shadow: 2px -2px 2px rgba(0, 0, 0, 0.3); + margin-right: 2px; + padding-bottom: 2px; + padding-top: 2px; +} +.lm_header .lm_tab .lm_close_tab { + width: 11px; + height: 11px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAATElEQVR4nG3OwQ0DMQwDwZGRBtR/j1YJzMc5+IDoR+yCVO29g+pu981MFgqZmRdAfU7+CYWcbF11LwALjpBL0N0qybNx/RPU+gOeiS/+XCRwDlTgkQAAAABJRU5ErkJggg==); + background-position: center center; + background-repeat: no-repeat; + top: 4px; + right: 6px; + opacity: 0.4; +} +.lm_header .lm_tab .lm_close_tab:hover { + opacity: 1; +} +.lm_header .lm_tab.lm_active { + border-bottom: none; + box-shadow: 0 -2px 2px #000000; + padding-bottom: 3px; +} +.lm_header .lm_tab.lm_active .lm_close_tab { + opacity: 1; +} +.lm_dragProxy.lm_bottom .lm_header .lm_tab, +.lm_stack.lm_bottom .lm_header .lm_tab { + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); +} +.lm_dragProxy.lm_bottom .lm_header .lm_tab.lm_active, +.lm_stack.lm_bottom .lm_header .lm_tab.lm_active { + box-shadow: 0 2px 2px #000000; +} +.lm_selected .lm_header { + background-color: #452500; +} +.lm_tab:hover, +.lm_tab.lm_active { + background: #222222; + color: #dddddd; +} +.lm_header .lm_controls .lm_tabdropdown:before { + color: #ffffff; +} +.lm_controls > li { + position: relative; + background-position: center center; + background-repeat: no-repeat; + opacity: 0.4; + transition: opacity 300ms ease; +} +.lm_controls > li:hover { + opacity: 1; +} +.lm_controls .lm_popout { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAPklEQVR4nI2Q0QoAIAwCNfr/X7aXCpGN8snBdgejJOzckpkxs9jR6K6T5JpU0nWl5pSXTk7qwh8SnNT+CAAWCgkKFpuSWsUAAAAASUVORK5CYII=); +} +.lm_controls .lm_maximise { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==); +} +.lm_controls .lm_close { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAQUlEQVR4nHXOQQ4AMAgCQeT/f6aXpsGK3jSTuCVJAAr7iBdoAwCKd0nwfaAdHbYERw5b44+E8JoBjEYGMBq5gAYP3usUDu2IvoUAAAAASUVORK5CYII=); +} +.lm_maximised .lm_header { + background-color: #000000; +} +.lm_maximised .lm_controls .lm_maximise { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAJ0lEQVR4nGP8//8/AzGAiShVI1YhCwMDA8OsWbPwBmZaWhoj0SYCAN1lBxMAX4n0AAAAAElFTkSuQmCC); +} +.lm_transition_indicator { + background-color: #000000; + border: 1px dashed #555555; +} +.lm_popin { + cursor: pointer; +} +.lm_popin .lm_bg { + background: #ffffff; + opacity: 0.3; +} +.lm_popin .lm_icon { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAJCAYAAADpeqZqAAAAWklEQVR4nJWOyw3AIAxDHcQC7L8jbwT3AlJBfNp3SiI7dtRaLSlKKeoA1oEsKSQZCEluexw8Tm3ohk+E7bnOUHUGcNh+HwbBygw4AZ7FN/Lt84p0l+yTflV8AKQyLdcCRJi/AAAAAElFTkSuQmCC); + background-position: center center; + background-repeat: no-repeat; + border-left: 1px solid #eeeeee; + border-top: 1px solid #eeeeee; + opacity: 0.7; +} +.lm_popin:hover .lm_icon { + opacity: 1; +} /*# sourceMappingURL=goldenlayout-dark-theme.css.map */ |
