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/CollectionTreeView.tsx | |
| parent | 8f14e688220096ccecfd1aa0dd54b00e48f92270 (diff) | |
| parent | 6f49d067b58caf6297f7ae7687cf05b627c27a1d (diff) | |
merge with master
Diffstat (limited to 'src/client/views/collections/CollectionTreeView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 142 | 
1 files changed, 96 insertions, 46 deletions
| diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index b67d6f965..2814c0502 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,5 +1,5 @@  import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; +import { faCaretDown, faCaretRight, faTrashAlt, faAngleRight } from '@fortawesome/free-solid-svg-icons';  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';  import { action, observable, trace } from "mobx";  import { observer } from "mobx-react"; @@ -9,15 +9,17 @@ import { CollectionSubView } from "./CollectionSubView";  import "./CollectionTreeView.scss";  import React = require("react");  import { Document, listSpec } from '../../../new_fields/Schema'; -import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types'; -import { Doc } from '../../../new_fields/Doc'; -import { Id } from '../../../new_fields/RefField'; +import { Cast, StrCast, BoolCast, FieldValue, NumCast } from '../../../new_fields/Types'; +import { Doc, DocListCast } from '../../../new_fields/Doc'; +import { Id } from '../../../new_fields/FieldSymbols';  import { ContextMenu } from '../ContextMenu';  import { undoBatch } from '../../util/UndoManager'; -import { Main } from '../Main';  import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';  import { CollectionDockingView } from './CollectionDockingView';  import { DocumentManager } from '../../util/DocumentManager'; +import { Docs } from '../../documents/Documents'; +import { MainView } from '../MainView'; +import { CollectionViewType } from './CollectionBaseView';  export interface TreeViewProps { @@ -25,6 +27,7 @@ export interface TreeViewProps {      deleteDoc: (doc: Doc) => void;      moveDocument: DragManager.MoveFunction;      dropAction: "alias" | "copy" | undefined; +    addDocTab: (doc: Doc, where: string) => void;  }  export enum BulletType { @@ -34,6 +37,7 @@ export enum BulletType {  }  library.add(faTrashAlt); +library.add(faAngleRight);  library.add(faCaretDown);  library.add(faCaretRight); @@ -45,15 +49,27 @@ class TreeView extends React.Component<TreeViewProps> {      @observable _collapsed: boolean = true; -    delete = () => this.props.deleteDoc(this.props.document); +    @undoBatch delete = () => this.props.deleteDoc(this.props.document); + +    @undoBatch openRight = async () => { +        if (this.props.document.dockingConfig) { +            MainView.Instance.openWorkspace(this.props.document); +        } else { +            this.props.addDocTab(this.props.document, "openRight"); +        } +    }      get children() {          return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed?    .filter(doc => FieldValue(doc));      } +    onPointerDown = (e: React.PointerEvent) => { +        e.stopPropagation(); +    } +      @action -    remove = (document: Document) => { -        let children = Cast(this.props.document.data, listSpec(Doc), []); +    remove = (document: Document, key: string) => { +        let children = Cast(this.props.document[key], listSpec(Doc), []);          if (children) {              children.splice(children.indexOf(document), 1);          } @@ -65,7 +81,7 @@ class TreeView extends React.Component<TreeViewProps> {              return true;          }          //TODO This should check if it was removed -        this.remove(document); +        this.remove(document, "data");          return addDoc(document);      } @@ -79,6 +95,15 @@ class TreeView extends React.Component<TreeViewProps> {          return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>;      } +    @action +    onMouseEnter = () => { +        this._isOver = true; +    } +    @observable _isOver: boolean = false; +    @action +    onMouseLeave = () => { +        this._isOver = false; +    }      /**       * Renders the EditableView title element for placement into the tree.       */ @@ -87,7 +112,8 @@ class TreeView extends React.Component<TreeViewProps> {          let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.dropAction);          let editableView = (titleString: string) =>              (<EditableView -                display={"inline"} +                oneLine={!this._isOver ? true : false} +                display={"inline-block"}                  contents={titleString}                  height={36}                  GetValue={() => StrCast(this.props.document.title)} @@ -97,46 +123,73 @@ class TreeView extends React.Component<TreeViewProps> {                      return true;                  }}              />); +        let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []) : []; +        let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : ( +            <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}> +                <FontAwesomeIcon icon="angle-right" size="lg" /> +                {/* <FontAwesomeIcon icon="angle-right" size="lg" /> */} +            </div>);          return ( -            <div className="docContainer" ref={reference} onPointerDown={onItemDown}> +            <div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} +                style={{ background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }} +                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>                  {editableView(StrCast(this.props.document.title))} -                {/* <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div> */} +                {openRight} +                {/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}              </div >);      }      onWorkspaceContextMenu = (e: React.MouseEvent): void => { -        if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 -            ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => Main.Instance.openWorkspace(this.props.document)) }); -            ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) }); -            if (DocumentManager.Instance.getDocumentViews(this.props.document).length) { -                ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) }); +        if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 +            ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) }); +            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" }); +            if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) { +                ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, "inTab"), icon: "folder" }); +                ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight"), icon: "caret-square-right" }); +                if (DocumentManager.Instance.getDocumentViews(this.props.document).length) { +                    ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) }); +                } +                ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)) }); +            } else { +                ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });              } -            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });              ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);              e.stopPropagation();          }      } -    onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; } -    onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; } +    onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; }; +    onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; };      render() {          let bulletType = BulletType.List; -        let contentElement: JSX.Element | null = (null); -        var children = Cast(this.props.document.data, listSpec(Doc)); -        if (children) { // add children for a collection -            if (!this._collapsed) { -                bulletType = BulletType.Collapsible; -                contentElement = <ul> -                    {TreeView.GetChildElements(children, this.remove, this.move, this.props.dropAction)} -                </ul >; -            } -            else bulletType = BulletType.Collapsed; +        let contentElement: (JSX.Element | null)[] = []; +        let keys = Array.from(Object.keys(this.props.document)); +        if (this.props.document.proto instanceof Doc) { +            keys.push(...Array.from(Object.keys(this.props.document.proto))); +            while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);          } +        keys.map(key => { +            let docList = DocListCast(this.props.document[key]); +            let doc = Cast(this.props.document[key], Doc); +            if (doc instanceof Doc || docList.length) { +                if (!this._collapsed) { +                    bulletType = BulletType.Collapsible; +                    let spacing = (key === "data") ? 0 : -10; +                    contentElement.push(<ul key={key + "more"}> +                        {(key === "data") ? (null) : +                            <span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>} +                        <div style={{ display: "block", marginTop: `${spacing}px` }}> +                            {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction, this.props.addDocTab)} +                        </div> +                    </ul >); +                } else { +                    bulletType = BulletType.Collapsed; +                } +            } +        });          return <div className="treeViewItem-container" -            style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }} -            onContextMenu={this.onWorkspaceContextMenu} -            onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> +            onContextMenu={this.onWorkspaceContextMenu}>              <li className="collection-child">                  {this.renderBullet(bulletType)}                  {this.renderTitle()} @@ -144,9 +197,9 @@ class TreeView extends React.Component<TreeViewProps> {              </li>          </div>;      } -    public static GetChildElements(docs: Doc[], remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) { -        return docs.filter(child => !child.excludeFromLibrary).filter(doc => FieldValue(doc)).map(child => -            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />); +    public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => void) { +        return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child => +            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} addDocTab={addDocTab} />);      }  } @@ -160,27 +213,24 @@ export class CollectionTreeView extends CollectionSubView(Document) {          }      }      onContextMenu = (e: React.MouseEvent): void => { -        if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 -            ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => Main.Instance.createNewWorkspace()) }); -        } -        if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) { -            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) }); +        // 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.props.Document.excludeFromLibrary) { // excludeFromLibrary means this is the user document +            ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()) }); +            ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)) });          }      }      render() { -        trace(); -        const children = this.children;          let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType; -        if (!children) { +        if (!this.childDocs) {              return (null);          } -        let childElements = TreeView.GetChildElements(children, this.remove, this.props.moveDocument, dropAction); +        let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction, this.props.addDocTab);          return (              <div id="body" className="collectionTreeView-dropTarget"                  style={{ borderRadius: "inherit" }}                  onContextMenu={this.onContextMenu} -                onWheel={(e: React.WheelEvent) => e.stopPropagation()} +                onWheel={(e: React.WheelEvent) => this.props.isSelected() && e.stopPropagation()}                  onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>                  <div className="coll-title">                      <EditableView | 
