Browse Source

mol-plugin-ui: StructureComponentControls

David Sehnal 5 years ago
parent
commit
e5acce03e5

+ 5 - 1
src/mol-plugin-state/manager/structure.ts

@@ -49,9 +49,13 @@ export class StructureHierarchyManager {
 
     constructor(private plugin: PluginContext) {
         plugin.state.dataState.events.changed.subscribe(e => {
-            if (e.inTransaction) return;
+            if (e.inTransaction || plugin.behaviors.state.isAnimating.value) return;
             this.sync();
         });
+
+        plugin.behaviors.state.isAnimating.subscribe(isAnimating => {
+            if (!isAnimating && !plugin.behaviors.state.isUpdating.value) this.sync();
+        })
     }
 }
 

+ 5 - 0
src/mol-plugin-state/manager/structure/hierarchy.ts

@@ -192,9 +192,14 @@ function visitCell(t: StateTransform, tree: StateTree, state: BuildState): boole
         if (StateObject.hasTag(cell.obj!, t)) {
             const stop = f(state, cell);
             if (stop === false) return false;
+            return true;
         }
     }
 
+    if (state.currentComponent && SO.Molecule.Structure.Representation3D.is(cell.obj)) {
+        createOrUpdateRefList(state, cell, state.currentComponent.representations, StructureRepresentationRef, cell, state.currentComponent);
+    }
+
     return true;
 }
 

+ 1 - 1
src/mol-plugin-ui/controls/action-menu.tsx

