Browse Source

mol-plugin: ghost object support, tree UI tweaks

David Sehnal 6 years ago
parent
commit
86ec6e8786

+ 19 - 1
src/mol-plugin/behavior/static/state.ts

@@ -59,9 +59,27 @@ export function ApplyAction(ctx: PluginContext) {
 }
 
 export function RemoveObject(ctx: PluginContext) {
-    PluginCommands.State.RemoveObject.subscribe(ctx, ({ state, ref }) => {
+    function remove(state: State, ref: string) {
         const tree = state.build().delete(ref).getTree();
         return ctx.runTask(state.updateTree(tree));
+    }
+
+    PluginCommands.State.RemoveObject.subscribe(ctx, ({ state, ref, removeParentGhosts }) => {
+        if (removeParentGhosts) {
+            const tree = state.tree;
+            let curr = tree.transforms.get(ref);
+            if (curr.parent === ref) return remove(state, ref);
+
+            while (true) {
+                const children = tree.children.get(curr.parent);
+                if (curr.parent === curr.ref || children.size > 1) return remove(state, curr.ref);
+                const parent = tree.transforms.get(curr.parent);
+                if (!parent.props || !parent.props.isGhost) return remove(state, curr.ref);
+                curr = parent;
+            }
+        } else {
+            remove(state, ref);
+        }
     });
 }
 

+ 1 - 1
src/mol-plugin/command.ts

@@ -18,7 +18,7 @@ export const PluginCommands = {
         ApplyAction: PluginCommand<{ state: State, action: StateAction.Instance, ref?: StateTransform.Ref }>(),
         Update: PluginCommand<{ state: State, tree: State.Tree | State.Builder, doNotLogTiming?: boolean }>(),
 
-        RemoveObject: PluginCommand<{ state: State, ref: StateTransform.Ref }>(),
+        RemoveObject: PluginCommand<{ state: State, ref: StateTransform.Ref, removeParentGhosts?: boolean }>(),
 
         ToggleExpanded: PluginCommand<{ state: State, ref: StateTransform.Ref }>({ isImmediate: true }),
         ToggleVisibility: PluginCommand<{ state: State, ref: StateTransform.Ref }>({ isImmediate: true }),

+ 2 - 2
src/mol-plugin/state/actions/structure.ts

@@ -71,7 +71,7 @@ const DownloadStructure = StateAction.build({
         default: throw new Error(`${(src as any).name} not supported.`);
     }
 
-    const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams);
+    const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams, { props: { isGhost: true }});
     const traj = createModelTree(data, src.name === 'url' ? src.params.format : 'cif');
     return state.updateTree(createStructureTree(ctx, traj, params.source.params.supportProps));
 });
@@ -89,7 +89,7 @@ export const OpenStructure = StateAction.build({
 
 function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: 'pdb' | 'cif' = 'cif') {
     const parsed = format === 'cif'
-        ? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
+        ? b.apply(StateTransforms.Data.ParseCif, void 0, { props: { isGhost: true }}).apply(StateTransforms.Model.TrajectoryFromMmCif)
         : b.apply(StateTransforms.Model.TrajectoryFromPDB);
 
     return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });

+ 1 - 1
src/mol-plugin/ui/plugin.tsx

@@ -6,7 +6,7 @@
 
 import * as React from 'react';
 import { PluginContext } from '../context';
-import { StateTree } from './state-tree';
+import { StateTree } from './state/tree';
 import { Viewport, ViewportControls } from './viewport';
 import { Controls, TrajectoryControls, LociLabelControl } from './controls';
 import { PluginUIComponent, PluginReactContext } from './base';

+ 30 - 15
src/mol-plugin/ui/state-tree.tsx → src/mol-plugin/ui/state/tree.tsx

@@ -8,16 +8,16 @@ import * as React from 'react';
 import { PluginStateObject } from 'mol-plugin/state/objects';
 import { State, StateObject } from 'mol-state'
 import { PluginCommands } from 'mol-plugin/command';
-import { PluginUIComponent } from './base';
+import { PluginUIComponent } from '../base';
 
 export class StateTree extends PluginUIComponent<{ state: State }> {
     render() {
         const n = this.props.state.tree.root.ref;
-        return <StateTreeNode state={this.props.state} nodeRef={n} />;
+        return <StateTreeNode state={this.props.state} nodeRef={n} depth={0} />;
     }
 }
 
