Bläddra i källkod

mol-plugin: indicate unobserved changed in state tree, better home screen

David Sehnal 5 år sedan
förälder
incheckning
11e660a81a

+ 4 - 1
src/mol-plugin/context.ts

@@ -23,7 +23,7 @@ import { merge } from 'rxjs';
 import { BuiltInPluginBehaviors } from './behavior';
 import { PluginBehavior } from './behavior/behavior';
 import { PluginCommand, PluginCommands } from './command';
-import { PluginLayout } from './layout';
+import { PluginLayout, LeftPanelTabName } from './layout';
 import { PluginSpec } from './spec';
 import { PluginState } from './state';
 import { DataFormatRegistry } from './state/actions/data-format';
@@ -100,6 +100,9 @@ export class PluginContext {
         },
         labels: {
             highlight: this.ev.behavior<{ entries: ReadonlyArray<LociLabelEntry> }>({ entries: [] })
+        },
+        layout: {
+            leftPanelTabName: this.ev.behavior<LeftPanelTabName>('root')
         }
     } as const
 

+ 2 - 0
src/mol-plugin/layout.ts

@@ -30,6 +30,8 @@ export const PluginLayoutStateParams = {
 }
 export type PluginLayoutStateProps = PD.Values<typeof PluginLayoutStateParams>
 
