aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionTreeView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections/CollectionTreeView.tsx')
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx308
1 files changed, 194 insertions, 114 deletions
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8b06d9ac4..6acef434e 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,20 +1,32 @@
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+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";
-import { CollectionViewBase } from "./CollectionViewBase";
-import { Document } from "../../../fields/Document";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
-import React = require("react")
-import { TextField } from "../../../fields/TextField";
-import { observable, action } from "mobx";
-import "./CollectionTreeView.scss";
+import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
import { EditableView } from "../EditableView";
-import { setupDrag } from "../../util/DragManager";
-import { FieldWaiting } from "../../../fields/Field";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
+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, DocListCast } from '../../../new_fields/Doc';
+import { Id } from '../../../new_fields/RefField';
+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 { List } from '../../../new_fields/List';
+import { Docs } from '../../documents/Documents';
+
export interface TreeViewProps {
- document: Document;
- deleteDoc: (doc: Document) => void;
+ document: Doc;
+ deleteDoc: (doc: Doc) => void;
+ moveDocument: DragManager.MoveFunction;
+ dropAction: "alias" | "copy" | undefined;
}
export enum BulletType {
@@ -23,151 +35,219 @@ export enum BulletType {
List
}
+library.add(faTrashAlt);
+library.add(faAngleRight);
+library.add(faCaretDown);
+library.add(faCaretRight);
+
@observer
/**
* Component that takes in a document prop and a boolean whether it's collapsed or not.
*/
class TreeView extends React.Component<TreeViewProps> {
- @observable
- collapsed: boolean = false;
+ @observable _collapsed: boolean = true;
+
+ @undoBatch delete = () => this.props.deleteDoc(this.props.document);
- delete = () => {
- this.props.deleteDoc(this.props.document);
+ @undoBatch openRight = async () => {
+ if (this.props.document.dockingConfig) {
+ Main.Instance.openWorkspace(this.props.document);
+ } else {
+ CollectionDockingView.Instance.AddRightSplit(this.props.document);
+ }
}
+ 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) => {
- var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- if (children && children !== FieldWaiting) {
- children.Data.splice(children.Data.indexOf(document), 1);
+ remove = (document: Document, key: string) => {
+ let children = Cast(this.props.document[key], listSpec(Doc), []);
+ if (children) {
+ children.splice(children.indexOf(document), 1);
}
}
- renderBullet(type: BulletType) {
- let onClicked = action(() => this.collapsed = !this.collapsed);
+ @action
+ move: DragManager.MoveFunction = (document, target, addDoc) => {
+ if (this.props.document === target) {
+ return true;
+ }
+ //TODO This should check if it was removed
+ this.remove(document, "data");
+ return addDoc(document);
+ }
+ renderBullet(type: BulletType) {
+ let onClicked = action(() => this._collapsed = !this._collapsed);
+ let bullet: IconProp | undefined = undefined;
switch (type) {
- case BulletType.Collapsed:
- return <div className="bullet" onClick={onClicked}>&#9654;</div>
- case BulletType.Collapsible:
- return <div className="bullet" onClick={onClicked}>&#9660;</div>
- case BulletType.List:
- return <div className="bullet">&mdash;</div>
+ case BulletType.Collapsed: bullet = "caret-right"; break;
+ case BulletType.Collapsible: bullet = "caret-down"; break;
}
+ 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.
*/
renderTitle() {
- let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
-
- // if the title hasn't loaded, immediately return the div
- if (!title || title === "<Waiting>") {
- return <div key={this.props.document.Id}></div>;
- }
-
- return <div className="docContainer"> <EditableView contents={title.Data}
- height={36} GetValue={() => {
- let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
- if (title && title !== "<Waiting>")
- return title.Data;
- return "";
- }} SetValue={(value: string) => {
- this.props.document.SetData(KeyStore.Title, value, TextField);
- return true;
- }} />
- <div className="delete-button" onClick={this.delete}>x</div>
- </div >
+ let reference = React.createRef<HTMLDivElement>();
+ let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.dropAction);
+ let editableView = (titleString: string) =>
+ (<EditableView
+ oneLine={!this._isOver ? true : false}
+ display={"block"}
+ contents={titleString}
+ height={36}
+ GetValue={() => StrCast(this.props.document.title)}
+ SetValue={(value: string) => {
+ let target = this.props.document.proto ? this.props.document.proto : this.props.document;
+ target.title = value;
+ return true;
+ }}
+ />);
+ let dataDocs = 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} 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))}
+ {openRight}
+ {/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}
+ </div >);
}
- render() {
- var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-
- let reference = React.createRef<HTMLDivElement>();
- let onItemDown = setupDrag(reference, () => this.props.document);
- let titleElement = this.renderTitle();
-
- // check if this document is a collection
- if (children && children !== FieldWaiting) {
- let subView;
-
- // if uncollapsed, then add the children elements
- if (!this.collapsed) {
- // render all children elements
- let childrenElement = (children.Data.map(value =>
- <TreeView document={value} deleteDoc={this.remove} />)
- )
- subView =
- <li key={this.props.document.Id} >
- {this.renderBullet(BulletType.Collapsible)}
- {titleElement}
- <ul key={this.props.document.Id}>
- {childrenElement}
- </ul>
- </li>
- } else {
- subView = <li key={this.props.document.Id}>
- {this.renderBullet(BulletType.Collapsed)}
- {titleElement}
- </li>
+ 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) });
+ ContextMenu.Instance.addItem({
+ description: "Open Fields", event: () => CollectionDockingView.Instance.AddRightSplit(Docs.KVPDocument(this.props.document,
+ { title: this.props.document.title + ".kvp", width: 300, height: 300 }))
+ });
+ 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)) });
}
-
- return <div className="treeViewItem-container" onPointerDown={onItemDown} ref={reference}>
- {subView}
- </div>
+ 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; };
- // otherwise this is a normal leaf node
- else {
- return <li key={this.props.document.Id}>
- {this.renderBullet(BulletType.List)}
- {titleElement}
- </li>;
+ render() {
+ let bulletType = BulletType.List;
+ 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)}
+ </div>
+ </ul >);
+ } else {
+ bulletType = BulletType.Collapsed;
+ }
+ }
+ });
+ return <div className="treeViewItem-container"
+ onContextMenu={this.onWorkspaceContextMenu}>
+ <li className="collection-child">
+ {this.renderBullet(bulletType)}
+ {this.renderTitle()}
+ {contentElement}
+ </li>
+ </div>;
+ }
+ public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
+ return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child =>
+ <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
}
}
-
@observer
-export class CollectionTreeView extends CollectionViewBase {
-
+export class CollectionTreeView extends CollectionSubView(Document) {
@action
remove = (document: Document) => {
- var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- if (children && children !== FieldWaiting) {
- children.Data.splice(children.Data.indexOf(document), 1);
+ let children = Cast(this.props.Document.data, listSpec(Doc), []);
+ if (children) {
+ children.splice(children.indexOf(document), 1);
+ }
+ }
+ 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)) });
}
}
-
render() {
- let titleStr = "";
- let title = this.props.Document.GetT<TextField>(KeyStore.Title, TextField);
- if (title && title !== FieldWaiting) {
- titleStr = title.Data;
+ let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
+ if (!this.children) {
+ return (null);
}
-
- var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- let childrenElement = !children || children === FieldWaiting ? (null) :
- (children.Data.map(value =>
- <TreeView document={value} key={value.Id} deleteDoc={this.remove} />)
- )
+ let childElements = TreeView.GetChildElements(this.children, false, this.remove, this.props.moveDocument, dropAction);
return (
- <div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
- <h3>
- <EditableView contents={titleStr}
- height={72} GetValue={() => {
- return this.props.Document.Title;
- }} SetValue={(value: string) => {
- this.props.Document.SetData(KeyStore.Title, value, TextField);
+ <div id="body" className="collectionTreeView-dropTarget"
+ style={{ borderRadius: "inherit" }}
+ onContextMenu={this.onContextMenu}
+ onWheel={(e: React.WheelEvent) => e.stopPropagation()}
+ onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
+ <div className="coll-title">
+ <EditableView
+ contents={this.props.Document.title}
+ display={"inline"}
+ height={72}
+ GetValue={() => StrCast(this.props.Document.title)}
+ SetValue={(value: string) => {
+ let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+ target.title = value;
return true;
}} />
- </h3>
+ </div>
<ul className="no-indent">
- {childrenElement}
+ {childElements}
</ul>
</div >
);