import * as GoldenLayout from "golden-layout"; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; import { action, computed, observable, reaction } from "mobx"; import { DragManager } from "../../util/DragManager"; import { DocumentView } from "../nodes/DocumentView"; import { Document } from "../../../fields/Document"; import "./CollectionDockingView.scss"; import { CollectionViewBase, COLLECTION_BORDER_WIDTH, CollectionViewProps } from "./CollectionViewBase"; import React = require("react"); import * as ReactDOM from 'react-dom'; import Measure from "react-measure"; import { Utils } from "../../../Utils"; import { FieldId, FieldWaiting } from "../../../fields/Field"; import { Server } from "../../Server"; import { observer } from "mobx-react"; import { ListField } from "../../../fields/ListField"; import { KeyStore } from "../../../fields/KeyStore"; @observer export class CollectionDockingView extends CollectionViewBase { public static Instance: CollectionDockingView; public static LayoutString() { return CollectionViewBase.LayoutString("CollectionDockingView"); } public static makeDocumentConfig(document: Document) { return { type: 'react-component', component: 'DocumentFrameRenderer', title: document.Title, props: { documentId: document.Id, CollectionDockingView: CollectionDockingView.Instance, } } } private _goldenLayout: any = null; private _dragDiv: any = null; private _dragParent: HTMLElement | null = null; private _dragElement: HTMLDivElement | undefined; private _dragFakeElement: HTMLDivElement | undefined; private _containerRef = React.createRef(); private _makeFullScreen: boolean = false; private _maximizedStack: any = null; constructor(props: CollectionViewProps) { super(props); CollectionDockingView.Instance = this; (window as any).React = React; (window as any).ReactDOM = ReactDOM; } public StartOtherDrag(dragElement: HTMLDivElement, dragDoc: Document) { this._dragElement = dragElement; this._dragParent = dragElement.parentElement; // bcz: we want to copy this document into the header, not move it there. // However, GoldenLayout is setup to move things, so we have to do some kludgy stuff: // - create a temporary invisible div and register that as a DragSource with GoldenLayout this._dragDiv = document.createElement("div"); this._dragDiv.style.opacity = 0; DragManager.Root().appendChild(this._dragDiv); this._goldenLayout.createDragSource(this._dragDiv, CollectionDockingView.makeDocumentConfig(dragDoc)); // - add our document to that div so that GoldenLayout will get the move events its listening for this._dragDiv.appendChild(this._dragElement); // - add a duplicate of our document to the original document's container // (GoldenLayout will be removing our original one) this._dragFakeElement = dragElement.cloneNode(true) as HTMLDivElement; this._dragParent!.appendChild(this._dragFakeElement); // all of this must be undone when the document has been dropped (see tabCreated) } public OpenFullScreen(document: Document) { this._makeFullScreen = true; this._goldenLayout.root.contentItems[0].addChild(CollectionDockingView.makeDocumentConfig(document)); } public CloseFullScreen() { if (this._maximizedStack) { this._maximizedStack.header.controlsContainer.find('.lm_close').click(); this._maximizedStack = null; } } // // Creates a vertical split on the right side of the docking view, and then adds the Document to that split // public AddRightSplit(document: Document) { var str = JSON.stringify(this._goldenLayout.toConfig()); this.props.Document.SetText(KeyStore.Data, str) let newItemStackConfig = { type: 'stack', content: [CollectionDockingView.makeDocumentConfig(document)] }; var newContentItem = new this._goldenLayout._typeToItem[newItemStackConfig.type](this._goldenLayout, newItemStackConfig, parent); if (this._goldenLayout.root.contentItems[0].isRow) { var rowlayout = this._goldenLayout.root.contentItems[0]; var lastRowItem = rowlayout.contentItems[rowlayout.contentItems.length - 1]; lastRowItem.config["width"] *= 0.5; newContentItem.config["width"] = lastRowItem.config["width"]; rowlayout.addChild(newContentItem, rowlayout.contentItems.length, true); rowlayout.callDownwards('setSize'); } else { var collayout = this._goldenLayout.root.contentItems[0]; var newRow = collayout.layoutManager.createContentItem({ type: "row" }, this._goldenLayout); collayout.parent.replaceChild(collayout, newRow); newRow.addChild(newContentItem, undefined, true); newRow.addChild(collayout, 0, true); collayout.config["width"] = 50; newContentItem.config["width"] = 50; collayout.parent.callDownwards('setSize'); } } setupGoldenLayout() { var config = this.props.Document.GetText(KeyStore.Data, ""); this._goldenLayout = new GoldenLayout(JSON.parse(config)); this._goldenLayout.on('tabCreated', this.tabCreated); this._goldenLayout.on('stackCreated', this.stackCreated); this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer); this._goldenLayout.container = this._containerRef.current; this._goldenLayout.init(); } componentDidMount: () => void = () => { if (this._containerRef.current) { var cc = this.props.Document.Get(KeyStore.Data) if (cc == FieldWaiting) { reaction(() => this.props.Document.Get(KeyStore.Data), () => this.setupGoldenLayout()); } else { this.setupGoldenLayout(); } window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window } } componentWillUnmount: () => void = () => { window.removeEventListener('resize', this.onResize); } @action onResize = (event: any) => { var cur = this.props.ContainingDocumentView!.MainContent.current; // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height); } @action onPointerDown = (e: React.PointerEvent): void => { if (e.button === 2 && this.active) { e.stopPropagation(); e.preventDefault(); } else { if (e.buttons === 1 && this.active) { e.stopPropagation(); } } } tabCreated = (tab: any) => { { if (this._dragDiv) { this._dragDiv.removeChild(this._dragElement); this._dragParent!.removeChild(this._dragFakeElement!); this._dragParent!.appendChild(this._dragElement!); DragManager.Root().removeChild(this._dragDiv); this._dragDiv = null; } //tab.setTitle(tab.contentItem.config.componentState.title); tab.closeElement.off('click') //unbind the current click handler .click(function () { tab.contentItem.remove(); }); } } stackCreated = (stack: any) => { if (this._makeFullScreen) { this._maximizedStack = stack; setTimeout(function () { stack.header.controlsContainer.find('.lm_maximise').click() }, 10); this._makeFullScreen = false; } //stack.header.controlsContainer.find('.lm_popout').hide(); stack.header.controlsContainer.find('.lm_close') //get the close icon .off('click') //unbind the current click handler .click(function () { //if (confirm('really close this?')) { stack.remove(); //} }); } render() { const { fieldKey: fieldKey, Document: Document } = this.props; const value: Document[] = Document.GetData(fieldKey, ListField, []); // bcz: not sure why, but I need these to force the flexlayout to update when the collection size changes. // tfs: we should be able to use this.props.ScreenToLocalTransform to get s right? var s = this.props.ContainingDocumentView != undefined ? this.props.ContainingDocumentView!.ScalingToScreenSpace : 1; var w = Document.GetNumber(KeyStore.Width, 0) / s; var h = Document.GetNumber(KeyStore.Height, 0) / s; return (