diff options
author | ab <abdullah_ahmed@brown.edu> | 2019-07-29 15:44:37 -0400 |
---|---|---|
committer | ab <abdullah_ahmed@brown.edu> | 2019-07-29 15:44:37 -0400 |
commit | 38b5d646e62535504eb8667b840bf36cd7f2f6d8 (patch) | |
tree | 1481b6cdfb9a0853e8405a7dfb96a8fbd9066a9f /src/client/views/collections/CollectionTreeView.tsx | |
parent | c2dead205fe719881ca7e254c1872e03a2da9b3d (diff) | |
parent | e7ea2028f54787d6c92fb22b789f17b7268d3793 (diff) |
more improvemenets tmrw
Diffstat (limited to 'src/client/views/collections/CollectionTreeView.tsx')
-rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 180 |
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> + + {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); }} /> |