diff options
| author | Eleanor Eng <eleanor_eng@brown.edu> | 2019-06-04 10:29:02 -0400 | 
|---|---|---|
| committer | Eleanor Eng <eleanor_eng@brown.edu> | 2019-06-04 10:29:02 -0400 | 
| commit | 376ebd44a16dfa04aacd3582e87767aed1a01f36 (patch) | |
| tree | 3a9e623cf6689e1ea6975954596bf5bda6303249 /src/client/views/collections/CollectionDockingView.tsx | |
| parent | 8f14e688220096ccecfd1aa0dd54b00e48f92270 (diff) | |
| parent | 6f49d067b58caf6297f7ae7687cf05b627c27a1d (diff) | |
merge with master
Diffstat (limited to 'src/client/views/collections/CollectionDockingView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 221 | 
1 files changed, 168 insertions, 53 deletions
| diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index cfb1aef7d..dcc1bd95d 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,25 +1,28 @@ -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, observable, reaction, trace, runInAction } from "mobx"; +import { action, observable, reaction, Lambda } from "mobx";  import { observer } from "mobx-react";  import * as ReactDOM from 'react-dom';  import Measure from "react-measure"; -import { Utils, returnTrue, emptyFunction, returnOne, returnZero } from "../../../Utils"; +import * as GoldenLayout from "../../../client/goldenLayout"; +import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc"; +import { FieldId } from "../../../new_fields/RefField"; +import { listSpec } from "../../../new_fields/Schema"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { emptyFunction, returnTrue, Utils } from "../../../Utils"; +import { DocServer } from "../../DocServer"; +import { DragLinksAsDocuments, DragManager } from "../../util/DragManager"; +import { Transform } from '../../util/Transform';  import { undoBatch, UndoManager } from "../../util/UndoManager";  import { DocumentView } from "../nodes/DocumentView";  import "./CollectionDockingView.scss"; -import React = require("react");  import { SubCollectionViewProps } from "./CollectionSubView"; -import { DragManager, DragLinksAsDocuments } from "../../util/DragManager"; -import { Transform } from '../../util/Transform'; -import { Doc, Opt, Field } from "../../../new_fields/Doc"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { List } from "../../../new_fields/List"; -import { DocServer } from "../../DocServer"; -import { listSpec } from "../../../new_fields/Schema"; -import { Id, FieldId } from "../../../new_fields/RefField"; -import { faSignInAlt } from "@fortawesome/free-solid-svg-icons"; +import React = require("react"); +import { ParentDocSelector } from './ParentDocumentSelector'; +import { DocumentManager } from '../../util/DocumentManager'; +import { CollectionViewType } from './CollectionBaseView'; +import { Id } from '../../../new_fields/FieldSymbols'; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';  @observer  export class CollectionDockingView extends React.Component<SubCollectionViewProps> { @@ -72,11 +75,52 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp          this.stateChanged();      } +    @undoBatch +    @action +    public CloseRightSplit = (document: Doc): boolean => { +        let retVal = false; +        if (this._goldenLayout.root.contentItems[0].isRow) { +            retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { +                if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && +                    Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) { +                    child.contentItems[0].remove(); +                    this.layoutChanged(document); +                    return true; +                } else { +                    Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { +                        if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { +                            child.contentItems[j].remove(); +                            child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0); +                            let docs = Cast(this.props.Document.data, listSpec(Doc)); +                            docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1); +                            return true; +                        } +                        return false; +                    }); +                } +                return false; +            }); +        } +        if (retVal) { +            this.stateChanged(); +        } +        return retVal; +    } + +    @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()); +        if (removed) CollectionDockingView.Instance._removedDocs.push(removed); +        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: Doc, minimize: boolean = false) { +    public AddRightSplit = (document: Doc, minimize: boolean = false) => {          let docs = Cast(this.props.Document.data, listSpec(Doc));          if (docs) {              docs.push(document); @@ -103,17 +147,26 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              newContentItem.config.width = 50;          }          if (minimize) { -            newContentItem.config.width = 10; -            newContentItem.config.height = 10; +            // bcz: this makes the drag image show up better, but it also messes with fixed layout sizes +            // 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._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); -        this.stateChanged(); +        this.layoutChanged();          return newContentItem;      } +    @action +    public AddTab = (stack: any, document: Doc) => { +        let docs = Cast(this.props.Document.data, listSpec(Doc)); +        if (docs) { +            docs.push(document); +        } +        let docContentConfig = CollectionDockingView.makeDocumentConfig(document); +        var newContentItem = stack.layoutManager.createContentItem(docContentConfig, this._goldenLayout); +        stack.addChild(newContentItem.contentItems[0], undefined); +        this.layoutChanged(); +    }      setupGoldenLayout() {          var config = StrCast(this.props.Document.dockingConfig); @@ -128,6 +181,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp                  try {                      this._goldenLayout.unbind('itemDropped', this.itemDropped);                      this._goldenLayout.unbind('tabCreated', this.tabCreated); +                    this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);                      this._goldenLayout.unbind('stackCreated', this.stackCreated);                  } catch (e) { }                  this._goldenLayout.destroy(); @@ -135,6 +189,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              }              this._goldenLayout.on('itemDropped', this.itemDropped);              this._goldenLayout.on('tabCreated', this.tabCreated); +            this._goldenLayout.on('tabDestroyed', this.tabDestroyed);              this._goldenLayout.on('stackCreated', this.stackCreated);              this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);              this._goldenLayout.container = this._containerRef.current; @@ -148,12 +203,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              this._goldenLayout.init();          }      } +    reactionDisposer?: Lambda;      componentDidMount: () => void = () => {          if (this._containerRef.current) { -            reaction( +            this.reactionDisposer = reaction(                  () => StrCast(this.props.Document.dockingConfig),                  () => {                      if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) { +                        // Because this is in a set timeout, if this component unmounts right after mounting, +                        // we will leak a GoldenLayout, because we try to destroy it before we ever create it                          setTimeout(() => this.setupGoldenLayout(), 1);                      }                      this._ignoreStateChange = ""; @@ -167,12 +225,17 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              this._goldenLayout.unbind('itemDropped', this.itemDropped);              this._goldenLayout.unbind('tabCreated', this.tabCreated);              this._goldenLayout.unbind('stackCreated', this.stackCreated); +            this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);          } catch (e) {          }          if (this._goldenLayout) this._goldenLayout.destroy();          this._goldenLayout = null;          window.removeEventListener('resize', this.onResize); + +        if (this.reactionDisposer) { +            this.reactionDisposer(); +        }      }      @action      onResize = (event: any) => { @@ -209,6 +272,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp                  let y = e.clientY;                  let docid = (e.target as any).DashDocId;                  let tab = (e.target as any).parentElement as HTMLElement; +                let glTab = (e.target as any).Tab; +                if (glTab && glTab.contentItem && glTab.contentItem.parent) { +                    glTab.contentItem.parent.setActiveContentItem(glTab.contentItem); +                }                  DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {                      if (f instanceof Doc) {                          DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y, @@ -231,6 +298,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp      @undoBatch      stateChanged = () => { +        let docs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc)); +        CollectionDockingView.Instance._removedDocs.map(theDoc => +            docs && docs.indexOf(theDoc) !== -1 && +            docs.splice(docs.indexOf(theDoc), 1)); +        CollectionDockingView.Instance._removedDocs.length = 0;          var json = JSON.stringify(this._goldenLayout.toConfig());          this.props.Document.dockingConfig = json;          if (this.undohack && !this.hack) { @@ -251,44 +323,54 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp          return template.content.firstChild;      } -    tabCreated = (tab: any) => { +    tabCreated = async (tab: any) => {          if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { -            DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async f => { -                if (f instanceof Doc) { -                    const title = Cast(f.title, "string"); -                    if (title !== undefined) { -                        tab.titleElement[0].textContent = title; -                    } -                    const lf = await Cast(f.linkedFromDocs, listSpec(Doc)); -                    const lt = await Cast(f.linkedToDocs, listSpec(Doc)); -                    let count = (lf ? lf.length : 0) + (lt ? lt.length : 0); -                    let counter: any = this.htmlToElement(`<div class="messageCounter">${count}</div>`); +            if (tab.contentItem.config.fixed) { +                tab.contentItem.parent.config.fixed = true; +            } +            DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async doc => { +                if (doc instanceof Doc) { +                    let counter: any = this.htmlToElement(`<span class="messageCounter">0</div>`);                      tab.element.append(counter); +                    let upDiv = document.createElement("span"); +                    const stack = tab.contentItem.parent; +                    ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv); +                    tab.reactComponents = [upDiv]; +                    tab.element.append(upDiv);                      counter.DashDocId = tab.contentItem.config.props.documentId; -                    tab.reactionDisposer = reaction((): [List<Field> | null | undefined, List<Field> | null | undefined] => [lf, lt], -                        ([linkedFrom, linkedTo]) => { -                            let count = (linkedFrom ? linkedFrom.length : 0) + (linkedTo ? linkedTo.length : 0); -                            counter.innerHTML = count; -                        }); +                    tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title], +                        () => { +                            counter.innerHTML = DocListCast(doc.linkedFromDocs).length + DocListCast(doc.linkedToDocs).length; +                            tab.titleElement[0].textContent = doc.title; +                        }, { fireImmediately: true });                      tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;                  }              });          } +        tab.titleElement[0].Tab = tab;          tab.closeElement.off('click') //unbind the current click handler -            .click(function () { +            .click(async function () {                  if (tab.reactionDisposer) {                      tab.reactionDisposer();                  } -                DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async f => runInAction(() => { -                    if (f instanceof Doc) { -                        let docs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc)); -                        docs && docs.indexOf(f) !== -1 && docs.splice(docs.indexOf(f), 1); -                    } -                })); +                let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId); +                if (doc instanceof Doc) { +                    let theDoc = doc; +                    CollectionDockingView.Instance._removedDocs.push(theDoc); +                }                  tab.contentItem.remove();              });      } +    tabDestroyed = (tab: any) => { +        if (tab.reactComponents) { +            for (const ele of tab.reactComponents) { +                ReactDOM.unmountComponentAtNode(ele); +            } +        } +    } +    _removedDocs: Doc[] = []; +      stackCreated = (stack: any) => {          //stack.header.controlsContainer.find('.lm_popout').hide();          stack.header.controlsContainer.find('.lm_close') //get the close icon @@ -296,13 +378,21 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              .click(action(function () {                  //if (confirm('really close this?')) {                  stack.remove(); +                stack.contentItems.map(async (contentItem: any) => { +                    let doc = await DocServer.GetRefField(contentItem.config.props.documentId); +                    if (doc instanceof Doc) { +                        let theDoc = doc; +                        CollectionDockingView.Instance._removedDocs.push(theDoc); +                    } +                });                  //}              }));          stack.header.controlsContainer.find('.lm_popout') //get the close icon              .off('click') //unbind the current click handler              .click(action(function () { -                var url = DocServer.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId); -                let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400"); +                stack.config.fixed = !stack.config.fixed; +                // var url = DocServer.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId); +                // let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");              }));      } @@ -312,6 +402,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp                  onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />          );      } +  }  interface DockedFrameProps { @@ -324,7 +415,12 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {      @observable private _panelWidth = 0;      @observable private _panelHeight = 0;      @observable private _document: Opt<Doc>; - +    get _stack(): any { +        let parent = (this.props as any).glContainer.parent.parent; +        if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) +            return parent.parent.contentItems[1]; +        return parent; +    }      constructor(props: any) {          super(props);          DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc)); @@ -343,20 +439,38 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {          if (this._mainCont.current && this._mainCont.current.children) {              let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement);              scale = Utils.GetScreenTransform(this._mainCont.current).scale; -            return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling()); +            return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);          }          return Transform.Identity();      } +    get scaleToFitMultiplier() { +        let docWidth = NumCast(this._document!.width); +        let docHeight = NumCast(this._document!.height); +        if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1; +        if (StrCast(this._document!.layout).indexOf("Collection") === -1 || +            NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1; +        let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ? +            this._panelHeight / docHeight : this._panelWidth / docWidth); +        return scaling; +    }      get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; } +    addDocTab = (doc: Doc, location: string) => { +        if (location === "onRight") { +            CollectionDockingView.Instance.AddRightSplit(doc); +        } else { +            CollectionDockingView.Instance.AddTab(this._stack, doc); +        } +    }      get content() { -        if (!this._document) +        if (!this._document) {              return (null); +        }          return (              <div className="collectionDockingView-content" ref={this._mainCont} -                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}> -                <DocumentView key={this._document![Id]} Document={this._document!} -                    toggleMinimized={emptyFunction} +                style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}> +                <DocumentView key={this._document[Id]} Document={this._document} +                    bringToFront={emptyFunction}                      addDocument={undefined}                      removeDocument={undefined}                      ContentScaling={this.contentScaling} @@ -368,6 +482,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {                      parentActive={returnTrue}                      whenActiveChanged={emptyFunction}                      focus={emptyFunction} +                    addDocTab={this.addDocTab}                      ContainingCollectionView={undefined} />              </div >);      } @@ -375,7 +490,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {      render() {          let theContent = this.content;          return !this._document ? (null) : -            <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}> +            <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>                  {({ measureRef }) => <div ref={measureRef}>  {theContent} </div>}              </Measure>;      } | 