+export type LeftPanelTabName = 'none' | 'root' | 'data' | 'states' | 'settings' | 'help'
+
 interface RootState {
     top: string | null,
     bottom: string | null,

+ 14 - 0
src/mol-plugin/skin/base/components/misc.scss

@@ -150,7 +150,21 @@
 .msp-left-panel-controls-buttons-bottom {
     position: absolute;
     bottom: 0;
+}
+
+.msp-left-panel-controls-button-data {
+    position: relative;
+    height: $row-height;
 
+    > div {
+        position: absolute;
+        width: 6px;
+        height: 6px;
+        background: $entity-color-Group;
+        border-radius: 3px;
+        right: 6px;
+        bottom: 6px;
+    }
 }
 
 .msp-left-panel-controls {

+ 3 - 0
src/mol-plugin/state/actions/structure.ts

@@ -154,11 +154,14 @@ const DownloadStructure = StateAction.build({
             })
     }
 })(({ params, state }, plugin: PluginContext) => Task.create('Download Structure', async ctx => {
+    plugin.behaviors.layout.leftPanelTabName.next('data');
+
     const b = state.build();
     const src = params.source;
     let downloadParams: StateTransformer.Params<Download>[];
     let supportProps = false, asTrajectory = false, format: StructureFormat = 'cif';
 
+
     switch (src.name) {
         case 'url':
             downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary }];

+ 44 - 10
src/mol-plugin/ui/left-panel.tsx

@@ -16,19 +16,25 @@ import { Canvas3DParams } from '../../mol-canvas3d/canvas3d';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { StateSnapshots, RemoteStateSnapshots } from './state/snapshots';
 import { HelpContent } from './viewport/help';
+import { LeftPanelTabName } from '../layout';
 
-type TabName = 'none' | 'root' | 'data' | 'states' | 'settings' | 'help'
-
-export class LeftPanelControls extends PluginUIComponent<{}, { tab: TabName }> {
-    state = { tab: 'data' as TabName };
+export class LeftPanelControls extends PluginUIComponent<{}, { tab: LeftPanelTabName }> {
+    state = { tab: this.plugin.behaviors.layout.leftPanelTabName.value };
 
     componentDidMount() {
-        // this.subscribe(this.plugin.state.behavior.kind, () => this.forceUpdate());
+        this.subscribe(this.plugin.behaviors.layout.leftPanelTabName, tab => {
+            if (this.state.tab !== tab) this.setState({ tab });
+        });
+
+        this.subscribe(this.plugin.state.dataState.events.changed, state => {
+            if (this.state.tab !== 'data') return;
+            if (state.cells.size === 1) this.set('root');
+        });
     }
 
-    set(tab: TabName) {
+    set = (tab: LeftPanelTabName) => {
         if (this.state.tab === tab) {
-            this.setState({ tab: 'none' });
+            this.setState({ tab: 'none' }, () => this.plugin.behaviors.layout.leftPanelTabName.next('none'));
             PluginCommands.Layout.Update.dispatch(this.plugin, { state: { regionState: { ...this.plugin.layout.state.regionState, left: 'collapsed' } } });
             return;
         }
@@ -38,13 +44,13 @@ export class LeftPanelControls extends PluginUIComponent<{}, { tab: TabName }> {
             case 'settings': this.plugin.state.setKind('behavior'); break;
         }
 
-        this.setState({ tab });
+        this.setState({ tab }, () => this.plugin.behaviors.layout.leftPanelTabName.next(tab));
         if (this.plugin.layout.state.regionState.left !== 'full') {
             PluginCommands.Layout.Update.dispatch(this.plugin, { state: { regionState: { ...this.plugin.layout.state.regionState, left: 'full' } } });
         }
     }
 
-    tabs: { [K in TabName]: JSX.Element } = {
+    tabs: { [K in LeftPanelTabName]: JSX.Element } = {
         'none': <></>,
         'root': <>
             <SectionHeader icon='home' title='Home' />
@@ -69,10 +75,12 @@ export class LeftPanelControls extends PluginUIComponent<{}, { tab: TabName }> {
     render() {
         const tab = this.state.tab;
 
+        // TODO: show "changed dot" next to the 'data' tab icon indicating the state has changed.
         return <div className='msp-left-panel-controls'>
             <div className='msp-left-panel-controls-buttons'>
                 <IconButton icon='home' toggleState={tab === 'root'} onClick={() => this.set('root')} title='Home' />
-                <IconButton icon='flow-tree' toggleState={tab === 'data'} onClick={() => this.set('data')} title='State Tree' />
+                {/* <IconButton icon='flow-tree' toggleState={tab === 'data'} onClick={() => this.set('data')} title='State Tree' /> */}
+                <DataIcon set={this.set} />
                 <IconButton icon='floppy' toggleState={tab === 'states'} onClick={() => this.set('states')} title='Plugin State' />
                 <IconButton icon='help-circle' toggleState={tab === 'help'} onClick={() => this.set('help')} title='Help' />
                 <div className='msp-left-panel-controls-buttons-bottom'>
@@ -86,6 +94,32 @@ export class LeftPanelControls extends PluginUIComponent<{}, { tab: TabName }> {
     }
 }
 
+class DataIcon extends PluginUIComponent<{ set: (tab: LeftPanelTabName) => void }, { changed: boolean }> {
+    state = { changed: false };
+
+    get tab() {
+        return this.plugin.behaviors.layout.leftPanelTabName.value
+    }
+
+    componentDidMount() {
+        this.subscribe(this.plugin.behaviors.layout.leftPanelTabName, tab => {
+            if (this.tab === 'data') this.setState({ changed: false });
+        });
+
+        this.subscribe(this.plugin.state.dataState.events.changed, state => {
+            if (this.tab !== 'data') this.setState({ changed: true });
+        });
+    }
+
+    render() {
+        return <div className='msp-left-panel-controls-button-data'>
+            <IconButton icon='flow-tree' toggleState={this.tab === 'data'} onClick={() => this.props.set('data')} title='State Tree' />
+            {this.state.changed && <div />}
+        </div>;
+
+    }
+}
+
 class FullSettings extends PluginUIComponent {
     private setSettings = (p: { param: PD.Base<any>, name: string, value: any }) => {
         PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { [p.name]: p.value } });

+ 5 - 6
src/mol-plugin/ui/state/tree.tsx

@@ -9,8 +9,7 @@ import { PluginStateObject } from '../../../mol-plugin/state/objects';
 import { State, StateObject, StateTransform, StateObjectCell } from '../../../mol-state'
 import { PluginCommands } from '../../../mol-plugin/command';
 import { PluginUIComponent, _Props, _State } from '../base';
-import { StateObjectActions } from './actions';
-import { RemoteStateSnapshots } from './snapshots';
+import { Icon } from '../controls/common';
 
 export class StateTree extends PluginUIComponent<{ state: State }, { showActions: boolean }> {
     state = { showActions: true };
@@ -36,10 +35,10 @@ export class StateTree extends PluginUIComponent<{ state: State }, { showActions
     render() {
         const ref = this.props.state.tree.root.ref;
         if (this.state.showActions) {
-            return <>
-                <StateObjectActions state={this.props.state} nodeRef={ref} hideHeader={true} initiallyCollapsed={true} alwaysExpandFirst={true} />
-                <RemoteStateSnapshots listOnly />
-            </>
+            return <div style={{ margin: '10px', cursor: 'default' }}>
+                <p>Nothing to see here.</p>
+                <p>Structures can be loaded from the <Icon name='home' /> tab.</p>
+            </div>
         }
         return <StateTreeNode cell={this.props.state.cells.get(ref)!} depth={0} />;
     }

+ 2 - 2
src/mol-state/state.ts

@@ -46,7 +46,7 @@ class State {
             removed: this.ev<State.ObjectEvent & { obj?: StateObject }>()
         },
         log: this.ev<LogEntry>(),
-        changed: this.ev<void>(),
+        changed: this.ev<State>(),
         isUpdating: this.ev<boolean>()
     };
 
@@ -177,7 +177,7 @@ class State {
         } finally {
             this.spine.current = undefined;
 
-            if (updated) this.events.changed.next();
+            if (updated) this.events.changed.next(this);
             this.events.isUpdating.next(false);
         }
     }