diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 2 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 12 | ||||
-rw-r--r-- | src/client/util/DragManager.ts | 3 | ||||
-rw-r--r-- | src/client/views/GlobalKeyHandler.ts | 4 | ||||
-rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 487 | ||||
-rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/linking/LinkEditor.tsx | 8 | ||||
-rw-r--r-- | src/client/views/search/SearchBox.tsx | 2 | ||||
-rw-r--r-- | src/fields/Doc.ts | 4 | ||||
-rw-r--r-- | src/mobile/MobileInterface.tsx | 4 |
10 files changed, 215 insertions, 313 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 18ff993fe..9186cea87 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -848,7 +848,7 @@ export namespace Docs { { type: type, content: [ - ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth, config.path)) + ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth)) ] } ] diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c60403701..7937072da 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1025,7 +1025,7 @@ export class CurrentUserUtils { CurrentUserUtils.openDashboard(userDoc, copy); } - public static createNewDashboard = async (userDoc: Doc, id?: string) => { + public static createNewDashboard = (userDoc: Doc, id?: string) => { const myPresentations = userDoc.myPresentations as Doc; const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true); const dashboards = Cast(userDoc.myDashboards, Doc) as Doc; @@ -1051,11 +1051,13 @@ export class CurrentUserUtils { dashboardDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Create Dashboard"]); Doc.AddDocToList(dashboards, "data", dashboardDoc); - // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) - setTimeout(() => { - CurrentUserUtils.openDashboard(userDoc, dashboardDoc); - }, 0); + CurrentUserUtils.openDashboard(userDoc, dashboardDoc); } + + public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); } + public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); } + public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); } + public static get EmptyPane() { return Cast(Doc.UserDoc().emptyPane, Doc, null); } } Scripting.addGlobal(function openDragFactory(dragFactory: Doc) { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 0cca61841..8bf6faf03 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -117,10 +117,11 @@ export namespace DragManager { } export class DocumentDragData { - constructor(dragDoc: Doc[]) { + constructor(dragDoc: Doc[], dropAction?: dropActionType) { this.draggedDocuments = dragDoc; this.droppedDocuments = []; this.offset = [0, 0]; + this.dropAction = dropAction; } draggedDocuments: Doc[]; droppedDocuments: Doc[]; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 84b3d64fd..1cbbfd67b 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -99,7 +99,7 @@ export class KeyManager { if (main.isPointerDown) { DragManager.AbortDrag(); } else { - if (CollectionDockingView.Instance.HasFullScreen()) { + if (CollectionDockingView.Instance.HasFullScreen) { CollectionDockingView.Instance.CloseFullScreen(); } else { doDeselect = !ContextMenu.Instance.closeMenu(); @@ -253,7 +253,7 @@ export class KeyManager { break; case "o": const target = SelectionManager.SelectedDocuments()[0]; - target && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(target); + target && CollectionDockingView.OpenFullScreen(target.props.Document); break; case "r": preventDefault = false; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 816f784ff..201cada09 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -37,9 +37,8 @@ const _global = (window /* browser */ || global /* node */) as any; @observer export class CollectionDockingView extends CollectionSubView(doc => doc) { - @observable public static Instances: CollectionDockingView[] = []; - @computed public static get Instance() { return CollectionDockingView.Instances[0]; } - public static makeDocumentConfig(document: Doc, width?: number, libraryPath?: Doc[]) { + @observable public static Instance: CollectionDockingView; + public static makeDocumentConfig(document: Doc, width?: number) { return { type: 'react-component', component: 'DocumentFrameRenderer', @@ -47,79 +46,46 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { width: width, props: { documentId: document[Id], - libraryPath: libraryPath?.map(d => d[Id]) } }; } - @computed public get initialized() { - return this._goldenLayout !== null; - } - - @observable private _goldenLayout: any = null; + private _reactionDisposer?: IReactionDisposer; private _containerRef = React.createRef<HTMLDivElement>(); private _flush: UndoManager.Batch | undefined; private _ignoreStateChange = ""; - private _isPointerDown = false; - private _maximizedSrc: Opt<DocumentView>; + public tabMap: Set<any> = new Set(); + public get initialized() { return this._goldenLayout !== null; } + public get HasFullScreen() { return this._goldenLayout._maximisedItem !== null; } + @observable private _goldenLayout: any = null; constructor(props: SubCollectionViewProps) { super(props); - runInAction(() => !CollectionDockingView.Instances ? CollectionDockingView.Instances = [this] : CollectionDockingView.Instances.push(this)); + runInAction(() => CollectionDockingView.Instance = this); //Why is this here? (window as any).React = React; (window as any).ReactDOM = ReactDOM; DragManager.StartWindowDrag = this.StartOtherDrag; } + public StartOtherDrag = (e: any, dragDocs: Doc[]) => { - console.log("START drag batch"); !this._flush && (this._flush = UndoManager.StartBatch("golden layout drag")); const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) : - { - type: 'row', - content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc)) - }; + { type: 'row', content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc)) }; const dragSource = this._goldenLayout.createDragSource(document.createElement("div"), config); - dragSource._dragListener.on("dragStop", () => dragSource.destroy()); + dragSource._dragListener.on("dragStop", dragSource.destroy); dragSource._dragListener.onMouseDown(e); } @undoBatch - @action - public OpenFullScreen(docView: DocumentView, libraryPath?: Doc[]) { - if (docView.props.Document._viewType === CollectionViewType.Docking && docView.props.Document.layoutKey === "layout") { - return CurrentUserUtils.openDashboard(Doc.UserDoc(), docView.props.Document); - } - const document = Doc.MakeAlias(docView.props.Document); - const newItemStackConfig = { - type: 'stack', - content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)] - }; - const docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout); - this._goldenLayout.root.contentItems[0].addChild(docconfig); - docconfig.callDownwards('_$init'); - this._goldenLayout._$maximiseItem(docconfig); - this._maximizedSrc = docView; - this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); - this.stateChanged(); - SelectionManager.DeselectAll(); - } - - @undoBatch public CloseFullScreen = () => { const target = this._goldenLayout._maximisedItem; - if (target !== null && this._maximizedSrc) { - this._goldenLayout._maximisedItem.remove(); - SelectionManager.SelectDoc(this._maximizedSrc, false); - this._maximizedSrc = undefined; + if (target) { + target.remove(); this.stateChanged(); } } - public HasFullScreen = () => { - return this._goldenLayout._maximisedItem !== null; - } - @undoBatch @action public static CloseRightSplit(document: Opt<Doc>): boolean { @@ -136,17 +102,29 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { const retVal = !CollectionDockingView.Instance?._goldenLayout.root.contentItems[0].isRow ? false : Array.from(CollectionDockingView.Instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose)); - retVal && CollectionDockingView.Instance.layoutChanged(document); + retVal && document && CollectionDockingView.Instance.layoutChanged(); return retVal; } @undoBatch @action - layoutChanged(removed?: Doc) { - this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]); - this._goldenLayout.emit('stateChanged'); - this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); - this.stateChanged(); + public static OpenFullScreen(doc: Doc, libraryPath?: Doc[]) { + const instance = CollectionDockingView.Instance; + if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") { + return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + } + const newItemStackConfig = { + type: 'stack', + content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc), undefined)] + }; + const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); + instance._goldenLayout.root.contentItems[0].addChild(docconfig); + docconfig.callDownwards('_$init'); + instance._goldenLayout._$maximiseItem(docconfig); + instance._goldenLayout.emit('stateChanged'); + instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig()); + instance.stateChanged(); + return true; } public static ReplaceRightSplit(document: Doc, libraryPath?: Doc[], addToSplit?: boolean): boolean { @@ -156,14 +134,16 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)?.Document.isDisplayPanel) { - const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath); + const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined); + runInAction(() => document.isDisplayPanel = true); child.addChild(newItemStackConfig, undefined); !addToSplit && child.contentItems[0].remove(); return true; } return Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)?.Document.isDisplayPanel) { - const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath); + const newItemStackConfig = CollectionDockingView.makeDocumentConfig(document, undefined); + runInAction(() => document.isDisplayPanel = true); child.addChild(newItemStackConfig, undefined); !addToSplit && child.contentItems[j].remove(); return true; @@ -171,7 +151,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { return false; }); }); - retVal && instance.layoutChanged(document); + retVal && instance.layoutChanged(); return retVal; } @@ -183,9 +163,9 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { public static AddRightSplit(document: Doc, dontSelect: boolean = false, isDisplayPanel: Opt<boolean> = undefined) { if (!CollectionDockingView.Instance) return false; - const ind = CollectionDockingView.Instance._tabMap.findIndex((val) => val.doc === document); + const ind = Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === document); if (ind !== -1) { - const tab = CollectionDockingView.Instance._tabMap[ind].tab; + const tab = Array.from(CollectionDockingView.Instance.tabMap.keys())[ind]; const activeContentItem = tab.header.parent.getActiveContentItem(); if (tab.contentItem !== activeContentItem) { tab.header.parent.setActiveContentItem(tab.contentItem); @@ -198,7 +178,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { const instance = CollectionDockingView.Instance; const newItemStackConfig = { type: 'stack', - content: [CollectionDockingView.makeDocumentConfig(document, undefined, [])] + content: [CollectionDockingView.makeDocumentConfig(document, undefined)] }; const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); @@ -238,7 +218,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { const instance = CollectionDockingView.Instance; const newItemStackConfig = { type: 'stack', - content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)] + content: [CollectionDockingView.makeDocumentConfig(document, undefined)] }; const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); @@ -299,16 +279,20 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { // // Creates a vertical split on the right side of the docking view, and then adds the Document to that split // + @undoBatch public static UseRightSplit(document: Doc, libraryPath?: Doc[], shiftKey?: boolean) { if (shiftKey || !CollectionDockingView.ReplaceRightSplit(document, libraryPath, shiftKey)) { - CollectionDockingView.AddRightSplit(document, false, true); + return CollectionDockingView.AddRightSplit(document, false, true); } + return false; } - public AddTab = (stack: any, document: Doc, libraryPath?: Doc[]) => { - const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath); + @undoBatch + public static AddTab(stack: any, document: Doc, libraryPath?: Doc[]) { + const instance = CollectionDockingView.Instance; + const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined); if (stack === undefined) { - let stack: any = this._goldenLayout.root; + stack = instance._goldenLayout.root; while (!stack.isStack) { if (stack.contentItems.length) { stack = stack.contentItems[0]; @@ -318,41 +302,21 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { break; } } - if (stack) { - stack.addChild(docContentConfig); - } - } else { - stack.addChild(docContentConfig, undefined); } - this.layoutChanged(); + stack?.addChild(docContentConfig, undefined); + instance.layoutChanged(); return true; } - public ReplaceTab = (stack: any, document: Doc, libraryPath?: Doc[]) => { - const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath); - if (stack === undefined) { - let stack: any = this._goldenLayout.root; - while (!stack.isStack) { - if (stack.contentItems.length) { - stack = stack.contentItems[0]; - } else { - stack.addChild({ type: 'stack', content: [docContentConfig] }); - stack = undefined; - break; - } - } - if (stack) { - stack.addChild(docContentConfig); - } - } else { - stack.addChild(docContentConfig, undefined); - } - this.layoutChanged(); - return true; + @undoBatch + @action + layoutChanged() { + this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]); + this._goldenLayout.emit('stateChanged'); + this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); + this.stateChanged(); } - _tabMap: { tab: any, doc: Doc }[] = []; - async setupGoldenLayout() { const config = StrCast(this.props.Document.dockingConfig); if (config) { @@ -360,27 +324,23 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) ?? []; await Promise.all(docids.map(id => DocServer.GetRefField(id))); - if (!this._goldenLayout) { - runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config))); - } - else { + if (this._goldenLayout) { if (config === JSON.stringify(this._goldenLayout.toConfig())) { return; + } else { + try { + this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); + this._goldenLayout.unbind('stackCreated', this.stackCreated); + } catch (e) { } } - try { - this._goldenLayout.unbind('tabCreated', this.tabCreated); - this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); - this._goldenLayout.unbind('stackCreated', this.stackCreated); - } catch (e) { } - this._goldenLayout.destroy(); - runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config))); } - this._goldenLayout.on('tabCreated', this.tabCreated); + this.tabMap.clear(); + this._goldenLayout?.destroy(); + runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config))); this._goldenLayout.on('tabDestroyed', this.tabDestroyed); this._goldenLayout.on('stackCreated', this.stackCreated); this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer); this._goldenLayout.container = this._containerRef.current; - //this._goldenLayout.on("stateChanged", () => console.log("STATE CHANGED")); if (this._goldenLayout.config.maximisedItemId === '__glMaximised') { try { this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise(); @@ -391,17 +351,15 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { this._goldenLayout.init(); } } - reactionDisposer?: Lambda; + componentDidMount: () => void = () => { if (this._containerRef.current) { - const observer = new _global.ResizeObserver(this.onResize); - observer.observe(this._containerRef.current); - this.reactionDisposer = reaction( - () => StrCast(this.props.Document.dockingConfig), + new _global.ResizeObserver(this.onResize).observe(this._containerRef.current); + this._reactionDisposer = reaction(() => StrCast(this.props.Document.dockingConfig), config => { if (!this._goldenLayout || this._ignoreStateChange !== config) { this.setupGoldenLayout(); - DocListCast((Doc.UserDoc().myDashboards as Doc).data).map(d => d.dashboardBrush = false); + DocListCast(CurrentUserUtils.MyDashboards.data).map(d => d.dashboardBrush = false); this.props.Document.dashboardBrush = true; } this._ignoreStateChange = ""; @@ -410,24 +368,19 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window } } + componentWillUnmount: () => void = () => { try { this.props.Document.dashboardBrush = false; - this._goldenLayout.unbind('tabCreated', this.tabCreated); this._goldenLayout.unbind('stackCreated', this.stackCreated); this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); - } catch (e) { - - } + } catch (e) { } this._goldenLayout?.destroy(); - runInAction(() => { - CollectionDockingView.Instances.splice(CollectionDockingView.Instances.indexOf(this), 1); - this._goldenLayout = null; - }); window.removeEventListener('resize', this.onResize); - this.reactionDisposer?.(); + this._reactionDisposer?.(); } + @action onResize = (event: any) => { const cur = this._containerRef.current; @@ -438,31 +391,29 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { @action onPointerUp = (e: MouseEvent): void => { window.removeEventListener("pointerup", this.onPointerUp); - this._isPointerDown = false; if (this._flush) { setTimeout(() => { CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig()); this.stateChanged(); - console.log("END BATCH Up"); this._flush!.end(); this._flush = undefined; }, 10); } } + @action onPointerDown = (e: React.PointerEvent): void => { window.addEventListener("mouseup", this.onPointerUp); if (!(e.target as HTMLElement).closest("*.lm_content") && ((e.target as HTMLElement).closest("*.lm_tab") || (e.target as HTMLElement).closest("*.lm_stack"))) { - console.log("START BATCH dwn"); this._flush = UndoManager.StartBatch("golden layout edit"); } - if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) { - return; - } else { + if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && + Doc.GetSelectedTool() !== InkTool.Highlighter && Doc.GetSelectedTool() !== InkTool.Pen) { e.stopPropagation(); } } + public static Copy(doc: Doc) { let json = StrCast(doc.dockingConfig); const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); @@ -504,124 +455,18 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { Doc.GetProto(other).data = new List<Doc>(Array.from(otherSet.values())); } - tabCreated = (tab: any) => { - tab.titleElement[0].Tab = tab; - if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { - if (tab.contentItem.config.fixed) { - tab.contentItem.parent.config.fixed = true; - } - - const doc = DocServer.GetCachedRefField(tab.contentItem.config.props.documentId) as Doc; - if (doc instanceof Doc) { - this._tabMap.push({ tab: tab, doc: doc }); - tab.titleElement[0].onclick = (e: any) => { - if (Date.now() - tab.titleElement[0].lastClick < 1000) tab.titleElement[0].select(); - tab.titleElement[0].lastClick = Date.now(); - tab.titleElement[0].focus(); - }; - tab.titleElement[0].onchange = (e: any) => { - tab.titleElement[0].size = e.currentTarget.value.length + 1; - Doc.GetProto(doc).title = e.currentTarget.value, true; - }; - tab.titleElement[0].size = StrCast(doc.title).length + 1; - tab.titleElement[0].value = doc.title; - tab.titleElement[0].style["max-width"] = "100px"; - const gearSpan = document.createElement("span"); - gearSpan.className = "collectionDockingView-gear"; - gearSpan.style.position = "relative"; - gearSpan.style.paddingLeft = "0px"; - gearSpan.style.paddingRight = "12px"; - const stack = tab.contentItem.parent; - tab.element[0].onpointerdown = (e: any) => { - if (e.target.className !== "lm_close_tab") { - const view = DocumentManager.Instance.getDocumentView(doc); - view && SelectionManager.SelectDoc(view, false); - } - }; - // shifts the focus to this tab when another tab is dragged over it - tab.element[0].onmouseenter = (e: any) => { - if (!this._isPointerDown || !SnappingManager.GetIsDragging()) return; - const activeContentItem = tab.header.parent.getActiveContentItem(); - if (tab.contentItem !== activeContentItem) { - tab.header.parent.setActiveContentItem(tab.contentItem); - } - tab.setActive(true); - }; - const onDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, (e) => { - if (!(e as any).defaultPrevented) { - const dragData = new DragManager.DocumentDragData([doc]); - dragData.dropAction = doc.dropAction as dropActionType; - DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY); - return true; - } - return false; - }, returnFalse, emptyFunction); - }; - - tab.selectionDisposer = reaction(() => SelectionManager.SelectedDocuments(), - (sel) => { - const selected = sel.some(v => v.props.Document === doc); - selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && tab.header.parent.setActiveContentItem(tab.contentItem); - } - ); - tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)), - (views) => { - if (views.length) { - ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} > - <DockingViewButtonSelector views={() => views} Stack={stack} /> - </span>, - gearSpan); - tab.buttonDisposer?.(); - } - }, { fireImmediately: true }); - - tab.reactComponents = [gearSpan]; - tab.element.append(gearSpan); - tab.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => { - tab.titleElement[0].value = title; - tab.titleElement[0].style.padding = degree ? 0 : 2; - tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`; - }, { fireImmediately: true }); - //TODO why can't this just be doc instead of the id? - tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; - } - } - tab.closeElement.off('click') //unbind the current click handler - .click(function () { - tab.selectionDisposer?.(); - tab.reactionDisposer?.(); - tab.buttonDisposer?.(); - const doc = DocServer.GetCachedRefField(tab.contentItem.config.props.documentId); - if (doc instanceof Doc) { - const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); - recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); - SelectionManager.DeselectAll(); - } - CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig()); - tab.contentItem.remove(); - CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig()); - }); - } - tabDestroyed = (tab: any) => { - const ind = this._tabMap.findIndex((val) => val.tab === tab); - ind !== -1 && this._tabMap.splice(ind, 1); - if (tab.reactComponents) { - for (const ele of tab.reactComponents) { - ReactDOM.unmountComponentAtNode(ele); - } - } + this.tabMap.delete(tab); + Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); + tab.reactComponents && Array.from(tab.reactComponents).forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); } - stackCreated = (stack: any) => { - //stack.header.controlsContainer.find('.lm_popout').hide(); stack.header.element.on('mousedown', (e: any) => { if (e.target === stack.header.element[0] && e.button === 2) { - const emptyPane = Cast(Doc.UserDoc().emptyPane, Doc, null); + const emptyPane = CurrentUserUtils.EmptyPane; emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1; - this.AddTab(stack, Docs.Create.FreeformDocument([], { + CollectionDockingView.AddTab(stack, Docs.Create.FreeformDocument([], { _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}` })); } @@ -632,59 +477,125 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { .click(action(() => { //if (confirm('really close this?')) { stack.remove(); - stack.contentItems.forEach((contentItem: any) => { - const doc = Cast(DocServer.GetCachedRefField(contentItem.config.props.documentId), Doc, null); - if (doc instanceof Doc) { - const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); - recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); - } - }); + stack.contentItems.forEach((contentItem: any) => Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", contentItem.tab.DashDoc, undefined, true, true)); })); stack.header.controlsContainer.find('.lm_popout') //get the close icon .off('click') //unbind the current click handler .click(action(() => { // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size - const emptyPane = Cast(Doc.UserDoc().emptyPane, Doc, null); + const emptyPane = CurrentUserUtils.EmptyPane; emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1; - this.AddTab(stack, Docs.Create.FreeformDocument([], { + CollectionDockingView.AddTab(stack, Docs.Create.FreeformDocument([], { _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}` })); })); } render() { - if (this.props.renderDepth > 0) { - return <div style={{ width: "100%", height: "100%" }}>Nested dashboards can't be rendered</div>; - } - return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef} />; + return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef}> + {this.props.renderDepth > 0 ? "Nested dashboards can't be rendered" : (null)} + </div>; } } interface DockedFrameProps { documentId: FieldId; glContainer: any; - libraryPath: (FieldId[]); - //collectionDockingView: CollectionDockingView } @observer export class DockedFrameRenderer extends React.Component<DockedFrameProps> { _mainCont: HTMLDivElement | null = null; + _tabReaction: IReactionDisposer | undefined; @observable private _panelWidth = 0; @observable private _panelHeight = 0; - @observable private _document: Opt<Doc>; @observable private _isActive: boolean = false; - _tabReaction: IReactionDisposer | undefined; - get _stack(): any { - return (this.props as any).glContainer.parent.parent; - } - get _tab(): any { - const tab = (this.props as any).glContainer.tab?.element[0] as HTMLElement; - return tab?.getElementsByClassName("lm_title")?.[0]; - } + get stack(): any { return (this.props as any).glContainer.parent.parent; } + get tab() { return (this.props as any).glContainer.tab; } + get tabTitle(): any { return (this.tab?.element[0] as HTMLElement).getElementsByClassName("lm_title")?.[0]; } + get view() { return this._document && DocumentManager.Instance.getDocumentView(this._document); } + @observable _document: Doc | undefined; + constructor(props: any) { super(props); - this._document = Cast(DocServer.GetCachedRefField(this.props.documentId), Doc, null); + setTimeout(() => DocServer.GetRefField(this.tab.contentItem.config.props.documentId).then(doc => this.init(doc as Doc)), 0); + } + + @action + init = (doc: Doc) => { + const tab = this.tab; + tab._disposers = {} as { [name: string]: IReactionDisposer }; + tab.DashDoc = this._document = doc; + if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { + tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true); + + CollectionDockingView.Instance.tabMap.add(tab); + tab.titleElement[0].onclick = (e: any) => { + if (Date.now() - tab.titleElement[0].lastClick < 1000) tab.titleElement[0].select(); + tab.titleElement[0].lastClick = Date.now(); + tab.titleElement[0].focus(); + }; + tab.titleElement[0].onchange = (e: any) => { + tab.titleElement[0].size = e.currentTarget.value.length + 1; + Doc.GetProto(doc).title = e.currentTarget.value; + }; + tab.titleElement[0].size = StrCast(this._document.title).length + 1; + tab.titleElement[0].value = this._document.title; + tab.titleElement[0].style["max-width"] = "100px"; + const gearSpan = document.createElement("span"); + gearSpan.className = "collectionDockingView-gear"; + gearSpan.style.position = "relative"; + gearSpan.style.paddingLeft = "0px"; + gearSpan.style.paddingRight = "12px"; + const stack = tab.contentItem.parent; + tab.element[0].onpointerdown = (e: any) => { + e.target.className !== "lm_close_tab" && this.view && SelectionManager.SelectDoc(this.view, false); + }; + // 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()) { + tab.header.parent.setActiveContentItem(tab.contentItem); + } + tab.setActive(true); + }; + const onDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, (e) => { + !e.defaultPrevented && DragManager.StartDocumentDrag([gearSpan], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY); + return !e.defaultPrevented; + }, returnFalse, emptyFunction); + }; + + tab._disposers.selectionDisposer = reaction(() => SelectionManager.SelectedDocuments().some(v => v.props.Document === doc), + (selected) => { + selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && tab.header.parent.setActiveContentItem(tab.contentItem); + } + ); + tab._disposers.buttonDisposer = reaction(() => this.view, + (view) => { + if (view) { + ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} > + <DockingViewButtonSelector views={() => [view]} Stack={stack} /> + </span>, + gearSpan); + tab._disposers.buttonDisposer?.(); + } + }, { fireImmediately: true }); + + tab.reactComponents = [gearSpan]; + tab.element.append(gearSpan); + tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => { + tab.titleElement[0].value = title; + tab.titleElement[0].style.padding = degree ? 0 : 2; + tab.titleElement[0].style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`; + }, { fireImmediately: true }); + } + tab.closeElement.off('click') //unbind the current click handler + .click(function () { + Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); + Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true); + SelectionManager.DeselectAll(); + tab.contentItem.remove(); + }); } /** * Adds a document to the presentation view @@ -695,7 +606,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { if (unpin) DockedFrameRenderer.UnpinDoc(doc); else { //add this new doc to props.Document - const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; + const curPres = CurrentUserUtils.ActivePresentation; if (curPres) { const pinDoc = Doc.MakeAlias(doc); pinDoc.presentationTargetDoc = doc; @@ -717,7 +628,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { @action public static UnpinDoc(doc: Doc) { //add this new doc to props.Document - const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; + const curPres = CurrentUserUtils.ActivePresentation; if (curPres) { const ind = DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)); ind !== -1 && Doc.RemoveDocFromList(curPres, "data", DocListCast(curPres.data)[ind]); @@ -727,7 +638,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { componentDidMount() { const color = () => StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white"); const selected = () => SelectionManager.SelectedDocuments().some(v => v.props.Document === this._document); - const updateTabColor = () => this._tab && (this._tab.style.backgroundColor = selected() ? color() : ""); + const updateTabColor = () => this.tabTitle && (this.tabTitle.style.backgroundColor = selected() ? color() : ""); const observer = new _global.ResizeObserver(action((entries: any) => { for (const entry of entries) { this._panelWidth = entry.contentRect.width; @@ -750,10 +661,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { private onActiveContentItemChanged() { if (this.props.glContainer.tab && this._isActive !== this.props.glContainer.tab.isActive) { this._isActive = this.props.glContainer.tab.isActive; - this._isActive && setTimeout(() => { - const dv = this._document && DocumentManager.Instance.getFirstDocumentView(this._document); - dv && SelectionManager.SelectDoc(dv, false); - }); + this._isActive && setTimeout(() => this.view && SelectionManager.SelectDoc(this.view, false), 0); (CollectionDockingView.Instance as any)._goldenLayout.isInitialised && CollectionDockingView.Instance.stateChanged(); !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. } @@ -764,7 +672,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) : (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth) panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight; - nativeWidth = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0; nativeHeight = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeHeight) || this._panelHeight : 0; @@ -778,17 +685,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth))) { scaling = this._panelWidth / NumCast(this.layoutDoc!._nativeWidth); } else if (nativeW && nativeH) { - // if (this.layoutDoc!.type === DocumentType.PDF || this.layoutDoc!.type === DocumentType.WEB) { - // if ((this.layoutDoc?._fitWidth) || - // this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth)) { - // return this._panelWidth / NumCast(this.layoutDoc!._nativeWidth); - // } else { - // return this._panelHeight / NumCast(this.layoutDoc!._nativeHeight); - // } - // } const wscale = this.panelWidth() / nativeW; scaling = wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale; - } else scaling = 1; + } return scaling; } @@ -805,17 +704,14 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => { SelectionManager.DeselectAll(); - if (doc._viewType === CollectionViewType.Docking) { - return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); - } else if (location === "onRight") { - return CollectionDockingView.AddRightSplit(doc); - } else if (location === "close") { - return CollectionDockingView.CloseRightSplit(doc); - } else if (location === "replace") { - CollectionDockingView.UseRightSplit(doc); - return true; - } else {// if (location === "inPlace") { - return CollectionDockingView.Instance.AddTab(this._stack, doc, libraryPath); + if (doc._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + switch (location) { + case "onRight": return CollectionDockingView.AddRightSplit(doc); + case "close": return CollectionDockingView.CloseRightSplit(doc); + case "replace": return CollectionDockingView.UseRightSplit(doc); + case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); + case "inPlace": + default: return CollectionDockingView.AddTab(this.stack, doc); } } @@ -833,7 +729,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { childLayoutTemplate = () => Cast(this._document?.childLayoutTemplate, Doc, null); returnMiniSize = () => NumCast(this._document?._miniMapSize, 150); miniDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { + this._document && setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { this._document!._panX = clamp(NumCast(this._document!._panX) + delta[0] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.l, this.renderContentBounds.l + this.renderContentBounds.dim); this._document!._panY = clamp(NumCast(this._document!._panY) + delta[1] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.t, this.renderContentBounds.t + this.renderContentBounds.dim); return false; @@ -899,12 +795,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { @computed get docView() { TraceMobx(); if (!this._document) return (null); - const document = this._document; const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined; return <> - <DocumentView key={document[Id]} + <DocumentView key={this._document[Id]} LibraryPath={emptyPath} - Document={document} + Document={this._document} DataDoc={resolvedDataDoc} bringToFront={emptyFunction} rootSelected={returnTrue} @@ -927,7 +822,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} /> - {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)} + {this._document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)} </>; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index cdfa67769..fffbe65a3 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -738,7 +738,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll } onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout - if (!e.isPropagationStopped() && this.doc === Doc.UserDoc().myDashboards) { + if (!e.isPropagationStopped() && this.doc === CurrentUserUtils.MyDashboards) { ContextMenu.Instance.addItem({ description: "Create Dashboard", event: () => CurrentUserUtils.createNewDashboard(Doc.UserDoc()), icon: "plus" }); ContextMenu.Instance.addItem({ description: "Delete Dashboard", event: () => this.remove(this.doc), icon: "minus" }); e.stopPropagation(); diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 11a905fb6..8e391f995 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -380,6 +380,14 @@ export class LinkEditor extends React.Component<LinkEditorProps> { Always open in a new pane </div> <div className="linkEditor-followingDropdown-option" + onPointerDown={() => this.changeFollowBehavior("replace")}> + Always replace right tab + </div> + <div className="linkEditor-followingDropdown-option" + onPointerDown={() => this.changeFollowBehavior("fullScreen")}> + Always open full screen + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("inTab")}> Always open in a new tab </div> diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index a24761195..d7a8ed404 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -492,7 +492,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } render() { - const myDashboards = DocListCast(Cast(Doc.UserDoc().myDashboards, Doc, null).data); + const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data); return ( <div style={{ pointerEvents: "all" }} className="searchBox-container"> <div style={{ position: "absolute", left: 15, height: 32, alignItems: "center", display: "flex" }}> diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index cb4ca9a25..1401dd6a1 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -939,19 +939,16 @@ export namespace Doc { } export function IsBrushed(doc: Doc) { - return false; return computedFn(function IsBrushed(doc: Doc) { return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); })(doc); } // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message) export function IsBrushedDegreeUnmemoized(doc: Doc) { - return 0; if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0; return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0; } export function IsBrushedDegree(doc: Doc) { - return 0; return computedFn(function IsBrushDegree(doc: Doc) { return Doc.IsBrushedDegreeUnmemoized(doc); })(doc); @@ -980,7 +977,6 @@ export namespace Doc { let _lastDate = 0; export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) { - return; linkFollowUnhighlight(); Doc.HighlightDoc(destDoc, dataAndDisplayDocs); document.removeEventListener("pointerdown", linkFollowUnhighlight); diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 841862f49..d4eb76ecd 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -324,7 +324,7 @@ export class MobileInterface extends React.Component { ); } // stores dashboards documents as 'dashboards' variable - let dashboards = Cast(Doc.UserDoc().myDashboards, Doc) as Doc; + let dashboards = CurrentUserUtils.MyDashboards; if (this.dashboards) { dashboards = this.dashboards; } @@ -394,7 +394,7 @@ export class MobileInterface extends React.Component { */ @action createNewDashboard = async (id?: string) => { - const scens = Cast(Doc.UserDoc().myDashboards, Doc) as Doc; + const scens = CurrentUserUtils.MyDashboards; const dashboardCount = DocListCast(scens.data).length + 1; const freeformOptions: DocumentOptions = { x: 0, |