@@ -31,7 +31,7 @@ export namespace ActionMenu {
 
     export type OnSelect = (item: Item | undefined) => void
 
-    export type Items = string | Item | [Items]
+    export type Items =  string | Item | Items[]
     export type Item = { label: string, icon?: IconName, value: unknown }
 
     export function Item(label: string, value: unknown): Item

+ 3 - 1
src/mol-plugin-ui/skin/base/components/temp.scss

@@ -57,7 +57,7 @@
     height: $row-height;
     width: inherit;
 
-    > select {
+    > select, > button {
         margin: 0;
         flex: 1 1 auto;
         margin-right: 1px;
@@ -67,7 +67,9 @@
         background: none;
         padding: 0 $control-spacing;
         overflow: hidden;
+    }
 
+    > select {
         > option[value = _] {
             display: none;
         }

+ 4 - 3
src/mol-plugin-ui/state/common.tsx

@@ -100,7 +100,7 @@ namespace TransformControlBase {
     }
 }
 
-abstract class TransformControlBase<P, S extends TransformControlBase.ComponentState> extends PurePluginUIComponent<P, S> {
+abstract class TransformControlBase<P, S extends TransformControlBase.ComponentState> extends PurePluginUIComponent<P & { noMargin?: boolean, onApply?: () => void, applyLabel?: string }, S> {
     abstract applyAction(): Promise<void>;
     abstract getInfo(): StateTransformParameters.Props['info'];
     abstract getHeader(): StateTransformer.Definition['display'] | 'none';
@@ -141,6 +141,7 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
     }
 
     apply = async () => {
+        this.props.onApply?.();
         this.clearAutoApply();
         this.setState({ busy: true });
         try {
@@ -190,7 +191,7 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
 
         const showBack = this.isUpdate() && !(this.state.busy || this.state.isInitial);
 
-        return <div className={wrapClass}>
+        return <div className={wrapClass} style={{ marginBottom: this.props.noMargin ? 0 : void 0 }}>
             {display !== 'none' && <div className='msp-transform-header'>
                 <button className={`msp-btn msp-btn-block${isEmpty ? '' : ' msp-btn-collapse'}`} onClick={this.toggleExpanded} title={display.description}>
                     {!isEmpty && <Icon name={this.state.isCollapsed ? 'expand' : 'collapse'} />}
@@ -208,7 +209,7 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
                     <div className={`msp-transform-apply${!showBack ? ' msp-transform-apply-wider' : ''}`}>
                         <button className={`msp-btn msp-btn-block msp-btn-commit msp-btn-commit-${this.canApply() ? 'on' : 'off'}`} onClick={this.apply} disabled={!this.canApply()}>
                             {this.canApply() && <Icon name='ok' />}
-                            {this.applyText()}
+                            {this.props.applyLabel || this.applyText()}
                         </button>
                     </div>
                 </div>

+ 88 - 4
src/mol-plugin-ui/structure/components.tsx

@@ -7,7 +7,15 @@
 import * as React from 'react';
 import { CollapsableControls, CollapsableState, PurePluginUIComponent } from '../base';
 import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure';
-import { StructureComponentRef } from '../../mol-plugin-state/manager/structure/hierarchy';
+import { StructureComponentRef, StructureRepresentationRef } from '../../mol-plugin-state/manager/structure/hierarchy';
+import { Icon } from '../controls/icons';
+import { State, StateAction } from '../../mol-state';
+import { PluginCommands } from '../../mol-plugin/commands';
+import { ExpandGroup, IconButton } from '../controls/common';
+import { UpdateTransformControl } from '../state/update-transform';
+import { ActionMenu } from '../controls/action-menu';
+import { ApplyActionControl } from '../state/apply-action';
+import { StateTransforms } from '../../mol-plugin-state/transforms';
 
 interface StructureComponentControlState extends CollapsableState {
     isDisabled: boolean
@@ -29,13 +37,89 @@ export class StructureComponentControls extends CollapsableControls<{}, Structur
     renderControls() {
         const components = StructureHierarchyManager.getCommonComponentPivots(this.currentModels.value)
         return <>
-            {components.map((c, i) => <StructureComponentEntry key={i} component={c} />)}
+            {components.map(c => <StructureComponentEntry key={c.cell.transform.ref} component={c} />)}
         </>;
     }
 }
 
-class StructureComponentEntry extends PurePluginUIComponent<{ component: StructureComponentRef }> {
+const createRepr = StateAction.fromTransformer(StateTransforms.Representation.StructureRepresentation3D);
+class StructureComponentEntry extends PurePluginUIComponent<{ component: StructureComponentRef }, { showActions: boolean, showAddRepr: boolean }> {
+    state = { showActions: false, showAddRepr: false }
+
+    is(e: State.ObjectEvent) {
+        return e.ref === this.ref && e.state === this.props.component.cell.parent;
+    }
+
+    get ref() {
+        return this.props.component.cell.transform.ref;
+    }
+
+    componentDidMount() {
+        this.subscribe(this.plugin.events.state.cell.stateUpdated, e => {
+            if (this.is(e)) this.forceUpdate();
+        });
+    }
+
+    toggleVisible = (e: React.MouseEvent<HTMLElement>) => {
+        e.preventDefault();
+        PluginCommands.State.ToggleVisibility(this.plugin, { state: this.props.component.cell.parent, ref: this.ref });
+        e.currentTarget.blur();
+    }
+
+    remove(ref: string) {
+        return () => {
+            this.setState({ showActions: false });
+            PluginCommands.State.RemoveObject(this.plugin, { state: this.props.component.cell.parent, ref, removeParentGhosts: true });
+        }
+    }
+
+    
+    get actions(): ActionMenu.Items {
+        const ret = [
+            ActionMenu.Item(`${this.state.showAddRepr ? 'Hide ' : ''}Add Representation`, 'plus', this.toggleAddRepr),
+            ActionMenu.Item('Remove', 'remove', this.remove(this.ref))
+        ];
+        for (const repr of this.props.component.representations) {
+            ret.push(ActionMenu.Item(`Remove ${repr.cell.obj?.label}`, 'remove', this.remove(repr.cell.transform.ref)))
+        }
+        return ret;
+    }
+    
+    selectAction: ActionMenu.OnSelect = item => {
+        if (!item) return;
+        (item?.value as any)();
+    }
+    
+    toggleAddRepr = () => this.setState({ showActions: false, showAddRepr: !this.state.showAddRepr });
+    toggleActions = () => this.setState({ showActions: !this.state.showActions });
+
+    render() {
+        const component = this.props.component;
+        const cell = component.cell;
+        const label = cell.obj?.label;
+        return <>
+            <div className='msp-control-row'>
+                <span title={label}>{label}</span>
+                <div className='msp-select-row'>
+                    <button onClick={this.toggleVisible}><Icon name='visual-visibility' style={{ fontSize: '80%' }} /> {cell.state.isHidden ? 'Show' : 'Hide'}</button>
+                    <IconButton onClick={this.toggleActions} icon='menu' style={{ width: '64px' }} toggleState={this.state.showActions} title='Actions' />
+                </div>
+            </div>
+            {this.state.showActions && <ActionMenu items={this.actions} onSelect={this.selectAction} />}
+            <div className='msp-control-offset'>
+                {this.state.showAddRepr && 
+                    <ApplyActionControl plugin={this.plugin} state={cell.parent} action={createRepr} nodeRef={this.ref} hideHeader noMargin onApply={this.toggleAddRepr} applyLabel='Add' />}
+                {component.representations.map(r => <StructureRepresentationEntry key={r.cell.transform.ref} representation={r} />)}
+            </div>
+        </>;
+    }
+}
+
+class StructureRepresentationEntry extends PurePluginUIComponent<{ representation: StructureRepresentationRef }> {
     render() {
-        return <></>;
+        const repr = this.props.representation.cell;
+        return <ExpandGroup header={repr.obj?.label || ''} noOffset>
+            <UpdateTransformControl state={repr.parent} transform={repr.transform} customHeader='none' noMargin />
+        </ExpandGroup>;
     }
 }