-class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State }, { state: State, isCollapsed: boolean }> {
+class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State, depth: number }, { state: State, isCollapsed: boolean }> {
     is(e: State.ObjectEvent) {
         return e.ref === this.props.nodeRef && e.state === this.props.state;
     }
@@ -60,24 +60,34 @@ class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State },
     }
 
     render() {
-        if (this.props.state.cells.get(this.props.nodeRef)!.obj === StateObject.Null) return null;
+        const cell = this.props.state.cells.get(this.props.nodeRef)!;
+        if (cell.obj === StateObject.Null) return null;
 
         const cellState = this.cellState;
-
+        const showLabel = cell.status !== 'ok' || !cell.transform.props || !cell.transform.props.isGhost;
         const children = this.props.state.tree.children.get(this.props.nodeRef);
-        return <div>
-            <StateTreeNodeLabel nodeRef={this.props.nodeRef} state={this.props.state} />
+        const newDepth = showLabel ? this.props.depth + 1 : this.props.depth;
+
+        if (!showLabel) {
+            if (children.size === 0) return null;
+            return <div style={{ display: cellState.isCollapsed ? 'none' : 'block' }}>
+                {children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} depth={newDepth} />)}
+            </div>;
+        }
+
+        return <>
+            <StateTreeNodeLabel nodeRef={this.props.nodeRef} state={this.props.state} depth={this.props.depth} />
             {children.size === 0
                 ? void 0
-                : <div className='msp-tree-children' style={{ display: cellState.isCollapsed ? 'none' : 'block' }}>
-                    {children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} />)}
+                : <div style={{ display: cellState.isCollapsed ? 'none' : 'block' }}>
+                    {children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} depth={newDepth} />)}
                 </div>
             }
-        </div>;
+        </>;
     }
 }
 
-class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: State }, { state: State, isCurrent: boolean, isCollapsed: boolean }> {
+class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: State, depth: number }, { state: State, isCurrent: boolean, isCollapsed: boolean }> {
     is(e: State.ObjectEvent) {
         return e.ref === this.props.nodeRef && e.state === this.props.state;
     }
@@ -126,7 +136,7 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta
 
     remove = (e: React.MouseEvent<HTMLElement>) => {
         e.preventDefault();
-        PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef });
+        PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef, removeParentGhosts: true });
     }
 
     toggleVisible = (e: React.MouseEvent<HTMLElement>) => {
@@ -171,7 +181,11 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta
         } else {
             const obj = cell.obj as PluginStateObject.Any;
             const title = `${obj.label} ${obj.description ? obj.description : ''}`
-            label = <><a title={title} href='#' onClick={this.setCurrent}>{obj.label}</a> {obj.description ? <small>{obj.description}</small> : void 0}</>;
+            if (this.state.isCurrent) {
+                label = <><b>{obj.label}</b> {obj.description ? <small>{obj.description}</small> : void 0}</>;
+            } else {
+                label = <><a title={title} href='#' onClick={this.setCurrent}>{obj.label}</a> {obj.description ? <small>{obj.description}</small> : void 0}</>;
+            }
         }
 
         const children = this.props.state.tree.children.get(this.props.nodeRef);
@@ -181,7 +195,8 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta
             <span className='msp-icon msp-icon-visual-visibility' />
         </button>;
 
-        return <div className={`msp-tree-row${isCurrent ? ' msp-tree-row-current' : ''}`} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight}>
+        return <div className={`msp-tree-row${isCurrent ? ' msp-tree-row-current' : ''}`} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight}
+            style={{ marginLeft: this.state.isCurrent ? '0px' : `${this.props.depth * 10}px`, borderRadius: this.state.isCurrent ? '0' : void 0 }}>
             {isCurrent ? <b>{label}</b> : label}
             {children.size > 0 &&  <button onClick={this.toggleExpanded} className='msp-btn msp-btn-link msp-tree-toggle-exp-button'>
                 <span className={`msp-icon msp-icon-${cellState.isCollapsed ? 'expand' : 'collapse'}`} />
@@ -189,6 +204,6 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta
             {!cell.transform.props.isLocked && <button onClick={this.remove} className='msp-btn msp-btn-link msp-tree-remove-button'>
                 <span className='msp-icon msp-icon-remove' />
             </button>}{visibility}
-        </div>
+        </div>;
     }
 }