aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionTreeView.tsx
diff options
context:
space:
mode:
authorab <abdullah_ahmed@brown.edu>2019-07-29 15:44:37 -0400
committerab <abdullah_ahmed@brown.edu>2019-07-29 15:44:37 -0400
commit38b5d646e62535504eb8667b840bf36cd7f2f6d8 (patch)
tree1481b6cdfb9a0853e8405a7dfb96a8fbd9066a9f /src/client/views/collections/CollectionTreeView.tsx
parentc2dead205fe719881ca7e254c1872e03a2da9b3d (diff)
parente7ea2028f54787d6c92fb22b789f17b7268d3793 (diff)
more improvemenets tmrw
Diffstat (limited to 'src/client/views/collections/CollectionTreeView.tsx')
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx180
1 files changed, 112 insertions, 68 deletions
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d05cc375e..4d31c3ae7 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,9 +1,9 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight, faCamera, faExpand, faTrash, faBell, faCaretDown, faCaretRight, faArrowsAltH, faCaretSquareDown, faCaretSquareRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
+import { faAngleRight, faCamera, faExpand, faTrash, faBell, faCaretDown, faCaretRight, faArrowsAltH, faCaretSquareDown, faCaretSquareRight, faTrashAlt, faPlus, faMinus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, trace, untracked } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym, Opt } from '../../../new_fields/Doc';
+import { Doc, DocListCast, HeightSym, WidthSym, Opt, Field } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
@@ -26,6 +26,8 @@ import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
import { LinkManager } from '../../util/LinkManager';
+import { ComputedField } from '../../../new_fields/ScriptField';
+import { KeyValueBox } from '../nodes/KeyValueBox';
export interface TreeViewProps {
@@ -59,7 +61,7 @@ library.add(faCaretRight);
library.add(faCaretSquareDown);
library.add(faCaretSquareRight);
library.add(faArrowsAltH);
-
+library.add(faPlus, faMinus);
@observer
/**
* Component that takes in a document prop and a boolean whether it's collapsed or not.
@@ -68,33 +70,42 @@ class TreeView extends React.Component<TreeViewProps> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
- @observable __chosenKey: string = "";
- @computed get _chosenKey() { return this.__chosenKey ? this.__chosenKey : this.fieldKey; }
+ @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, "data"); }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
@observable _collapsed: boolean = true;
@computed get fieldKey() {
- let keys = Array.from(Object.keys(this.resolvedDataDoc)); // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
- if (this.resolvedDataDoc.proto instanceof Doc) {
- let arr = Array.from(Object.keys(this.resolvedDataDoc.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+ let target = this.props.document;
+ let keys = Array.from(Object.keys(target)); // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+ if (target.proto instanceof Doc) {
+ let arr = Array.from(Object.keys(target.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
keys.push(...arr);
while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
}
let keyList: string[] = [];
keys.map(key => {
- let docList = Cast(this.resolvedDataDoc[key], listSpec(Doc));
+ let docList = Cast(this.dataDoc[key], listSpec(Doc));
if (docList && docList.length > 0) {
keyList.push(key);
}
});
let layout = StrCast(this.props.document.layout);
- if (layout.indexOf("fieldKey={\"") !== -1) {
+ if (layout.indexOf("fieldKey={\"") !== -1 && layout.indexOf("fieldExt=") === -1) {
return layout.split("fieldKey={\"")[1].split("\"")[0];
}
return keyList.length ? keyList[0] : "data";
}
- @computed get resolvedDataDoc() { return BoolCast(this.props.document.isTemplate) && this.props.dataDoc ? this.props.dataDoc : this.props.document; }
+ @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; }
+ @computed get resolvedDataDoc() {
+ if (this.props.dataDoc === undefined && this.props.document.layout instanceof Doc) {
+ // if there is no dataDoc (ie, we're not rendering a template layout), but this document
+ // has a template layout document, then we will render the template layout but use
+ // this document as the data document for the layout.
+ return this.props.document;
+ }
+ return this.props.dataDoc ? this.props.dataDoc : undefined;
+ }
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer && this._treedropDisposer();
@@ -103,7 +114,7 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
- @undoBatch delete = () => this.props.deleteDoc(this.resolvedDataDoc);
+ @undoBatch delete = () => this.props.deleteDoc(this.dataDoc);
@undoBatch openRight = async () => this.props.addDocTab(this.props.document, undefined, "onRight");
onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
@@ -115,12 +126,12 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
onPointerLeave = (e: React.PointerEvent): void => {
- this.props.document.libraryBrush = undefined;
+ this.props.document.libraryBrush = false;
this._header!.current!.className = "treeViewItem-header";
document.removeEventListener("pointermove", this.onDragMove, true);
}
onDragMove = (e: PointerEvent): void => {
- this.props.document.libraryBrush = undefined;
+ this.props.document.libraryBrush = false;
let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
let rect = this._header!.current!.getBoundingClientRect();
let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
@@ -135,7 +146,7 @@ class TreeView extends React.Component<TreeViewProps> {
@action
remove = (document: Document, key: string): boolean => {
- let children = Cast(this.resolvedDataDoc[key], listSpec(Doc), []);
+ let children = Cast(this.dataDoc[key], listSpec(Doc), []);
if (children.indexOf(document) !== -1) {
children.splice(children.indexOf(document), 1);
return true;
@@ -151,8 +162,8 @@ class TreeView extends React.Component<TreeViewProps> {
indent = () => this.props.addDocument(this.props.document) && this.delete()
renderBullet() {
- let docList = Cast(this.resolvedDataDoc[this.fieldKey], listSpec(Doc));
- let doc = Cast(this.resolvedDataDoc[this.fieldKey], Doc);
+ let docList = Cast(this.dataDoc[this.fieldKey], listSpec(Doc));
+ let doc = Cast(this.dataDoc[this.fieldKey], Doc);
let isDoc = doc instanceof Doc || docList;
let c;
return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
@@ -164,58 +175,41 @@ class TreeView extends React.Component<TreeViewProps> {
editableView = (key: string, style?: string) => (<EditableView
oneLine={true}
display={"inline"}
- editing={this.resolvedDataDoc[Id] === TreeView.loadId}
+ editing={this.dataDoc[Id] === TreeView.loadId}
contents={StrCast(this.props.document[key])}
height={36}
fontStyle={style}
fontSize={12}
GetValue={() => StrCast(this.props.document[key])}
- SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc)[key] = value) ? true : true}
+ SetValue={(value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true}
OnFillDown={(value: string) => {
- Doc.GetProto(this.resolvedDataDoc)[key] = value;
- let doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ Doc.GetProto(this.dataDoc)[key] = value;
+ let doc = this.props.document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.detailedLayout)) : undefined;
+ if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
TreeView.loadId = doc[Id];
return this.props.addDocument(doc);
}}
OnTab={() => this.props.indentDocument && this.props.indentDocument()}
/>)
- @computed get keyList() {
- let keys = Array.from(Object.keys(this.resolvedDataDoc));
- if (this.resolvedDataDoc.proto instanceof Doc) {
- keys.push(...Array.from(Object.keys(this.resolvedDataDoc.proto)));
- }
- let keyList: string[] = keys.reduce((l, key) => {
- let listspec = DocListCast(this.resolvedDataDoc[key]);
- if (listspec && listspec.length) return [...l, key];
- return l;
- }, [] as string[]);
- keys.map(key => Cast(this.resolvedDataDoc[key], Doc) instanceof Doc && keyList.push(key));
- if (LinkManager.Instance.getAllRelatedLinks(this.props.document).length > 0) keyList.push("links");
- if (keyList.indexOf(this.fieldKey) !== -1) {
- keyList.splice(keyList.indexOf(this.fieldKey), 1);
- }
- keyList.splice(0, 0, this.fieldKey);
- return keyList.filter((item, index) => keyList.indexOf(item) >= index);
- }
/**
* Renders the EditableView title element for placement into the tree.
*/
renderTitle() {
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = SetupDrag(reference, () => this.resolvedDataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
+ let onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
let headerElements = (
- <span className="collectionTreeView-keyHeader" key={this._chosenKey + "chosen"}
+ <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
onPointerDown={action(() => {
- let ind = this.keyList.indexOf(this._chosenKey);
- ind = (ind + 1) % this.keyList.length;
- this.__chosenKey = this.keyList[ind];
- })} >
- {this._chosenKey}
+ this.props.document.treeViewExpandedView = this.treeViewExpandedView === "data" ? "fields" :
+ this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : "data";
+ this._collapsed = false;
+ })}>
+ {this.treeViewExpandedView}
</span>);
let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : [];
- let openRight = dataDocs && dataDocs.indexOf(this.resolvedDataDoc) !== -1 ? (null) : (
+ let openRight = dataDocs && dataDocs.indexOf(this.dataDoc) !== -1 ? (null) : (
<div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
<FontAwesomeIcon icon="angle-right" size="lg" />
</div>);
@@ -237,16 +231,15 @@ class TreeView extends React.Component<TreeViewProps> {
onWorkspaceContextMenu = (e: React.MouseEvent): void => {
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: (BoolCast(this.props.document.embed) ? "Collapse" : "Expand") + " inline", event: () => this.props.document.embed = !BoolCast(this.props.document.embed), icon: "expand" });
if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "inTab"), icon: "folder" });
ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "onRight"), icon: "caret-square-right" });
- if (DocumentManager.Instance.getDocumentViews(this.resolvedDataDoc).length) {
- ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.resolvedDataDoc).map(view => view.props.focus(this.props.document, true)), icon: "camera" });
+ if (DocumentManager.Instance.getDocumentViews(this.dataDoc).length) {
+ ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.dataDoc).map(view => view.props.focus(this.props.document, true)), icon: "camera" });
}
ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" });
} else {
- ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.resolvedDataDoc)), icon: "caret-square-right" });
+ ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.dataDoc)), icon: "caret-square-right" });
ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" });
}
ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
@@ -274,7 +267,7 @@ class TreeView extends React.Component<TreeViewProps> {
if (de.data.draggedDocuments[0] === this.props.document) return true;
let addDoc = (doc: Doc) => this.props.addDocument(doc, this.resolvedDataDoc, before);
if (inside) {
- let docList = Cast(this.resolvedDataDoc.data, listSpec(Doc));
+ let docList = Cast(this.dataDoc.data, listSpec(Doc));
if (docList !== undefined) {
addDoc = (doc: Doc) => { docList && docList.push(doc); return true; };
}
@@ -299,8 +292,8 @@ class TreeView extends React.Component<TreeViewProps> {
renderLinks = () => {
let ele: JSX.Element[] = [];
- let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this._chosenKey, doc, addBefore, before);
+ let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this.fieldKey, doc, addBefore, before);
let groups = LinkManager.Instance.getRelatedGroupedLinks(this.props.document);
groups.forEach((groupLinkDocs, groupType) => {
// let destLinks = groupLinkDocs.map(d => LinkManager.Instance.getOppositeAnchor(d, this.props.document));
@@ -326,7 +319,7 @@ class TreeView extends React.Component<TreeViewProps> {
@computed get boundsOfCollectionDocument() {
if (StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1) return undefined;
- let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc);
+ let layoutDoc = this.props.document;
return Doc.ComputeContentBounds(DocListCast(layoutDoc.data));
}
docWidth = () => {
@@ -344,27 +337,75 @@ class TreeView extends React.Component<TreeViewProps> {
})());
}
+ noOverlays = (doc: Doc) => ({ title: "", caption: "" });
+
+ expandedField = (doc?: Doc) => {
+ if (!doc) return <div />;
+ let realDoc = doc;
+
+ let ids: { [key: string]: string } = {};
+ Object.keys(doc).forEach(key => {
+ if (!(key in ids) && realDoc[key] !== ComputedField.undefined) {
+ ids[key] = key;
+ }
+ });
+
+ let rows: JSX.Element[] = [];
+ for (let key of Object.keys(ids).sort()) {
+ let contents = realDoc[key] ? realDoc[key] : undefined;
+ let contentElement: JSX.Element[] | JSX.Element = [];
+
+ if (contents instanceof Doc || Cast(contents, listSpec(Doc))) {
+ let docList = contents;
+ let remDoc = (doc: Doc) => this.remove(doc, key);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before);
+ contentElement = key === "links" ? this.renderLinks() :
+ TreeView.GetChildElements(docList instanceof Doc ? [docList] : DocListCast(docList), this.props.treeViewId, realDoc, undefined, key, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth);
+ } else {
+ contentElement = <EditableView
+ key="editableView"
+ contents={contents ? contents.toString() : "null"}
+ height={13}
+ fontSize={12}
+ GetValue={() => Field.toKeyValueString(realDoc, key)}
+ SetValue={(value: string) => KeyValueBox.SetField(realDoc, key, value)} />;
+ }
+ rows.push(<div style={{ display: "flex" }} key={key}>
+ <span style={{ fontWeight: "bold" }}>{key + ":"}</span>
+ &nbsp;
+ {contentElement}
+ </div>);
+ }
+ return rows;
+ }
+
render() {
let contentElement: (JSX.Element | null) = null;
- let docList = Cast(this.resolvedDataDoc[this._chosenKey], listSpec(Doc));
- let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.resolvedDataDoc, this._chosenKey, doc, addBefore, before);
- let doc = Cast(this.resolvedDataDoc[this._chosenKey], Doc);
+ let docList = Cast(this.dataDoc[this.fieldKey], listSpec(Doc));
+ let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc, addBefore, before);
if (!this._collapsed) {
- if (!this.props.document.embed) {
- contentElement = <ul key={this._chosenKey + "more"}>
- {this._chosenKey === "links" ? this.renderLinks() :
- TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.props.dataDoc, this._chosenKey, addDoc, remDoc, this.move,
+ if (this.treeViewExpandedView === "data") {
+ let doc = Cast(this.props.document[this.fieldKey], Doc);
+ contentElement = <ul key={this.fieldKey + "more"}>
+ {this.fieldKey === "links" ? this.renderLinks() :
+ TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
</ul >;
+ } else if (this.treeViewExpandedView === "fields") {
+ contentElement = <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
+ {this.expandedField(this.dataDoc)}
+ </div></ul>;
} else {
- let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc);
+ let layoutDoc = this.props.document;
contentElement = <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id] + this.props.document.title}>
<CollectionSchemaPreview
Document={layoutDoc}
DataDocument={this.resolvedDataDoc}
renderDepth={this.props.renderDepth}
+ showOverlays={this.noOverlays}
fitToBox={this.boundsOfCollectionDocument !== undefined}
width={this.docWidth}
height={this.docHeight}
@@ -433,7 +474,7 @@ class TreeView extends React.Component<TreeViewProps> {
dataDoc={dataDoc}
containingCollection={containingCollection}
treeViewId={treeViewId}
- key={child[Id] + "child " + i}
+ key={child[Id]}
indentDocument={indent}
renderDepth={renderDepth}
deleteDoc={remove}
@@ -456,6 +497,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
private treedropDisposer?: DragManager.DragDropDisposer;
private _mainEle?: HTMLDivElement;
+ @computed get chromeCollapsed() { return this.props.chromeCollapsed; }
+
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this.treedropDisposer && this.treedropDisposer();
if (this._mainEle = ele) {
@@ -479,8 +522,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
onContextMenu = (e: React.MouseEvent): void => {
// 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.workspaceLibrary) { // 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)) });
+ ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()), icon: "plus" });
+ ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)), icon: "minus" });
e.stopPropagation();
e.preventDefault();
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
@@ -547,7 +590,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true}
OnFillDown={(value: string) => {
Doc.GetProto(this.props.Document).title = value;
- let doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ let doc = this.props.Document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.detailedLayout)) : undefined;
+ if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
TreeView.loadId = doc[Id];
Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
}} />