diff --git a/src/mol-plugin/behavior/static/state.ts b/src/mol-plugin/behavior/static/state.ts index 3275404c6908cac51f2df2b1060eb47a41e4eae5..6ff152876d98c634ba8b8c76e1a453bdcca379f4 100644 --- a/src/mol-plugin/behavior/static/state.ts +++ b/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); + } }); } diff --git a/src/mol-plugin/command.ts b/src/mol-plugin/command.ts index b46600218ecf730408185890bc79c9be7a50395f..c188f4cb3c8a2f005ec3576f76ad0e4c240c250e 100644 --- a/src/mol-plugin/command.ts +++ b/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 }), diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index 3aadfee2de0702040851301e4d82530e857d85c8..080fc02633a0970e403b10fded502ae574764613 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/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 }); diff --git a/src/mol-plugin/ui/plugin.tsx b/src/mol-plugin/ui/plugin.tsx index 578ba4bb0c3ea49f119fb7b5a0e03dbee274a8c6..b78c717b44453fef97983ca84acfbe14b106405b 100644 --- a/src/mol-plugin/ui/plugin.tsx +++ b/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'; diff --git a/src/mol-plugin/ui/state-tree.tsx b/src/mol-plugin/ui/state/tree.tsx similarity index 81% rename from src/mol-plugin/ui/state-tree.tsx rename to src/mol-plugin/ui/state/tree.tsx index d91238056a0714691a169533c87f10c86155aa65..a4544a3b4088c943a1319919b61cc8bfa67d92c9 100644 --- a/src/mol-plugin/ui/state-tree.tsx +++ b/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>; } } \ No newline at end of file