diff options
Diffstat (limited to 'src/client/views/collections/TabDocView.tsx')
-rw-r--r-- | src/client/views/collections/TabDocView.tsx | 604 |
1 files changed, 314 insertions, 290 deletions
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 5cb7b149b..2d8b2564d 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -6,40 +6,182 @@ import { IReactionDisposer, ObservableSet, action, computed, makeObservable, obs 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 { ClientUtils, DashColor, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; +import { DocData, DocViews } 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 { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types'; 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 { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { UndoManager, undoable } from '../../util/UndoManager'; import { DashboardView } from '../DashboardView'; import { LightboxView } from '../LightboxView'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; +import { PinDocView, PinProps } from '../PinFuncs'; +import { StyleProp } from '../StyleProp'; +import { DefaultStyleProvider } from '../StyleProvider'; import { Colors } from '../global/globalEnums'; -import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView'; -import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView'; +import { DocumentView, returnEmptyDocViewList } from '../nodes/DocumentView'; +import { FieldViewProps } from '../nodes/FieldView'; import { KeyValueBox } from '../nodes/KeyValueBox'; -import { DashFieldView } from '../nodes/formattedText/DashFieldView'; -import { PinProps, PresBox, PresMovement } from '../nodes/trails'; +import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; +import { PresBox, PresMovement } from '../nodes/trails'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionView } from './CollectionView'; import './TabDocView.scss'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; + const _global = (window /* browser */ || global) /* node */ as any; +interface TabMinimapViewProps { + document: Doc; + tabView: () => DocumentView | undefined; + addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean; + PanelWidth: () => number; + PanelHeight: () => number; + background: () => string; +} +interface TabMiniThumbProps { + miniWidth: () => number; + miniHeight: () => number; + miniTop: () => number; + miniLeft: () => number; +} + +@observer +class TabMiniThumb extends React.Component<TabMiniThumbProps> { + render() { + const { miniWidth, miniHeight, miniLeft, miniTop } = this.props; + return <div className="miniThumb" style={{ width: `${miniWidth()}%`, height: `${miniHeight()}%`, left: `${miniLeft()}%`, top: `${miniTop()}%` }} />; + } +} +@observer +export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> { + static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => { + if (doc) { + switch (property.split(':')[0]) { + case StyleProp.PointerEvents: return 'none'; + case StyleProp.DocContents: { + const background = (() => { + switch (doc.type as DocumentType) { + case DocumentType.PDF: return 'pink'; + case DocumentType.AUDIO: return 'lightgreen'; + case DocumentType.WEB: return 'brown'; + case DocumentType.IMG: return 'blue'; + case DocumentType.MAP: return 'orange'; + case DocumentType.VID: return 'purple'; + case DocumentType.RTF: return 'yellow'; + case DocumentType.COL: return undefined; + default: return 'gray'; + } // prettier-ignore + })(); + return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />; + } + default: return DefaultStyleProvider(doc, props, property); + } // prettier-ignore + } + return undefined; + }; + + @computed get renderBounds() { + const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView; + const bounds = compView?.freeformData?.(true)?.bounds; + if (!bounds) return undefined; + const xbounds = bounds.r - bounds.x; + const ybounds = bounds.b - bounds.y; + 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 }; + } + @computed get xPadding() { + return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l)); + } + @computed get yPadding() { + return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l)); + } + 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 miniSize = this.returnMiniSize(); + doc && + setupMoveUpEvents( + this, + e, + action((moveEv, down: number[], delta: number[]) => { + const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 }; + doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim); + doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim); + return false; + }), + emptyFunction, + emptyFunction + ); + }; + popup = () => { + if (!this.renderBounds) return <div />; + const { renderBounds } = this; + 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() }}> + <CollectionFreeFormView + Document={this._props.document} + docViewPath={returnEmptyDocViewList} + 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 // don't render overlay Docs since they won't scale + isContentActive={emptyFunction} + isAnyChildContentActive={returnFalse} + select={emptyFunction} + isSelected={returnFalse} + dontRegisterView + fieldKey={Doc.LayoutFieldKey(this._props.document)} + addDocument={returnFalse} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelWidth={this.returnMiniSize} + PanelHeight={this.returnMiniSize} + ScreenToLocalTransform={Transform.Identity} + renderDepth={0} + whenChildContentsActiveChanged={emptyFunction} + focus={emptyFunction} + styleProvider={TabMinimapView.miniStyleProvider} + addDocTab={this._props.addDocTab} + // eslint-disable-next-line no-use-before-define + pinToPres={TabDocView.PinDoc} + childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} + childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} + searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} + fitContentsToBox={returnTrue} + xPadding={this.xPadding} + yPadding={this.yPadding} + /> + <div className="miniOverlay" onPointerDown={this.miniDown}> + <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} /> + </div> + </div> + ); + }; + render() { + 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={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} /> + </div> + ); + } +} + interface TabDocViewProps { documentId: FieldId; keyValue?: boolean; @@ -51,9 +193,101 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { _mainCont: HTMLDivElement | null = null; _tabReaction: IReactionDisposer | undefined; + /** + * Adds a document to the presentation view + * */ + @action + public static PinDoc(docIn: Doc | Doc[], pinProps: PinProps) { + const docs = toList(docIn); + + const batch = UndoManager.StartBatch('Pin doc to pres trail'); + const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); + + if (!Doc.ActivePresentation) { + Doc.AddDocToList(Doc.MyTrails, 'data', curPres); + Doc.ActivePresentation = curPres; + } + + docs.forEach(doc => { + // Edge Case 1: Cannot pin document to itself + if (doc === curPres) { + alert('Cannot pin presentation document to itself'); + return; + } + const anchorDoc = DocumentView.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps); + const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({}); + const targDoc = (pinDoc.presentation_targetDoc = anchorDoc ?? doc); + pinDoc.title = doc.title + ' - Slide'; + 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; + Doc.SetContainer(pinDoc, curPres); + // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time + 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_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 + const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null); + + if (pinProps.pinViewport) PinDocView(pinDoc, pinProps, anchorDoc ?? doc); + if (!pinProps?.audioRange && duration !== undefined) { + pinDoc.presentation_mediaStart = 'manual'; + pinDoc.presentation_mediaStop = 'manual'; + } + if (pinProps?.activeFrame !== undefined) { + pinDoc.config_activeFrame = pinProps?.activeFrame; + pinDoc.title = doc.title + ' (move)'; + pinDoc.presentation_movement = PresMovement.Pan; + } + if (pinProps?.currentFrame !== undefined) { + pinDoc.config_currentFrame = pinProps?.currentFrame; + pinDoc.title = doc.title + ' (move)'; + pinDoc.presentation_movement = PresMovement.Pan; + } + if (pinDoc.stroke_isInkMask) { + pinDoc.presentation_hideAfter = true; + pinDoc.presentation_hideBefore = true; + pinDoc.presentation_movement = PresMovement.None; + } + if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true; + Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement()); + PresBox.Instance?.clearSelectedArray(); + pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); // Update selected array + }); + if ( + // open the presentation trail if it's not already opened + !Array.from(CollectionDockingView.Instance?.tabMap ?? []) + .map(d => d.DashDoc) + .includes(curPres) + ) { + if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres); + CollectionDockingView.AddSplit(curPres, OpenWhereMod.right); + setTimeout(() => DocumentView.showDocument(docs.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things + } + setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs + } + + static Activate = (tabDoc: Doc) => { + const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue); + tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) + return tab !== undefined; + }; + // static ActivateTabView(doc: Doc) { + // const tabView = Array.from(TabDocView._allTabs).find(view => view._document === doc); + // if (!tabView?._activated && tabView?._document) { + // TabDocView.Activate(tabView?._document); + // return tabView; + // } + // return undefined; + // } constructor(props: any) { super(props); makeObservable(this); + DocumentView.activateTabView = TabDocView.Activate; + DocumentView.PinDoc = TabDocView.PinDoc; } @observable _activated: boolean = false; @@ -62,8 +296,14 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { @observable _hovering = false; @observable _isActive: boolean = false; @observable _isAnyChildContentActive = false; + public static IsSelected = (doc?: Doc) => { + if (Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected)) { + return true; + } + return false; + }; @computed get _isUserActivated() { - return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive; + return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive; } get _isContentActive() { return this._isUserActivated || this._hovering; @@ -90,7 +330,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { @action init = (tab: any, doc: Opt<Doc>) => { if (tab.contentItem === tab.header.parent.getActiveContentItem()) this._activated = true; - if (tab.DashDoc !== doc && doc && tab.hasOwnProperty('contentItem') && tab.contentItem.config.type !== 'stack') { + if (tab.DashDoc !== doc && doc && tab.contentItem?.config.type !== 'stack') { tab._disposers = {} as { [name: string]: IReactionDisposer }; tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true); tab.DashDoc = doc; @@ -105,7 +345,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { while (child?.children.length) { const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string'); if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; - if (next?.className?.toString().includes(DashFieldView.name)) break; if (next) child = next; else break; } @@ -129,37 +368,37 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { setupMoveUpEvents( this, e, - e => - !e.defaultPrevented && - DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY, undefined, () => { + moveEv => + !moveEv.defaultPrevented && + DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), moveEv.clientX, moveEv.clientY, undefined, () => { CollectionDockingView.CloseSplit(doc); }), returnFalse, - action(e => { + action(clickEv => { if (this.view) { - SelectionManager.SelectView(this.view, false); + DocumentView.SelectView(this.view, false); const child = getChild(); - simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); + simulateMouseClick(child, clickEv.clientX, clickEv.clientY + 30, clickEv.screenX, clickEv.screenY + 30); } else { this._activated = true; - setTimeout(() => this.view && SelectionManager.SelectView(this.view, false)); + setTimeout(() => this.view && DocumentView.SelectView(this.view, false)); } }) ); }; const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />; - const closeIcon = <FontAwesomeIcon icon={'eye'} />; + const closeIcon = <FontAwesomeIcon icon="eye" />; ReactDOM.createRoot(iconWrap).render(docIcon); ReactDOM.createRoot(closeWrap).render(closeIcon); tab.reactComponents = [iconWrap, closeWrap]; tab.element[0].prepend(iconWrap); tab._disposers.color = reaction( - () => ({ variant: SettingsManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }), + () => ({ variant: SnappingManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }), ({ variant, degree, highlight }) => { const color = highlight?.highlightIndex === Doc.DocBrushStatus.highlighted ? highlight.highlightColor : degree ? ['transparent', variant, variant, 'orange'][degree] : variant; - const textColor = color === variant ? SettingsManager.userColor : lightOrDark(color); + const textColor = color === variant ? SnappingManager.userColor ?? '' : lightOrDark(color); titleEle.style.color = textColor; iconWrap.style.color = textColor; closeWrap.style.color = textColor; @@ -185,19 +424,19 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { ); } // shifts the focus to this tab when another tab is dragged over it - tab.element[0].onmouseenter = (e: MouseEvent) => { + tab.element[0].onmouseenter = () => { if (SnappingManager.IsDragging && tab.contentItem !== tab.header.parent.getActiveContentItem()) { tab.header.parent.setActiveContentItem(tab.contentItem); tab.setActive(true); } this._document && Doc.BrushDoc(this._document); }; - tab.element[0].onmouseleave = (e: MouseEvent) => { + tab.element[0].onmouseleave = () => { this._document && Doc.UnBrushDoc(this._document); }; tab.element[0].oncontextmenu = (e: MouseEvent) => { - let child = getChild(); + const child = getChild(); if (child) { simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); e.stopPropagation(); @@ -208,7 +447,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected titleEle.onpointerdown = action((e: any) => { if (e.target.className !== 'lm_iconWrap') { - if (this.view) SelectionManager.SelectView(this.view, false); + if (this.view) DocumentView.SelectView(this.view, false); else this._activated = true; if (Date.now() - titleEle.lastClick < 1000) titleEle.select(); titleEle.lastClick = Date.now(); @@ -216,15 +455,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { } }); tab._disposers.selectionDisposer = reaction( - () => SelectionManager.IsSelected(this._document), + () => TabDocView.IsSelected(this._document), action(selected => { if (selected) this._activated = true; - const toggle = tab.element[0].children[2].children[0] as HTMLInputElement; if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) { undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')(); } - //toggle.style.fontWeight = selected ? 'bold' : ''; - // toggle.style.textTransform = selected ? "uppercase" : ""; }), { fireImmediately: true } ); @@ -232,101 +468,27 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { // highlight the tab when the tab document is brushed in any part of the UI tab._disposers.reactionDisposer = reaction( () => doc?.title, - title => (titleEle.value = title), + title => { + titleEle.value = title; + }, { fireImmediately: true } ); // clean up the tab when it is closed tab.closeElement - .off('click') //unbind the current click handler - .click(function () { + .off('click') // unbind the current click handler + .click(() => { Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); UndoManager.RunInBatch(() => tab.contentItem.remove(), 'delete tab'); }); } }; - /** - * Adds a document to the presentation view - **/ - @action - public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) { - const docList = docs instanceof Doc ? [docs] : docs; - - const batch = UndoManager.StartBatch('Pin doc to pres trail'); - const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); - - if (!Doc.ActivePresentation) { - Doc.AddDocToList(Doc.MyTrails, 'data', curPres); - Doc.ActivePresentation = curPres; - } - - docList.forEach(doc => { - // Edge Case 1: Cannot pin document to itself - if (doc === curPres) { - alert('Cannot pin presentation document to itself'); - return; - } - const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps); - const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({}); - const targDoc = (pinDoc.presentation_targetDoc = anchorDoc ?? doc); - pinDoc.title = doc.title + ' - Slide'; - 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; - Doc.SetContainer(pinDoc, curPres); - // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time - 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_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 - const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null); - - if (pinProps.pinViewport) PresBox.pinDocView(pinDoc, pinProps, anchorDoc ?? doc); - if (!pinProps?.audioRange && duration !== undefined) { - pinDoc.presentation_mediaStart = 'manual'; - pinDoc.presentation_mediaStop = 'manual'; - } - if (pinProps?.activeFrame !== undefined) { - pinDoc.config_activeFrame = pinProps?.activeFrame; - pinDoc.title = doc.title + ' (move)'; - pinDoc.presentation_movement = PresMovement.Pan; - } - if (pinProps?.currentFrame !== undefined) { - pinDoc.config_currentFrame = pinProps?.currentFrame; - pinDoc.title = doc.title + ' (move)'; - pinDoc.presentation_movement = PresMovement.Pan; - } - if (pinDoc.stroke_isInkMask) { - pinDoc.presentation_hideAfter = true; - pinDoc.presentation_hideBefore = true; - pinDoc.presentation_movement = PresMovement.None; - } - if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true; - Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement()); - PresBox.Instance?.clearSelectedArray(); - pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array - }); - if ( - // open the presentation trail if it's not already opened - !Array.from(CollectionDockingView.Instance?.tabMap ?? []) - .map(d => d.DashDoc) - .includes(curPres) - ) { - if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres); - CollectionDockingView.AddSplit(curPres, OpenWhereMod.right); - setTimeout(() => DocumentManager.Instance.showDocument(docList.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things - } - setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs - } - componentDidMount() { new _global.ResizeObserver( action((entries: any) => { + // eslint-disable-next-line no-restricted-syntax for (const entry of entries) { this._panelWidth = entry.contentRect.width; this._panelHeight = entry.contentRect.height; @@ -342,12 +504,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { } componentDidUpdate(prevProps: Readonly<TabDocViewProps>) { super.componentDidUpdate(prevProps); - this._view && DocumentManager.Instance.AddView(this._view); + this._view && DocumentView.addView(this._view); } componentWillUnmount() { this._tabReaction?.(); - this._view && DocumentManager.Instance.RemoveView(this._view); + this._view && DocumentView.removeView(this._view); runInAction(() => TabDocView._allTabs.delete(this)); this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged); @@ -361,7 +523,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { private onActiveContentItemChanged(contentItem: any) { if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; - if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => SelectionManager.SelectView(this._view, false)); + if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false)); !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } } @@ -373,50 +535,45 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { // "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, // "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right // lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox - addDocTab = (doc: Doc, location: OpenWhere) => { - SelectionManager.DeselectAll(); + addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => { + const docs = toList(docsIn); + DocumentView.DeselectAll(); const whereFields = location.split(':'); const keyValue = whereFields.includes(OpenWhereMod.keyvalue); const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; const panelName = whereFields.length > 1 ? whereFields.lastElement() : ''; - if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc); + if (docs[0]?.dockingConfig && !keyValue) return DashboardView.openDashboard(docs[0]); // prettier-ignore switch (whereFields[0]) { case undefined: case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) { - const lightboxView = !doc.annotationOn && DocCast(doc.embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.embedContainer)) : undefined; + const lightboxView = !docs[0].annotationOn && DocCast(docs[0].embedContainer) ? DocumentView.getFirstDocumentView(DocCast(docs[0].embedContainer)) : undefined; const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.Document)]; if (lightboxView && (!data || data instanceof List)) { - lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>([doc]); + lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>(docs); return true; } } - return LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox); - case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); - case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, panelName, undefined, keyValue); - case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue); - case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue); + return LightboxView.Instance.AddDocTab(docs[0], OpenWhere.lightbox); + case OpenWhere.close: return CollectionDockingView.CloseSplit(docs[0], whereMods); + case OpenWhere.replace: return CollectionDockingView.ReplaceTab(docs[0], whereMods, this.stack, panelName, undefined, keyValue); + case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(docs[0], whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue); + case OpenWhere.add:default:return CollectionDockingView.AddSplit(docs[0], whereMods, this.stack, undefined, keyValue); } }; remDocTab = (doc: Doc | Doc[]) => { if (doc === this._document) { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); CollectionDockingView.CloseSplit(this._document); return true; } return false; }; - getCurrentFrame = () => { - return NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame); - }; - static Activate = (tabDoc: Doc) => { - const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc && !tab.contentItem.config.props.keyValue); - tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) - return tab !== undefined; - }; + getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame); + @action - focusFunc = (doc: Doc, options: FocusViewOptions) => { + focusFunc = () => { if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) } @@ -426,7 +583,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { @observable _forceInvalidateScreenToLocal = 0; ScreenToLocalTransform = () => { this._forceInvalidateScreenToLocal; - const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement); + const { translateX, translateY } = ClientUtils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement); return CollectionDockingView.Instance?.ScreenToLocalBoxXf().translate(-translateX, -translateY) ?? Transform.Identity(); }; PanelWidth = () => this._panelWidth; @@ -434,7 +591,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { miniMapColor = () => Colors.MEDIUM_GRAY; tabView = () => this._view; disableMinimap = () => !this._document; - whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive); + whenChildContentActiveChanges = (isActive: boolean) => { + this._isAnyChildContentActive = isActive; + }; isContentActive = () => this._isContentActive; waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined); @computed get docView() { @@ -443,7 +602,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { <DocumentView key={this._document[Id]} ref={action((r: DocumentView) => { - this._lastView && DocumentManager.Instance.RemoveView(this._lastView); + this._lastView && DocumentView.removeView(this._lastView); this._view = r; this._lastView = this._view; })} @@ -452,7 +611,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { hideTitle={this._props.keyValue} Document={this._document} TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined} - onBrowseClickScript={DocumentView.exploreMode} waitForDoubleClickToClick={this.waitForDoubleClick} isContentActive={this.isContentActive} isDocumentActive={returnFalse} @@ -465,9 +623,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { addDocument={undefined} removeDocument={this.remDocTab} addDocTab={this.addDocTab} - suppressSetHeight={this._document._layout_fitWidth ? true : false} + suppressSetHeight={!!this._document._layout_fitWidth} ScreenToLocalTransform={this.ScreenToLocalTransform} - dontCenter={'y'} + dontCenter="y" whenChildContentsActiveChanged={this.whenChildContentActiveChanges} focus={this.focusFunc} containerViewPath={returnEmptyDoclist} @@ -485,19 +643,24 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { style={{ fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined, }} - onPointerOver={action(() => (this._hovering = true))} - onPointerLeave={action(() => (this._hovering = false))} - onDragOver={action(() => (this._hovering = true))} - onDragLeave={action(() => (this._hovering = false))} + onPointerOver={action(() => { this._hovering = true; })} // prettier-ignore + onPointerLeave={action(() => { this._hovering = false; })} // prettier-ignore + onDragOver={action(() => { this._hovering = true; })} // prettier-ignore + onDragLeave={action(() => { this._hovering = false; })} // prettier-ignore ref={ref => { - if ((this._mainCont = ref)) { + this._mainCont = ref; + if (this._mainCont) { if (this._lastTab) { - this._view && DocumentManager.Instance.RemoveView(this._view); + this._view && DocumentView.removeView(this._view); } 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))); - new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref); + 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(() => this._forceInvalidateScreenToLocal++)).observe(ref); } }}> {this.docView} @@ -505,142 +668,3 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { ); } } - -interface TabMinimapViewProps { - document: Doc; - tabView: () => DocumentView | undefined; - addDocTab: (doc: Doc, where: OpenWhere) => boolean; - PanelWidth: () => number; - PanelHeight: () => number; - background: () => string; -} -interface TabMiniThumbProps { - miniWidth: () => number; - miniHeight: () => number; - 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 ObservableReactComponent<TabMinimapViewProps> { - static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => { - if (doc) { - switch (property.split(':')[0]) { - default: - return DefaultStyleProvider(doc, props, property); - case StyleProp.PointerEvents: - return 'none'; - case StyleProp.DocContents: - const background = ((type: DocumentType) => { - // prettier-ignore - switch (type) { - case DocumentType.PDF: return 'pink'; - case DocumentType.AUDIO: return 'lightgreen'; - case DocumentType.WEB: return 'brown'; - case DocumentType.IMG: return 'blue'; - case DocumentType.MAP: return 'orange'; - case DocumentType.VID: return 'purple'; - case DocumentType.RTF: return 'yellow'; - case DocumentType.COL: return undefined; - default: return 'gray'; - } - })(doc.type as DocumentType); - return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />; - } - } - }; - - @computed get renderBounds() { - const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView; - const bounds = compView?.freeformData?.(true)?.bounds; - if (!bounds) return undefined; - const xbounds = bounds.r - bounds.x; - const ybounds = bounds.b - bounds.y; - 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 }; - } - @computed get xPadding() { - return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l)); - } - @computed get yPadding() { - return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l)); - } - 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 miniSize = this.returnMiniSize(); - doc && - setupMoveUpEvents( - this, - e, - action((e: PointerEvent, down: number[], delta: number[]) => { - const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 }; - doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim); - doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim); - return false; - }), - emptyFunction, - emptyFunction - ); - }; - 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 miniSize = this.returnMiniSize(); - return ( - <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}> - <CollectionFreeFormView - Document={this._props.document} - docViewPath={returnEmptyDocViewList} - 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 - isContentActive={emptyFunction} - isAnyChildContentActive={returnFalse} - select={emptyFunction} - isSelected={returnFalse} - dontRegisterView={true} - fieldKey={Doc.LayoutFieldKey(this._props.document)} - addDocument={returnFalse} - moveDocument={returnFalse} - removeDocument={returnFalse} - PanelWidth={this.returnMiniSize} - PanelHeight={this.returnMiniSize} - ScreenToLocalTransform={Transform.Identity} - renderDepth={0} - whenChildContentsActiveChanged={emptyFunction} - focus={emptyFunction} - styleProvider={TabMinimapView.miniStyleProvider} - addDocTab={this._props.addDocTab} - pinToPres={TabDocView.PinDoc} - childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} - childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} - searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} - fitContentsToBox={returnTrue} - xPadding={this.xPadding} - yPadding={this.yPadding} - /> - <div className="miniOverlay" onPointerDown={this.miniDown}> - <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} /> - </div> - </div> - ); - }; - render() { - 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> - ); - } -} |