aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionDockingView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections/CollectionDockingView.tsx')
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx461
1 files changed, 222 insertions, 239 deletions
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e3d50eb80..c51fba908 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,297 +1,280 @@
-import FlexLayout from "flexlayout-react";
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 } from "mobx";
+import { action, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
import { Document } from "../../../fields/Document";
-import { KeyStore } from "../../../fields/Key";
-import { ListField } from "../../../fields/ListField";
-import { NumberField } from "../../../fields/NumberField";
-import { DragManager } from "../../util/DragManager";
-import { Transform } from "../../util/Transform";
+import { KeyStore } from "../../../fields/KeyStore";
+import Measure from "react-measure";
+import { FieldId, Opt, Field } from "../../../fields/Field";
+import { Utils } from "../../../Utils";
+import { Server } from "../../Server";
+import { undoBatch } from "../../util/UndoManager";
import { DocumentView } from "../nodes/DocumentView";
import "./CollectionDockingView.scss";
-import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
+import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
import React = require("react");
-import { changeDependenciesStateTo0 } from "mobx/lib/internal";
+import { SubCollectionViewProps } from "./CollectionViewBase";
@observer
-export class CollectionDockingView extends CollectionViewBase {
+export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
+ public static Instance: CollectionDockingView;
+ public static makeDocumentConfig(document: Document) {
+ return {
+ type: 'react-component',
+ component: 'DocumentFrameRenderer',
+ title: document.Title,
+ props: {
+ documentId: document.Id,
+ //collectionDockingView: CollectionDockingView.Instance
+ }
+ }
+ }
- private static UseGoldenLayout = true;
- public static LayoutString() { return CollectionViewBase.LayoutString("CollectionDockingView"); }
+ private _goldenLayout: any = null;
private _containerRef = React.createRef<HTMLDivElement>();
- @computed
- private get modelForFlexLayout() {
- const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
- const value: Document[] = Document.GetData(fieldKey, ListField, []);
- var docs = value.map(doc => {
- return { type: 'tabset', weight: 50, selected: 0, children: [{ type: "tab", name: doc.Title, component: doc.Id }] };
- });
- return FlexLayout.Model.fromJson({
- global: {}, borders: [],
- layout: {
- "type": "row",
- "weight": 100,
- "children": docs
- }
- });
+ private _fullScreen: any = null;
+
+ constructor(props: SubCollectionViewProps) {
+ super(props);
+ CollectionDockingView.Instance = this;
+ (window as any).React = React;
+ (window as any).ReactDOM = ReactDOM;
}
- @computed
- private get modelForGoldenLayout(): any {
- const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
- const value: Document[] = Document.GetData(fieldKey, ListField, []);
- var docs = value.map(doc => {
- return { type: 'component', componentName: 'documentViewComponent', componentState: { doc: doc } };
- });
- return new GoldenLayout({
- settings: {
- selectionEnabled: true
- }, content: [{ type: 'row', content: docs }]
- });
+ public StartOtherDrag(dragDoc: Document, e: any) {
+ this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: e.button })
}
- constructor(props: CollectionViewProps) {
- super(props);
+
+ @action
+ public OpenFullScreen(document: Document) {
+ let newItemStackConfig = {
+ type: 'stack',
+ content: [CollectionDockingView.makeDocumentConfig(document)]
+ }
+ var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
+ this._goldenLayout.root.contentItems[0].addChild(docconfig);
+ docconfig.callDownwards('_$init');
+ this._goldenLayout._$maximiseItem(docconfig);
+ this._fullScreen = docconfig;
+ this.stateChanged();
}
+ @action
+ public CloseFullScreen() {
+ if (this._fullScreen) {
+ this._goldenLayout._$minimiseItem(this._fullScreen);
+ this._goldenLayout.root.contentItems[0].removeChild(this._fullScreen);
+ this._fullScreen = null;
+ this.stateChanged();
+ }
+ }
+
+ //
+ // Creates a vertical split on the right side of the docking view, and then adds the Document to that split
+ //
+ @action
+ public AddRightSplit(document: Document, minimize: boolean = false) {
+ this._goldenLayout.emit('stateChanged');
+ let newItemStackConfig = {
+ type: 'stack',
+ content: [CollectionDockingView.makeDocumentConfig(document)]
+ }
+
+ var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
+ if (this._goldenLayout.root.contentItems[0].isRow) {
+ this._goldenLayout.root.contentItems[0].addChild(newContentItem);
+ }
+ 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;
+ }
+ if (minimize) {
+ newContentItem.config["width"] = 10;
+ newContentItem.config["height"] = 10;
+ }
+ newContentItem.callDownwards('_$init');
+ this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
+ this._goldenLayout.emit('stateChanged');
+ this.stateChanged();
+ return newContentItem;
+ }
+
+ setupGoldenLayout() {
+ var config = this.props.Document.GetText(KeyStore.Data, "");
+ if (config) {
+ if (!this._goldenLayout) {
+ this._goldenLayout = new GoldenLayout(JSON.parse(config));
+ }
+ else {
+ if (config == JSON.stringify(this._goldenLayout.toConfig()))
+ return;
+ try {
+ this._goldenLayout.unbind('itemDropped', this.itemDropped);
+ this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ } catch (e) { }
+ this._goldenLayout.destroy();
+ this._goldenLayout = new GoldenLayout(JSON.parse(config));
+ }
+ this._goldenLayout.on('itemDropped', this.itemDropped);
+ this._goldenLayout.on('tabCreated', this.tabCreated);
+ this._goldenLayout.on('stackCreated', this.stackCreated);
+ this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
+ this._goldenLayout.container = this._containerRef.current;
+ if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
+ try {
+ this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise();
+ } catch (e) {
+ this._goldenLayout.config.maximisedItemId = null;
+ }
+ }
+ this._goldenLayout.init();
+ }
+ }
componentDidMount: () => void = () => {
- if (this._containerRef.current && CollectionDockingView.UseGoldenLayout) {
- this.goldenLayoutFactory();
+ if (this._containerRef.current) {
+ reaction(
+ () => this.props.Document.GetText(KeyStore.Data, ""),
+ () => this.setupGoldenLayout(), { fireImmediately: true });
+
window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
}
}
componentWillUnmount: () => void = () => {
+ this._goldenLayout.unbind('itemDropped', this.itemDropped);
+ this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ this._goldenLayout.destroy();
+ this._goldenLayout = null;
window.removeEventListener('resize', this.onResize);
}
- private nextId = (function () { var _next_id = 0; return function () { return _next_id++; } })();
-
-
@action
onResize = (event: any) => {
- var cur = this.props.ContainingDocumentView!.MainContent.current;
+ var cur = this._containerRef.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
- CollectionDockingView.myLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+ this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
}
+ _flush: boolean = false;
+ @action
+ onPointerUp = (e: React.PointerEvent): void => {
+ if (this._flush) {
+ this._flush = false;
+ setTimeout(() => this.stateChanged(), 10);
+ }
+ }
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (e.button === 2 && this.active) {
+ if (e.button === 2 && this.props.active()) {
e.stopPropagation();
e.preventDefault();
} else {
- if (e.buttons === 1 && this.active) {
- e.stopPropagation();
+ var className = (e.target as any).className;
+ if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") {
+ this._flush = true;
}
- }
- }
-
- flexLayoutFactory = (node: any): any => {
- var component = node.getComponent();
- if (component === "button") {
- return <button>{node.getName()}</button>;
- }
- const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
- const value: Document[] = Document.GetData(fieldKey, ListField, []);
- for (var i: number = 0; i < value.length; i++) {
- if (value[i].Id === component) {
- return (<DocumentView key={value[i].Id} Document={value[i]}
- AddDocument={this.addDocument} RemoveDocument={this.removeDocument}
- GetTransform={() => Transform.Identity}
- Scaling={1}
- ContainingCollectionView={this} DocumentView={undefined} />);
+ if (e.buttons === 1 && this.props.active()) {
+ e.stopPropagation();
}
}
- if (component === "text") {
- return (<div className="panel">Panel {node.getName()}</div>);
- }
}
- public static myLayout: any = null;
-
- private static _dragDiv: any = null;
- private static _dragParent: HTMLElement | null = null;
- private static _dragElement: HTMLDivElement;
- private static _dragFakeElement: HTMLDivElement;
- public static StartOtherDrag(dragElement: HTMLDivElement, dragDoc: Document) {
- var newItemConfig = {
- type: 'component',
- componentName: 'documentViewComponent',
- componentState: { doc: dragDoc }
- };
- 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);
- CollectionDockingView.myLayout.createDragSource(this._dragDiv, newItemConfig);
-
- // - 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)
+ @undoBatch
+ stateChanged = () => {
+ var json = JSON.stringify(this._goldenLayout.toConfig());
+ this.props.Document.SetText(KeyStore.Data, json)
}
- _makeFullScreen: boolean = false;
- _maximizedStack: any = null;
- public static OpenFullScreen(document: Document) {
- var newItemConfig = {
- type: 'component',
- componentName: 'documentViewComponent',
- componentState: { doc: document }
- };
- CollectionDockingView.myLayout._makeFullScreen = true;
- CollectionDockingView.myLayout.root.contentItems[0].addChild(newItemConfig);
+ itemDropped = () => {
+ this.stateChanged();
}
- public static CloseFullScreen() {
- if (CollectionDockingView.myLayout._maximizedStack != null) {
- CollectionDockingView.myLayout._maximizedStack.header.controlsContainer.find('.lm_close').click();
- CollectionDockingView.myLayout._maximizedStack = null;
- }
+ tabCreated = (tab: any) => {
+ tab.closeElement.off('click') //unbind the current click handler
+ .click(function () {
+ tab.contentItem.remove();
+ });
}
- //
- // Creates a vertical split on the right side of the docking view, and then adds the Document to that split
- //
- public static AddRightSplit(document: Document) {
- var newItemConfig = {
- type: 'component',
- componentName: 'documentViewComponent',
- componentState: { doc: document }
- }
- let newItemStackConfig = {
- type: 'stack',
- content: [newItemConfig]
- };
- var newContentItem = new CollectionDockingView.myLayout._typeToItem[newItemStackConfig.type](CollectionDockingView.myLayout, newItemStackConfig, parent);
+ stackCreated = (stack: any) => {
+ //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(action(function () {
+ //if (confirm('really close this?')) {
+ stack.remove();
+ //}
+ }));
+ }
- if (CollectionDockingView.myLayout.root.contentItems[0].isRow) {
- var rowlayout = CollectionDockingView.myLayout.root.contentItems[0];
- var lastRowItem = rowlayout.contentItems[rowlayout.contentItems.length - 1];
+ render() {
+ return (
+ <div className="collectiondockingview-container" id="menuContainer"
+ onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef}
+ style={{
+ width: "100%",
+ height: "100%",
+ borderStyle: "solid",
+ borderWidth: `${COLLECTION_BORDER_WIDTH}px`,
+ }} />
+ );
+ }
+}
- lastRowItem.config["width"] *= 0.5;
- newContentItem.config["width"] = lastRowItem.config["width"];
- rowlayout.addChild(newContentItem, rowlayout.contentItems.length, true);
- rowlayout.callDownwards('setSize');
- }
- else {
- var collayout = CollectionDockingView.myLayout.root.contentItems[0];
- var newRow = collayout.layoutManager.createContentItem({ type: "row" }, CollectionDockingView.myLayout);
- collayout.parent.replaceChild(collayout, newRow);
+interface DockedFrameProps {
+ documentId: FieldId,
+ //collectionDockingView: CollectionDockingView
+}
+@observer
+export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
- newRow.addChild(newContentItem, undefined, true);
- newRow.addChild(collayout, 0, true);
+ private _mainCont = React.createRef<HTMLDivElement>();
+ @observable private _panelWidth = 0;
+ @observable private _panelHeight = 0;
+ @observable private _document: Opt<Document>;
- collayout.config["width"] = 50;
- newContentItem.config["width"] = 50;
- collayout.parent.callDownwards('setSize');
- }
+ constructor(props: any) {
+ super(props);
+ Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document));
}
- goldenLayoutFactory() {
- CollectionDockingView.myLayout = this.modelForGoldenLayout;
- var layout = CollectionDockingView.myLayout;
- CollectionDockingView.myLayout.on('tabCreated', function (tab: any) {
- if (CollectionDockingView._dragDiv) {
- CollectionDockingView._dragDiv.removeChild(CollectionDockingView._dragElement);
- CollectionDockingView._dragParent!.removeChild(CollectionDockingView._dragFakeElement);
- CollectionDockingView._dragParent!.appendChild(CollectionDockingView._dragElement);
- DragManager.Root().removeChild(CollectionDockingView._dragDiv);
- CollectionDockingView._dragDiv = null;
- }
- tab.setTitle(tab.contentItem.config.componentState.doc.Title);
- tab.closeElement.off('click') //unbind the current click handler
- .click(function () {
- tab.contentItem.remove();
- });
- });
-
- CollectionDockingView.myLayout.on('stackCreated', function (stack: any) {
- if (CollectionDockingView.myLayout._makeFullScreen) {
- CollectionDockingView.myLayout._maximizedStack = stack;
- CollectionDockingView.myLayout._maxstack = stack.header.controlsContainer.find('.lm_maximise');
- }
- //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();
- //}
- });
- });
+ private _nativeWidth = () => { return this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth); }
+ private _nativeHeight = () => { return this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight); }
+ private _contentScaling = () => { return this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth); }
- var me = this;
- CollectionDockingView.myLayout.registerComponent('documentViewComponent', function (container: any, state: any) {
- // bcz: this is crufty
- // calling html() causes a div tag to be added in the DOM with id 'containingDiv'.
- // Apparently, we need to wait to allow a live html div element to actually be instantiated.
- // After a timeout, we lookup the live html div element and add our React DocumentView to it.
- var containingDiv = "component_" + me.nextId();
- container.getElement().html("<div id='" + containingDiv + "'></div>");
- setTimeout(function () {
- var htmlElement = document.getElementById(containingDiv);
- container.on('resize', (e: any) => {
- // state.doc.SetNumber(KeyStore.Width, htmlElement!.clientWidth);
- // if (htmlElement!.clientHeight > 0)
- // state.doc.SetNumber(KeyStore.Height, htmlElement!.clientHeight);
- })
- ReactDOM.render((
- <DocumentView key={state.doc.Id} Document={state.doc}
- AddDocument={me.addDocument} RemoveDocument={me.removeDocument}
- GetTransform={() => Transform.Identity}
- Scaling={1}
- ContainingCollectionView={me} DocumentView={undefined} />
- ),
- htmlElement
- );
- if (CollectionDockingView.myLayout._maxstack != null) {
- CollectionDockingView.myLayout._maxstack.click();
- }
- }, 0);
- });
- CollectionDockingView.myLayout.container = this._containerRef.current;
- CollectionDockingView.myLayout.init();
+ ScreenToLocalTransform = () => {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!);
+ return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this._contentScaling())
}
-
render() {
- const { CollectionFieldKey: fieldKey, DocumentForCollection: 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.
- var s = this.props.ContainingDocumentView != undefined ? this.props.ContainingDocumentView!.ScalingToScreenSpace : 1;
- var w = Document.GetData(KeyStore.Width, NumberField, Number(0)) / s;
- var h = Document.GetData(KeyStore.Height, NumberField, Number(0)) / s;
-
- var chooseLayout = () => {
- if (!CollectionDockingView.UseGoldenLayout)
- return <FlexLayout.Layout model={this.modelForFlexLayout} factory={this.flexLayoutFactory} />;
- }
-
- return (
- <div className="border" style={{
- borderStyle: "solid",
- borderWidth: `${COLLECTION_BORDER_WIDTH}px`,
- }}>
- <div className="collectiondockingview-container" id="menuContainer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} ref={this._containerRef}
- style={{
- width: CollectionDockingView.UseGoldenLayout || s > 1 ? "100%" : w - 2 * COLLECTION_BORDER_WIDTH,
- height: CollectionDockingView.UseGoldenLayout || s > 1 ? "100%" : h - 2 * COLLECTION_BORDER_WIDTH
- }} >
- {chooseLayout()}
- </div>
+ if (!this._document)
+ return (null);
+ var content =
+ <div className="collectionDockingView-content" ref={this._mainCont}>
+ <DocumentView key={this._document.Id} Document={this._document}
+ AddDocument={undefined}
+ RemoveDocument={undefined}
+ ContentScaling={this._contentScaling}
+ PanelWidth={this._nativeWidth}
+ PanelHeight={this._nativeHeight}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ isTopMost={true}
+ SelectOnLoad={false}
+ focus={(doc: Document) => { }}
+ ContainingCollectionView={undefined} />
</div>
- );
+
+ return <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
+ {({ measureRef }) => <div ref={measureRef}> {content} </div>}
+ </Measure>
}
} \ No newline at end of file